Skip to content

Commit 0ea408e

Browse files
rcatal01thebentern
andauthored
fix: MQTT settings silently fail to persist when broker is unreachable (#9934)
* fix: MQTT settings silently fail to persist when broker is unreachable isValidConfig() was testing broker connectivity via connectPubSub() as part of config validation. When the broker was unreachable (network not ready, DNS failure, server down), the function returned false, causing AdminModule to skip saving settings entirely — silently. This removes the connectivity test from isValidConfig(), which now only validates configuration correctness (TLS support, default server port). Connectivity is handled by the MQTT module's existing reconnect loop. Fixes #9107 * Add client warning notification when MQTT broker is unreachable Per maintainer feedback: instead of silently saving when the broker can't be reached, send a WARNING notification to the client saying "MQTT settings saved, but could not reach the MQTT server." Settings still always persist regardless of connectivity — the core fix from the previous commit is preserved. The notification is purely advisory so users know to double-check their server address and credentials if the connection test fails. When the network is not available at all, the connectivity check is skipped entirely with a log message. * Address Copilot review feedback - Fix warning message wording: "Settings will be saved" instead of "Settings saved" (notification fires before AdminModule persists) - Add null check on clientNotificationPool.allocZeroed() to prevent crash if pool is exhausted (matches AdminModule::sendWarning pattern) - Fix test comments to accurately describe conditional connectivity check behavior and IS_RUNNING_TESTS compile-out * Remove connectivity check from isValidConfig entirely Reverts the advisory connectivity check added in the previous commit. While the intent was to warn users about unreachable brokers, connectPubSub() mutates the isConnected state of the running MQTT module and performs synchronous network operations that can block the config-save path. The cleanest approach: isValidConfig() validates config correctness only (TLS support, default server port). The MQTT reconnect loop handles connectivity after settings are persisted and the device reboots. If the broker is unreachable, the user will see it in the MQTT connection status — no special notification needed. This returns to the simpler design from the first commit, which was tested on hardware and confirmed working. * Use lightweight TCP check instead of connectPubSub for validation Per maintainer feedback: users need connectivity feedback, but connectPubSub() mutates the module's isConnected state. This uses a standalone MQTTClient TCP connection test that: - Checks if the server IP/port is reachable - Sends a WARNING notification if unreachable - Does NOT establish an MQTT session or mutate any module state - Does NOT block saving — isValidConfig always returns true The TCP test client is created locally, used, and destroyed within the function scope. No side effects on the running MQTT module. --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
1 parent 644d0d4 commit 0ea408e

File tree

2 files changed

+34
-31
lines changed

2 files changed

+34
-31
lines changed

src/mqtt/MQTT.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -651,22 +651,34 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC
651651

652652
if (config.enabled && !config.proxy_to_client_enabled) {
653653
#if HAS_NETWORKING
654-
std::unique_ptr<MQTTClient> clientConnection;
655654
if (config.tls_enabled) {
656-
#if MQTT_SUPPORTS_TLS
657-
MQTTClientTLS *tlsClient = new MQTTClientTLS;
658-
clientConnection.reset(tlsClient);
659-
tlsClient->setInsecure();
660-
#else
655+
#if !MQTT_SUPPORTS_TLS
661656
LOG_ERROR("Invalid MQTT config: tls_enabled is not supported on this node");
662657
return false;
663658
#endif
664-
} else {
665-
clientConnection.reset(new MQTTClient);
666659
}
667-
std::unique_ptr<PubSubClient> pubSub(new PubSubClient);
660+
// Perform a lightweight TCP connectivity check without using connectPubSub(),
661+
// which mutates the module's isConnected state. This only checks if the server
662+
// is reachable — it does not establish an MQTT session.
663+
// Settings are always saved regardless of the result.
668664
if (isConnectedToNetwork()) {
669-
return connectPubSub(parsed, *pubSub, (client != nullptr) ? *client : *clientConnection);
665+
MQTTClient testClient;
666+
if (!testClient.connect(parsed.serverAddr.c_str(), parsed.serverPort)) {
667+
const char *warning = "Could not reach the MQTT server. Settings will be saved, but please verify the server "
668+
"address and credentials.";
669+
LOG_WARN(warning);
670+
#if !IS_RUNNING_TESTS
671+
meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed();
672+
if (cn) {
673+
cn->level = meshtastic_LogRecord_Level_WARNING;
674+
cn->time = getValidTime(RTCQualityFromNet);
675+
strncpy(cn->message, warning, sizeof(cn->message) - 1);
676+
cn->message[sizeof(cn->message) - 1] = '\0';
677+
service->sendClientNotification(cn);
678+
}
679+
#endif
680+
}
681+
testClient.stop();
670682
}
671683
#else
672684
const char *warning = "Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network";

test/test_mqtt/MQTT.cpp

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -818,16 +818,13 @@ void test_configEmptyIsValid(void)
818818
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
819819
}
820820

821-
// Empty 'enabled' configuration is valid.
821+
// Empty 'enabled' configuration is valid. A lightweight TCP check may be performed
822+
// but does not affect the result.
822823
void test_configEnabledEmptyIsValid(void)
823824
{
824825
meshtastic_ModuleConfig_MQTTConfig config = {.enabled = true};
825-
MockPubSubServer client;
826826

827-
TEST_ASSERT_TRUE(MQTTUnitTest::isValidConfig(config, &client));
828-
TEST_ASSERT_TRUE(client.connected_);
829-
TEST_ASSERT_EQUAL_STRING(default_mqtt_address, client.host_.c_str());
830-
TEST_ASSERT_EQUAL(1883, client.port_);
827+
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
831828
}
832829

833830
// Configuration with the default server is valid.
@@ -846,38 +843,32 @@ void test_configWithDefaultServerAndInvalidPort(void)
846843
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
847844
}
848845

849-
// isValidConfig connects to a custom host and port.
846+
// Custom host and port is valid. TCP reachability is checked but does not block saving.
850847
void test_configCustomHostAndPort(void)
851848
{
852849
meshtastic_ModuleConfig_MQTTConfig config = {.enabled = true, .address = "server:1234"};
853-
MockPubSubServer client;
854850

855-
TEST_ASSERT_TRUE(MQTTUnitTest::isValidConfig(config, &client));
856-
TEST_ASSERT_TRUE(client.connected_);
857-
TEST_ASSERT_EQUAL_STRING("server", client.host_.c_str());
858-
TEST_ASSERT_EQUAL(1234, client.port_);
851+
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
859852
}
860853

861-
// isValidConfig returns false if a connection cannot be established.
862-
void test_configWithConnectionFailure(void)
854+
// An unreachable server is still a valid config — settings always save.
855+
// A warning notification is sent in non-test builds, but isValidConfig returns true.
856+
void test_configWithUnreachableServerIsStillValid(void)
863857
{
864858
meshtastic_ModuleConfig_MQTTConfig config = {.enabled = true, .address = "server"};
865-
MockPubSubServer client;
866-
client.refuseConnection_ = true;
867859

868-
TEST_ASSERT_FALSE(MQTTUnitTest::isValidConfig(config, &client));
860+
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
869861
}
870862

871863
// isValidConfig returns true when tls_enabled is supported, or false otherwise.
872864
void test_configWithTLSEnabled(void)
873865
{
874866
meshtastic_ModuleConfig_MQTTConfig config = {.enabled = true, .address = "server", .tls_enabled = true};
875-
MockPubSubServer client;
876867

877868
#if MQTT_SUPPORTS_TLS
878-
TEST_ASSERT_TRUE(MQTTUnitTest::isValidConfig(config, &client));
869+
TEST_ASSERT_TRUE(MQTT::isValidConfig(config));
879870
#else
880-
TEST_ASSERT_FALSE(MQTTUnitTest::isValidConfig(config, &client));
871+
TEST_ASSERT_FALSE(MQTT::isValidConfig(config));
881872
#endif
882873
}
883874

@@ -927,7 +918,7 @@ void setup()
927918
RUN_TEST(test_configWithDefaultServer);
928919
RUN_TEST(test_configWithDefaultServerAndInvalidPort);
929920
RUN_TEST(test_configCustomHostAndPort);
930-
RUN_TEST(test_configWithConnectionFailure);
921+
RUN_TEST(test_configWithUnreachableServerIsStillValid);
931922
RUN_TEST(test_configWithTLSEnabled);
932923
exit(UNITY_END());
933924
}

0 commit comments

Comments
 (0)