From fc1e6ccb8cd90463c9e2647e3fba9c10b7543ad3 Mon Sep 17 00:00:00 2001 From: Iris Date: Sat, 26 Jul 2025 13:13:02 +0300 Subject: [PATCH 1/5] Rename Platformio.ini to platformio.ini (#7468) --- .../nrf52840/diy/WashTastic/{Platformio.ini => platformio.ini} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename variants/nrf52840/diy/WashTastic/{Platformio.ini => platformio.ini} (95%) diff --git a/variants/nrf52840/diy/WashTastic/Platformio.ini b/variants/nrf52840/diy/WashTastic/platformio.ini similarity index 95% rename from variants/nrf52840/diy/WashTastic/Platformio.ini rename to variants/nrf52840/diy/WashTastic/platformio.ini index e67c25910..881b961e1 100644 --- a/variants/nrf52840/diy/WashTastic/Platformio.ini +++ b/variants/nrf52840/diy/WashTastic/platformio.ini @@ -10,4 +10,4 @@ build_flags = ${nrf52840_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/nrf52840/diy/nrf52_promicro_diy_tcxo> lib_deps = ${nrf52840_base.lib_deps} -debug_tool = jlink \ No newline at end of file +debug_tool = jlink From a506dc6b65d8d4dbf5b7106de598e89d8d25484f Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 26 Jul 2025 05:38:24 -0500 Subject: [PATCH 2/5] Fix MQTT config bugs (#7446) * Fix mqtt config bugs * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Add client notification * Verbiage * Update src/mqtt/MQTT.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mqtt/MQTT.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mqtt/MQTT.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update src/mqtt/MQTT.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Remove test that is no longer true * This test no longer exists * Fix client notification crap * Suppress false positive * Revert "Suppress false positive" This reverts commit bead96eaee31f11ed631c016eb0424055e923a29. * Try macro exclusion * Derp * Fix --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Jason P --- src/mesh/MeshService.h | 2 +- src/modules/AdminModule.cpp | 7 ++++++- src/mqtt/MQTT.cpp | 27 +++++++++++++++++++++------ test/test_mqtt/MQTT.cpp | 23 +++++++++++++---------- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/src/mesh/MeshService.h b/src/mesh/MeshService.h index 89d3b15d0..f7d79366e 100644 --- a/src/mesh/MeshService.h +++ b/src/mesh/MeshService.h @@ -146,7 +146,7 @@ class MeshService virtual void sendMqttMessageToClientProxy(meshtastic_MqttClientProxyMessage *m); /// Send a ClientNotification to the phone - void sendClientNotification(meshtastic_ClientNotification *cn); + virtual void sendClientNotification(meshtastic_ClientNotification *cn); /// Send an error response to the phone void sendRoutingErrorResponse(meshtastic_Routing_Error error, const meshtastic_MeshPacket *mp); diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 33d5e1016..4b910a959 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -798,8 +798,11 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { - if (!hasOpenEditTransaction) + // If we are in an open transaction or configuring MQTT, defer disabling Bluetooth + // Otherwise, disable Bluetooth to prevent the phone from interfering with the config + if (!hasOpenEditTransaction && c.which_payload_variant != meshtastic_ModuleConfig_mqtt_tag) disableBluetooth(); + switch (c.which_payload_variant) { case meshtastic_ModuleConfig_mqtt_tag: #if MESHTASTIC_EXCLUDE_MQTT @@ -810,6 +813,8 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) if (!MQTT::isValidConfig(c.payload_variant.mqtt)) { return false; } + // Disable Bluetooth to prevent interference during MQTT configuration + disableBluetooth(); moduleConfig.has_mqtt = true; moduleConfig.mqtt = c.payload_variant.mqtt; #endif diff --git a/src/mqtt/MQTT.cpp b/src/mqtt/MQTT.cpp index 091612827..21d4a8fa0 100644 --- a/src/mqtt/MQTT.cpp +++ b/src/mqtt/MQTT.cpp @@ -39,6 +39,7 @@ #include #define ntohl __ntohl #endif +#include MQTT *mqtt; @@ -624,18 +625,32 @@ bool MQTT::isValidConfig(const meshtastic_ModuleConfig_MQTTConfig &config, MQTTC return connectPubSub(parsed, *pubSub, (client != nullptr) ? *client : *clientConnection); } #else - LOG_ERROR("Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network"); + const char *warning = "Invalid MQTT config: proxy_to_client_enabled must be enabled on nodes that do not have a network"; + LOG_ERROR(warning); +#if !IS_RUNNING_TESTS + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, warning, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination + service->sendClientNotification(cn); +#endif return false; #endif } const bool defaultServer = isDefaultServer(parsed.serverAddr); - if (defaultServer && config.tls_enabled) { - LOG_ERROR("Invalid MQTT config: TLS was enabled, but the default server does not support TLS"); - return false; - } if (defaultServer && parsed.serverPort != PubSubConfig::defaultPort) { - LOG_ERROR("Invalid MQTT config: Unsupported port '%d' for the default MQTT server", parsed.serverPort); + const char *warning = "Invalid MQTT config: default server address must not have a port specified"; + LOG_ERROR(warning); +#if !IS_RUNNING_TESTS + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + strncpy(cn->message, warning, sizeof(cn->message) - 1); + cn->message[sizeof(cn->message) - 1] = '\0'; // Ensure null termination + service->sendClientNotification(cn); +#endif return false; } return true; diff --git a/test/test_mqtt/MQTT.cpp b/test/test_mqtt/MQTT.cpp index 8047079ba..32d81f6b4 100644 --- a/test/test_mqtt/MQTT.cpp +++ b/test/test_mqtt/MQTT.cpp @@ -27,6 +27,12 @@ #include #include +#if defined(UNIT_TEST) +#define IS_RUNNING_TESTS 1 +#else +#define IS_RUNNING_TESTS 0 +#endif + namespace { // Minimal router needed to receive messages from MQTT. @@ -56,7 +62,13 @@ class MockMeshService : public MeshService messages_.emplace_back(*m); releaseMqttClientProxyMessageToPool(m); } - std::list messages_; // Messages received from the MeshService. + void sendClientNotification(meshtastic_ClientNotification *n) override + { + notifications_.emplace_back(*n); + releaseClientNotificationToPool(n); + } + std::list messages_; // Messages received from the MeshService. + std::list notifications_; // Notifications received from the MeshService. }; // Minimal NodeDB needed to return values from getMeshNode. @@ -823,14 +835,6 @@ void test_configWithDefaultServerAndInvalidPort(void) TEST_ASSERT_FALSE(MQTT::isValidConfig(config)); } -// Configuration with the default server and tls_enabled = true is invalid. -void test_configWithDefaultServerAndInvalidTLSEnabled(void) -{ - meshtastic_ModuleConfig_MQTTConfig config = {.tls_enabled = true}; - - TEST_ASSERT_FALSE(MQTT::isValidConfig(config)); -} - // isValidConfig connects to a custom host and port. void test_configCustomHostAndPort(void) { @@ -911,7 +915,6 @@ void setup() RUN_TEST(test_configEnabledEmptyIsValid); RUN_TEST(test_configWithDefaultServer); RUN_TEST(test_configWithDefaultServerAndInvalidPort); - RUN_TEST(test_configWithDefaultServerAndInvalidTLSEnabled); RUN_TEST(test_configCustomHostAndPort); RUN_TEST(test_configWithConnectionFailure); RUN_TEST(test_configWithTLSEnabled); From df8b629c2c4c43b5939e7153a427100c1b769a42 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Sat, 26 Jul 2025 23:09:26 +1000 Subject: [PATCH 3/5] Clear position on GPS deactivation, unless using fixed position (#7464) * Clear position on GPS deactivation, unless using fixed position As reported by @dreimal8 , and confirmed by @tuxmobil , when using and then subsequently disabling GPS the last position retrieved from the GPS was stored and continued to be broadcast. This change introduces a check to see if we are transitioning from GPS Enabled to GPS Disabled or Not Present. If we are, and fixed position is not in use, then we clear the local position. This will prevent inaccurate and undesired position broadcasts for those who disable their GPS. Fixes https://github.com/meshtastic/firmware/issues/7228 * Update triple click to also clear position --------- Co-authored-by: Ben Meadors --- src/modules/AdminModule.cpp | 9 +++++++++ src/modules/SystemCommandsModule.cpp | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 4b910a959..87d423f21 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -638,7 +638,16 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) case meshtastic_Config_position_tag: LOG_INFO("Set config: Position"); config.has_position = true; + // If we have turned off the GPS (disabled or not present) and we're not using fixed position, + // clear the stored position since it may not get updated + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED && + c.payload_variant.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED && + config.position.fixed_position == false && c.payload_variant.position.fixed_position == false) { + nodeDB->clearLocalPosition(); + saveChanges(SEGMENT_NODEDATABASE | SEGMENT_CONFIG, false); + } config.position = c.payload_variant.position; + // Save nodedb as well in case we got a fixed position packet break; case meshtastic_Config_power_tag: diff --git a/src/modules/SystemCommandsModule.cpp b/src/modules/SystemCommandsModule.cpp index 2d534bd67..74b9678f4 100644 --- a/src/modules/SystemCommandsModule.cpp +++ b/src/modules/SystemCommandsModule.cpp @@ -89,6 +89,11 @@ int SystemCommandsModule::handleInputEvent(const InputEvent *event) #if !MESHTASTIC_EXCLUDE_GPS if (gps) { LOG_WARN("GPS Toggle2"); + if (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED && + config.position.fixed_position == false) { + nodeDB->clearLocalPosition(); + nodeDB->saveToDisk(); + } gps->toggleGpsMode(); const char *msg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED) ? "GPS Enabled" : "GPS Disabled"; From 7c5e2c539387b178661afa4279f6c5e92a4dec2f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 26 Jul 2025 16:21:49 -0500 Subject: [PATCH 4/5] Update protobufs (#7473) Co-authored-by: thebentern <9000580+thebentern@users.noreply.github.com> --- protobufs | 2 +- src/mesh/generated/meshtastic/deviceonly.pb.h | 2 +- src/mesh/generated/meshtastic/mesh.pb.h | 13 +++++++++---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/protobufs b/protobufs index d31cd890d..9bac2886f 160000 --- a/protobufs +++ b/protobufs @@ -1 +1 @@ -Subproject commit d31cd890d58ffa7e3524e0685a8617bbd181a1c6 +Subproject commit 9bac2886f9344f25716921467a82e8b0326107cd diff --git a/src/mesh/generated/meshtastic/deviceonly.pb.h b/src/mesh/generated/meshtastic/deviceonly.pb.h index e709db6c4..ba1a52b27 100644 --- a/src/mesh/generated/meshtastic/deviceonly.pb.h +++ b/src/mesh/generated/meshtastic/deviceonly.pb.h @@ -362,7 +362,7 @@ extern const pb_msgdesc_t meshtastic_BackupPreferences_msg; #define MESHTASTIC_MESHTASTIC_DEVICEONLY_PB_H_MAX_SIZE meshtastic_BackupPreferences_size #define meshtastic_BackupPreferences_size 2271 #define meshtastic_ChannelFile_size 718 -#define meshtastic_DeviceState_size 1724 +#define meshtastic_DeviceState_size 1728 #define meshtastic_NodeInfoLite_size 196 #define meshtastic_PositionLite_size 28 #define meshtastic_UserLite_size 98 diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h index abc06e635..8e6524042 100644 --- a/src/mesh/generated/meshtastic/mesh.pb.h +++ b/src/mesh/generated/meshtastic/mesh.pb.h @@ -935,6 +935,9 @@ typedef struct _meshtastic_MyNodeInfo { char pio_env[40]; /* The indicator for whether this device is running event firmware and which */ meshtastic_FirmwareEdition firmware_edition; + /* The number of nodes in the nodedb. + This is used by the phone to know how many NodeInfo packets to expect on want_config */ + uint16_t nodedb_count; } meshtastic_MyNodeInfo; /* Debug output from the device. @@ -1322,7 +1325,7 @@ extern "C" { #define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0} #define meshtastic_NodeInfo_init_default {0, false, meshtastic_User_init_default, false, meshtastic_Position_init_default, 0, 0, false, meshtastic_DeviceMetrics_init_default, 0, 0, false, 0, 0, 0, 0} -#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN} +#define meshtastic_MyNodeInfo_init_default {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_default {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_default {0, 0, 0, 0} #define meshtastic_FromRadio_init_default {0, 0, {meshtastic_MeshPacket_init_default}} @@ -1353,7 +1356,7 @@ extern "C" { #define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0} #define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0} #define meshtastic_NodeInfo_init_zero {0, false, meshtastic_User_init_zero, false, meshtastic_Position_init_zero, 0, 0, false, meshtastic_DeviceMetrics_init_zero, 0, 0, false, 0, 0, 0, 0} -#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN} +#define meshtastic_MyNodeInfo_init_zero {0, 0, 0, {0, {0}}, "", _meshtastic_FirmwareEdition_MIN, 0} #define meshtastic_LogRecord_init_zero {"", 0, "", _meshtastic_LogRecord_Level_MIN} #define meshtastic_QueueStatus_init_zero {0, 0, 0, 0} #define meshtastic_FromRadio_init_zero {0, 0, {meshtastic_MeshPacket_init_zero}} @@ -1477,6 +1480,7 @@ extern "C" { #define meshtastic_MyNodeInfo_device_id_tag 12 #define meshtastic_MyNodeInfo_pio_env_tag 13 #define meshtastic_MyNodeInfo_firmware_edition_tag 14 +#define meshtastic_MyNodeInfo_nodedb_count_tag 15 #define meshtastic_LogRecord_message_tag 1 #define meshtastic_LogRecord_time_tag 2 #define meshtastic_LogRecord_source_tag 3 @@ -1710,7 +1714,8 @@ X(a, STATIC, SINGULAR, UINT32, reboot_count, 8) \ X(a, STATIC, SINGULAR, UINT32, min_app_version, 11) \ X(a, STATIC, SINGULAR, BYTES, device_id, 12) \ X(a, STATIC, SINGULAR, STRING, pio_env, 13) \ -X(a, STATIC, SINGULAR, UENUM, firmware_edition, 14) +X(a, STATIC, SINGULAR, UENUM, firmware_edition, 14) \ +X(a, STATIC, SINGULAR, UINT32, nodedb_count, 15) #define meshtastic_MyNodeInfo_CALLBACK NULL #define meshtastic_MyNodeInfo_DEFAULT NULL @@ -1993,7 +1998,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg; #define meshtastic_LowEntropyKey_size 0 #define meshtastic_MeshPacket_size 378 #define meshtastic_MqttClientProxyMessage_size 501 -#define meshtastic_MyNodeInfo_size 79 +#define meshtastic_MyNodeInfo_size 83 #define meshtastic_NeighborInfo_size 258 #define meshtastic_Neighbor_size 22 #define meshtastic_NodeInfo_size 323 From 28aeb0f09e9bb633d5ba8b6511050ac778e030c0 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sat, 26 Jul 2025 19:55:54 -0500 Subject: [PATCH 5/5] Validate Serial config console override modes (#7470) * Validate serial config console override modes * Update src/modules/SerialModule.cpp Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Disable * Guard serial module * Guards --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/modules/AdminModule.cpp | 32 ++++-- src/modules/SerialModule.cpp | 20 ++++ src/modules/SerialModule.h | 2 + test/test_serial/SerialModule.cpp | 156 ++++++++++++++++++++++++++++++ 4 files changed, 202 insertions(+), 8 deletions(-) create mode 100644 test/test_serial/SerialModule.cpp diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp index 87d423f21..1a9c3a7a7 100644 --- a/src/modules/AdminModule.cpp +++ b/src/modules/AdminModule.cpp @@ -43,6 +43,10 @@ #if !defined(ARCH_STM32WL) && !MESHTASTIC_EXCLUDE_I2C #include "motion/AccelerometerThread.h" #endif +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) +#include "SerialModule.h" +#endif AdminModule *adminModule; bool hasOpenEditTransaction; @@ -807,10 +811,12 @@ void AdminModule::handleSetConfig(const meshtastic_Config &c) bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) { - // If we are in an open transaction or configuring MQTT, defer disabling Bluetooth + // If we are in an open transaction or configuring MQTT or Serial (which have validation), defer disabling Bluetooth // Otherwise, disable Bluetooth to prevent the phone from interfering with the config - if (!hasOpenEditTransaction && c.which_payload_variant != meshtastic_ModuleConfig_mqtt_tag) + if (!hasOpenEditTransaction && + !IS_ONE_OF(c.which_payload_variant, meshtastic_ModuleConfig_mqtt_tag, meshtastic_ModuleConfig_serial_tag)) { disableBluetooth(); + } switch (c.which_payload_variant) { case meshtastic_ModuleConfig_mqtt_tag: @@ -830,6 +836,14 @@ bool AdminModule::handleSetModuleConfig(const meshtastic_ModuleConfig &c) break; case meshtastic_ModuleConfig_serial_tag: LOG_INFO("Set module config: Serial"); +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) + if (!SerialModule::isValidConfig(c.payload_variant.serial)) { + LOG_ERROR("Invalid serial config"); + return false; + } + disableBluetooth(); // Disable Bluetooth to prevent interference during Serial configuration +#endif moduleConfig.has_serial = true; moduleConfig.serial = c.payload_variant.serial; break; @@ -985,9 +999,10 @@ void AdminModule::handleGetConfig(const meshtastic_MeshPacket &req, const uint32 // So even if we internally use 0 to represent 'use default' we still need to send the value we are // using to the app (so that even old phone apps work with new device loads). // r.get_radio_response.preferences.ls_secs = getPref_ls_secs(); - // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally private - // and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password); - // r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag; + // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally + // private and useful for users to know current provisioning) + // hideSecret(r.get_radio_response.preferences.wifi_password); r.get_config_response.which_payloadVariant = + // Config_ModuleConfig_telemetry_tag; res.which_payload_variant = meshtastic_AdminMessage_get_config_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); @@ -1071,9 +1086,10 @@ void AdminModule::handleGetModuleConfig(const meshtastic_MeshPacket &req, const // So even if we internally use 0 to represent 'use default' we still need to send the value we are // using to the app (so that even old phone apps work with new device loads). // r.get_radio_response.preferences.ls_secs = getPref_ls_secs(); - // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally private - // and useful for users to know current provisioning) hideSecret(r.get_radio_response.preferences.wifi_password); - // r.get_config_response.which_payloadVariant = Config_ModuleConfig_telemetry_tag; + // hideSecret(r.get_radio_response.preferences.wifi_ssid); // hmm - leave public for now, because only minimally + // private and useful for users to know current provisioning) + // hideSecret(r.get_radio_response.preferences.wifi_password); r.get_config_response.which_payloadVariant = + // Config_ModuleConfig_telemetry_tag; res.which_payload_variant = meshtastic_AdminMessage_get_module_config_response_tag; setPassKey(&res); myReply = allocDataProtobuf(res); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f3921ef19..f3091e5bf 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -74,6 +74,26 @@ static Print *serialPrint = &Serial2; char serialBytes[512]; size_t serialPayloadSize; +bool SerialModule::isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config) +{ + if (config.override_console_serial_port && !IS_ONE_OF(config.mode, meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA, + meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO)) { + const char *warning = + "Invalid Serial config: override console serial port is only supported in NMEA and CalTopo output-only modes."; + LOG_ERROR(warning); +#if !IS_RUNNING_TESTS + meshtastic_ClientNotification *cn = clientNotificationPool.allocZeroed(); + cn->level = meshtastic_LogRecord_Level_ERROR; + cn->time = getValidTime(RTCQualityFromNet); + snprintf(cn->message, sizeof(cn->message), "%s", warning); + service->sendClientNotification(cn); +#endif + return false; + } + + return true; +} + SerialModuleRadio::SerialModuleRadio() : MeshModule("SerialModuleRadio") { switch (moduleConfig.serial.mode) { diff --git a/src/modules/SerialModule.h b/src/modules/SerialModule.h index fa86db28f..1c74c927c 100644 --- a/src/modules/SerialModule.h +++ b/src/modules/SerialModule.h @@ -20,6 +20,8 @@ class SerialModule : public StreamAPI, private concurrency::OSThread public: SerialModule(); + static bool isValidConfig(const meshtastic_ModuleConfig_SerialConfig &config); + protected: virtual int32_t runOnce() override; diff --git a/test/test_serial/SerialModule.cpp b/test/test_serial/SerialModule.cpp new file mode 100644 index 000000000..1bccf04a7 --- /dev/null +++ b/test/test_serial/SerialModule.cpp @@ -0,0 +1,156 @@ +#include "DebugConfiguration.h" +#include "TestUtil.h" +#include + +#ifdef ARCH_PORTDUINO +#include "configuration.h" + +#if defined(UNIT_TEST) +#define IS_RUNNING_TESTS 1 +#else +#define IS_RUNNING_TESTS 0 +#endif + +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) +#include "modules/SerialModule.h" +#endif + +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) + +// Test that empty configuration is valid. +void test_serialConfigEmptyIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = {}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that basic enabled configuration is valid. +void test_serialConfigEnabledIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = {.enabled = true}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and NMEA mode is valid. +void test_serialConfigWithOverrideConsoleNmeaModeIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and CalTopo mode is valid. +void test_serialConfigWithOverrideConsoleCalTopoModeIsValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO}; + + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and DEFAULT mode is invalid. +void test_serialConfigWithOverrideConsoleDefaultModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and SIMPLE mode is invalid. +void test_serialConfigWithOverrideConsoleSimpleModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and TEXTMSG mode is invalid. +void test_serialConfigWithOverrideConsoleTextMsgModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that configuration with override_console_serial_port and PROTO mode is invalid. +void test_serialConfigWithOverrideConsoleProtoModeIsInvalid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = { + .enabled = true, .override_console_serial_port = true, .mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO}; + + TEST_ASSERT_FALSE(SerialModule::isValidConfig(config)); +} + +// Test that various modes work without override_console_serial_port. +void test_serialConfigVariousModesWithoutOverrideAreValid(void) +{ + meshtastic_ModuleConfig_SerialConfig config = {.enabled = true, .override_console_serial_port = false}; + + // Test DEFAULT mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_DEFAULT; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test SIMPLE mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_SIMPLE; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test TEXTMSG mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test PROTO mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_PROTO; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test NMEA mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_NMEA; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); + + // Test CALTOPO mode + config.mode = meshtastic_ModuleConfig_SerialConfig_Serial_Mode_CALTOPO; + TEST_ASSERT_TRUE(SerialModule::isValidConfig(config)); +} + +#endif // Architecture check + +void setup() +{ + initializeTestEnvironment(); + +#if (defined(ARCH_ESP32) || defined(ARCH_NRF52) || defined(ARCH_RP2040)) && !defined(CONFIG_IDF_TARGET_ESP32S2) && \ + !defined(CONFIG_IDF_TARGET_ESP32C3) + UNITY_BEGIN(); + RUN_TEST(test_serialConfigEmptyIsValid); + RUN_TEST(test_serialConfigEnabledIsValid); + RUN_TEST(test_serialConfigWithOverrideConsoleNmeaModeIsValid); + RUN_TEST(test_serialConfigWithOverrideConsoleCalTopoModeIsValid); + RUN_TEST(test_serialConfigWithOverrideConsoleDefaultModeIsInvalid); + RUN_TEST(test_serialConfigWithOverrideConsoleSimpleModeIsInvalid); + RUN_TEST(test_serialConfigWithOverrideConsoleTextMsgModeIsInvalid); + RUN_TEST(test_serialConfigWithOverrideConsoleProtoModeIsInvalid); + RUN_TEST(test_serialConfigVariousModesWithoutOverrideAreValid); + exit(UNITY_END()); +#else + LOG_WARN("This test requires ESP32, NRF52, or RP2040 architecture"); + UNITY_BEGIN(); + UNITY_END(); +#endif +} +#else +void setup() +{ + initializeTestEnvironment(); + LOG_WARN("This test requires the ARCH_PORTDUINO variant"); + UNITY_BEGIN(); + UNITY_END(); +} +#endif +void loop() {}