From 753eb72f7f3300ca22f65efc3e61b8587fd51707 Mon Sep 17 00:00:00 2001 From: agessaman Date: Sat, 25 Apr 2026 21:43:20 -0700 Subject: [PATCH] feat(mqtt): add EastIdahoMesh preset and enhance CLI preset listing Added the EastIdahoMesh preset to the MQTT bridge implementation, increasing the total number of built-in presets. Updated the CLI to support listing available MQTT presets with pagination, improving user experience when configuring MQTT connections. Adjusted related documentation to reflect these changes. --- MQTT_IMPLEMENTATION.md | 10 +++-- src/helpers/CommonCLI.cpp | 77 ++++++++++++++++++++++++++++++++++++++- src/helpers/MQTTPresets.h | 3 +- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/MQTT_IMPLEMENTATION.md b/MQTT_IMPLEMENTATION.md index f66f3888..8e907f61 100644 --- a/MQTT_IMPLEMENTATION.md +++ b/MQTT_IMPLEMENTATION.md @@ -77,7 +77,7 @@ get mqtt.status The MQTT bridge implementation provides: - Up to 6 MQTT connection slots with built-in presets -- Built-in presets for LetsMesh Analyzer (US/EU), MeshMapper, MeshRank, Waev, Meshomatic, CascadiaMesh, and TennMesh +- Built-in presets for LetsMesh Analyzer (US/EU), MeshMapper, MeshRank, Waev, Meshomatic, CascadiaMesh, EastIdahoMesh, and TennMesh - Custom broker support with username/password authentication - JWT (Ed25519 device signing) authentication for most preset brokers; TennMesh uses a fixed username/password (plain MQTT) - WSS (WebSocket Secure), direct MQTT/TLS, and plain MQTT (TennMesh) transport @@ -107,6 +107,7 @@ The MQTT bridge uses a slot-based architecture with up to 6 concurrent connectio | `nashmesh` | mqtt://mqtt.nashme.sh:1883 | Username/password (fixed in firmware) | Plain MQTT | | `chimesh` | wss://mqtt.chimesh.org:443 | JWT (Ed25519) | WSS | | `meshat.se` | mqtts://mqtt.meshat.se:8883 | Username/password (fixed in firmware) | MQTT over TLS | +| `eastidahomesh` | wss://broker.eastidahomesh.net:443 | None | WSS | | `custom` | User-configured | Username/Password | MQTT or WSS | | `none` | (disabled) | — | — | @@ -237,6 +238,7 @@ Each slot (1-6) supports the following commands: - `set mqttN.preset nashmesh` - Set slot N to NashMesh - `set mqttN.preset chimesh` - Set slot N to ChicagolandMesh - `set mqttN.preset meshat.se` - Set slot N to Meshat.se +- `set mqttN.preset eastidahomesh` - Set slot N to EastIdahoMesh (WSS/TLS, no auth; packets on `meshcore/{IATA}/{PUBLIC_KEY}/packets`) - `set mqttN.preset custom` - Set slot N to custom broker (configure server/port/username/password) - `set mqttN.preset none` - Disable slot N - `set mqttN.server ` - Set custom server hostname for slot N @@ -313,7 +315,7 @@ When a slot's preset is `custom`, you can define a custom topic template using p If no custom topic is set, custom slots default to: `meshcore/{iata}/{device}/{type}` -**Note:** Topic templates only apply to `custom` preset slots. Built-in presets (analyzer-us, analyzer-eu, meshmapper, meshrank, tennmesh, etc.) always use their hardcoded topic format. +**Note:** Topic templates only apply to `custom` preset slots. Built-in presets (analyzer-us, analyzer-eu, meshmapper, meshrank, eastidahomesh, tennmesh, etc.) always use their hardcoded topic format. ### MQTT Shared Commands @@ -322,6 +324,8 @@ These settings apply across all MQTT slots: #### Get Commands - `get mqtt.origin` - Get device origin name - `get mqtt.iata` - Get IATA code +- `get mqtt.presets` - List available MQTT presets (paginated, comma-separated) +- `get mqtt.presets ` - Continue list from index shown in `... next:` - `get mqtt.status` - Get MQTT status summary (connection info per slot) - `get mqtt.packets` - Get packet message setting (on/off) - `get mqtt.raw` - Get raw message setting (on/off) @@ -504,7 +508,7 @@ Minimal raw packet data for map integration. ### Slot-Based Preset System - Up to 6 concurrent MQTT connections (with PSRAM), 2 without PSRAM -- Built-in presets for LetsMesh Analyzer (US/EU), MeshMapper, MeshRank, Waev, Meshomatic, CascadiaMesh, and TennMesh +- Built-in presets for LetsMesh Analyzer (US/EU), MeshMapper, MeshRank, Waev, Meshomatic, CascadiaMesh, EastIdahoMesh, and TennMesh - Custom broker support with username/password auth and custom topic templates - JWT (Ed25519) for most preset brokers; MeshRank uses token-in-topic; TennMesh uses fixed username/password over plain MQTT - WSS (WebSocket Secure), direct MQTT over TLS, and plain MQTT (TennMesh) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 0853d82a..d75a3c94 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -41,6 +41,64 @@ static uint32_t _atoi(const char* sp) { return n; } +#ifdef WITH_MQTT_BRIDGE +static int getMQTTPresetNameCount() { + // Include virtual presets accepted by CLI parser. + return MQTT_PRESET_COUNT + 2; // built-ins + custom + none +} + +static const char* getMQTTPresetNameByIndex(int index) { + if (index < MQTT_PRESET_COUNT) return MQTT_PRESETS[index].name; + if (index == MQTT_PRESET_COUNT) return MQTT_PRESET_CUSTOM; + if (index == MQTT_PRESET_COUNT + 1) return MQTT_PRESET_NONE; + return nullptr; +} + +static void formatMQTTPresetListReply(char* reply, size_t reply_size, int start) { + if (!reply || reply_size == 0) return; + reply[0] = '\0'; + + const int total = getMQTTPresetNameCount(); + if (start < 0 || start >= total) { + snprintf(reply, reply_size, "Error: preset list start must be 0-%d", total - 1); + return; + } + + // Keep room for continuation marker and null terminator. + const size_t reserve_for_next = 18; + size_t used = 0; + bool wrote_any = false; + + int index = start; + while (index < total) { + const char* name = getMQTTPresetNameByIndex(index); + if (!name) break; + size_t name_len = strlen(name); + size_t room = reply_size - used; + if (room <= reserve_for_next) break; + size_t needed = name_len + (wrote_any ? 1 : 0); // comma separator + if (needed >= room - reserve_for_next) break; + if (wrote_any) { + reply[used++] = ','; + } + memcpy(reply + used, name, name_len); + used += name_len; + reply[used] = '\0'; + wrote_any = true; + index++; + } + + if (!wrote_any) { + strcpy(reply, "Error: list page too small"); + return; + } + + if (index < total) { + snprintf(reply + used, reply_size - used, "... next:%d", index); + } +} +#endif + static bool isValidName(const char *n) { while (*n) { if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; @@ -1362,7 +1420,7 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep } } } else { - strcpy(reply, "Error: valid presets are: analyzer-us, analyzer-eu, meshmapper, meshrank, waev, meshomatic, cascadiamesh, tennmesh, nashmesh, custom, none"); + strcpy(reply, "Error: unknown preset. Use 'get mqtt.presets'"); } } else if (memcmp(subcmd, "server ", 7) == 0) { StrHelper::strncpy(_prefs->mqtt_slot_host[slot], &subcmd[7], sizeof(_prefs->mqtt_slot_host[slot])); @@ -1605,6 +1663,23 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep sprintf(reply, "> %s", _prefs->mqtt_origin); } else if (memcmp(config, "mqtt.iata", 9) == 0) { sprintf(reply, "> %s", _prefs->mqtt_iata); + } else if (memcmp(config, "mqtt.presets", 12) == 0 && (config[12] == '\0' || config[12] == ' ')) { + int start = 0; + if (config[12] == ' ') { + const char* start_arg = &config[13]; + if (*start_arg == '\0') { + strcpy(reply, "Error: usage get mqtt.presets [start]"); + return; + } + for (const char* sp = start_arg; *sp; sp++) { + if (*sp < '0' || *sp > '9') { + strcpy(reply, "Error: usage get mqtt.presets [start]"); + return; + } + } + start = (int)_atoi(start_arg); + } + formatMQTTPresetListReply(reply, 160, start); } else if (memcmp(config, "mqtt.status", 11) == 0) { MQTTBridge::formatMqttStatusReply(reply, 160, _prefs); } else if (memcmp(config, "mqtt.packets", 12) == 0) { diff --git a/src/helpers/MQTTPresets.h b/src/helpers/MQTTPresets.h index 801a1837..81734538 100644 --- a/src/helpers/MQTTPresets.h +++ b/src/helpers/MQTTPresets.h @@ -99,7 +99,7 @@ static const char ISRG_ROOT_X1[] PROGMEM = "-----END CERTIFICATE-----\n"; // Number of built-in presets -static const int MQTT_PRESET_COUNT = 11; +static const int MQTT_PRESET_COUNT = 12; // Built-in preset definitions (stored in flash) static const MQTTPresetDef MQTT_PRESETS[MQTT_PRESET_COUNT] = { @@ -114,6 +114,7 @@ static const MQTTPresetDef MQTT_PRESETS[MQTT_PRESET_COUNT] = { { "nashmesh", "mqtt://mqtt.nashme.sh:1883", nullptr, nullptr, MQTT_AUTH_USERPASS, MQTT_TOPIC_MESHCORE, 0, true, 55, "meshdev", "large4cats" }, { "chimesh", "wss://mqtt.chimesh.org:443", "mqtt.chimesh.org", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, { "meshat.se", "mqtts://mqtt.meshat.se:8883", nullptr, ISRG_ROOT_X1, MQTT_AUTH_USERPASS, MQTT_TOPIC_MESHCORE, 0, true, 55, "msh", "msh" }, + { "eastidahomesh","wss://broker.eastidahomesh.net:443", nullptr, ISRG_ROOT_X1, MQTT_AUTH_NONE, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr }, }; // Find a preset by name, returns nullptr if not found