From 7e0ffdbea372c8ecf4483e18398b004090d681c7 Mon Sep 17 00:00:00 2001 From: agessaman Date: Mon, 6 Jul 2026 12:34:53 -0700 Subject: [PATCH] feat(mqtt): clarify custom endpoint requirements in code and documentation Updated MQTT configuration to succeed when port is defined in mqttN.server but not explicitly set in mqttN.port. Updated documentation to specify that when a full URL with a scheme is provided, the port setting is optional. Added an example for local development using a plain WebSocket URL, enhancing clarity for users configuring custom MQTT brokers. --- MQTT_IMPLEMENTATION.md | 14 +++++++++++++- src/helpers/bridges/MQTTBridge.cpp | 11 +++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/MQTT_IMPLEMENTATION.md b/MQTT_IMPLEMENTATION.md index 4ec105f4..2e855457 100644 --- a/MQTT_IMPLEMENTATION.md +++ b/MQTT_IMPLEMENTATION.md @@ -328,10 +328,11 @@ For community brokers that support the MeshCore JWT auth protocol (same as the b ```bash set mqtt3.preset custom set mqtt3.server wss://my-broker.example.com:443/mqtt -set mqtt3.port 443 set mqtt3.audience my-broker.example.com ``` +When the server is given as a full URL with a scheme (`mqtt://`, `mqtts://`, `ws://`, `wss://`), `set mqttN.port` is optional — an explicit port in the URL is used as-is, and without one the scheme's default port applies. + When `audience` is set, the device will: - Connect with username `v1_{PUBLIC_KEY}` and an Ed25519-signed JWT as the password - Automatically renew tokens before expiry (default 24h lifetime) @@ -342,6 +343,17 @@ To revert a slot back to username/password auth, clear the audience: set mqtt3.audience ``` +#### Example: Local Development Broker (plain WebSocket, no TLS) + +For local development (e.g. a LAN broker without SSL termination), use a full `ws://` URL. Non-TLS transports (`ws://`, `mqtt://`) skip certificate verification entirely: +```bash +set mqtt3.preset custom +set mqtt3.server ws://192.168.1.50:9001/mqtt +set mqtt3.audience my-local-broker +``` + +The `audience` line is optional — set it if your local broker uses the same JWT auth as the production presets, or use `set mqtt3.username` / `set mqtt3.password` instead. + #### Example: Custom Broker with Custom Topic Template ```bash set mqtt3.preset custom diff --git a/src/helpers/bridges/MQTTBridge.cpp b/src/helpers/bridges/MQTTBridge.cpp index 9dcf5561..98876e56 100644 --- a/src/helpers/bridges/MQTTBridge.cpp +++ b/src/helpers/bridges/MQTTBridge.cpp @@ -107,13 +107,20 @@ static bool isWiFiConfigValid(const NodePrefs* prefs) { #ifdef WITH_MQTT_BRIDGE +// A custom slot endpoint is complete if a port is set, or if the host is a +// full URI with a scheme (esp-mqtt applies scheme default ports, and the URI +// builder in setupSlot() preserves any embedded port/path). +static bool customEndpointComplete(const char* host, uint16_t port) { + return host[0] != '\0' && (port != 0 || strstr(host, "://") != nullptr); +} + bool MQTTBridge::isConfigValid(const NodePrefs* prefs) { if (!prefs || !isWiFiConfigValid(prefs)) return false; for (int i = 0; i < RUNTIME_MQTT_SLOTS; i++) { const char* preset_name = prefs->mqtt_slot_preset[i]; if (preset_name[0] == '\0' || strcmp(preset_name, MQTT_PRESET_NONE) == 0) continue; if (strcmp(preset_name, MQTT_PRESET_CUSTOM) == 0) { - if (prefs->mqtt_slot_host[i][0] != '\0' && prefs->mqtt_slot_port[i] != 0) return true; + if (customEndpointComplete(prefs->mqtt_slot_host[i], prefs->mqtt_slot_port[i])) return true; } else if (findMQTTPreset(preset_name) != nullptr) { return true; } @@ -1925,7 +1932,7 @@ void MQTTBridge::applySlotPreset(int slot_index, const char* preset_name) { slot.enabled = true; slot.preset = nullptr; // Custom broker settings should already be set via setSlotCustomBroker - if (_initialized && strlen(slot.host) > 0 && slot.port > 0) { + if (_initialized && customEndpointComplete(slot.host, slot.port)) { setupSlot(slot_index); } return;