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.
This commit is contained in:
agessaman
2026-04-25 21:43:20 -07:00
parent 32449e62cc
commit 753eb72f7f
3 changed files with 85 additions and 5 deletions
+7 -3
View File
@@ -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 <hostname>` - 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 <start>` - Continue list from index shown in `... next:<idx>`
- `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)
+76 -1
View File
@@ -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) {
+2 -1
View File
@@ -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