Use rollover count at the time of the event, not the current value to

produce correct dates. Use 64-bit integers for timestamp to ease
arithmetic.
This commit is contained in:
Jaakko Haakana 2025-07-20 21:37:11 +03:00
parent dbf2def0e2
commit 001d254cd3
4 changed files with 36 additions and 33 deletions

View file

@ -339,6 +339,15 @@ void init_serial() {
#endif // DEBUG_VIA_USB #endif // DEBUG_VIA_USB
} }
void update_overflow(unsigned long currentMillis) {
// Check if millis overflowed
if (currentMillis < lastMillisOverflowCheck) {
// We have overflowed, increase rollover count
datalayer.system.status.millisrolloverCount++;
}
lastMillisOverflowCheck = currentMillis;
}
void check_interconnect_available() { void check_interconnect_available() {
if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) { if (datalayer.battery.status.voltage_dV == 0 || datalayer.battery2.status.voltage_dV == 0) {
return; // Both voltage values need to be available to start check return; // Both voltage values need to be available to start check
@ -519,11 +528,8 @@ void update_calculated_values() {
datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh; datalayer.battery.status.reported_remaining_capacity_Wh = datalayer.battery2.status.remaining_capacity_Wh;
} }
} }
// Check if millis has overflowed. Used in events to keep better track of time
if (currentMillis < lastMillisOverflowCheck) { // Overflow detected update_overflow(currentMillis); // Update millis rollover count
datalayer.system.status.millisrolloverCount++;
}
lastMillisOverflowCheck = currentMillis;
} }
void check_reset_reason() { void check_reset_reason() {
@ -581,3 +587,9 @@ void check_reset_reason() {
break; break;
} }
} }
uint64_t get_timestamp(unsigned long currentMillis) {
update_overflow(currentMillis);
return (uint64_t)datalayer.system.status.millisrolloverCount * (uint64_t)std::numeric_limits<uint32_t>::max() +
(uint64_t)currentMillis;
}

View file

@ -3,13 +3,6 @@
#include "../../../USER_SETTINGS.h" #include "../../../USER_SETTINGS.h"
typedef struct {
EVENTS_ENUM_TYPE event;
uint8_t millisrolloverCount;
uint32_t timestamp;
uint8_t data;
} EVENT_LOG_ENTRY_TYPE;
typedef struct { typedef struct {
EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS]; EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
EVENTS_LEVEL_TYPE level; EVENTS_LEVEL_TYPE level;
@ -30,7 +23,6 @@ void init_events(void) {
for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) { for (uint16_t i = 0; i < EVENT_NOF_EVENTS; i++) {
events.entries[i].data = 0; events.entries[i].data = 0;
events.entries[i].timestamp = 0; events.entries[i].timestamp = 0;
events.entries[i].millisrolloverCount = 0;
events.entries[i].occurences = 0; events.entries[i].occurences = 0;
events.entries[i].MQTTpublished = false; // Not published by default events.entries[i].MQTTpublished = false; // Not published by default
} }
@ -155,7 +147,6 @@ void reset_all_events() {
events.entries[i].data = 0; events.entries[i].data = 0;
events.entries[i].state = EVENT_STATE_INACTIVE; events.entries[i].state = EVENT_STATE_INACTIVE;
events.entries[i].timestamp = 0; events.entries[i].timestamp = 0;
events.entries[i].millisrolloverCount = 0;
events.entries[i].occurences = 0; events.entries[i].occurences = 0;
events.entries[i].MQTTpublished = false; // Not published by default events.entries[i].MQTTpublished = false; // Not published by default
} }
@ -396,6 +387,8 @@ EVENTS_LEVEL_TYPE get_event_level(void) {
return events.level; return events.level;
} }
uint64_t get_timestamp(unsigned long currentMillis);
/* Local functions */ /* Local functions */
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) { static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
@ -416,8 +409,7 @@ static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched) {
} }
// We should set the event, update event info // We should set the event, update event info
events.entries[event].timestamp = millis(); events.entries[event].timestamp = get_timestamp(millis());
events.entries[event].millisrolloverCount = datalayer.system.status.millisrolloverCount;
events.entries[event].data = data; events.entries[event].data = data;
// Check if the event is latching // Check if the event is latching
events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE; events.entries[event].state = latched ? EVENT_STATE_ACTIVE_LATCHED : EVENT_STATE_ACTIVE;
@ -448,17 +440,11 @@ static void update_bms_status(void) {
// Function to compare events by timestamp descending // Function to compare events by timestamp descending
bool compareEventsByTimestampDesc(const EventData& a, const EventData& b) { bool compareEventsByTimestampDesc(const EventData& a, const EventData& b) {
if (a.event_pointer->millisrolloverCount != b.event_pointer->millisrolloverCount) {
return a.event_pointer->millisrolloverCount > b.event_pointer->millisrolloverCount;
}
return a.event_pointer->timestamp > b.event_pointer->timestamp; return a.event_pointer->timestamp > b.event_pointer->timestamp;
} }
// Function to compare events by timestamp ascending // Function to compare events by timestamp ascending
bool compareEventsByTimestampAsc(const EventData& a, const EventData& b) { bool compareEventsByTimestampAsc(const EventData& a, const EventData& b) {
if (a.event_pointer->millisrolloverCount != b.event_pointer->millisrolloverCount) {
return a.event_pointer->millisrolloverCount < b.event_pointer->millisrolloverCount;
}
return a.event_pointer->timestamp < b.event_pointer->timestamp; return a.event_pointer->timestamp < b.event_pointer->timestamp;
} }

View file

@ -129,12 +129,11 @@ typedef enum {
} EVENTS_STATE_TYPE; } EVENTS_STATE_TYPE;
typedef struct { typedef struct {
uint32_t timestamp; // Time in seconds since startup when the event occurred uint64_t timestamp;
uint8_t millisrolloverCount; // number of times millis rollovers before timestamp uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage
uint8_t data; // Custom data passed when setting the event, for example cell number for under voltage uint8_t occurences; // Number of occurrences since startup
uint8_t occurences; // Number of occurrences since startup EVENTS_LEVEL_TYPE level; // Event level, i.e. ERROR/WARNING...
EVENTS_LEVEL_TYPE level; // Event level, i.e. ERROR/WARNING... EVENTS_STATE_TYPE state; // Event state, i.e. ACTIVE/INACTIVE...
EVENTS_STATE_TYPE state; // Event state, i.e. ACTIVE/INACTIVE...
bool MQTTpublished; bool MQTTpublished;
} EVENTS_STRUCT_TYPE; } EVENTS_STRUCT_TYPE;

View file

@ -1,4 +1,5 @@
#include "events_html.h" #include "events_html.h"
#include <limits>
#include "../../datalayer/datalayer.h" #include "../../datalayer/datalayer.h"
const char EVENTS_HTML_START[] = R"=====( const char EVENTS_HTML_START[] = R"=====(
@ -10,7 +11,9 @@ const char EVENTS_HTML_END[] = R"=====(
button:hover { background-color: #3A4A52; }</style> button:hover { background-color: #3A4A52; }</style>
<button onclick="askClear()">Clear all events</button> <button onclick="askClear()">Clear all events</button>
<button onclick="home()">Back to main page</button> <button onclick="home()">Back to main page</button>
<style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style><script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(Date.now()-(4294967296*+n.innerText.split(";")[0]+ +n.innerText.split(";")[1])).toLocaleString())})}function askClear(){window.confirm("Are you sure you want to clear all events?")&&(window.location.href="/clearevents")}function home(){window.location.href="/"}window.onload=function(){showEvent()}</script> <style>.event:nth-child(even){background-color:#455a64}.event:nth-child(odd){background-color:#394b52}</style>
<script>function showEvent(){document.querySelectorAll(".event").forEach(function(e){var n=e.querySelector(".sec-ago");n&&(n.innerText=new Date(Number(BigInt(Date.now()) - BigInt(n.innerText))).toLocaleString())})}function askClear(){window.confirm("Are you sure you want to clear all events?")&&(window.location.href="/clearevents")}function home(){window.location.href="/"}window.onload=function(){showEvent()}
</script>
)====="; )=====";
static std::vector<EventData> order_events; static std::vector<EventData> order_events;
@ -34,12 +37,15 @@ String events_processor(const String& var) {
} }
// Sort events by timestamp // Sort events by timestamp
std::sort(order_events.begin(), order_events.end(), compareEventsByTimestampDesc); std::sort(order_events.begin(), order_events.end(), compareEventsByTimestampDesc);
unsigned long timestamp_now = millis(); uint64_t current_timestamp =
(uint64_t)datalayer.system.status.millisrolloverCount * (uint64_t)std::numeric_limits<uint32_t>::max() +
(uint64_t)millis();
// Generate HTML and debug output // Generate HTML and debug output
for (const auto& event : order_events) { for (const auto& event : order_events) {
EVENTS_ENUM_TYPE event_handle = event.event_handle; EVENTS_ENUM_TYPE event_handle = event.event_handle;
event_pointer = event.event_pointer; event_pointer = event.event_pointer;
#ifdef DEBUG_LOG #ifdef DEBUG_LOG
logging.println("Showing Event: " + String(get_event_enum_string(event_handle)) + logging.println("Showing Event: " + String(get_event_enum_string(event_handle)) +
" count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) + " count: " + String(event_pointer->occurences) + " seconds: " + String(event_pointer->timestamp) +
@ -49,8 +55,8 @@ String events_processor(const String& var) {
content.concat("<div class='event'>"); content.concat("<div class='event'>");
content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>"); content.concat("<div>" + String(get_event_enum_string(event_handle)) + "</div>");
content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>"); content.concat("<div>" + String(get_event_level_string(event_handle)) + "</div>");
content.concat("<div class='sec-ago'>" + String(datalayer.system.status.millisrolloverCount) + ";" + // Frontend expects to see time difference (in ms) from now to event
String(timestamp_now - event_pointer->timestamp) + "</div>"); content.concat("<div class='sec-ago'>" + String(current_timestamp - event_pointer->timestamp) + "</div>");
content.concat("<div>" + String(event_pointer->occurences) + "</div>"); content.concat("<div>" + String(event_pointer->occurences) + "</div>");
content.concat("<div>" + String(event_pointer->data) + "</div>"); content.concat("<div>" + String(event_pointer->data) + "</div>");
content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>"); content.concat("<div>" + String(get_event_message_string(event_handle)) + "</div>");
@ -80,7 +86,7 @@ String events_processor(const String& var) {
function showEvent() { function showEvent() {
document.querySelectorAll(".event").forEach(function (e) { document.querySelectorAll(".event").forEach(function (e) {
var n = e.querySelector(".sec-ago"); var n = e.querySelector(".sec-ago");
n && (n.innerText = new Date(Date.now() - (+n.innerText.split(";")[0] * 4294967296 + +n.innerText.split(";")[1])).toLocaleString()); n && (n.innerText = new Date(Number(BigInt(Date.now()) - BigInt(n.innerText))).toLocaleString());
}); });
} }
function askClear() { function askClear() {