diff --git a/src/helpers/MQTTMessageBuilder.cpp b/src/helpers/MQTTMessageBuilder.cpp index d4e9ae86..691c44f9 100644 --- a/src/helpers/MQTTMessageBuilder.cpp +++ b/src/helpers/MQTTMessageBuilder.cpp @@ -1,9 +1,21 @@ #include "MQTTMessageBuilder.h" #include +#include #include #include #include "MeshCore.h" +void MQTTMessageBuilder::formatIsoTimestampForMqtt(time_t now, Timezone* timezone, char* buffer, size_t buffer_size) { + if (!buffer || buffer_size == 0) return; + time_t local_wall = timezone ? timezone->toLocal(now) : now; + struct tm* tm_info = localtime(&local_wall); + if (tm_info && strftime(buffer, buffer_size, "%Y-%m-%dT%H:%M:%S.000000", tm_info) > 0) { + return; + } + strncpy(buffer, "2024-01-01T12:00:00.000000", buffer_size - 1); + buffer[buffer_size - 1] = '\0'; +} + int MQTTMessageBuilder::buildStatusMessage( JsonDocument& doc, const char* origin, @@ -182,22 +194,11 @@ int MQTTMessageBuilder::buildPacketJSON( ) { if (!packet) return 0; - // Get current device time (should be UTC since system timezone is set to UTC) time_t now = time(nullptr); - - // Convert to local time using timezone library (for timestamp field only) - time_t local_time = timezone ? timezone->toLocal(now) : now; - struct tm* local_timeinfo = localtime(&local_time); - - // Format timestamp in ISO 8601 format (LOCAL TIME) char timestamp[32]; - if (local_timeinfo) { - strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", local_timeinfo); - } else { - strcpy(timestamp, "2024-01-01T12:00:00.000000"); - } + formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); - // Get UTC time (since system timezone is UTC, time() returns UTC) + // Packet time/date: UTC (gmtime), same family as meshcoretomqtt serial fields struct tm* utc_timeinfo = gmtime(&now); // Format time and date (ALWAYS UTC) @@ -266,22 +267,10 @@ int MQTTMessageBuilder::buildPacketJSONFromRaw( ) { if (!packet || !raw_data || raw_len <= 0) return 0; - // Get current device time (should be UTC since system timezone is set to UTC) time_t now = time(nullptr); - - // Convert to local time using timezone library (for timestamp field only) - time_t local_time = timezone ? timezone->toLocal(now) : now; - struct tm* local_timeinfo = localtime(&local_time); - - // Format timestamp in ISO 8601 format (LOCAL TIME) char timestamp[32]; - if (local_timeinfo) { - strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", local_timeinfo); - } else { - strcpy(timestamp, "2024-01-01T12:00:00.000000"); - } + formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); - // Get UTC time (since system timezone is UTC, time() returns UTC) struct tm* utc_timeinfo = gmtime(&now); // Format time and date (ALWAYS UTC) @@ -344,20 +333,9 @@ int MQTTMessageBuilder::buildRawJSON( ) { if (!packet) return 0; - // Get current device time time_t now = time(nullptr); - - // Convert to local time using timezone library - time_t local_time = timezone ? timezone->toLocal(now) : now; - struct tm* timeinfo = localtime(&local_time); - - // Format timestamp in ISO 8601 format char timestamp[32]; - if (timeinfo) { - strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", timeinfo); - } else { - strcpy(timestamp, "2024-01-01T12:00:00.000000"); - } + formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); // Convert packet to hex // MAX_TRANS_UNIT is 255, so max hex size is 510 chars + null = 511 bytes diff --git a/src/helpers/MQTTMessageBuilder.h b/src/helpers/MQTTMessageBuilder.h index 17c649bc..22166116 100644 --- a/src/helpers/MQTTMessageBuilder.h +++ b/src/helpers/MQTTMessageBuilder.h @@ -11,12 +11,24 @@ * This class handles the formatting of mesh packets and device status * into JSON messages for MQTT publishing according to the MeshCore * packet capture specification. + * + * Timestamps in JSON use the configured Timezone (prefs) for status, packet, and + * raw topics. Packet payloads also include separate `time` and `date` strings in + * UTC (gmtime) so they stay aligned with meshcoretomqtt serial regex fields. */ class MQTTMessageBuilder { private: static const int JSON_BUFFER_SIZE = 1024; public: + /** + * Format the MQTT JSON `timestamp` field (same rule for status, packet, raw). + * Applies Timezone prefs via `timezone->toLocal(now)`; if timezone is nullptr, + * uses `now` unchanged. Output is naive local wall time, ISO-like + * "%Y-%m-%dT%H:%M:%S.000000". + */ + static void formatIsoTimestampForMqtt(time_t now, Timezone* timezone, char* buffer, size_t buffer_size); + /** * Build status message JSON * @@ -27,7 +39,7 @@ public: * @param radio Radio information * @param client_version Client version * @param status Connection status ("online" or "offline") - * @param timestamp ISO 8601 timestamp + * @param timestamp ISO-like timestamp (see formatIsoTimestampForMqtt) * @param buffer Output buffer for JSON string * @param buffer_size Size of output buffer * @param battery_mv Battery voltage in millivolts (optional, -1 to omit) @@ -71,10 +83,10 @@ public: * * @param origin Device name * @param origin_id Device public key (hex string) - * @param timestamp ISO 8601 timestamp + * @param timestamp ISO-like timestamp (see formatIsoTimestampForMqtt) * @param direction Packet direction ("rx" or "tx") - * @param time Time in HH:MM:SS format - * @param date Date in DD/MM/YYYY format + * @param time Time in HH:MM:SS (UTC, gmtime; meshcoretomqtt serial parity) + * @param date Date in DD/MM/YYYY (UTC, gmtime) * @param len Total packet length * @param packet_type Packet type code * @param route Routing type @@ -114,7 +126,7 @@ public: * * @param origin Device name * @param origin_id Device public key (hex string) - * @param timestamp ISO 8601 timestamp + * @param timestamp ISO-like timestamp (see formatIsoTimestampForMqtt) * @param raw Raw packet data (hex string) * @param buffer Output buffer for JSON string * @param buffer_size Size of output buffer diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index a57fc661..9c11b7f0 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #ifdef WITH_SNMP #include "../SNMPAgent.h" @@ -1676,13 +1677,9 @@ void MQTTBridge::publishStatusToSlot(int index) { char timestamp[32]; char radio_info[64]; - // Get current timestamp in ISO 8601 format - struct tm timeinfo; - if (getLocalTime(&timeinfo)) { - strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", &timeinfo); - } else { - strcpy(timestamp, "2024-01-01T12:00:00.000000"); - } + // Status timestamp: same prefs-based wall clock as packet/raw JSON `timestamp` + // (not libc getLocalTime — SNTP uses UTC offset 0; prefs Timezone is separate). + MQTTMessageBuilder::formatIsoTimestampForMqtt(time(nullptr), _timezone, timestamp, sizeof(timestamp)); snprintf(radio_info, sizeof(radio_info), "%.6f,%.1f,%d,%d", _prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr); @@ -2370,13 +2367,9 @@ bool MQTTBridge::publishStatus() { char timestamp[32]; char radio_info[64]; - // Get current timestamp in ISO 8601 format - struct tm timeinfo; - if (getLocalTime(&timeinfo)) { - strftime(timestamp, sizeof(timestamp), "%Y-%m-%dT%H:%M:%S.000000", &timeinfo); - } else { - strcpy(timestamp, "2024-01-01T12:00:00.000000"); - } + // Status timestamp: same prefs-based wall clock as packet/raw JSON `timestamp` + // (not libc getLocalTime — SNTP uses UTC offset 0; prefs Timezone is separate). + MQTTMessageBuilder::formatIsoTimestampForMqtt(time(nullptr), _timezone, timestamp, sizeof(timestamp)); snprintf(radio_info, sizeof(radio_info), "%.6f,%.1f,%d,%d", _prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr);