Preserve MQTT settings across slot changes
This commit is contained in:
1 parent
aa8caa4ed2
commit
cb79dcd4e3
16 files changed
+2209
-1999
No files matched your search
@@ -437,7 +437,7 @@ Each slot (1-6) supports the following commands:
|
||||
- `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.port <port>` - Set custom server port for slot N (0-65535; 0 clears the override and uses a full URI's default port)
|
||||
- `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)
|
||||
|
||||
@@ -253,7 +253,10 @@ get mqtt.status
|
||||
|
||||
Set `mqtt1.username` / `mqtt1.password` if required by that broker. Configure only
|
||||
as many slots as the board supports. `set mqtt1.preset none` disables slot 1;
|
||||
other configured slots remain enabled. `set mqtt.status off` disables status
|
||||
other configured slots remain enabled, within the build's supported slot count.
|
||||
Disabled and partially configured slots remain saved across reboot. For a custom
|
||||
server URL, `set mqtt1.port 0` clears the port override so the URI supplies it.
|
||||
`set mqtt.status off` disables status
|
||||
messages, not MQTT itself. See the [MQTT reference](https://github.com/mikecarper/MeshCore/blob/keymindCascade/MQTT_IMPLEMENTATION.md)
|
||||
for presets, TLS, credentials, and slot limits.
|
||||
|
||||
|
||||
@@ -1906,8 +1906,10 @@ void MyMesh::begin(bool has_display, bool radio_available) {
|
||||
_mqtt_enabled = CompanionMqttSetupPortal::loadEnabled();
|
||||
// Keep settings available to the USB CLI even when this boot selects BLE.
|
||||
applyMQTTDefaults(&_mqtt_prefs);
|
||||
_mqtt_configured = CompanionMqttSetupPortal::loadStoredConfig(_mqtt_prefs);
|
||||
if (!_mqtt_configured) applyMQTTDefaults(&_mqtt_prefs);
|
||||
if (!CompanionMqttSetupPortal::loadStoredConfig(_mqtt_prefs)) {
|
||||
applyMQTTDefaults(&_mqtt_prefs);
|
||||
}
|
||||
_mqtt_configured = CompanionMqttSetupPortal::hasConfiguredSlot(_mqtt_prefs);
|
||||
// Companion WiFi owns the connection and its mesh-wifi NVS setting is
|
||||
// canonical. Keep MQTT reconnects from restoring a stale MQTT-pref value.
|
||||
_mqtt_prefs.wifi_power_save = getCompanionWiFiPowerSave();
|
||||
@@ -3356,7 +3358,8 @@ void MyMesh::onConfigBatchEnd() {
|
||||
// loadStoredConfig only replaces its destination after validation. Pass
|
||||
// the live preferences directly: another 2.8 KB copy here nests with the
|
||||
// loader's scratch copy and NVS calls, overflowing the ESP32 loop stack.
|
||||
_mqtt_configured = CompanionMqttSetupPortal::loadStoredConfig(_mqtt_prefs);
|
||||
CompanionMqttSetupPortal::loadStoredConfig(_mqtt_prefs);
|
||||
_mqtt_configured = CompanionMqttSetupPortal::hasConfiguredSlot(_mqtt_prefs);
|
||||
// The standalone Companion setting remains canonical even if MQTT config
|
||||
// verification reloaded an older copy of this field.
|
||||
_mqtt_prefs.wifi_power_save = getCompanionWiFiPowerSave();
|
||||
@@ -3751,9 +3754,9 @@ void MyMesh::execCommand(char* cmd, char* reply) {
|
||||
changed = wcCopyValue(_mqtt_prefs.mqtt_slot_host[slot],
|
||||
sizeof(_mqtt_prefs.mqtt_slot_host[slot]), value);
|
||||
} else if (strcmp(field, "port") == 0) {
|
||||
long port;
|
||||
if (!wcParseLong(value, 1, 65535, port)) {
|
||||
strcpy(reply, "Error: port must be 1-65535");
|
||||
uint16_t port;
|
||||
if (!mesh::cli::parseMqttPort(value, port)) {
|
||||
strcpy(reply, "Error: port must be 0-65535 (0 uses the URI default)");
|
||||
return;
|
||||
}
|
||||
_mqtt_prefs.mqtt_slot_port[slot] = static_cast<uint16_t>(port);
|
||||
|
||||
@@ -98,6 +98,7 @@ def firmware_commands():
|
||||
found.add(lit.strip())
|
||||
return found - NOT_OFFERED
|
||||
ROUND_TRIPS = [
|
||||
("set mqtt1.port 0", "get mqtt1.port", "0"),
|
||||
("set usb.logging on", "get usb.logging", "on"),
|
||||
("set usb.logging off", "get usb.logging", "off"),
|
||||
("set powersaving on", "get powersaving", "on"),
|
||||
|
||||
@@ -467,9 +467,9 @@ def apply_slot_set(cfg, idx, field, val):
|
||||
try:
|
||||
p = int(val)
|
||||
except ValueError:
|
||||
return False, "Error: port must be between 1 and 65535"
|
||||
if not (1 <= p <= 65535):
|
||||
return False, "Error: port must be between 1 and 65535"
|
||||
return False, "Error: port must be 0-65535 (0 uses the URI default)"
|
||||
if not (0 <= p <= 65535):
|
||||
return False, "Error: port must be 0-65535 (0 uses the URI default)"
|
||||
slot["port"] = p
|
||||
return True, "OK"
|
||||
if field == "filter":
|
||||
|
||||
@@ -675,6 +675,14 @@ inline bool parseUnsignedIntegerStrict(const char* text, uint32_t& result) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Zero clears a custom port override; a full broker URI supplies its default.
|
||||
inline bool parseMqttPort(const char* text, uint16_t& result) {
|
||||
uint32_t value;
|
||||
if (!parseUnsignedIntegerStrict(text, value) || value > 65535) return false;
|
||||
result = static_cast<uint16_t>(value);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Copy and split a bounded CLI argument string on runs of spaces/tabs. This
|
||||
// provides scanf-like token handling without pulling scanf into firmware. The
|
||||
// caller controls the maximum accepted token count through field_capacity.
|
||||
|
||||
@@ -574,14 +574,14 @@ bool CommonCLI::handleObserverSetCmd(uint32_t sender_timestamp, const char* conf
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(subcmd, "port ", 5) == 0) {
|
||||
int port = atoi(&subcmd[5]);
|
||||
if (port > 0 && port <= 65535) {
|
||||
uint16_t port;
|
||||
if (mesh::cli::parseMqttPort(&subcmd[5], port)) {
|
||||
_mqtt_prefs.mqtt_slot_port[slot] = port;
|
||||
saveObserverPrefs();
|
||||
_callbacks->restartBridgeSlot(slot);
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error: port must be between 1 and 65535");
|
||||
strcpy(reply, "Error: port must be 0-65535 (0 uses the URI default)");
|
||||
}
|
||||
} else if (memcmp(subcmd, "username ", 9) == 0) {
|
||||
if (valueTooLong(&subcmd[9], sizeof(_mqtt_prefs.mqtt_slot_username[slot]), reply, "username")) return true;
|
||||
|
||||
@@ -612,31 +612,38 @@ bool CompanionMqttSetupPortal::loadStoredConfig(MQTTPrefs& prefs) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* preset = loaded.mqtt_slot_preset[0];
|
||||
const bool custom = strcmp(preset, MQTT_PRESET_CUSTOM) == 0;
|
||||
const MQTTPresetDef* preset_def = custom ? nullptr : findMQTTPreset(preset);
|
||||
if (!preset[0] || (!custom && !preset_def)) return false;
|
||||
if (custom) {
|
||||
const char* host = loaded.mqtt_slot_host[0];
|
||||
if (!host[0]
|
||||
|| (loaded.mqtt_slot_port[0] == 0 && strstr(host, "://") == nullptr)
|
||||
|| (!loaded.mqtt_slot_topic[0][0] && !isThreeLetterCode(loaded.mqtt_iata))) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (preset_def->topic_style == MQTT_TOPIC_MESHCORE
|
||||
&& !isThreeLetterCode(loaded.mqtt_iata)) return false;
|
||||
if (preset_def->topic_style == MQTT_TOPIC_MESHRANK
|
||||
&& !loaded.mqtt_slot_token[0][0]) return false;
|
||||
if (mqttPresetNeedsSlotCredentials(preset_def)
|
||||
&& (!loaded.mqtt_slot_username[0][0] || !loaded.mqtt_slot_password[0][0])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Incomplete and disabled slots are valid saved settings. Readiness is a
|
||||
// separate decision, so disabling slot 1 or saving a broker in stages cannot
|
||||
// discard all slots and credentials on the next boot.
|
||||
prefs = loaded;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CompanionMqttSetupPortal::hasConfiguredSlot(const MQTTPrefs& prefs) {
|
||||
for (int i = 0; i < RUNTIME_MQTT_SLOTS; ++i) {
|
||||
const char* preset = prefs.mqtt_slot_preset[i];
|
||||
if (!preset[0] || strcmp(preset, MQTT_PRESET_NONE) == 0) continue;
|
||||
if (strcmp(preset, MQTT_PRESET_CUSTOM) == 0) {
|
||||
const char* host = prefs.mqtt_slot_host[i];
|
||||
if (host[0] && (prefs.mqtt_slot_port[i] != 0 || strstr(host, "://"))
|
||||
&& (prefs.mqtt_slot_topic[i][0] || isThreeLetterCode(prefs.mqtt_iata))) {
|
||||
return true;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const MQTTPresetDef* def = findMQTTPreset(preset);
|
||||
if (!def) continue;
|
||||
if (def->topic_style == MQTT_TOPIC_MESHCORE
|
||||
&& !isThreeLetterCode(prefs.mqtt_iata)) continue;
|
||||
if (def->topic_style == MQTT_TOPIC_MESHRANK
|
||||
&& !prefs.mqtt_slot_token[i][0]) continue;
|
||||
if (mqttPresetNeedsSlotUsername(def) && !prefs.mqtt_slot_username[i][0]) continue;
|
||||
if (mqttPresetNeedsSlotPassword(def) && !prefs.mqtt_slot_password[i][0]) continue;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CompanionMqttSetupPortal::loadEnabled() {
|
||||
Preferences nvs;
|
||||
if (!nvs.begin(NVS_NAMESPACE, false)) return true;
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
bool isActive() const { return _active; }
|
||||
|
||||
static bool loadStoredConfig(MQTTPrefs& prefs);
|
||||
static bool hasConfiguredSlot(const MQTTPrefs& prefs);
|
||||
static bool saveStoredConfig(const MQTTPrefs& prefs);
|
||||
static bool loadEnabled();
|
||||
static bool saveEnabled(bool enabled);
|
||||
|
||||
+1960
-1958
File diff suppressed because it is too large.
Load diff
@@ -1671,7 +1671,9 @@ void WebConfigServer::handleStatus(AsyncWebServerRequest* req) {
|
||||
#ifdef WITH_MQTT_BRIDGE
|
||||
doc["runtime_slots"] = has_mqtt ? RUNTIME_MQTT_SLOTS : 0;
|
||||
doc["max_slots"] = has_mqtt ? MAX_MQTT_SLOTS : 0;
|
||||
doc["active_slots"] = has_mqtt ? MQTTBridge::getMaxActiveSlots() : 0;
|
||||
const int active_slots = MQTTBridge::getMaxActiveSlots();
|
||||
doc["active_slots"] = has_mqtt
|
||||
? (active_slots < RUNTIME_MQTT_SLOTS ? active_slots : RUNTIME_MQTT_SLOTS) : 0;
|
||||
#else
|
||||
doc["runtime_slots"] = 0;
|
||||
doc["max_slots"] = 0;
|
||||
|
||||
@@ -114,6 +114,22 @@ TEST(CLICommandUtils, NormalizesCapitalizedSetVerb) {
|
||||
EXPECT_STREQ("set altpath 600000,0d2784,F8DADA", command);
|
||||
}
|
||||
|
||||
TEST(CLICommandUtils, MqttPortSupportsDefaultAndRejectsMalformedValues) {
|
||||
uint16_t port = 99;
|
||||
EXPECT_TRUE(mesh::cli::parseMqttPort("0", port));
|
||||
EXPECT_EQ(0, port);
|
||||
EXPECT_TRUE(mesh::cli::parseMqttPort("1883", port));
|
||||
EXPECT_EQ(1883, port);
|
||||
EXPECT_TRUE(mesh::cli::parseMqttPort("65535", port));
|
||||
EXPECT_EQ(65535, port);
|
||||
const char* invalid[] = {nullptr, "", "-1", "65536", "1883junk", "999999999999999999999"};
|
||||
for (const char* value : invalid) {
|
||||
port = 99;
|
||||
EXPECT_FALSE(mesh::cli::parseMqttPort(value, port));
|
||||
EXPECT_EQ(99, port);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CLICommandUtils, LoggingToggleHasOneGrammarForEveryRole) {
|
||||
bool enabled = false, reboot = false;
|
||||
ASSERT_TRUE(mesh::cli::parseLoggingToggle("on", enabled, reboot));
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise the firmware's MQTT NVS loader and slot readiness on the host."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SOURCE = ROOT / "src/helpers/CompanionMqttSetupPortal.cpp"
|
||||
|
||||
|
||||
def function(source, signature):
|
||||
start = source.index(signature)
|
||||
opening = source.index("{", start)
|
||||
depth = 1
|
||||
end = opening + 1
|
||||
while depth:
|
||||
depth += (source[end] == "{") - (source[end] == "}")
|
||||
end += 1
|
||||
return source[start:end] + "\n"
|
||||
|
||||
|
||||
PREAMBLE = r"""
|
||||
#include <cassert>
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <helpers/CompanionMqttPrefsNvs.h>
|
||||
constexpr int MAX_MQTT_SLOTS = MQTT_PREFS_SLOT_COUNT;
|
||||
constexpr int RUNTIME_MQTT_SLOTS = TEST_RUNTIME_SLOTS;
|
||||
constexpr int MQTT_TOPIC_MESHCORE = 0, MQTT_TOPIC_MESHRANK = 1;
|
||||
const char* MQTT_PRESET_CUSTOM = "custom";
|
||||
const char* MQTT_PRESET_NONE = "none";
|
||||
const char* NVS_NAMESPACE = "mesh-mqtt";
|
||||
const char* NVS_VERSION_KEY = "version";
|
||||
const char* NVS_PREFS_KEY = "prefs";
|
||||
struct MQTTPresetDef { int topic_style; bool username, password; };
|
||||
const MQTTPresetDef* findMQTTPreset(const char* name) {
|
||||
static MQTTPresetDef analyzer{MQTT_TOPIC_MESHCORE, false, false};
|
||||
static MQTTPresetDef credentials{MQTT_TOPIC_MESHCORE, true, false};
|
||||
if (strcmp(name, "analyzer-us") == 0) return &analyzer;
|
||||
if (strcmp(name, "username-only") == 0) return &credentials;
|
||||
return nullptr;
|
||||
}
|
||||
bool mqttPresetNeedsSlotUsername(const MQTTPresetDef* p) { return p->username; }
|
||||
bool mqttPresetNeedsSlotPassword(const MQTTPresetDef* p) { return p->password; }
|
||||
void applyMQTTDefaults(MQTTPrefs* p) {
|
||||
memset(p, 0, sizeof(*p));
|
||||
for (auto& preset : p->mqtt_slot_preset) strcpy(preset, "none");
|
||||
}
|
||||
MQTTPrefs saved;
|
||||
struct Preferences {
|
||||
bool begin(const char*, bool) { return true; }
|
||||
bool isKey(const char*) { return true; }
|
||||
uint16_t getUShort(const char*, uint16_t) { return MQTT_PREFS_VERSION; }
|
||||
size_t getBytesLength(const char*) { return CompanionMqttPrefsNvs::kWriteSize; }
|
||||
size_t getBytes(const char*, void* dest, size_t length) {
|
||||
memcpy(dest, &saved, length);
|
||||
return length;
|
||||
}
|
||||
void end() {}
|
||||
};
|
||||
struct CompanionMqttSetupPortal {
|
||||
static bool loadStoredConfig(MQTTPrefs&);
|
||||
static bool hasConfiguredSlot(const MQTTPrefs&);
|
||||
};
|
||||
"""
|
||||
|
||||
SCENARIOS = r"""
|
||||
int main() {
|
||||
MQTTPrefs result{};
|
||||
applyMQTTDefaults(&saved);
|
||||
// Disabled slots still carry reusable settings and credentials.
|
||||
strcpy(saved.mqtt_slot_password[0], "keep-this-secret");
|
||||
strcpy(saved.mqtt_slot_host[0], "configured.example");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(memcmp(&result, &saved, sizeof(saved)) == 0);
|
||||
assert(!CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
|
||||
// Saving a custom broker in stages must not reset it on the next boot.
|
||||
strcpy(saved.mqtt_slot_preset[0], "custom");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(strcmp(result.mqtt_slot_host[0], "configured.example") == 0);
|
||||
assert(!CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
|
||||
// A full URI needs no separate port override. Topic replaces IATA for custom.
|
||||
strcpy(saved.mqtt_slot_host[0], "mqtt://127.0.0.1:1");
|
||||
strcpy(saved.mqtt_slot_topic[0], "test/{device}/{type}");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
|
||||
if (RUNTIME_MQTT_SLOTS > 1) {
|
||||
// Slot 1 may be disabled while another slot is the only usable broker.
|
||||
applyMQTTDefaults(&saved);
|
||||
strcpy(saved.mqtt_slot_preset[1], "custom");
|
||||
strcpy(saved.mqtt_slot_host[1], "mqtt://127.0.0.1:1");
|
||||
strcpy(saved.mqtt_slot_topic[1], "test");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
assert(strcmp(result.mqtt_slot_preset[0], "none") == 0);
|
||||
}
|
||||
|
||||
// Saved slots outside this image's runtime limit remain stored, but do not run.
|
||||
applyMQTTDefaults(&saved);
|
||||
strcpy(saved.mqtt_slot_preset[MAX_MQTT_SLOTS - 1], "custom");
|
||||
strcpy(saved.mqtt_slot_host[MAX_MQTT_SLOTS - 1], "mqtt://127.0.0.1:1");
|
||||
strcpy(saved.mqtt_slot_topic[MAX_MQTT_SLOTS - 1], "test");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(!CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
assert(memcmp(&result, &saved, sizeof(saved)) == 0);
|
||||
|
||||
applyMQTTDefaults(&saved);
|
||||
strcpy(saved.mqtt_slot_preset[0], "analyzer-us");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(!CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
strcpy(saved.mqtt_iata, "SEA");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
strcpy(saved.mqtt_slot_preset[0], "username-only");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(!CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
strcpy(saved.mqtt_slot_username[0], "user");
|
||||
assert(CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(CompanionMqttSetupPortal::hasConfiguredSlot(result));
|
||||
|
||||
// Corrupt records still fail without overwriting live preferences.
|
||||
MQTTPrefs before = result;
|
||||
saved.mqtt_tx_enabled = 255;
|
||||
assert(!CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(memcmp(&before, &result, sizeof(before)) == 0);
|
||||
saved.mqtt_tx_enabled = 0;
|
||||
memset(saved.mqtt_slot_password[0], 'X', sizeof(saved.mqtt_slot_password[0]));
|
||||
assert(!CompanionMqttSetupPortal::loadStoredConfig(result));
|
||||
assert(memcmp(&before, &result, sizeof(before)) == 0);
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
class CompanionMqttConfigTests(unittest.TestCase):
|
||||
def test_saved_settings_and_readiness_are_independent(self):
|
||||
source = SOURCE.read_text()
|
||||
actual = source[source.index("template <size_t N>"):
|
||||
source.index("static bool readLine")]
|
||||
for signature in (
|
||||
"static bool isThreeLetterCode",
|
||||
"bool CompanionMqttSetupPortal::loadStoredConfig",
|
||||
"bool CompanionMqttSetupPortal::hasConfiguredSlot",
|
||||
):
|
||||
actual += function(source, signature)
|
||||
with tempfile.TemporaryDirectory(prefix="companion-mqtt-config-") as tmp:
|
||||
cpp = Path(tmp) / "config.cpp"
|
||||
cpp.write_text(PREAMBLE + actual + SCENARIOS)
|
||||
for slots in (1, 3):
|
||||
with self.subTest(runtime_slots=slots):
|
||||
binary = Path(tmp) / ("config-%d" % slots)
|
||||
compile_result = subprocess.run([
|
||||
"c++", "-std=c++17", "-Wall", "-Wextra", "-Werror",
|
||||
"-DWITH_MQTT_BRIDGE=1", "-DTEST_RUNTIME_SLOTS=%d" % slots,
|
||||
"-I", str(ROOT / "src"), str(cpp), "-o", str(binary),
|
||||
], text=True, capture_output=True)
|
||||
self.assertEqual(compile_result.returncode, 0, compile_result.stderr)
|
||||
result = subprocess.run([str(binary)], text=True, capture_output=True)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -15,6 +15,7 @@ struct MQTTBridge { void end(); };
|
||||
struct CompanionMqttSetupPortal {
|
||||
static bool saveStoredConfig(const MQTTPrefs&);
|
||||
static bool loadStoredConfig(MQTTPrefs&);
|
||||
static bool hasConfiguredSlot(const MQTTPrefs&);
|
||||
};
|
||||
struct MyMesh {
|
||||
void onConfigBatchEnd();
|
||||
|
||||
@@ -116,7 +116,7 @@ class WebConfigUiRuntimeTest(unittest.TestCase):
|
||||
|
||||
def test_companion_console_runs_commands_and_uses_role_suggestions(self):
|
||||
status, config = self.setup_values()
|
||||
status.update(mode="lan", cli=True, mqtt=True, active_slots=1,
|
||||
status.update(mode="lan", cli=True, mqtt=True, active_slots=5, runtime_slots=1,
|
||||
password_supported=False)
|
||||
prelude = """
|
||||
<script>
|
||||
@@ -188,7 +188,7 @@ class WebConfigUiRuntimeTest(unittest.TestCase):
|
||||
"set mqtt1.preset ", "get name", "get radio"):
|
||||
self.assertIn(command, suggestions)
|
||||
for command in ("erase", "password ", "setperm ", "get prv.key",
|
||||
"set bridge.enabled ", "get mqtt1.password"):
|
||||
"set bridge.enabled ", "get mqtt1.password", "set mqtt2.preset "):
|
||||
self.assertNotIn(command, suggestions)
|
||||
|
||||
def test_console_stays_hidden_when_disabled_or_on_setup_ap(self):
|
||||
|
||||
+3
-3
@@ -741,7 +741,7 @@ function boot(){
|
||||
st.passwordSupported=!!s.password_supported;st.needsPassword=!!s.needs_password;
|
||||
st.radioOptional=!!s.radio_optional;
|
||||
st.needsSetup=!!s.needs_setup;st.caps=s.capabilities||0;
|
||||
st.nslots=st.hasMqtt?(s.active_slots||s.runtime_slots||0):0;
|
||||
st.nslots=st.hasMqtt?Math.min(s.active_slots||s.runtime_slots||0,s.runtime_slots||s.active_slots||0):0;
|
||||
if(s.max_cmds>0)CLI_MAX=s.max_cmds;
|
||||
st.fw=s.fw||"";st.build=s.build_date||"";st.role=s.role||"";st.board=s.board||"";
|
||||
$("#h-name").textContent=s.name||"MeshCore";
|
||||
@@ -1128,7 +1128,7 @@ function buildSlots(sel){
|
||||
+slotFieldHtml(i,"username","text","Username","")
|
||||
+slotFieldHtml(i,"password","password","Password","")
|
||||
+slotFieldHtml(i,"server","text","Server host","")
|
||||
+slotFieldHtml(i,"port","number","Port","")
|
||||
+slotFieldHtml(i,"port","number","Port","0 uses the default port of a full server URL")
|
||||
+slotFieldHtml(i,"topic","text","Topic template","Placeholders: {iata} {device} {type} {token}")
|
||||
+slotFieldHtml(i,"audience","text","JWT audience","Optional - blank for user/pass auth")
|
||||
+packetFilterHtml(i)
|
||||
@@ -1944,7 +1944,7 @@ var CLI_KEYS=[
|
||||
var CLI_SLOT=[
|
||||
["preset","preset",1],
|
||||
["server","custom broker hostname"],
|
||||
["port","broker port {1-65535}"],
|
||||
["port","broker port {0-65535; 0 uses URI default}"],
|
||||
["username","username"],
|
||||
["password","password"],
|
||||
["token","token (required by some presets)"],
|
||||
|
||||
Reference in new issue
Block a user