diff --git a/MQTT_IMPLEMENTATION.md b/MQTT_IMPLEMENTATION.md index aab4e04c..95a4a833 100644 --- a/MQTT_IMPLEMENTATION.md +++ b/MQTT_IMPLEMENTATION.md @@ -2,6 +2,53 @@ This document describes the MQTT bridge implementation that allows MeshCore repeaters to uplink packet data to multiple MQTT brokers. +## Quick Start Guide + +### Essential Commands to Get MQTT Repeater Running + +**1. Connect to device console via repeater login or serial console (115200 baud)** +```bash +# Connect to device via serial +``` + +**2. Configure WiFi Credentials** +```bash +set wifi.ssid YourWiFiNetwork +set wifi.pwd YourWiFiPassword +``` + +**3. Reboot to Connect to WiFi** +```bash +reboot +``` + +**4. Verify Configuration** +```bash +get wifi.ssid +get bridge.enabled +get mqtt.origin +get mqtt.iata +``` +If you wish to change mqtt.iata, use `set mqtt.iata XXX` + +**5. Restart Bridge (if needed)** +```bash +# Option A: Toggle bridge off then on +set bridge.enabled off +set bridge.enabled on + +# Option B: Full device reboot +reboot +``` + +**That's it!** The device will now: +- Connect to WiFi automatically +- Start uplinking mesh packets to Let's Mesh Analyzer +- Publish to both custom MQTT broker and Let's Mesh Analyzer servers +- Use device name as MQTT origin (set automatically) + +--- + ## Overview The MQTT bridge implementation provides: diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index d096d14b..269e9c7b 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -2,6 +2,10 @@ #include #include +#ifdef WITH_MQTT_BRIDGE + #include +#endif + #define AUTO_OFF_MILLIS 20000 // 20 seconds #define BOOT_SCREEN_MILLIS 4000 // 4 seconds @@ -77,6 +81,17 @@ void UITask::renderCurrScreen() { _display->setCursor(0, 30); sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr); _display->print(tmp); + +#ifdef WITH_MQTT_BRIDGE + // Display IP address for MQTT bridge devices + if (WiFi.status() == WL_CONNECTED) { + IPAddress ip = WiFi.localIP(); + _display->setCursor(0, 40); + _display->setColor(DisplayDriver::LIGHT); + snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + _display->print(tmp); + } +#endif } } diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 70275458..4ce194d6 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -6,6 +6,37 @@ // Using ESP32's built-in certificate bundle +// Helper function to strip quotes from strings (both single and double quotes) +static void stripQuotes(char* str, size_t max_len) { + if (!str || max_len == 0) return; + + size_t len = strlen(str); + if (len == 0) return; + + // Remove leading quote (single or double) + if (str[0] == '"' || str[0] == '\'') { + memmove(str, str + 1, len); + len--; + } + + // Remove trailing quote (single or double) + if (len > 0 && (str[len-1] == '"' || str[len-1] == '\'')) { + str[len-1] = '\0'; + } +} + +// Helper function to check if WiFi credentials are valid +static bool isWiFiConfigValid(const NodePrefs* prefs) { + // Check if WiFi SSID is configured (not empty) + if (strlen(prefs->wifi_ssid) == 0) { + return false; + } + + // WiFi password can be empty for open networks, so we don't check it + + return true; +} + #ifdef WITH_MQTT_BRIDGE MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc, mesh::LocalIdentity *identity) @@ -29,11 +60,11 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc _raw_enabled = false; _tx_enabled = false; // Disable TX packets by default - // Initialize MQTT server settings with defaults - strncpy(_prefs->mqtt_server, "your-mqtt-broker.com", sizeof(_prefs->mqtt_server) - 1); - _prefs->mqtt_port = 1883; - strncpy(_prefs->mqtt_username, "your-username", sizeof(_prefs->mqtt_username) - 1); - strncpy(_prefs->mqtt_password, "your-password", sizeof(_prefs->mqtt_password) - 1); + // Initialize MQTT server settings with defaults (empty/null values) + _prefs->mqtt_server[0] = '\0'; // Empty string + _prefs->mqtt_port = 0; // Invalid port + _prefs->mqtt_username[0] = '\0'; // Empty string + _prefs->mqtt_password[0] = '\0'; // Empty string // Override with build flags if defined #ifdef MQTT_SERVER @@ -59,14 +90,19 @@ MQTTBridge::MQTTBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCCloc void MQTTBridge::begin() { MQTT_DEBUG_PRINTLN("Initializing MQTT Bridge..."); - // Validate configuration once and store the result - _config_valid = isMQTTConfigValid(); - if (!_config_valid) { - MQTT_DEBUG_PRINTLN("MQTT Bridge initialization skipped - configuration not valid"); + // Check if WiFi credentials are configured first + if (!isWiFiConfigValid(_prefs)) { + MQTT_DEBUG_PRINTLN("MQTT Bridge initialization skipped - WiFi credentials not configured"); return; } - MQTT_DEBUG_PRINTLN("MQTT configuration is valid - proceeding with initialization"); + // Validate custom MQTT broker configuration (optional) + _config_valid = isMQTTConfigValid(); + if (!_config_valid) { + MQTT_DEBUG_PRINTLN("No valid custom MQTT server configured - analyzer servers will still work"); + } else { + MQTT_DEBUG_PRINTLN("Custom MQTT server configuration is valid"); + } // Update origin and IATA from preferences strncpy(_origin, _prefs->mqtt_origin, sizeof(_origin) - 1); @@ -74,32 +110,19 @@ void MQTTBridge::begin() { strncpy(_iata, _prefs->mqtt_iata, sizeof(_iata) - 1); _iata[sizeof(_iata) - 1] = '\0'; + // Strip quotes from MQTT server configuration if present + stripQuotes(_prefs->mqtt_server, sizeof(_prefs->mqtt_server)); + stripQuotes(_prefs->mqtt_username, sizeof(_prefs->mqtt_username)); + stripQuotes(_prefs->mqtt_password, sizeof(_prefs->mqtt_password)); + // Strip quotes from origin if present MQTT_DEBUG_PRINTLN("Origin before stripping: '%s'", _origin); - size_t origin_len = strlen(_origin); - if (origin_len > 0) { - if (_origin[0] == '"') { - memmove(_origin, _origin + 1, origin_len); - origin_len--; - } - if (origin_len > 0 && _origin[origin_len-1] == '"') { - _origin[origin_len-1] = '\0'; - } - } + stripQuotes(_origin, sizeof(_origin)); MQTT_DEBUG_PRINTLN("Origin after stripping: '%s'", _origin); // Strip quotes from IATA if present MQTT_DEBUG_PRINTLN("IATA before stripping: '%s'", _iata); - size_t iata_len = strlen(_iata); - if (iata_len > 0) { - if (_iata[0] == '"') { - memmove(_iata, _iata + 1, iata_len); - iata_len--; - } - if (iata_len > 0 && _iata[iata_len-1] == '"') { - _iata[iata_len-1] = '\0'; - } - } + stripQuotes(_iata, sizeof(_iata)); MQTT_DEBUG_PRINTLN("IATA after stripping: '%s'", _iata); // Update enabled flags from preferences @@ -251,6 +274,10 @@ bool MQTTBridge::isConfigValid(const NodePrefs* prefs) { return true; } +bool MQTTBridge::isReady() const { + return _initialized && isWiFiConfigValid(_prefs); +} + void MQTTBridge::loop() { if (!_initialized) return; @@ -276,9 +303,19 @@ void MQTTBridge::loop() { } void MQTTBridge::onPacketReceived(mesh::Packet *packet) { - if (!_initialized || !_packets_enabled || !_config_valid) { - MQTT_DEBUG_PRINTLN("Packet received but not processing - initialized: %s, packets_enabled: %s, config_valid: %s", - _initialized ? "true" : "false", _packets_enabled ? "true" : "false", _config_valid ? "true" : "false"); + if (!_initialized || !_packets_enabled) { + MQTT_DEBUG_PRINTLN("Packet received but not processing - initialized: %s, packets_enabled: %s", + _initialized ? "true" : "false", _packets_enabled ? "true" : "false"); + return; + } + + // Check if we have any valid brokers to send to + bool has_valid_brokers = _config_valid || + (_analyzer_us_enabled && _analyzer_us_client) || + (_analyzer_eu_enabled && _analyzer_eu_client); + + if (!has_valid_brokers) { + MQTT_DEBUG_PRINTLN("Packet received but no valid brokers available - discarding"); return; } @@ -288,7 +325,7 @@ void MQTTBridge::onPacketReceived(mesh::Packet *packet) { } void MQTTBridge::sendPacket(mesh::Packet *packet) { - if (!_initialized || !_packets_enabled || !_tx_enabled || !_config_valid) return; + if (!_initialized || !_packets_enabled || !_tx_enabled) return; // Queue packet for transmission (only if TX enabled) queuePacket(packet, true); @@ -374,9 +411,18 @@ void MQTTBridge::connectToBrokers() { } void MQTTBridge::processPacketQueue() { - if (_queue_count == 0 || !isAnyBrokerConnected() || !_config_valid) { + if (_queue_count == 0) { + return; + } + + // Check if we have any connected brokers (custom or analyzer) + bool has_connected_brokers = isAnyBrokerConnected() || + (_analyzer_us_enabled && _analyzer_us_client && _analyzer_us_client->connected()) || + (_analyzer_eu_enabled && _analyzer_eu_client && _analyzer_eu_client->connected()); + + if (!has_connected_brokers) { if (_queue_count > 0) { - MQTT_DEBUG_PRINTLN("Queue has %d packets but no brokers connected or config invalid", _queue_count); + MQTT_DEBUG_PRINTLN("Queue has %d packets but no brokers connected", _queue_count); } return; } @@ -470,7 +516,7 @@ void MQTTBridge::publishStatus() { } void MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx) { - if (!packet || !_config_valid) return; + if (!packet) return; char json_buffer[1024]; char origin_id[65]; @@ -495,22 +541,24 @@ void MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx) { } if (len > 0) { - // Publish to all connected brokers - for (int i = 0; i < MAX_MQTT_BROKERS_COUNT; i++) { - if (_brokers[i].enabled && _brokers[i].connected) { - char topic[128]; - snprintf(topic, sizeof(topic), "meshcore/%s/%s/packets", _iata, _device_id); - MQTT_DEBUG_PRINTLN("Publishing packet to topic: %s", topic); - - // Set broker for this connection (PsychicMqttClient uses URI format) - char broker_uri[128]; - snprintf(broker_uri, sizeof(broker_uri), "mqtt://%s:%d", _brokers[i].host, _brokers[i].port); - _mqtt_client->setServer(broker_uri); - _mqtt_client->publish(topic, 1, false, json_buffer, strlen(json_buffer)); // qos=1, retained=false + // Publish to custom brokers (only if config is valid) + if (_config_valid) { + for (int i = 0; i < MAX_MQTT_BROKERS_COUNT; i++) { + if (_brokers[i].enabled && _brokers[i].connected) { + char topic[128]; + snprintf(topic, sizeof(topic), "meshcore/%s/%s/packets", _iata, _device_id); + // MQTT_DEBUG_PRINTLN("Publishing packet to topic: %s", topic); + + // Set broker for this connection (PsychicMqttClient uses URI format) + char broker_uri[128]; + snprintf(broker_uri, sizeof(broker_uri), "mqtt://%s:%d", _brokers[i].host, _brokers[i].port); + _mqtt_client->setServer(broker_uri); + _mqtt_client->publish(topic, 1, false, json_buffer, strlen(json_buffer)); // qos=1, retained=false + } } } - // Also publish to Let's Mesh Analyzer servers + // Always publish to Let's Mesh Analyzer servers (independent of custom broker config) char analyzer_topic[128]; snprintf(analyzer_topic, sizeof(analyzer_topic), "meshcore/%s/%s/packets", _iata, _device_id); publishToAnalyzerServers(analyzer_topic, json_buffer, false); @@ -518,7 +566,7 @@ void MQTTBridge::publishPacket(mesh::Packet* packet, bool is_tx) { } void MQTTBridge::publishRaw(mesh::Packet* packet) { - if (!packet || !_config_valid) return; + if (!packet) return; char json_buffer[512]; char origin_id[65]; @@ -533,21 +581,23 @@ void MQTTBridge::publishRaw(mesh::Packet* packet) { ); if (len > 0) { - // Publish to all connected brokers - for (int i = 0; i < MAX_MQTT_BROKERS_COUNT; i++) { - if (_brokers[i].enabled && _brokers[i].connected) { - char topic[128]; - snprintf(topic, sizeof(topic), "meshcore/%s/%s/raw", _iata, _device_id); - - // Set broker for this connection (PsychicMqttClient uses URI format) - char broker_uri[128]; - snprintf(broker_uri, sizeof(broker_uri), "mqtt://%s:%d", _brokers[i].host, _brokers[i].port); - _mqtt_client->setServer(broker_uri); - _mqtt_client->publish(topic, 1, false, json_buffer, strlen(json_buffer)); // qos=1, retained=false + // Publish to custom brokers (only if config is valid) + if (_config_valid) { + for (int i = 0; i < MAX_MQTT_BROKERS_COUNT; i++) { + if (_brokers[i].enabled && _brokers[i].connected) { + char topic[128]; + snprintf(topic, sizeof(topic), "meshcore/%s/%s/raw", _iata, _device_id); + + // Set broker for this connection (PsychicMqttClient uses URI format) + char broker_uri[128]; + snprintf(broker_uri, sizeof(broker_uri), "mqtt://%s:%d", _brokers[i].host, _brokers[i].port); + _mqtt_client->setServer(broker_uri); + _mqtt_client->publish(topic, 1, false, json_buffer, strlen(json_buffer)); // qos=1, retained=false + } } } - // Also publish to Let's Mesh Analyzer servers + // Always publish to Let's Mesh Analyzer servers (independent of custom broker config) char analyzer_topic[128]; snprintf(analyzer_topic, sizeof(analyzer_topic), "meshcore/%s/%s/raw", _iata, _device_id); publishToAnalyzerServers(analyzer_topic, json_buffer, false); diff --git a/src/helpers/bridges/MQTTBridge.h b/src/helpers/bridges/MQTTBridge.h index bd7a991b..4f6c03c4 100644 --- a/src/helpers/bridges/MQTTBridge.h +++ b/src/helpers/bridges/MQTTBridge.h @@ -183,6 +183,13 @@ public: */ static bool isConfigValid(const NodePrefs* prefs); + /** + * Check if MQTT bridge is ready to operate (has WiFi credentials) + * + * @return true if WiFi credentials are configured and bridge can connect + */ + bool isReady() const; + /** * Main loop handler * - Maintains broker connections