From 618c849d53247d107235aa2404c635a4fb132dec Mon Sep 17 00:00:00 2001 From: agessaman Date: Thu, 18 Jun 2026 15:03:02 -0700 Subject: [PATCH] feat(mqtt): enhance timestamp formatting with microsecond precision Updated the MQTTMessageBuilder to include microsecond precision in the timestamp formatting. The formatIsoTimestampForMqtt function now accepts a microsecond parameter, allowing for more accurate time representation. Modified related functions in MQTTBridge to utilize the new timestamp formatting, ensuring consistency across MQTT messages. --- src/helpers/MQTTMessageBuilder.cpp | 40 ++++++++++++++++++++++-------- src/helpers/MQTTMessageBuilder.h | 9 +++++-- src/helpers/bridges/MQTTBridge.cpp | 9 +++++-- 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/helpers/MQTTMessageBuilder.cpp b/src/helpers/MQTTMessageBuilder.cpp index e20ec631..d768cc40 100644 --- a/src/helpers/MQTTMessageBuilder.cpp +++ b/src/helpers/MQTTMessageBuilder.cpp @@ -3,18 +3,26 @@ #include #include #include +#include #include #include "MeshCore.h" -void MQTTMessageBuilder::formatIsoTimestampForMqtt(time_t now, Timezone* timezone, char* buffer, size_t buffer_size) { +void MQTTMessageBuilder::formatIsoTimestampForMqtt(time_t now, long usec, Timezone* timezone, char* buffer, size_t buffer_size) { if (!buffer || buffer_size == 0) return; // Always emit UTC with an explicit "+00:00" offset, matching Python's // datetime.now(timezone.utc).isoformat(). The system clock is UTC (SNTP offset 0), // so gmtime() is correct regardless of the prefs Timezone (now unused here). (void)timezone; + // Clamp the sub-second to a valid microsecond range so the "%06ld" field can never + // overflow to 7 digits or go negative on a bad clock read. + if (usec < 0) usec = 0; + else if (usec > 999999) usec = 999999; struct tm* tm_info = gmtime(&now); - if (tm_info && strftime(buffer, buffer_size, "%Y-%m-%dT%H:%M:%S.000000+00:00", tm_info) > 0) { - return; + if (tm_info) { + size_t n = strftime(buffer, buffer_size, "%Y-%m-%dT%H:%M:%S", tm_info); + if (n > 0 && snprintf(buffer + n, buffer_size - n, ".%06ld+00:00", usec) > 0) { + return; + } } strncpy(buffer, "2024-01-01T12:00:00.000000+00:00", buffer_size - 1); buffer[buffer_size - 1] = '\0'; @@ -231,9 +239,13 @@ int MQTTMessageBuilder::buildPacketJSON( ) { if (!packet) return 0; - time_t now = time(nullptr); + // One wall-clock read: tv_sec feeds both the timestamp and the UTC time/date + // fields below (kept consistent), tv_usec is the real sub-second. + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); + time_t now = now_tv.tv_sec; char timestamp[40]; - formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); + formatIsoTimestampForMqtt(now, now_tv.tv_usec, timezone, timestamp, sizeof(timestamp)); // Packet time/date: UTC (gmtime), same family as meshcoretomqtt serial fields struct tm* utc_timeinfo = gmtime(&now); @@ -305,9 +317,13 @@ int MQTTMessageBuilder::buildPacketJSONFromRaw( ) { if (!packet || !raw_data || raw_len <= 0) return 0; - time_t now = time(nullptr); + // One wall-clock read: tv_sec feeds both the timestamp and the UTC time/date + // fields below (kept consistent), tv_usec is the real sub-second. + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); + time_t now = now_tv.tv_sec; char timestamp[40]; - formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); + formatIsoTimestampForMqtt(now, now_tv.tv_usec, timezone, timestamp, sizeof(timestamp)); struct tm* utc_timeinfo = gmtime(&now); @@ -370,11 +386,13 @@ int MQTTMessageBuilder::buildRawJSON( size_t buffer_size ) { if (!packet) return 0; - - time_t now = time(nullptr); + + // One wall-clock read: tv_sec for the timestamp, tv_usec for the real sub-second. + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); char timestamp[40]; - formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); - + formatIsoTimestampForMqtt(now_tv.tv_sec, now_tv.tv_usec, timezone, timestamp, sizeof(timestamp)); + // Convert packet to hex // MAX_TRANS_UNIT is 255, so max hex size is 510 chars + null = 511 bytes char raw_hex[1024]; diff --git a/src/helpers/MQTTMessageBuilder.h b/src/helpers/MQTTMessageBuilder.h index f12d41ef..b8911744 100644 --- a/src/helpers/MQTTMessageBuilder.h +++ b/src/helpers/MQTTMessageBuilder.h @@ -26,11 +26,16 @@ public: /** * Format the MQTT JSON `timestamp` field (same rule for status, packet, raw). * Always UTC with an explicit "+00:00" offset, ISO-8601 - * "%Y-%m-%dT%H:%M:%S.000000+00:00" (matches Python + * "%Y-%m-%dT%H:%M:%S.uuuuuu+00:00" (matches Python * datetime.now(timezone.utc).isoformat()). The `timezone` parameter is retained * for API compatibility but ignored — the system clock is UTC. + * + * `usec` is the sub-second component in microseconds (0..999999), normally taken + * from the same gettimeofday() read as `now` so the two don't tear at a second + * boundary. It is a real sub-second (SNTP-maintained wall clock), not a literal; + * pass 0 if no sub-second source is available. */ - static void formatIsoTimestampForMqtt(time_t now, Timezone* timezone, char* buffer, size_t buffer_size); + static void formatIsoTimestampForMqtt(time_t now, long usec, Timezone* timezone, char* buffer, size_t buffer_size); /** * Build status message JSON diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index d8c255cf..677e814b 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #ifdef WITH_SNMP @@ -1712,7 +1713,9 @@ void MQTTBridge::publishStatusToSlot(int index) { // Status timestamp: UTC with explicit +00:00 offset, same as packet/raw JSON // `timestamp` (system clock is UTC — SNTP offset 0; prefs Timezone is separate). - MQTTMessageBuilder::formatIsoTimestampForMqtt(time(nullptr), _timezone, timestamp, sizeof(timestamp)); + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); + MQTTMessageBuilder::formatIsoTimestampForMqtt(now_tv.tv_sec, now_tv.tv_usec, _timezone, timestamp, sizeof(timestamp)); snprintf(radio_info, sizeof(radio_info), "%.6f,%.1f,%d,%d", _prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr); @@ -2423,7 +2426,9 @@ bool MQTTBridge::publishStatus() { // Status timestamp: UTC with explicit +00:00 offset, same as packet/raw JSON // `timestamp` (system clock is UTC — SNTP offset 0; prefs Timezone is separate). - MQTTMessageBuilder::formatIsoTimestampForMqtt(time(nullptr), _timezone, timestamp, sizeof(timestamp)); + struct timeval now_tv; + gettimeofday(&now_tv, nullptr); + MQTTMessageBuilder::formatIsoTimestampForMqtt(now_tv.tv_sec, now_tv.tv_usec, _timezone, timestamp, sizeof(timestamp)); snprintf(radio_info, sizeof(radio_info), "%.6f,%.1f,%d,%d", _prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr);