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:
Adam Gessaman committed 2026-05-25 12:04:23 -07:00
1 parent 39eea82b3b
commit a1d47277b4
4 files changed
+51 -9

No files matched your search

+8
View File
@@ -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]));
+10 -3
View File
@@ -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
+28 -3
View File
@@ -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()) {