Compare commits

...
Author SHA1 Message Date
Ben MeadorsandClaude Opus 4.8 d5bddf5111 fix(mqtt): drop undeclared jsonTopic refs in reinitTopics()
reinitTopics() assigned to a jsonTopic member that does not exist on this
branch (the MQTT class only has cryptTopic and mapTopic), so MQTT.cpp failed
to compile ("'jsonTopic' was not declared in this scope") and broke every
build that compiles it. Remove the jsonTopic lines so reinitTopics() rebuilds
exactly the topics the original constructor did.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 10:42:48 -05:00
copilot-swe-agent[bot]andGitHub 1c41163bc1 Merge remote-tracking branch 'origin/develop' into copilot/bug-fix-lora-region-mqtt-topic
# Conflicts:
#	src/mqtt/MQTT.cpp
2026-06-04 21:47:48 +00:00
copilot-swe-agent[bot]andGitHub f86eccd51b Format MQTT test with trunk style 2026-06-02 13:26:17 +00:00
Ben MeadorsandGitHub 4687db8283 Merge branch 'develop' into copilot/bug-fix-lora-region-mqtt-topic 2026-05-31 20:52:18 -04:00
Ben MeadorsandGitHub 25d55285ca Merge branch 'develop' into copilot/bug-fix-lora-region-mqtt-topic 2026-05-27 10:18:34 -05:00
copilot-swe-agent[bot]andGitHub 8c414262db Fix: call mqtt->reinitTopics() on region change via menuhandler 2026-05-27 13:45:12 +00:00
copilot-swe-agent[bot]andGitHub e9dafd810d Fix: update MQTT topics when LoRa region changes
When the LoRa region is changed via AdminModule::handleSetConfig,
moduleConfig.mqtt.root is updated (e.g. from msh/US to msh/EU_868)
but the running MQTT instance kept using the stale topic strings
(cryptTopic / jsonTopic / mapTopic) that were set at construction time.

Introduce MQTT::reinitTopics() which:
- resets the topic strings to their base values and prepends the
  current moduleConfig.mqtt.root, and
- disconnects from the broker so the next reconnect re-subscribes
  under the new topic prefix.

Call reinitTopics() from MQTT's constructor (replacing the inline
block) so the logic lives in one place, and call it from
AdminModule::handleSetConfig right after moduleConfig.mqtt.root is
rewritten on a region change.

Add a unit test (test_reinitTopicsUpdatesOnRegionChange) that verifies
both the updated subscriptions and the updated publish topic after a
simulated region change.
2026-05-27 13:25:48 +00:00
copilot-swe-agent[bot]andGitHub 349833d5d3 Initial plan 2026-05-27 12:56:33 +00:00
5 changed files with 74 additions and 9 deletions
+8
View File
@@ -23,6 +23,9 @@
#include "mesh/RadioLibInterface.h"
#include "modules/AdminModule.h"
#include "modules/CannedMessageModule.h"
#if !MESHTASTIC_EXCLUDE_MQTT
#include "mqtt/MQTT.h"
#endif
#include "modules/ExternalNotificationModule.h"
#include "modules/KeyVerificationModule.h"
#include "modules/TraceRouteModule.h"
@@ -259,6 +262,11 @@ void menuHandler::LoraRegionPicker(uint32_t duration)
// Default broker is in use, so subscribe to the appropriate MQTT root topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
changes |= SEGMENT_MODULECONFIG;
#if !MESHTASTIC_EXCLUDE_MQTT
if (mqtt) {
mqtt->reinitTopics();
}
#endif
}
service->reloadConfig(changes);
+5
View File
@@ -830,6 +830,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c, bool fromOthers)
if (strncmp(moduleConfig.mqtt.root, default_mqtt_root, strlen(default_mqtt_root)) == 0) {
// Default root is in use, so subscribe to the appropriate MQTT topic for this region
sprintf(moduleConfig.mqtt.root, "%s/%s", default_mqtt_root, myRegion->name);
#if !MESHTASTIC_EXCLUDE_MQTT
if (mqtt) {
mqtt->reinitTopics();
}
#endif
}
changes = SEGMENT_CONFIG | SEGMENT_MODULECONFIG;
} else {
+25 -9
View File
@@ -303,6 +303,30 @@ void mqttInit()
new MQTT();
}
void MQTT::reinitTopics()
{
cryptTopic = "/2/e/";
mapTopic = "/2/map/";
if (*moduleConfig.mqtt.root) {
cryptTopic = moduleConfig.mqtt.root + cryptTopic;
mapTopic = moduleConfig.mqtt.root + mapTopic;
isConfiguredForDefaultRootTopic = isDefaultRootTopic(moduleConfig.mqtt.root);
} else {
cryptTopic = "msh" + cryptTopic;
mapTopic = "msh" + mapTopic;
isConfiguredForDefaultRootTopic = true;
}
#if HAS_NETWORKING
// Force a broker reconnect so subscriptions are refreshed with the new topic prefix
if (pubSub.connected()) {
pubSub.disconnect();
}
isConnected = false;
#endif
}
#if HAS_NETWORKING
MQTT::MQTT() : MQTT(std::unique_ptr<MQTTClient>(new MQTTClient())) {}
MQTT::MQTT(std::unique_ptr<MQTTClient> _mqttClient)
@@ -317,15 +341,7 @@ MQTT::MQTT() : concurrency::OSThread("mqtt"), mqttQueue(MAX_MQTT_QUEUE)
assert(!mqtt);
mqtt = this;
if (*moduleConfig.mqtt.root) {
cryptTopic = moduleConfig.mqtt.root + cryptTopic;
mapTopic = moduleConfig.mqtt.root + mapTopic;
isConfiguredForDefaultRootTopic = isDefaultRootTopic(moduleConfig.mqtt.root);
} else {
cryptTopic = "msh" + cryptTopic;
mapTopic = "msh" + mapTopic;
isConfiguredForDefaultRootTopic = true;
}
reinitTopics();
if (moduleConfig.mqtt.map_reporting_enabled && moduleConfig.mqtt.has_map_report_settings) {
map_position_precision = Default::getConfiguredOrDefault(moduleConfig.mqtt.map_report_settings.position_precision,
+6
View File
@@ -58,6 +58,12 @@ class MQTT : private concurrency::OSThread
bool isUsingDefaultServer() { return isConfiguredForDefaultServer; }
bool isUsingDefaultRootTopic() { return isConfiguredForDefaultRootTopic; }
/** Re-read moduleConfig.mqtt.root and rebuild the topic strings.
* Call this after changing the MQTT root (e.g. on LoRa region change).
* Also forces a broker reconnect so subscriptions are refreshed.
*/
void reinitTopics();
/// Validate the meshtastic_ModuleConfig_MQTTConfig.
static bool isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config) { return isValidConfig(config, nullptr); }
+30
View File
@@ -815,6 +815,35 @@ void test_customMqttRoot(void)
[] { return pubsub->subscriptions_.count("custom/2/e/test/+") && pubsub->subscriptions_.count("custom/2/e/PKI/+"); }));
}
// After a LoRa region change, reinitTopics() updates the publish topic and forces
// the broker to reconnect with updated subscriptions.
void test_reinitTopicsUpdatesOnRegionChange(void)
{
// Start MQTT with a US region root.
strcpy(moduleConfig.mqtt.root, "msh/US");
MQTTUnitTest::restart();
TEST_ASSERT_TRUE(loopUntil(
[] { return pubsub->subscriptions_.count("msh/US/2/e/test/+") && pubsub->subscriptions_.count("msh/US/2/e/PKI/+"); }));
// Simulate region change: update the root and call reinitTopics().
strcpy(moduleConfig.mqtt.root, "msh/EU_868");
pubsub->subscriptions_.clear();
pubsub->published_.clear();
mqtt->reinitTopics();
// Verify that subscriptions are refreshed with the new region prefix.
TEST_ASSERT_TRUE(loopUntil([] {
return pubsub->subscriptions_.count("msh/EU_868/2/e/test/+") && pubsub->subscriptions_.count("msh/EU_868/2/e/PKI/+");
}));
// Verify that publish also uses the new topic prefix.
mqtt->onSend(encrypted, decoded, 0);
TEST_ASSERT_EQUAL(1, pubsub->published_.size());
const auto &[topic, payload] = pubsub->published_.front();
TEST_ASSERT_EQUAL_STRING("msh/EU_868/2/e/test/!12345678", topic.c_str());
}
// Empty configuration is valid.
void test_configEmptyIsValid(void)
{
@@ -919,6 +948,7 @@ void setup()
RUN_TEST(test_disabled);
RUN_TEST(test_mqttInitSkipsAllocationWhenDisabled);
RUN_TEST(test_customMqttRoot);
RUN_TEST(test_reinitTopicsUpdatesOnRegionChange);
RUN_TEST(test_configEmptyIsValid);
RUN_TEST(test_configEnabledEmptyIsValid);
RUN_TEST(test_configWithDefaultServer);