From 787af7b0261aece4bb4f689ea788eae061cb343e Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 25 Oct 2025 23:41:50 -0700 Subject: [PATCH] added timezone handling, updated mqtt packet output to use the proper timezones for timestamp and time/date, updated mqtt packet to use raw data instead of parsed packet structure, updated packet upload to use proper packet hash --- examples/simple_repeater/MyMesh.cpp | 12 +++- src/helpers/CommonCLI.cpp | 20 ++++--- src/helpers/CommonCLI.h | 2 - src/helpers/MQTTMessageBuilder.cpp | 88 ++++++++++++++++++++++++++++- src/helpers/MQTTMessageBuilder.h | 14 +++++ src/helpers/bridges/MQTTBridge.cpp | 37 +++++++++--- src/helpers/bridges/MQTTBridge.h | 18 ++++++ variants/heltec_v3/platformio.ini | 2 +- 8 files changed, 171 insertions(+), 22 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index ab874412..9568b332 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -343,12 +343,19 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { mesh::Utils::printHex(Serial, raw, len); Serial.println(); #endif + +#ifdef WITH_BRIDGE + if (_prefs.bridge_enabled) { + // Store raw radio data for MQTT messages + bridge.storeRawRadioData(raw, len, snr, rssi); + } +#endif } void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { #ifdef WITH_BRIDGE if (_prefs.bridge_pkt_src == 1) { - bridge.sendPacket(pkt); + bridge.onPacketReceived(pkt); } #endif @@ -712,7 +719,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc // bridge defaults _prefs.bridge_enabled = 1; // enabled _prefs.bridge_delay = 500; // milliseconds - _prefs.bridge_pkt_src = 0; // logTx + _prefs.bridge_pkt_src = 1; // logRx (RX packets) _prefs.bridge_baud = 115200; // baud rate _prefs.bridge_channel = 1; // channel 1 @@ -729,6 +736,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.mqtt_status_enabled = 1; // enabled _prefs.mqtt_packets_enabled = 1; // enabled _prefs.mqtt_raw_enabled = 0; // disabled + _prefs.mqtt_tx_enabled = 0; // disabled (RX only for now) _prefs.mqtt_status_interval = 300000; // 5 minutes // WiFi defaults diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 89a1c483..bcccdf7a 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -70,7 +70,6 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 -<<<<<<< HEAD file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 (from upstream) // MQTT settings - skip reading from main prefs file (now stored separately) // For backward compatibility, we'll skip these bytes if they exist in old files @@ -178,7 +177,6 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 -<<<<<<< HEAD file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 (from upstream) // MQTT settings - no longer saved here (stored in separate /mqtt_prefs file) // Write zeros/padding to maintain file format compatibility @@ -401,8 +399,10 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", _prefs->mqtt_status_enabled ? "on" : "off"); } else if (memcmp(config, "mqtt.packets", 12) == 0) { sprintf(reply, "> %s", _prefs->mqtt_packets_enabled ? "on" : "off"); - } else if (memcmp(config, "mqtt.raw", 8) == 0) { - sprintf(reply, "> %s", _prefs->mqtt_raw_enabled ? "on" : "off"); + } else if (memcmp(config, "mqtt.raw", 8) == 0) { + sprintf(reply, "> %s", _prefs->mqtt_raw_enabled ? "on" : "off"); + } else if (memcmp(config, "mqtt.tx", 7) == 0) { + sprintf(reply, "> %s", _prefs->mqtt_tx_enabled ? "on" : "off"); } else if (memcmp(config, "mqtt.interval", 13) == 0) { sprintf(reply, "> %d", (uint32_t)_prefs->mqtt_status_interval); } else if (memcmp(config, "wifi.ssid", 9) == 0) { @@ -623,10 +623,14 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->mqtt_packets_enabled = memcmp(&config[13], "on", 2) == 0; savePrefs(); strcpy(reply, "OK"); - } else if (memcmp(config, "mqtt.raw ", 9) == 0) { - _prefs->mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0; - savePrefs(); - strcpy(reply, "OK"); + } else if (memcmp(config, "mqtt.raw ", 9) == 0) { + _prefs->mqtt_raw_enabled = memcmp(&config[9], "on", 2) == 0; + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "mqtt.tx ", 8) == 0) { + _prefs->mqtt_tx_enabled = memcmp(&config[8], "on", 2) == 0; + savePrefs(); + strcpy(reply, "OK"); } else if (memcmp(config, "mqtt.interval ", 15) == 0) { uint32_t interval = _atoi(&config[15]); if (interval >= 1000 && interval <= 3600000) { // 1 second to 1 hour diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 58e04356..6781795d 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -111,8 +111,6 @@ struct MQTTPrefs { uint8_t mqtt_analyzer_eu_enabled; // Enable EU analyzer server char mqtt_owner_public_key[65]; // Owner public key (hex string, same length as repeater public key) char mqtt_email[64]; // Owner email address for matching nodes with owners -======= ->>>>>>> 0b761e8 (Add timezone support to MQTT Bridge and CLI configuration) }; #endif diff --git a/src/helpers/MQTTMessageBuilder.cpp b/src/helpers/MQTTMessageBuilder.cpp index 987042f3..f9cd8942 100644 --- a/src/helpers/MQTTMessageBuilder.cpp +++ b/src/helpers/MQTTMessageBuilder.cpp @@ -2,6 +2,7 @@ #include #include #include +#include "MeshCore.h" int MQTTMessageBuilder::buildStatusMessage( const char* origin, @@ -146,9 +147,11 @@ int MQTTMessageBuilder::buildPacketJSON( int packet_type = packet->getPayloadType(); const char* route_str = getRouteTypeString(packet->isRouteDirect() ? 1 : 0); - // Create hash (simplified - use first 8 bytes of packet) + // Create proper packet hash using MeshCore's calculatePacketHash method char hash_str[17]; - bytesToHex(packet->payload, min(8, (int)packet->payload_len), hash_str, sizeof(hash_str)); + uint8_t packet_hash[MAX_HASH_SIZE]; + packet->calculatePacketHash(packet_hash); + bytesToHex(packet_hash, MAX_HASH_SIZE, hash_str, sizeof(hash_str)); // Build path string for direct packets char path_str[128] = ""; @@ -173,6 +176,87 @@ int MQTTMessageBuilder::buildPacketJSON( ); } +int MQTTMessageBuilder::buildPacketJSONFromRaw( + const uint8_t* raw_data, + int raw_len, + mesh::Packet* packet, + bool is_tx, + const char* origin, + const char* origin_id, + float snr, + float rssi, + Timezone* timezone, + char* buffer, + size_t buffer_size +) { + 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"); + } + + // Get UTC time (since system timezone is UTC, time() returns UTC) + struct tm* utc_timeinfo = gmtime(&now); + + // Format time and date (ALWAYS UTC) + char time_str[16]; + char date_str[16]; + if (utc_timeinfo) { + strftime(time_str, sizeof(time_str), "%H:%M:%S", utc_timeinfo); + strftime(date_str, sizeof(date_str), "%d/%m/%Y", utc_timeinfo); + } else { + strcpy(time_str, "12:00:00"); + strcpy(date_str, "01/01/2024"); + } + + // Convert raw radio data to hex (this includes radio headers) + char raw_hex[512]; + bytesToHex(raw_data, raw_len, raw_hex, sizeof(raw_hex)); + + // Get packet characteristics from the parsed packet + int packet_type = packet->getPayloadType(); + const char* route_str = getRouteTypeString(packet->isRouteDirect() ? 1 : 0); + + // Create proper packet hash using MeshCore's calculatePacketHash method + char hash_str[17]; + uint8_t packet_hash[MAX_HASH_SIZE]; + packet->calculatePacketHash(packet_hash); + bytesToHex(packet_hash, MAX_HASH_SIZE, hash_str, sizeof(hash_str)); + + // Build path string for direct packets + char path_str[128] = ""; + if (packet->isRouteDirect() && packet->path_len > 0) { + // Simplified path representation + snprintf(path_str, sizeof(path_str), "path_len_%d", packet->path_len); + } + + return buildPacketMessage( + origin, origin_id, timestamp, + is_tx ? "tx" : "rx", + time_str, date_str, + raw_len, // Use actual raw radio data length + packet_type, route_str, + packet->payload_len, + raw_hex, + snr, // Use actual SNR from radio + rssi, // Use actual RSSI from radio + hash_str, + packet->isRouteDirect() ? path_str : nullptr, + buffer, buffer_size + ); +} + int MQTTMessageBuilder::buildRawJSON( mesh::Packet* packet, const char* origin, diff --git a/src/helpers/MQTTMessageBuilder.h b/src/helpers/MQTTMessageBuilder.h index 9b39c712..3c96f90b 100644 --- a/src/helpers/MQTTMessageBuilder.h +++ b/src/helpers/MQTTMessageBuilder.h @@ -128,6 +128,20 @@ public: size_t buffer_size ); + static int buildPacketJSONFromRaw( + const uint8_t* raw_data, + int raw_len, + mesh::Packet* packet, + bool is_tx, + const char* origin, + const char* origin_id, + float snr, + float rssi, + Timezone* timezone, + char* buffer, + size_t buffer_size + ); + /** * Convert packet to raw JSON message * diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 27b79154..252a0cf4 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -11,7 +11,7 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc _active_brokers(0), _queue_head(0), _queue_tail(0), _queue_count(0), _last_status_publish(0), _status_interval(300000), // 5 minutes default _ntp_client(_ntp_udp, "pool.ntp.org", 0, 60000), _last_ntp_sync(0), _ntp_synced(false), - _timezone(nullptr) { + _timezone(nullptr), _last_raw_len(0), _last_snr(0), _last_rssi(0), _last_raw_timestamp(0) { // Initialize default values strncpy(_origin, "MeshCore-Repeater", sizeof(_origin) - 1); @@ -22,6 +22,7 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc _status_enabled = true; _packets_enabled = true; _raw_enabled = false; + _tx_enabled = false; // Disable TX packets by default // Initialize packet queue memset(_packet_queue, 0, sizeof(_packet_queue)); @@ -71,6 +72,7 @@ void MQTTBridge::begin() { _status_enabled = _prefs->mqtt_status_enabled; _packets_enabled = _prefs->mqtt_packets_enabled; _raw_enabled = _prefs->mqtt_raw_enabled; + _tx_enabled = _prefs->mqtt_tx_enabled; _status_interval = _prefs->mqtt_status_interval; MQTT_DEBUG_PRINTLN("Origin: %s, IATA: %s", _origin, _iata); @@ -173,9 +175,9 @@ void MQTTBridge::onPacketReceived(mesh::Packet *packet) { } void MQTTBridge::sendPacket(mesh::Packet *packet) { - if (!_initialized || !_packets_enabled) return; + if (!_initialized || !_packets_enabled || !_tx_enabled) return; - // Queue packet for transmission + // Queue packet for transmission (only if TX enabled) queuePacket(packet, true); } @@ -319,10 +321,20 @@ void MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx) { strncpy(origin_id, _device_id, sizeof(origin_id) - 1); origin_id[sizeof(origin_id) - 1] = '\0'; - // Build packet message - int len = MQTTMessageBuilder::buildPacketJSON( - packet, is_tx, _origin, origin_id, _timezone, json_buffer, sizeof(json_buffer) - ); + // Build packet message using raw radio data if available + int len; + if (_last_raw_len > 0 && (millis() - _last_raw_timestamp) < 1000) { + // Use raw radio data (within 1 second of packet) + len = MQTTMessageBuilder::buildPacketJSONFromRaw( + _last_raw_data, _last_raw_len, packet, is_tx, _origin, origin_id, + _last_snr, _last_rssi, _timezone, json_buffer, sizeof(json_buffer) + ); + } else { + // Fallback to reconstructed packet data + len = MQTTMessageBuilder::buildPacketJSON( + packet, is_tx, _origin, origin_id, _timezone, json_buffer, sizeof(json_buffer) + ); + } if (len > 0) { // Publish to all connected brokers @@ -450,6 +462,17 @@ void MQTTBridge::setBoardModel(const char* board_model) { _board_model[sizeof(_board_model) - 1] = '\0'; } +void MQTTBridge::storeRawRadioData(const uint8_t* raw_data, int len, float snr, float rssi) { + if (len > 0 && len <= sizeof(_last_raw_data)) { + memcpy(_last_raw_data, raw_data, len); + _last_raw_len = len; + _last_snr = snr; + _last_rssi = rssi; + _last_raw_timestamp = millis(); + MQTT_DEBUG_PRINTLN("Stored raw radio data: %d bytes, SNR=%.1f, RSSI=%.1f", len, snr, rssi); + } +} + void MQTTBridge::setMessageTypes(bool status, bool packets, bool raw) { _status_enabled = status; _packets_enabled = packets; diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index 58483c13..74615db7 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -74,6 +74,7 @@ private: bool _status_enabled; bool _packets_enabled; bool _raw_enabled; + bool _tx_enabled; unsigned long _last_status_publish; unsigned long _status_interval; @@ -99,6 +100,13 @@ private: // Timezone handling Timezone* _timezone; + // Raw radio data storage + uint8_t _last_raw_data[256]; + int _last_raw_len; + float _last_snr; + float _last_rssi; + unsigned long _last_raw_timestamp; + // Internal methods void connectToBrokers(); void processPacketQueue(); @@ -212,6 +220,16 @@ public: */ void setBoardModel(const char* board_model); + /** + * Stores raw radio data for MQTT messages + * + * @param raw_data Raw radio transmission data + * @param len Length of raw data + * @param snr Signal-to-noise ratio + * @param rssi Received signal strength indicator + */ + void storeRawRadioData(const uint8_t* raw_data, int len, float snr, float rssi); + /** * Enable/disable message types * diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 489fd43e..8439d85c 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -104,7 +104,7 @@ lib_deps = [env:Heltec_v3_repeater_bridge_mqtt] extends = Heltec_lora32_v3 -upload_port = /dev/cu.usbserial-2 +upload_port = /dev/cu.usbserial-4 build_flags = ${Heltec_lora32_v3.build_flags} -D DISPLAY_CLASS=SSD1306Display