mirror of
https://github.com/mikecarper/MeshCore.git
synced 2026-09-27 01:48:10 +00:00
Add INW Mesh preset and enhance credential handling in MQTTBridge
- Introduced a new MQTT preset for INW Mesh with username/password authentication. - Updated documentation to include the new preset and its configuration requirements. - Enhanced credential handling in MQTTBridge to support dynamic username/password assignment for presets requiring per-slot credentials. - Improved feedback in CLI for users to set credentials when needed.
This commit is contained in:
@@ -111,6 +111,7 @@ The MQTT bridge uses a slot-based architecture with up to 6 concurrent connectio
|
||||
| `coloradomesh` | wss://mqtt.meshcore.coloradomesh.org:1883 | JWT (Ed25519) | WSS |
|
||||
| `meshcore-ca-1` | mqtt1.meshcore.ca:443 | JWT (Ed25519) | WSS |
|
||||
| `meshcore-ca-2` | mqtt2.meshcore.ca:443 | JWT (Ed25519) | WSS |
|
||||
| `inwmesh` | scope.inwmesh.org:8883 | Username/password (per slot via `mqttN.username` / `mqttN.password`) | MQTT over TLS |
|
||||
| `custom` | User-configured | Username/Password | MQTT or WSS |
|
||||
| `none` | (disabled) | — | — |
|
||||
|
||||
@@ -288,18 +289,19 @@ Each slot (1-6) supports the following commands:
|
||||
- `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 coloradomesh` - Set slot N to ColoradoMesh
|
||||
- `set mqttN.preset inwmesh` - Set slot N to INW Mesh Scope (`mqtts://scope.inwmesh.org:8883`; set `mqttN.username` and `mqttN.password`)
|
||||
- `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
|
||||
- `set mqttN.port <port>` - Set custom server port for slot N (1-65535)
|
||||
- `set mqttN.username <username>` - Set custom username for slot N
|
||||
- `set mqttN.password <password>` - Set custom password for slot N
|
||||
- `set mqttN.username <username>` - Set username for slot N (`custom` preset, or presets like `inwmesh` that require per-device credentials)
|
||||
- `set mqttN.password <password>` - Set password for slot N (`custom` preset, or presets like `inwmesh` that require per-device credentials)
|
||||
- `set mqttN.token <token>` - Set per-slot token (required for MeshRank preset)
|
||||
- `set mqttN.topic <template>` - Set custom topic template (custom preset only, see below)
|
||||
- `set mqttN.audience <audience>` - Set JWT audience for custom slot (enables Ed25519 JWT auth)
|
||||
- `set mqttN.audience` - Clear JWT audience (reverts to username/password auth)
|
||||
|
||||
**Note:** Custom server/port/username/password settings only apply when the slot's preset is `custom`.
|
||||
**Note:** Custom server/port settings only apply when the slot's preset is `custom`. Username/password also apply to built-in presets that use per-slot credentials (e.g. `inwmesh`); other userpass presets (`tennmesh`, `nashmesh`, `meshat.se`) ship fixed credentials in firmware.
|
||||
|
||||
#### Example: Configure MeshRank on Slot 3
|
||||
```bash
|
||||
|
||||
@@ -1452,6 +1452,12 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (p && p->topic_style == MQTT_TOPIC_MESHCORE &&
|
||||
(strlen(_prefs->mqtt_iata) == 0 || strcmp(_prefs->mqtt_iata, "XXX") == 0)) {
|
||||
sprintf(reply, "OK - slot %d preset: %s (run 'set mqtt.iata <airport_code>' to publish)", slot + 1, preset_name);
|
||||
} else if (p && mqttPresetNeedsSlotCredentials(p) &&
|
||||
(_prefs->mqtt_slot_username[slot][0] == '\0' ||
|
||||
_prefs->mqtt_slot_password[slot][0] == '\0')) {
|
||||
sprintf(reply,
|
||||
"OK - slot %d preset: %s (run 'set mqtt%d.username <user>' and 'set mqtt%d.password <pass>' to connect)",
|
||||
slot + 1, preset_name, slot + 1, slot + 1);
|
||||
} else {
|
||||
sprintf(reply, "OK - slot %d preset: %s", slot + 1, preset_name);
|
||||
}
|
||||
@@ -1475,10 +1481,12 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} else if (memcmp(subcmd, "username ", 9) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_slot_username[slot], &subcmd[9], sizeof(_prefs->mqtt_slot_username[slot]));
|
||||
savePrefs();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(subcmd, "password ", 9) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_slot_password[slot], &subcmd[9], sizeof(_prefs->mqtt_slot_password[slot]));
|
||||
savePrefs();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(subcmd, "token ", 6) == 0) {
|
||||
StrHelper::strncpy(_prefs->mqtt_slot_token[slot], &subcmd[6], sizeof(_prefs->mqtt_slot_token[slot]));
|
||||
|
||||
@@ -36,10 +36,16 @@ struct MQTTPresetDef {
|
||||
unsigned long token_lifetime; // JWT token lifetime in seconds (0 = use default 86400)
|
||||
bool allow_retain; // Whether the broker allows the MQTT retain flag
|
||||
uint16_t keepalive; // MQTT keepalive in seconds (0 = library default 120s)
|
||||
const char* userpass_username; // MQTT_AUTH_USERPASS: broker username; else nullptr
|
||||
const char* userpass_password; // MQTT_AUTH_USERPASS: broker password; else nullptr
|
||||
const char* userpass_username; // MQTT_AUTH_USERPASS: embedded username, or nullptr to use mqttN.username
|
||||
const char* userpass_password; // MQTT_AUTH_USERPASS: embedded password, or nullptr to use mqttN.password
|
||||
};
|
||||
|
||||
// True when preset uses MQTT_AUTH_USERPASS but credentials come from slot prefs (mqttN.username/password).
|
||||
static inline bool mqttPresetNeedsSlotCredentials(const MQTTPresetDef* preset) {
|
||||
return preset && preset->auth_type == MQTT_AUTH_USERPASS &&
|
||||
(!preset->userpass_username || !preset->userpass_password);
|
||||
}
|
||||
|
||||
// Google Trust Services - GTS Root R4 (used by LetsMesh Analyzer)
|
||||
static const char GTS_ROOT_R4[] PROGMEM =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
@@ -99,7 +105,7 @@ static const char ISRG_ROOT_X1[] PROGMEM =
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
// Number of built-in presets
|
||||
static const int MQTT_PRESET_COUNT = 17;
|
||||
static const int MQTT_PRESET_COUNT = 18;
|
||||
|
||||
// Built-in preset definitions (stored in flash)
|
||||
static const MQTTPresetDef MQTT_PRESETS[MQTT_PRESET_COUNT] = {
|
||||
@@ -121,6 +127,7 @@ static const MQTTPresetDef MQTT_PRESETS[MQTT_PRESET_COUNT] = {
|
||||
{ "dutchmeshcore-2", "wss://collector2.dutchmeshcore.nl:443/mqtt", "collector2.dutchmeshcore.nl", GTS_ROOT_R4, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr },
|
||||
{ "meshcore-ca-1", "wss://mqtt1.meshcore.ca:443/mqtt", "mqtt1.meshcore.ca", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr },
|
||||
{ "meshcore-ca-2", "wss://mqtt2.meshcore.ca:443/mqtt", "mqtt2.meshcore.ca", ISRG_ROOT_X1, MQTT_AUTH_JWT, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr },
|
||||
{ "inwmesh", "mqtts://scope.inwmesh.org:8883", nullptr, ISRG_ROOT_X1, MQTT_AUTH_USERPASS, MQTT_TOPIC_MESHCORE, 0, true, 55, nullptr, nullptr },
|
||||
};
|
||||
|
||||
// Find a preset by name, returns nullptr if not found
|
||||
|
||||
@@ -544,6 +544,12 @@ void MQTTBridge::begin() {
|
||||
if (preset) {
|
||||
_slots[i].enabled = true;
|
||||
_slots[i].preset = preset;
|
||||
if (mqttPresetNeedsSlotCredentials(preset)) {
|
||||
strncpy(_slots[i].username, _prefs->mqtt_slot_username[i], sizeof(_slots[i].username) - 1);
|
||||
_slots[i].username[sizeof(_slots[i].username) - 1] = '\0';
|
||||
strncpy(_slots[i].password, _prefs->mqtt_slot_password[i], sizeof(_slots[i].password) - 1);
|
||||
_slots[i].password[sizeof(_slots[i].password) - 1] = '\0';
|
||||
}
|
||||
} else {
|
||||
MQTT_DEBUG_PRINTLN("MQTT%d: unknown preset '%s', disabling", i + 1, preset_name);
|
||||
_slots[i].enabled = false;
|
||||
@@ -1143,9 +1149,12 @@ void MQTTBridge::setupSlot(int index) {
|
||||
if (slot.auth_token[0] != '\0') {
|
||||
slot.client->setCredentials(_jwt_username, slot.auth_token);
|
||||
}
|
||||
} else if (slot.preset->auth_type == MQTT_AUTH_USERPASS &&
|
||||
slot.preset->userpass_username && slot.preset->userpass_password) {
|
||||
slot.client->setCredentials(slot.preset->userpass_username, slot.preset->userpass_password);
|
||||
} else if (slot.preset->auth_type == MQTT_AUTH_USERPASS) {
|
||||
if (slot.preset->userpass_username && slot.preset->userpass_password) {
|
||||
slot.client->setCredentials(slot.preset->userpass_username, slot.preset->userpass_password);
|
||||
} else if (strlen(slot.username) > 0) {
|
||||
slot.client->setCredentials(slot.username, slot.password);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Custom broker slot — build persistent URI
|
||||
@@ -1817,6 +1826,12 @@ void MQTTBridge::applySlotPreset(int slot_index, const char* preset_name) {
|
||||
if (preset) {
|
||||
slot.enabled = true;
|
||||
slot.preset = preset;
|
||||
if (mqttPresetNeedsSlotCredentials(preset)) {
|
||||
strncpy(slot.username, _prefs->mqtt_slot_username[slot_index], sizeof(slot.username) - 1);
|
||||
slot.username[sizeof(slot.username) - 1] = '\0';
|
||||
strncpy(slot.password, _prefs->mqtt_slot_password[slot_index], sizeof(slot.password) - 1);
|
||||
slot.password[sizeof(slot.password) - 1] = '\0';
|
||||
}
|
||||
if (_initialized) {
|
||||
char reason[80];
|
||||
if (!isSlotReady(slot_index, reason, sizeof(reason))) {
|
||||
@@ -1967,6 +1982,16 @@ bool MQTTBridge::isSlotReady(int index, char* reason_buf, size_t reason_size) co
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (mqttPresetNeedsSlotCredentials(slot.preset)) {
|
||||
if (_prefs->mqtt_slot_username[index][0] == '\0') {
|
||||
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.username <user>", index + 1);
|
||||
return false;
|
||||
}
|
||||
if (_prefs->mqtt_slot_password[index][0] == '\0') {
|
||||
if (reason_buf) snprintf(reason_buf, reason_size, "set mqtt%d.password <pass>", index + 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Custom slot without a topic template uses meshcore format, needs IATA
|
||||
if (_prefs->mqtt_slot_topic[index][0] == '\0' && !isIATAValid()) {
|
||||
|
||||
Reference in New Issue
Block a user