diff --git a/build.sh b/build.sh index 0810682a..bff89b16 100755 --- a/build.sh +++ b/build.sh @@ -124,8 +124,8 @@ build_firmware() { # get git commit sha COMMIT_HASH=$(git rev-parse --short HEAD) - # set firmware build date - FIRMWARE_BUILD_DATE=$(date '+%d-%b-%Y') + # set firmware build date (e.g. "6 Jun 2026"; %-d drops the leading zero on GNU date / the Linux CI runner) + FIRMWARE_BUILD_DATE=$(date '+%-d %b %Y') # get FIRMWARE_VERSION, which should be provided by the environment if [ -z "$FIRMWARE_VERSION" ]; then @@ -133,7 +133,7 @@ build_firmware() { exit 1 fi - # set firmware version string + # set firmware version string (used for the output filename) # e.g: v1.0.0-abcdef FIRMWARE_VERSION_STRING="${FIRMWARE_VERSION}-${COMMIT_HASH}" @@ -141,8 +141,18 @@ build_firmware() { # e.g: RAK_4631_Repeater-v1.0.0-SHA FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}" + # Tag the *embedded* version for observer builds, e.g. v1.0.0-observer-abcdef, + # so `ver`, the MQTT firmware_version/client_version, and SNMP all identify the + # fork. The filename above is intentionally left untagged: the env name already + # contains "observer", and the web flasher keys off that existing pattern. + VARIANT_TAG="" + case "$1" in + *observer*) VARIANT_TAG="-observer" ;; + esac + EMBEDDED_VERSION_STRING="${FIRMWARE_VERSION}${VARIANT_TAG}-${COMMIT_HASH}" + # add firmware version info to end of existing platformio build flags in environment vars - export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${EMBEDDED_VERSION_STRING}\"'" # disable debug flags if requested disable_debug_flags diff --git a/src/helpers/MQTTMessageBuilder.cpp b/src/helpers/MQTTMessageBuilder.cpp index 691c44f9..bf581ef9 100644 --- a/src/helpers/MQTTMessageBuilder.cpp +++ b/src/helpers/MQTTMessageBuilder.cpp @@ -7,12 +7,15 @@ 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) { + // 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; + 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; } - strncpy(buffer, "2024-01-01T12:00:00.000000", buffer_size - 1); + strncpy(buffer, "2024-01-01T12:00:00.000000+00:00", buffer_size - 1); buffer[buffer_size - 1] = '\0'; } @@ -195,7 +198,7 @@ int MQTTMessageBuilder::buildPacketJSON( if (!packet) return 0; time_t now = time(nullptr); - char timestamp[32]; + char timestamp[40]; formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); // Packet time/date: UTC (gmtime), same family as meshcoretomqtt serial fields @@ -268,7 +271,7 @@ int MQTTMessageBuilder::buildPacketJSONFromRaw( if (!packet || !raw_data || raw_len <= 0) return 0; time_t now = time(nullptr); - char timestamp[32]; + char timestamp[40]; formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); struct tm* utc_timeinfo = gmtime(&now); @@ -334,7 +337,7 @@ int MQTTMessageBuilder::buildRawJSON( if (!packet) return 0; time_t now = time(nullptr); - char timestamp[32]; + char timestamp[40]; formatIsoTimestampForMqtt(now, timezone, timestamp, sizeof(timestamp)); // Convert packet to hex diff --git a/src/helpers/MQTTMessageBuilder.h b/src/helpers/MQTTMessageBuilder.h index 22166116..02a9db6d 100644 --- a/src/helpers/MQTTMessageBuilder.h +++ b/src/helpers/MQTTMessageBuilder.h @@ -12,9 +12,11 @@ * 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. + * Timestamps in the JSON `timestamp` field are emitted in UTC with an explicit + * "+00:00" offset (equivalent to Python datetime.now(timezone.utc).isoformat()) 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: @@ -23,9 +25,10 @@ private: 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". + * Always UTC with an explicit "+00:00" offset, ISO-8601 + * "%Y-%m-%dT%H:%M:%S.000000+00:00" (matches Python + * datetime.now(timezone.utc).isoformat()). The `timezone` parameter is retained + * for API compatibility but ignored — the system clock is UTC. */ static void formatIsoTimestampForMqtt(time_t now, Timezone* timezone, char* buffer, size_t buffer_size); diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index d46429f2..e7817db1 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -1706,11 +1706,11 @@ void MQTTBridge::publishStatusToSlot(int index) { #endif char origin_id[65]; - char timestamp[32]; + char timestamp[40]; char radio_info[64]; - // Status timestamp: same prefs-based wall clock as packet/raw JSON `timestamp` - // (not libc getLocalTime — SNTP uses UTC offset 0; prefs Timezone is separate). + // 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)); snprintf(radio_info, sizeof(radio_info), "%.6f,%.1f,%d,%d", @@ -2412,11 +2412,11 @@ bool MQTTBridge::publishStatus() { char* json_buffer = _status_json_buffer; #endif char origin_id[65]; - char timestamp[32]; + char timestamp[40]; char radio_info[64]; - // Status timestamp: same prefs-based wall clock as packet/raw JSON `timestamp` - // (not libc getLocalTime — SNTP uses UTC offset 0; prefs Timezone is separate). + // 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)); snprintf(radio_info, sizeof(radio_info), "%.6f,%.1f,%d,%d", @@ -3085,9 +3085,14 @@ void MQTTBridge::optimizeMqttClientConfig(PsychicMqttClient* client, bool needs_ esp_mqtt_client_config_t* config = client->getMqttConfig(); if (config) { #if defined(ESP_IDF_VERSION_MAJOR) && ESP_IDF_VERSION_MAJOR >= 5 - if (config->buffer.out_size == 0 || config->buffer.out_size > MQTT_CLIENT_BUFFER_SIZE) { - config->buffer.out_size = MQTT_CLIENT_BUFFER_SIZE; - } + // Keep the output buffer (used to build the CONNECT/PUBLISH frames) in lockstep + // with the input buffer. setBufferSize() above only sets buffer.size, so out_size + // must be set here. The previous conditional only ever shrank out_size or set it + // from 0 — when a slot was reconfigured from a small non-JWT buffer (512) up to + // the JWT buffer (896), out_size stayed at 512 and the JWT CONNECT frame (username + // + ~537-768B token) overflowed it, producing esp-mqtt "Connect message cannot be + // created". Always matching MQTT_CLIENT_BUFFER_SIZE fixes the grow case. + config->buffer.out_size = MQTT_CLIENT_BUFFER_SIZE; #endif } }