From 59e2b81848efbc5007786edca74cc9313573efa6 Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Tue, 12 May 2026 08:29:30 +0100 Subject: [PATCH 1/8] Change of 3 click operation to SOS - placeholder --- examples/companion_radio/ui-new/UITask.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 94a8ee3efa..aab47d650f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -863,11 +863,14 @@ char UITask::handleDoubleClick(char c) { } char UITask::handleTripleClick(char c) { - MESH_DEBUG_PRINTLN("UITask: triple click triggered"); - checkDisplayOn(c); - toggleBuzzer(); - c = 0; - return c; + MESH_DEBUG_PRINTLN("UITask: triple click triggered"); + checkDisplayOn(c); + + notify(UIEventType::ack); + showAlert("TRIPLE CLICK", 1500); + + c = 0; + return c; } bool UITask::getGPSState() { From bf5649fa3f63fa9fab7316a204607ed7815df4fc Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Tue, 12 May 2026 12:45:30 +0100 Subject: [PATCH 2/8] Experimental SOS mesh feature for T-Echo --- src/helpers/sensors/EnvironmentSensorManager.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 32413ebc03..f7d3bf6744 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -27,6 +27,9 @@ class EnvironmentSensorManager : public SensorManager { bool gps_detected = false; bool gps_active = false; uint32_t gps_update_interval_sec = 1; // Default 1 second + char sos_message[128] = "SOS - Não preciso de ajuda! {gps}"; + char sos_channel[8] = "5"; + #if ENV_INCLUDE_GPS LocationProvider* _location; From 225a38d71f9faccc664c19b4c359623c6588c0ba Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Tue, 12 May 2026 12:46:02 +0100 Subject: [PATCH 3/8] Experimental SOS mesh feature for T-Echo --- examples/companion_radio/MyMesh.cpp | 56 +++++++++++++++++++ examples/companion_radio/MyMesh.h | 2 +- examples/companion_radio/ui-new/UITask.cpp | 9 ++- .../sensors/EnvironmentSensorManager.cpp | 18 ++++++ 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index e8c1914bad..dc4f053ced 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2177,6 +2177,62 @@ void MyMesh::loop() { #endif } +bool MyMesh::sendSOS() { + + const char *sosMsg = sensors.getSettingByKey("sos_message"); + const char *sosChannel = sensors.getSettingByKey("sos_channel"); + + const char *template_text = + (sosMsg && strlen(sosMsg) > 0) + ? sosMsg + : "SOS - preciso de ajuda! {gps}"; + + uint8_t channel_idx = + (sosChannel && strlen(sosChannel) > 0) + ? atoi(sosChannel) + : 5; // default SOSTEST + + ChannelDetails channel; + bool success = getChannel(channel_idx, channel); + + if (!success) + return false; + + char gps[64]; + + bool has_gps = + !(sensors.node_lat == 0.0 && sensors.node_lon == 0.0); + + if (has_gps) { + snprintf(gps, sizeof(gps), + "%.6f,%.6f", + sensors.node_lat, + sensors.node_lon + ); + } else { + snprintf(gps, sizeof(gps), + "GPS sem fix" + ); + } + + String finalText = String(template_text); + finalText.replace("{gps}", gps); + + char text[140]; + strncpy(text, finalText.c_str(), sizeof(text) - 1); + text[sizeof(text) - 1] = '\0'; + + uint32_t msg_timestamp = millis(); + + return sendGroupMessage( + msg_timestamp, + channel.channel, + _prefs.node_name, + text, + strlen(text) + ); +} + bool MyMesh::advert() { mesh::Packet* pkt; if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index aeff591cf4..321375d7d1 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -98,8 +98,8 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { void loop(); void handleCmdFrame(size_t len); bool advert(); + bool sendSOS(); void enterCLIRescue(); - int getRecentlyHeard(AdvertPath dest[], int max_num); protected: diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index aab47d650f..ed29363193 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -866,13 +866,20 @@ char UITask::handleTripleClick(char c) { MESH_DEBUG_PRINTLN("UITask: triple click triggered"); checkDisplayOn(c); + bool ok = the_mesh.sendSOS(); + notify(UIEventType::ack); - showAlert("TRIPLE CLICK", 1500); + + if (ok) + showAlert("SOS SENT", 1500); + else + showAlert("SOS FAILED", 1500); c = 0; return c; } + bool UITask::getGPSState() { if (_sensors != NULL) { int num = _sensors->getNumSettings(); diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 19472406d8..8c22af038b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -545,6 +545,7 @@ int EnvironmentSensorManager::getNumSettings() const { #if ENV_INCLUDE_GPS if (gps_detected) settings++; // only show GPS setting if GPS is detected #endif + settings += 2; // sos_message, sos_channel return settings; } @@ -557,6 +558,8 @@ const char* EnvironmentSensorManager::getSettingName(int i) const { #endif // convenient way to add params (needed for some tests) // if (i == settings++) return "param.2"; + if (i == settings++) return "sos_message"; + if (i == settings++) return "sos_channel"; return NULL; } @@ -569,6 +572,8 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const { #endif // convenient way to add params ... // if (i == settings++) return "2"; + if (i == settings++) return sos_message; + if (i == settings++) return sos_channel; return NULL; } @@ -592,6 +597,19 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val return true; } #endif + + if (strcmp(name, "sos_message") == 0) { + strncpy(sos_message, value, sizeof(sos_message) - 1); + sos_message[sizeof(sos_message) - 1] = '\0'; + return true; + } + + if (strcmp(name, "sos_channel") == 0) { + strncpy(sos_channel, value, sizeof(sos_channel) - 1); + sos_channel[sizeof(sos_channel) - 1] = '\0'; + return true; + } + return false; // not supported } From d9d0d3e4a072ac5dfc58c7b03c27a4911a64eb0a Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Tue, 12 May 2026 12:58:27 +0100 Subject: [PATCH 4/8] SOS cannel set to 9 --- src/helpers/sensors/EnvironmentSensorManager.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index f7d3bf6744..297647f942 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -27,8 +27,8 @@ class EnvironmentSensorManager : public SensorManager { bool gps_detected = false; bool gps_active = false; uint32_t gps_update_interval_sec = 1; // Default 1 second - char sos_message[128] = "SOS - Não preciso de ajuda! {gps}"; - char sos_channel[8] = "5"; + char sos_message[128] = "SOS - preciso de ajuda! {gps}"; + char sos_channel[8] = "9"; #if ENV_INCLUDE_GPS From 3277138358774974a2f57894ec8d4d365b9be43b Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Tue, 12 May 2026 15:47:34 +0100 Subject: [PATCH 5/8] Feature de SOS no ecran do T-Echo com long press --- examples/companion_radio/ui-new/UITask.cpp | 20 +++++++++++++++++++- examples/companion_radio/ui-new/icons.h | 11 +++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ed29363193..61e1d31a1f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -82,6 +82,7 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, + SOS, #if ENV_INCLUDE_GPS == 1 GPS, #endif @@ -281,8 +282,16 @@ class HomeScreen : public UIScreen { display.setColor(DisplayDriver::GREEN); display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32); display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL); + } else if (_page == HomePage::SOS) { + display.setColor(DisplayDriver::RED); + display.setTextSize(2); + display.drawXbm((display.width() - 32) / 2, 18, sos_icon, 32, 32); + display.setTextSize(1); + display.setColor(DisplayDriver::GREEN); + display.drawTextCentered(display.width() / 2, 64 - 11, "send: " PRESS_LABEL); +} #if ENV_INCLUDE_GPS == 1 - } else if (_page == HomePage::GPS) { + else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); char buf[50]; int y = 18; @@ -434,6 +443,15 @@ class HomeScreen : public UIScreen { } return true; } + if (c == KEY_ENTER && _page == HomePage::SOS) { + _task->notify(UIEventType::ack); + if (the_mesh.sendSOS()) { + _task->showAlert("SOS SENT", 1000); + } else { + _task->showAlert("SOS FAILED", 1000); + } + return true; +} #if ENV_INCLUDE_GPS == 1 if (c == KEY_ENTER && _page == HomePage::GPS) { _task->toggleGPS(); diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index cbe237902d..d40aacdf05 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -119,4 +119,15 @@ static const uint8_t advert_icon[] = { static const uint8_t muted_icon[] = { 0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20 +}; + +static const uint8_t sos_icon[] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xc1, 0xf8, 0x1f, 0x83, 0x80, 0xf0, 0x0f, 0x01, 0x80, 0xf0, 0x0f, 0x01, + 0x9f, 0xf3, 0xcf, 0x3f, 0x9f, 0xf3, 0xcf, 0x3f, 0x9f, 0xf3, 0xcf, 0x3f, 0x81, 0xf3, 0xcf, 0x03, + 0x80, 0xf3, 0xcf, 0x01, 0xc0, 0xf3, 0xcf, 0x81, 0xfc, 0xf3, 0xcf, 0xf9, 0xfc, 0xf3, 0xcf, 0xf9, + 0xfc, 0xf3, 0xcf, 0xf9, 0x80, 0xf0, 0x0f, 0x01, 0x80, 0xf0, 0x0f, 0x01, 0xc1, 0xf8, 0x1f, 0x83, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; \ No newline at end of file From 4d77cba9f9bfa5d304d2044cd64bcdc0af9b0549 Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Wed, 20 May 2026 11:08:10 +0100 Subject: [PATCH 6/8] Enable coordinates message with link --- examples/companion_radio/MyMesh.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index dc4f053ced..ed87760add 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2190,7 +2190,7 @@ bool MyMesh::sendSOS() { uint8_t channel_idx = (sosChannel && strlen(sosChannel) > 0) ? atoi(sosChannel) - : 5; // default SOSTEST + : 9; // default SOSTEST ChannelDetails channel; bool success = getChannel(channel_idx, channel); @@ -2205,7 +2205,7 @@ bool MyMesh::sendSOS() { if (has_gps) { snprintf(gps, sizeof(gps), - "%.6f,%.6f", + "%.6f, %.6f", sensors.node_lat, sensors.node_lon ); @@ -2218,7 +2218,19 @@ bool MyMesh::sendSOS() { String finalText = String(template_text); finalText.replace("{gps}", gps); - char text[140]; + if (has_gps) { + char mapsLink[96]; + + snprintf(mapsLink, sizeof(mapsLink), + "\nhttps://maps.google.com/?q=%.6f,%.6f", + sensors.node_lat, + sensors.node_lon + ); + + finalText += mapsLink; + } + + char text[192]; strncpy(text, finalText.c_str(), sizeof(text) - 1); text[sizeof(text) - 1] = '\0'; From 851796727f323834dec4cf1ac2288e3b1fb21c13 Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Wed, 20 May 2026 12:16:46 +0100 Subject: [PATCH 7/8] Ignore channel name to send SOS, use channel 9 by default with fallback to channel 0 if 9 does not exists --- examples/companion_radio/MyMesh.cpp | 54 +++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index ed87760add..e001878e05 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -144,6 +144,16 @@ #define AUTO_ADD_ROOM_SERVER (1 << 3) // 0x08 - auto-add Room Server (ADV_TYPE_ROOM) #define AUTO_ADD_SENSOR (1 << 4) // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR) +#define SOS_DEBUG 0 + +#if SOS_DEBUG +#define SOS_LOG(...) Serial.printf(__VA_ARGS__) +#define SOS_LOGLN(x) Serial.println(x) +#else +#define SOS_LOG(...) +#define SOS_LOGLN(x) +#endif + void MyMesh::writeOKFrame() { uint8_t buf[1]; buf[0] = RESP_CODE_OK; @@ -2179,6 +2189,8 @@ void MyMesh::loop() { bool MyMesh::sendSOS() { + SOS_LOGLN("[SOS] sendSOS() START"); + const char *sosMsg = sensors.getSettingByKey("sos_message"); const char *sosChannel = sensors.getSettingByKey("sos_channel"); @@ -2187,16 +2199,35 @@ bool MyMesh::sendSOS() { ? sosMsg : "SOS - preciso de ajuda! {gps}"; - uint8_t channel_idx = - (sosChannel && strlen(sosChannel) > 0) - ? atoi(sosChannel) - : 9; // default SOSTEST + uint8_t channel_idx = 9; // default SOS slot + + if (sosChannel && strlen(sosChannel) > 0) { + int parsed = atoi(sosChannel); + + if (parsed >= 0 && parsed <= 255) { + channel_idx = (uint8_t)parsed; + } + } + + SOS_LOG("[SOS] Using requested channel slot: %u\n", channel_idx); ChannelDetails channel; bool success = getChannel(channel_idx, channel); - if (!success) - return false; + if (!success) { + SOS_LOG("[SOS] Channel slot %u not found, falling back to Public slot 0\n", channel_idx); + + channel_idx = 0; + success = getChannel(channel_idx, channel); + + if (!success) { + SOS_LOGLN("[SOS] ABORT: Public channel slot 0 also not found"); + return false; + } + } + + SOS_LOG("[SOS] Final channel slot: %u\n", channel_idx); + SOS_LOG("[SOS] channel.channel: %u\n", channel.channel); char gps[64]; @@ -2210,9 +2241,7 @@ bool MyMesh::sendSOS() { sensors.node_lon ); } else { - snprintf(gps, sizeof(gps), - "GPS sem fix" - ); + snprintf(gps, sizeof(gps), "GPS sem fix"); } String finalText = String(template_text); @@ -2236,13 +2265,18 @@ bool MyMesh::sendSOS() { uint32_t msg_timestamp = millis(); - return sendGroupMessage( + bool sent = sendGroupMessage( msg_timestamp, channel.channel, _prefs.node_name, text, strlen(text) ); + + SOS_LOG("[SOS] sendGroupMessage result: %d\n", sent); + SOS_LOGLN("[SOS] sendSOS() END"); + + return sent; } bool MyMesh::advert() { From 356c650baafa71735a2115634625f31a9869cdac Mon Sep 17 00:00:00 2001 From: Pedro Caldeira Date: Wed, 20 May 2026 18:17:36 +0100 Subject: [PATCH 8/8] Version Change in include .SOS literal --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 321375d7d1..8f3d418087 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 11 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "19 Apr 2026" +#define FIRMWARE_BUILD_DATE "20 May 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.15.0" +#define FIRMWARE_VERSION "v1.15.0.SOS" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8ed0317e69..cf9f893cb4 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -69,11 +69,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "19 Apr 2026" + #define FIRMWARE_BUILD_DATE "20 May 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.15.0" + #define FIRMWARE_VERSION "v1.15.0.SOS" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 1b35ae95a1..5e27762843 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -27,11 +27,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "19 Apr 2026" + #define FIRMWARE_BUILD_DATE "20 May 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.15.0" + #define FIRMWARE_VERSION "v1.15.0.SOS" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ee5d5e025a..87fe6a814c 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -38,7 +38,7 @@ #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.14.1" + #define FIRMWARE_VERSION "v1.15.0.SOS" #endif #define FIRMWARE_ROLE "sensor"