From 4b70ee863df0b6711da561c4694f97c2e765716e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 27 Jun 2025 19:55:17 +0100 Subject: [PATCH 001/546] Serial bridge implementation --- examples/simple_repeater/main.cpp | 18 +++- platformio.ini | 1 + src/Dispatcher.cpp | 24 ++++- src/Dispatcher.h | 5 ++ src/bridge/serial/Packet.h | 58 +++++++++++++ src/bridge/serial/PacketQueue.h | 113 ++++++++++++++++++++++++ src/bridge/serial/SerialBridge.cpp | 135 +++++++++++++++++++++++++++++ src/bridge/serial/SerialBridge.h | 73 ++++++++++++++++ 8 files changed, 422 insertions(+), 5 deletions(-) create mode 100644 src/bridge/serial/Packet.h create mode 100644 src/bridge/serial/PacketQueue.h create mode 100644 src/bridge/serial/SerialBridge.cpp create mode 100644 src/bridge/serial/SerialBridge.h diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index c33cadda..4edca23d 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -72,6 +72,11 @@ static UITask ui_task(display); #endif +#ifdef BRIDGE_OVER_SERIAL +#include "bridge/serial/SerialBridge.h" +bridge::SerialBridge *bridge_interface; +#endif + #define FIRMWARE_ROLE "repeater" #define PACKET_LOG_FILE "/packet_log" @@ -752,7 +757,14 @@ void setup() { } #endif - if (!radio_init()) { halt(); } +#ifdef BRIDGE_OVER_SERIAL + bridge_interface = new bridge::SerialBridge(); + bridge_interface->setup(); +#endif + + if (!radio_init()) { + halt(); + } fast_rng.begin(radio_get_rng_seed()); @@ -825,6 +837,10 @@ void loop() { command[0] = 0; // reset command buffer } +#ifdef BRIDGE_OVER_SERIAL + bridge_interface->loop(); +#endif + the_mesh.loop(); sensors.loop(); } diff --git a/platformio.ini b/platformio.ini index 90e7cfb0..36cdf760 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,6 +32,7 @@ build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 build_src_filter = +<*.cpp> + + + ; ----------------- ESP32 --------------------- diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 7f39dc49..08cca61e 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -1,7 +1,7 @@ #include "Dispatcher.h" #if MESH_PACKET_LOGGING - #include +#include #endif #include @@ -104,6 +104,7 @@ void Dispatcher::loop() { processRecvPacket(pkt); } } + checkRecv(); checkSend(); } @@ -115,6 +116,18 @@ void Dispatcher::checkRecv() { { uint8_t raw[MAX_TRANS_UNIT+1]; int len = _radio->recvRaw(raw, MAX_TRANS_UNIT); + +#ifdef BRIDGE_OVER_SERIAL + // We are basically stamping metadata from the last RF packet into something that came from serial, + // it works for now. But long term the code on checkRecv() should be refactored to be able to deal + // with both use cases without dupeing the existing code. I've chosen [for now] not to dupe and just + // fake the metadata. + + if (len == 0) { + len = bridge_interface->getPacket(raw); + } +#endif + if (len > 0) { logRxRaw(_radio->getLastSNR(), _radio->getLastRSSI(), raw, len); @@ -280,7 +293,11 @@ void Dispatcher::checkSend() { } outbound_expiry = futureMillis(max_airtime); - #if MESH_PACKET_LOGGING +#ifdef BRIDGE_OVER_SERIAL + bridge_interface->sendPacket(outbound); +#endif + +#if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); Serial.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", len, outbound->getPayloadType(), outbound->isRouteDirect() ? "D" : "F", outbound->payload_len); @@ -290,7 +307,7 @@ void Dispatcher::checkSend() { } else { Serial.printf("\n"); } - #endif +#endif } } } @@ -328,5 +345,4 @@ bool Dispatcher::millisHasNowPassed(unsigned long timestamp) const { unsigned long Dispatcher::futureMillis(int millis_from_now) const { return _ms->getMillis() + millis_from_now; } - } \ No newline at end of file diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 2200f81b..98184b50 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -6,6 +6,11 @@ #include #include +#ifdef BRIDGE_OVER_SERIAL +#include "bridge/serial/SerialBridge.h" +extern bridge::SerialBridge *bridge_interface; +#endif + namespace mesh { /** diff --git a/src/bridge/serial/Packet.h b/src/bridge/serial/Packet.h new file mode 100644 index 00000000..fbb4155a --- /dev/null +++ b/src/bridge/serial/Packet.h @@ -0,0 +1,58 @@ +/** + * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios + * Copyright (c) 2025 Scott Powell / rippleradios.com + * + * This project is maintained by the contributors listed in + * https://github.com/ripplebiz/MeshCore/graphs/contributors + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#pragma once + +#include "MeshCore.h" + +#include + +namespace bridge { + +/* + * +----------------------------------------------------+ + * | SERIAL PACKET SPEC | + * +-----------+---------+------------------------------+ + * | TYPE | NAME | DESCRIPTION | + * +-----------+---------+------------------------------+ + * | uint16_t | MAGIC | The packet start marker | + * | uint16_t | LEN | Length of the payload | + * | uint16_t | CRC | Checksum for error checking | + * | uint8_t[] | PAYLOAD | Actual rf packet data | + * +-----------+---------+------------------------------+ + */ +#define SERIAL_PKT_MAGIC 0xdead + +struct Packet { + uint16_t magic, len, crc; + uint8_t payload[MAX_TRANS_UNIT]; + + Packet() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} +}; + +} // namespace bridge \ No newline at end of file diff --git a/src/bridge/serial/PacketQueue.h b/src/bridge/serial/PacketQueue.h new file mode 100644 index 00000000..a83d8f2e --- /dev/null +++ b/src/bridge/serial/PacketQueue.h @@ -0,0 +1,113 @@ +/** + * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios + * Copyright (c) 2025 Scott Powell / rippleradios.com + * + * This project is maintained by the contributors listed in + * https://github.com/ripplebiz/MeshCore/graphs/contributors + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#pragma once + +#include "MeshCore.h" + +#include +#include + +#if MESH_PACKET_LOGGING +#include +#endif + +#ifndef MAX_QUEUE_SIZE +#define MAX_QUEUE_SIZE 8 +#endif + +namespace bridge { + +class PacketQueue { +private: + struct { + size_t len; + uint8_t bytes[MAX_TRANS_UNIT]; + } buffer[MAX_QUEUE_SIZE]; + +protected: + uint16_t head = 0, tail = 0; + +public: + size_t available() const { return (tail - head + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE; } + + size_t enqueue(const uint8_t *bytes, const uint8_t len) { + if (len == 0 || len > MAX_TRANS_UNIT) { +#if BRIDGE_DEBUG + Serial.printf("BRIDGE: enqueue() failed len=%d\n", len); +#endif + return 0; + } + + if ((tail + 1) % MAX_QUEUE_SIZE == head) { // Buffer full + head = (head + 1) % MAX_QUEUE_SIZE; // Overwrite oldest packet + } + + buffer[tail].len = len; + memcpy(buffer[tail].bytes, bytes, len); + +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: enqueue() len=%d payload[5]=", len); + for (size_t i = 0; i < len && i < 5; ++i) { + Serial.printf("0x%02x ", buffer[tail].bytes[i]); + } + Serial.printf("\n"); +#endif + + tail = (tail + 1) % MAX_QUEUE_SIZE; + return len; + } + + size_t enqueue(const mesh::Packet *pkt) { + uint8_t bytes[MAX_TRANS_UNIT]; + const size_t len = pkt->writeTo(bytes); + return enqueue(bytes, len); + } + + size_t dequeue(uint8_t *bytes) { + if (head == tail) { // Buffer empty + return 0; + } + + const size_t len = buffer[head].len; + memcpy(bytes, buffer[head].bytes, len); + head = (head + 1) % MAX_QUEUE_SIZE; + +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: dequeue() payload[5]="); + for (size_t i = 0; i < len && i < 5; ++i) { + Serial.printf("0x%02x ", bytes[i]); + } + Serial.printf("\n"); +#endif + + return len; + } +}; + +} // namespace bridge diff --git a/src/bridge/serial/SerialBridge.cpp b/src/bridge/serial/SerialBridge.cpp new file mode 100644 index 00000000..1e83f9e6 --- /dev/null +++ b/src/bridge/serial/SerialBridge.cpp @@ -0,0 +1,135 @@ +/** + * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios + * Copyright (c) 2025 Scott Powell / rippleradios.com + * + * This project is maintained by the contributors listed in + * https://github.com/ripplebiz/MeshCore/graphs/contributors + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#include "SerialBridge.h" + +#include "MeshCore.h" +#include "Packet.h" + +// Alternative for ESP32 +// Alternative for RP2040 +#include + +namespace bridge { +#ifdef BRIDGE_OVER_SERIAL + +#if !defined(BRIDGE_OVER_SERIAL_RX) || !defined(BRIDGE_OVER_SERIAL_TX) +#error You must define RX and TX pins +#endif + +void SerialBridge::setup() { +#if defined(ESP32) + BRIDGE_OVER_SERIAL.setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); +#elif defined(RP2040_PLATFORM) + BRIDGE_OVER_SERIAL.setPinout(BRIDGE_OVER_SERIAL_TX, BRIDGE_OVER_SERIAL_RX); +#else +#error SerialBridge was not tested on the current platform +#endif + BRIDGE_OVER_SERIAL.begin(115200); + Serial.printf("Bridge over serial: enabled\n"); +} + +void SerialBridge::loop() { + readFromSerial(); + writeToSerial(); +} + +bool SerialBridge::shouldRetransmit(const mesh::Packet *pkt) { + if (pkt->isMarkedDoNotRetransmit()) { + return false; + } +} + +size_t SerialBridge::getPacket(uint8_t *bytes) { + return rx_queue.dequeue(bytes); +} + +size_t SerialBridge::sendPacket(const mesh::Packet *pkt) { + if (shouldRetransmit(pkt)) return 0; + const size_t len = tx_queue.enqueue(pkt); + return len; +} + +void SerialBridge::readFromSerial() { + static constexpr uint16_t size = sizeof(bridge::Packet) + 1; + static uint8_t buffer[size]; + static uint16_t tail = 0; + + while (BRIDGE_OVER_SERIAL.available()) { + buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read(); + tail = (tail + 1) % size; + +#if BRIDGE_OVER_SERIAL_DEBUG + Serial.printf("%02x ", buffer[(tail - 1 + size) % size]); +#endif + + // Check for complete packet by looking back to where the magic number should be + uint16_t head = (tail - sizeof(bridge::Packet) + size) % size; + const uint16_t magic = buffer[head] | (buffer[(head + 1) % size] << 8); + + if (magic == SERIAL_PKT_MAGIC) { + const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); + const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); + + uint8_t payload[MAX_TRANS_UNIT]; + for (size_t i = 0; i < len; i++) { + payload[i] = buffer[(head + 6 + i) % size]; + } + + const bool valid = verifyCRC(payload, len, crc); + +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: Read from serial len=%d crc=0x%04x\n", len, crc); +#endif + + if (verifyCRC(payload, len, crc)) { +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: Received packet was validated\n"); +#endif + rx_queue.enqueue(payload, len); + } + } + } +} + +void SerialBridge::writeToSerial() { + bridge::Packet pkt; + if (!tx_queue.available()) return; + pkt.len = tx_queue.dequeue(pkt.payload); + + if (pkt.len > 0) { + pkt.crc = SerialBridge::calculateCRC(pkt.payload, pkt.len); + BRIDGE_OVER_SERIAL.write((uint8_t *)&pkt, sizeof(bridge::Packet)); +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: Write to serial len=%d crc=0x%04x\n", pkt.len, pkt.crc); +#endif + } +} + +#endif +} // namespace bridge diff --git a/src/bridge/serial/SerialBridge.h b/src/bridge/serial/SerialBridge.h new file mode 100644 index 00000000..80bb2360 --- /dev/null +++ b/src/bridge/serial/SerialBridge.h @@ -0,0 +1,73 @@ +/** + * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios + * Copyright (c) 2025 Scott Powell / rippleradios.com + * + * This project is maintained by the contributors listed in + * https://github.com/ripplebiz/MeshCore/graphs/contributors + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + */ +#pragma once + +#include "Packet.h" +#include "PacketQueue.h" + +#include + +namespace bridge { + +class SerialBridge { +private: + PacketQueue rx_queue, tx_queue; + +protected: + bool shouldRetransmit(const mesh::Packet *); + +public: + void loop(); + void setup(); + void readFromSerial(); + void writeToSerial(); + + size_t getPacket(uint8_t *); + size_t sendPacket(const mesh::Packet *); + + static uint16_t calculateCRC(const uint8_t *bytes, size_t len) { + // Fletcher-16 + // https://en.wikipedia.org/wiki/Fletcher%27s_checksum + uint8_t sum1 = 0, sum2 = 0; + + for (size_t i = 0; i < len; i++) { + sum1 = (sum1 + bytes[i]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; + }; + + static bool verifyCRC(const uint8_t *bytes, size_t len, uint16_t crc) { + uint16_t computedChecksum = calculateCRC(bytes, len); + return (computedChecksum == crc); + } +}; + +} // namespace bridge From 2f77cef04b65f1d76a01e3b8c74086732d43a8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 29 Jun 2025 16:28:11 +0100 Subject: [PATCH 002/546] Add config flags to variants --- variants/heltec_v3/platformio.ini | 3 +++ variants/lilygo_tlora_v2_1/platformio.ini | 3 +++ variants/waveshare_rp2040_lora/platformio.ini | 3 +++ 3 files changed, 9 insertions(+) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 8f9b1a27..173be80c 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -47,6 +47,9 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 +; -D BRIDGE_OVER_SERIAL=Serial2 +; -D BRIDGE_OVER_SERIAL_RX=5 +; -D BRIDGE_OVER_SERIAL_TX=6 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index d9cecfc2..bd351c5a 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -44,6 +44,9 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 +; -D BRIDGE_OVER_SERIAL=Serial2 +; -D BRIDGE_OVER_SERIAL_RX=34 +; -D BRIDGE_OVER_SERIAL_TX=25 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 2730734d..b1e4714a 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -35,6 +35,9 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 +; -D BRIDGE_OVER_SERIAL=Serial2 +; -D BRIDGE_OVER_SERIAL_RX=9 +; -D BRIDGE_OVER_SERIAL_TX=8 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} From ac056fb0b98ecd9ce0da6cda8693f1cdffb97cb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 8 Jul 2025 14:04:21 +0100 Subject: [PATCH 003/546] Remove serial bridge implementation and implement simplified version directly in the repeater source code. --- examples/simple_repeater/main.cpp | 121 +++++++++++++++++++++----- src/Dispatcher.cpp | 15 ---- src/Dispatcher.h | 5 -- src/bridge/serial/Packet.h | 58 ------------- src/bridge/serial/PacketQueue.h | 113 ------------------------ src/bridge/serial/SerialBridge.cpp | 135 ----------------------------- src/bridge/serial/SerialBridge.h | 73 ---------------- 7 files changed, 100 insertions(+), 420 deletions(-) delete mode 100644 src/bridge/serial/Packet.h delete mode 100644 src/bridge/serial/PacketQueue.h delete mode 100644 src/bridge/serial/SerialBridge.cpp delete mode 100644 src/bridge/serial/SerialBridge.h diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 131dc496..96969f96 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -72,17 +72,35 @@ static UITask ui_task(display); #endif -#ifdef BRIDGE_OVER_SERIAL -#include "bridge/serial/SerialBridge.h" -bridge::SerialBridge *bridge_interface; -#endif - #define FIRMWARE_ROLE "repeater" #define PACKET_LOG_FILE "/packet_log" /* ------------------------------ Code -------------------------------- */ +#ifdef BRIDGE_OVER_SERIAL +#define SERIAL_PKT_MAGIC 0xbeef + +struct SerialPacket { + uint16_t magic, len, crc; + uint8_t payload[MAX_TRANS_UNIT]; + SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} +}; + +// Fletcher-16 +// https://en.wikipedia.org/wiki/Fletcher%27s_checksum +static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { + uint8_t sum1 = 0, sum2 = 0; + + for (size_t i = 0; i < len; i++) { + sum1 = (sum1 + bytes[i]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; +}; +#endif + #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 @@ -297,6 +315,20 @@ protected: } } void logTx(mesh::Packet* pkt, int len) override { +#ifdef BRIDGE_OVER_SERIAL + SerialPacket spkt; + spkt.len = pkt->writeTo(spkt.payload); + + if (spkt.len > 0) { + spkt.crc = fletcher16(spkt.payload, spkt.len); + BRIDGE_OVER_SERIAL.write((uint8_t *)&spkt, sizeof(SerialPacket)); + +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: Write to serial len=%d crc=0x%04x\n", spkt.len, spkt.crc); +#endif + } +#endif + if (_logging) { File f = openAppend(PACKET_LOG_FILE); if (f) { @@ -358,9 +390,9 @@ protected: } else if (strcmp((char *) &data[4], _prefs.guest_password) == 0) { // check guest password is_admin = false; } else { - #if MESH_DEBUG +#if MESH_DEBUG MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); - #endif +#endif return; } @@ -377,15 +409,15 @@ protected: uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - #if 0 +#if 0 memcpy(&reply_data[4], "OK", 2); // legacy response - #else +#else reply_data[4] = RESP_SERVER_LOGIN_OK; reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) reply_data[6] = is_admin ? 1 : 0; reply_data[7] = 0; // FUTURE: reserved getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness - #endif +#endif if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response @@ -562,9 +594,9 @@ public: next_local_advert = next_flood_advert = 0; _logging = false; - #if MAX_NEIGHBOURS +#if MAX_NEIGHBOURS memset(neighbours, 0, sizeof(neighbours)); - #endif +#endif // defaults memset(&_prefs, 0, sizeof(_prefs)); @@ -712,6 +744,44 @@ public: } void loop() { +#ifdef BRIDGE_OVER_SERIAL + static constexpr uint16_t size = sizeof(SerialPacket) + 1; + static uint8_t buffer[size]; + static uint16_t tail = 0; + + while (BRIDGE_OVER_SERIAL.available()) { + buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read(); + MESH_DEBUG_PRINT("%02x ", buffer[tail]); + tail = (tail + 1) % size; + + // Check for complete packet by looking back to where the magic number should be + uint16_t head = (tail - sizeof(SerialPacket) + size) % size; + const uint16_t magic = buffer[head] | (buffer[(head + 1) % size] << 8); + + if (magic == SERIAL_PKT_MAGIC) { + uint8_t bytes[MAX_TRANS_UNIT]; + const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); + const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); + + for (size_t i = 0; i < len; i++) { + bytes[i] = buffer[(head + 6 + i) % size]; + } + + uint16_t f16 = fletcher16(bytes, len); + +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: Read from serial len=%d crc=0x%04x valid=%s\n", len, crc, (f16 == crc) ? "true" : "false"); +#endif + + if (f16 == crc) { + mesh::Packet *pkt = _mgr->allocNew(); + pkt->readFrom(bytes, len); + _mgr->queueInbound(pkt, millis()); + } + } + } +#endif + mesh::Mesh::loop(); if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { @@ -745,6 +815,24 @@ static char command[80]; void setup() { Serial.begin(115200); + +#ifdef BRIDGE_OVER_SERIAL +#if defined(ESP32) + BRIDGE_OVER_SERIAL.setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); +#elif defined(RP2040_PLATFORM) + BRIDGE_OVER_SERIAL.setRX(BRIDGE_OVER_SERIAL_RX); + BRIDGE_OVER_SERIAL.setTX(BRIDGE_OVER_SERIAL_TX); +#elif defined(STM32_PLATFORM) + BRIDGE_OVER_SERIAL.setRx(BRIDGE_OVER_SERIAL_RX); + BRIDGE_OVER_SERIAL.setTx(BRIDGE_OVER_SERIAL_TX); +#else +#error SerialBridge was not tested on the current platform +#endif + + BRIDGE_OVER_SERIAL.begin(115200); + MESH_DEBUG_PRINTLN("Bridge over serial: enabled"); +#endif + delay(1000); board.begin(); @@ -757,11 +845,6 @@ void setup() { } #endif -#ifdef BRIDGE_OVER_SERIAL - bridge_interface = new bridge::SerialBridge(); - bridge_interface->setup(); -#endif - if (!radio_init()) { halt(); } @@ -837,10 +920,6 @@ void loop() { command[0] = 0; // reset command buffer } -#ifdef BRIDGE_OVER_SERIAL - bridge_interface->loop(); -#endif - the_mesh.loop(); sensors.loop(); } diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 08cca61e..1a6e833b 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -117,17 +117,6 @@ void Dispatcher::checkRecv() { uint8_t raw[MAX_TRANS_UNIT+1]; int len = _radio->recvRaw(raw, MAX_TRANS_UNIT); -#ifdef BRIDGE_OVER_SERIAL - // We are basically stamping metadata from the last RF packet into something that came from serial, - // it works for now. But long term the code on checkRecv() should be refactored to be able to deal - // with both use cases without dupeing the existing code. I've chosen [for now] not to dupe and just - // fake the metadata. - - if (len == 0) { - len = bridge_interface->getPacket(raw); - } -#endif - if (len > 0) { logRxRaw(_radio->getLastSNR(), _radio->getLastRSSI(), raw, len); @@ -293,10 +282,6 @@ void Dispatcher::checkSend() { } outbound_expiry = futureMillis(max_airtime); -#ifdef BRIDGE_OVER_SERIAL - bridge_interface->sendPacket(outbound); -#endif - #if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); Serial.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 98184b50..2200f81b 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -6,11 +6,6 @@ #include #include -#ifdef BRIDGE_OVER_SERIAL -#include "bridge/serial/SerialBridge.h" -extern bridge::SerialBridge *bridge_interface; -#endif - namespace mesh { /** diff --git a/src/bridge/serial/Packet.h b/src/bridge/serial/Packet.h deleted file mode 100644 index fbb4155a..00000000 --- a/src/bridge/serial/Packet.h +++ /dev/null @@ -1,58 +0,0 @@ -/** - * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios - * Copyright (c) 2025 Scott Powell / rippleradios.com - * - * This project is maintained by the contributors listed in - * https://github.com/ripplebiz/MeshCore/graphs/contributors - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -#pragma once - -#include "MeshCore.h" - -#include - -namespace bridge { - -/* - * +----------------------------------------------------+ - * | SERIAL PACKET SPEC | - * +-----------+---------+------------------------------+ - * | TYPE | NAME | DESCRIPTION | - * +-----------+---------+------------------------------+ - * | uint16_t | MAGIC | The packet start marker | - * | uint16_t | LEN | Length of the payload | - * | uint16_t | CRC | Checksum for error checking | - * | uint8_t[] | PAYLOAD | Actual rf packet data | - * +-----------+---------+------------------------------+ - */ -#define SERIAL_PKT_MAGIC 0xdead - -struct Packet { - uint16_t magic, len, crc; - uint8_t payload[MAX_TRANS_UNIT]; - - Packet() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} -}; - -} // namespace bridge \ No newline at end of file diff --git a/src/bridge/serial/PacketQueue.h b/src/bridge/serial/PacketQueue.h deleted file mode 100644 index a83d8f2e..00000000 --- a/src/bridge/serial/PacketQueue.h +++ /dev/null @@ -1,113 +0,0 @@ -/** - * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios - * Copyright (c) 2025 Scott Powell / rippleradios.com - * - * This project is maintained by the contributors listed in - * https://github.com/ripplebiz/MeshCore/graphs/contributors - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -#pragma once - -#include "MeshCore.h" - -#include -#include - -#if MESH_PACKET_LOGGING -#include -#endif - -#ifndef MAX_QUEUE_SIZE -#define MAX_QUEUE_SIZE 8 -#endif - -namespace bridge { - -class PacketQueue { -private: - struct { - size_t len; - uint8_t bytes[MAX_TRANS_UNIT]; - } buffer[MAX_QUEUE_SIZE]; - -protected: - uint16_t head = 0, tail = 0; - -public: - size_t available() const { return (tail - head + MAX_QUEUE_SIZE) % MAX_QUEUE_SIZE; } - - size_t enqueue(const uint8_t *bytes, const uint8_t len) { - if (len == 0 || len > MAX_TRANS_UNIT) { -#if BRIDGE_DEBUG - Serial.printf("BRIDGE: enqueue() failed len=%d\n", len); -#endif - return 0; - } - - if ((tail + 1) % MAX_QUEUE_SIZE == head) { // Buffer full - head = (head + 1) % MAX_QUEUE_SIZE; // Overwrite oldest packet - } - - buffer[tail].len = len; - memcpy(buffer[tail].bytes, bytes, len); - -#if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: enqueue() len=%d payload[5]=", len); - for (size_t i = 0; i < len && i < 5; ++i) { - Serial.printf("0x%02x ", buffer[tail].bytes[i]); - } - Serial.printf("\n"); -#endif - - tail = (tail + 1) % MAX_QUEUE_SIZE; - return len; - } - - size_t enqueue(const mesh::Packet *pkt) { - uint8_t bytes[MAX_TRANS_UNIT]; - const size_t len = pkt->writeTo(bytes); - return enqueue(bytes, len); - } - - size_t dequeue(uint8_t *bytes) { - if (head == tail) { // Buffer empty - return 0; - } - - const size_t len = buffer[head].len; - memcpy(bytes, buffer[head].bytes, len); - head = (head + 1) % MAX_QUEUE_SIZE; - -#if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: dequeue() payload[5]="); - for (size_t i = 0; i < len && i < 5; ++i) { - Serial.printf("0x%02x ", bytes[i]); - } - Serial.printf("\n"); -#endif - - return len; - } -}; - -} // namespace bridge diff --git a/src/bridge/serial/SerialBridge.cpp b/src/bridge/serial/SerialBridge.cpp deleted file mode 100644 index 1e83f9e6..00000000 --- a/src/bridge/serial/SerialBridge.cpp +++ /dev/null @@ -1,135 +0,0 @@ -/** - * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios - * Copyright (c) 2025 Scott Powell / rippleradios.com - * - * This project is maintained by the contributors listed in - * https://github.com/ripplebiz/MeshCore/graphs/contributors - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -#include "SerialBridge.h" - -#include "MeshCore.h" -#include "Packet.h" - -// Alternative for ESP32 -// Alternative for RP2040 -#include - -namespace bridge { -#ifdef BRIDGE_OVER_SERIAL - -#if !defined(BRIDGE_OVER_SERIAL_RX) || !defined(BRIDGE_OVER_SERIAL_TX) -#error You must define RX and TX pins -#endif - -void SerialBridge::setup() { -#if defined(ESP32) - BRIDGE_OVER_SERIAL.setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); -#elif defined(RP2040_PLATFORM) - BRIDGE_OVER_SERIAL.setPinout(BRIDGE_OVER_SERIAL_TX, BRIDGE_OVER_SERIAL_RX); -#else -#error SerialBridge was not tested on the current platform -#endif - BRIDGE_OVER_SERIAL.begin(115200); - Serial.printf("Bridge over serial: enabled\n"); -} - -void SerialBridge::loop() { - readFromSerial(); - writeToSerial(); -} - -bool SerialBridge::shouldRetransmit(const mesh::Packet *pkt) { - if (pkt->isMarkedDoNotRetransmit()) { - return false; - } -} - -size_t SerialBridge::getPacket(uint8_t *bytes) { - return rx_queue.dequeue(bytes); -} - -size_t SerialBridge::sendPacket(const mesh::Packet *pkt) { - if (shouldRetransmit(pkt)) return 0; - const size_t len = tx_queue.enqueue(pkt); - return len; -} - -void SerialBridge::readFromSerial() { - static constexpr uint16_t size = sizeof(bridge::Packet) + 1; - static uint8_t buffer[size]; - static uint16_t tail = 0; - - while (BRIDGE_OVER_SERIAL.available()) { - buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read(); - tail = (tail + 1) % size; - -#if BRIDGE_OVER_SERIAL_DEBUG - Serial.printf("%02x ", buffer[(tail - 1 + size) % size]); -#endif - - // Check for complete packet by looking back to where the magic number should be - uint16_t head = (tail - sizeof(bridge::Packet) + size) % size; - const uint16_t magic = buffer[head] | (buffer[(head + 1) % size] << 8); - - if (magic == SERIAL_PKT_MAGIC) { - const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); - const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); - - uint8_t payload[MAX_TRANS_UNIT]; - for (size_t i = 0; i < len; i++) { - payload[i] = buffer[(head + 6 + i) % size]; - } - - const bool valid = verifyCRC(payload, len, crc); - -#if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: Read from serial len=%d crc=0x%04x\n", len, crc); -#endif - - if (verifyCRC(payload, len, crc)) { -#if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: Received packet was validated\n"); -#endif - rx_queue.enqueue(payload, len); - } - } - } -} - -void SerialBridge::writeToSerial() { - bridge::Packet pkt; - if (!tx_queue.available()) return; - pkt.len = tx_queue.dequeue(pkt.payload); - - if (pkt.len > 0) { - pkt.crc = SerialBridge::calculateCRC(pkt.payload, pkt.len); - BRIDGE_OVER_SERIAL.write((uint8_t *)&pkt, sizeof(bridge::Packet)); -#if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: Write to serial len=%d crc=0x%04x\n", pkt.len, pkt.crc); -#endif - } -} - -#endif -} // namespace bridge diff --git a/src/bridge/serial/SerialBridge.h b/src/bridge/serial/SerialBridge.h deleted file mode 100644 index 80bb2360..00000000 --- a/src/bridge/serial/SerialBridge.h +++ /dev/null @@ -1,73 +0,0 @@ -/** - * MeshCore - A new lightweight, hybrid routing mesh protocol for packet radios - * Copyright (c) 2025 Scott Powell / rippleradios.com - * - * This project is maintained by the contributors listed in - * https://github.com/ripplebiz/MeshCore/graphs/contributors - * - * MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in all - * copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - * - */ -#pragma once - -#include "Packet.h" -#include "PacketQueue.h" - -#include - -namespace bridge { - -class SerialBridge { -private: - PacketQueue rx_queue, tx_queue; - -protected: - bool shouldRetransmit(const mesh::Packet *); - -public: - void loop(); - void setup(); - void readFromSerial(); - void writeToSerial(); - - size_t getPacket(uint8_t *); - size_t sendPacket(const mesh::Packet *); - - static uint16_t calculateCRC(const uint8_t *bytes, size_t len) { - // Fletcher-16 - // https://en.wikipedia.org/wiki/Fletcher%27s_checksum - uint8_t sum1 = 0, sum2 = 0; - - for (size_t i = 0; i < len; i++) { - sum1 = (sum1 + bytes[i]) % 255; - sum2 = (sum2 + sum1) % 255; - } - - return (sum2 << 8) | sum1; - }; - - static bool verifyCRC(const uint8_t *bytes, size_t len, uint16_t crc) { - uint16_t computedChecksum = calculateCRC(bytes, len); - return (computedChecksum == crc); - } -}; - -} // namespace bridge From 92ee1820c42041dce922b8e5917eb46533f9e217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 8 Jul 2025 16:02:10 +0100 Subject: [PATCH 004/546] Add null check for packet allocation and clean up Dispatcher --- examples/simple_repeater/main.cpp | 13 ++++++++++--- src/Dispatcher.cpp | 9 ++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 96969f96..428e932f 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -774,9 +774,16 @@ public: #endif if (f16 == crc) { - mesh::Packet *pkt = _mgr->allocNew(); - pkt->readFrom(bytes, len); - _mgr->queueInbound(pkt, millis()); + Packet *pkt = _mgr->allocNew(); + + if (pkt != NULL) { + pkt->readFrom(bytes, len); + _mgr->queueInbound(pkt, millis()); + } else { +#if MESH_PACKET_LOGGING + Serial.printf("BRIDGE: Unable to allocate new Packet *pkt"); +#endif + } } } } diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 1a6e833b..7f39dc49 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -1,7 +1,7 @@ #include "Dispatcher.h" #if MESH_PACKET_LOGGING -#include + #include #endif #include @@ -104,7 +104,6 @@ void Dispatcher::loop() { processRecvPacket(pkt); } } - checkRecv(); checkSend(); } @@ -116,7 +115,6 @@ void Dispatcher::checkRecv() { { uint8_t raw[MAX_TRANS_UNIT+1]; int len = _radio->recvRaw(raw, MAX_TRANS_UNIT); - if (len > 0) { logRxRaw(_radio->getLastSNR(), _radio->getLastRSSI(), raw, len); @@ -282,7 +280,7 @@ void Dispatcher::checkSend() { } outbound_expiry = futureMillis(max_airtime); -#if MESH_PACKET_LOGGING + #if MESH_PACKET_LOGGING Serial.print(getLogDateTime()); Serial.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", len, outbound->getPayloadType(), outbound->isRouteDirect() ? "D" : "F", outbound->payload_len); @@ -292,7 +290,7 @@ void Dispatcher::checkSend() { } else { Serial.printf("\n"); } -#endif + #endif } } } @@ -330,4 +328,5 @@ bool Dispatcher::millisHasNowPassed(unsigned long timestamp) const { unsigned long Dispatcher::futureMillis(int millis_from_now) const { return _ms->getMillis() + millis_from_now; } + } \ No newline at end of file From 97b51900f8667515f4f1a6f9064f5d280a0b480f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Tue, 8 Jul 2025 21:45:49 +0100 Subject: [PATCH 005/546] More robust handling of pkt len --- examples/simple_repeater/main.cpp | 45 ++++++++++++++++++------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 428e932f..fe065a33 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -79,7 +79,7 @@ /* ------------------------------ Code -------------------------------- */ #ifdef BRIDGE_OVER_SERIAL -#define SERIAL_PKT_MAGIC 0xbeef +#define SERIAL_PKT_MAGIC 0xcafe struct SerialPacket { uint16_t magic, len, crc; @@ -316,15 +316,17 @@ protected: } void logTx(mesh::Packet* pkt, int len) override { #ifdef BRIDGE_OVER_SERIAL - SerialPacket spkt; - spkt.len = pkt->writeTo(spkt.payload); + SerialPacket serialpkt; + size_t seriallen = pkt->writeTo(serialpkt.payload); - if (spkt.len > 0) { - spkt.crc = fletcher16(spkt.payload, spkt.len); - BRIDGE_OVER_SERIAL.write((uint8_t *)&spkt, sizeof(SerialPacket)); + if (seriallen - 1 < MAX_TRANS_UNIT - 1) { + serialpkt.len = seriallen; + serialpkt.crc = fletcher16(serialpkt.payload, serialpkt.len); + BRIDGE_OVER_SERIAL.write((uint8_t *)&serialpkt, sizeof(SerialPacket)); #if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: Write to serial len=%d crc=0x%04x\n", spkt.len, spkt.crc); + Serial.print(getLogDateTime()); + Serial.printf(": BRIDGE: Write to serial len=%d crc=0x%04x\n", serialpkt.len, serialpkt.crc); #endif } #endif @@ -763,26 +765,31 @@ public: const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); - for (size_t i = 0; i < len; i++) { - bytes[i] = buffer[(head + 6 + i) % size]; - } + if (len - 1 < MAX_TRANS_UNIT - 1) { + for (size_t i = 0; i < len; i++) { + bytes[i] = buffer[(head + 6 + i) % size]; + } - uint16_t f16 = fletcher16(bytes, len); + uint16_t f16 = fletcher16(bytes, len); #if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: Read from serial len=%d crc=0x%04x valid=%s\n", len, crc, (f16 == crc) ? "true" : "false"); + Serial.print(getLogDateTime()); + Serial.printf(": BRIDGE: Read from serial len=%d crc=0x%04x valid=%s\n", len, crc, + (f16 == crc) ? "true" : "false"); #endif - if (f16 == crc) { - Packet *pkt = _mgr->allocNew(); + if (f16 == crc) { + mesh::Packet *pkt = _mgr->allocNew(); - if (pkt != NULL) { - pkt->readFrom(bytes, len); - _mgr->queueInbound(pkt, millis()); - } else { + if (pkt == NULL) { #if MESH_PACKET_LOGGING - Serial.printf("BRIDGE: Unable to allocate new Packet *pkt"); + Serial.print(getLogDateTime()); + Serial.printf(": BRIDGE: Unable to allocate new Packet *pkt\n"); #endif + } else { + pkt->readFrom(bytes, len); + _mgr->queueInbound(pkt, millis()); + } } } } From 04042e3ca0c1f0c52ddd110d589bdb446d5de4eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Wed, 9 Jul 2025 11:03:35 +0100 Subject: [PATCH 006/546] Refactor serial bridge handling --- examples/simple_repeater/main.cpp | 170 +++++++++--------- platformio.ini | 1 - variants/heltec_v3/platformio.ini | 25 ++- variants/lilygo_tlora_v2_1/platformio.ini | 25 ++- variants/waveshare_rp2040_lora/platformio.ini | 19 +- 5 files changed, 147 insertions(+), 93 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index fe065a33..e9683fd3 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -80,25 +80,6 @@ #ifdef BRIDGE_OVER_SERIAL #define SERIAL_PKT_MAGIC 0xcafe - -struct SerialPacket { - uint16_t magic, len, crc; - uint8_t payload[MAX_TRANS_UNIT]; - SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} -}; - -// Fletcher-16 -// https://en.wikipedia.org/wiki/Fletcher%27s_checksum -static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { - uint8_t sum1 = 0, sum2 = 0; - - for (size_t i = 0; i < len; i++) { - sum1 = (sum1 + bytes[i]) % 255; - sum2 = (sum2 + sum1) % 255; - } - - return (sum2 << 8) | sum1; -}; #endif #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS @@ -267,6 +248,89 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif } +#ifdef BRIDGE_OVER_SERIAL + struct SerialPacket { + uint16_t magic, len, crc; + uint8_t payload[MAX_TRANS_UNIT]; + SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} + }; + + // Fletcher-16 + // https://en.wikipedia.org/wiki/Fletcher%27s_checksum + inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { + uint8_t sum1 = 0, sum2 = 0; + + for (size_t i = 0; i < len; i++) { + sum1 = (sum1 + bytes[i]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; + }; + + inline void serialBridgeSendPkt(const mesh::Packet *pkt) { + SerialPacket spkt; + spkt.len = pkt->writeTo(spkt.payload); + spkt.crc = fletcher16(spkt.payload, spkt.len); + BRIDGE_OVER_SERIAL.write((uint8_t *)&spkt, sizeof(SerialPacket)); + +#if MESH_PACKET_LOGGING + Serial.printf("%s: BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), spkt.len, spkt.crc); +#endif + } + + inline void serialBridgeReceivePkt() { + static constexpr uint16_t size = sizeof(SerialPacket) + 1; + static uint8_t buffer[size]; + static uint16_t tail = 0; + + while (BRIDGE_OVER_SERIAL.available()) { + buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read(); + MESH_DEBUG_PRINT("%02x ", buffer[tail]); + tail = (tail + 1) % size; + + // Check for complete packet by looking back to where the magic number should be + const uint16_t head = (tail - sizeof(SerialPacket) + size) % size; + if ((buffer[head] | (buffer[(head + 1) % size] << 8)) != SERIAL_PKT_MAGIC) { + return; + } + + uint8_t bytes[MAX_TRANS_UNIT]; + const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); + + if (len == 0 || len > sizeof(bytes)) { + MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, invalid packet len", getLogDateTime()); + return; + } + + for (size_t i = 0; i < len; i++) { + bytes[i] = buffer[(head + 6 + i) % size]; + } + + const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); + const uint16_t f16 = fletcher16(bytes, len); + +#if MESH_PACKET_LOGGING + Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, crc); +#endif + + if ((f16 != crc)) { + MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, invalid packet checksum", getLogDateTime()); + return; + } + + mesh::Packet *pkt = _mgr->allocNew(); + if (pkt == NULL) { + MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, no unused packets available", getLogDateTime()); + return; + } + + pkt->readFrom(bytes, len); + _mgr->queueInbound(pkt, futureMillis(0)); + } + } +#endif + protected: float getAirtimeBudgetFactor() const override { return _prefs.airtime_factor; @@ -316,21 +380,10 @@ protected: } void logTx(mesh::Packet* pkt, int len) override { #ifdef BRIDGE_OVER_SERIAL - SerialPacket serialpkt; - size_t seriallen = pkt->writeTo(serialpkt.payload); - - if (seriallen - 1 < MAX_TRANS_UNIT - 1) { - serialpkt.len = seriallen; - serialpkt.crc = fletcher16(serialpkt.payload, serialpkt.len); - BRIDGE_OVER_SERIAL.write((uint8_t *)&serialpkt, sizeof(SerialPacket)); - -#if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.printf(": BRIDGE: Write to serial len=%d crc=0x%04x\n", serialpkt.len, serialpkt.crc); -#endif + if (!pkt->isMarkedDoNotRetransmit()) { + serialBridgeSendPkt(pkt); } #endif - if (_logging) { File f = openAppend(PACKET_LOG_FILE); if (f) { @@ -747,53 +800,7 @@ public: void loop() { #ifdef BRIDGE_OVER_SERIAL - static constexpr uint16_t size = sizeof(SerialPacket) + 1; - static uint8_t buffer[size]; - static uint16_t tail = 0; - - while (BRIDGE_OVER_SERIAL.available()) { - buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read(); - MESH_DEBUG_PRINT("%02x ", buffer[tail]); - tail = (tail + 1) % size; - - // Check for complete packet by looking back to where the magic number should be - uint16_t head = (tail - sizeof(SerialPacket) + size) % size; - const uint16_t magic = buffer[head] | (buffer[(head + 1) % size] << 8); - - if (magic == SERIAL_PKT_MAGIC) { - uint8_t bytes[MAX_TRANS_UNIT]; - const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); - const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); - - if (len - 1 < MAX_TRANS_UNIT - 1) { - for (size_t i = 0; i < len; i++) { - bytes[i] = buffer[(head + 6 + i) % size]; - } - - uint16_t f16 = fletcher16(bytes, len); - -#if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.printf(": BRIDGE: Read from serial len=%d crc=0x%04x valid=%s\n", len, crc, - (f16 == crc) ? "true" : "false"); -#endif - - if (f16 == crc) { - mesh::Packet *pkt = _mgr->allocNew(); - - if (pkt == NULL) { -#if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.printf(": BRIDGE: Unable to allocate new Packet *pkt\n"); -#endif - } else { - pkt->readFrom(bytes, len); - _mgr->queueInbound(pkt, millis()); - } - } - } - } - } + serialBridgeReceivePkt(); #endif mesh::Mesh::loop(); @@ -829,6 +836,7 @@ static char command[80]; void setup() { Serial.begin(115200); + delay(1000); #ifdef BRIDGE_OVER_SERIAL #if defined(ESP32) @@ -842,13 +850,9 @@ void setup() { #else #error SerialBridge was not tested on the current platform #endif - BRIDGE_OVER_SERIAL.begin(115200); - MESH_DEBUG_PRINTLN("Bridge over serial: enabled"); #endif - delay(1000); - board.begin(); #ifdef DISPLAY_CLASS diff --git a/platformio.ini b/platformio.ini index 36cdf760..90e7cfb0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -32,7 +32,6 @@ build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 build_src_filter = +<*.cpp> + - + ; ----------------- ESP32 --------------------- diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 704958d1..16e65bf2 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -49,9 +49,6 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -; -D BRIDGE_OVER_SERIAL=Serial2 -; -D BRIDGE_OVER_SERIAL_RX=5 -; -D BRIDGE_OVER_SERIAL_TX=6 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -61,6 +58,28 @@ lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} +[env:Heltec_v3_Bridge] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D BRIDGE_OVER_SERIAL=Serial2 + -D BRIDGE_OVER_SERIAL_RX=5 + -D BRIDGE_OVER_SERIAL_TX=6 + -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_v3_room_server] extends = Heltec_lora32_v3 build_flags = diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index a2169f1f..adf4a7e4 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -57,9 +57,6 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -; -D BRIDGE_OVER_SERIAL=Serial2 -; -D BRIDGE_OVER_SERIAL_RX=34 -; -D BRIDGE_OVER_SERIAL_TX=25 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 @@ -67,6 +64,28 @@ lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} ${esp32_ota.lib_deps} +[env:LilyGo_TLora_V2_1_1_6_Bridge] +extends = LilyGo_TLora_V2_1_1_6 +build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + + +<../examples/simple_repeater> +build_flags = + ${LilyGo_TLora_V2_1_1_6.build_flags} + -D ADVERT_NAME='"TLora-V2.1-1.6 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D BRIDGE_OVER_SERIAL=Serial2 + -D BRIDGE_OVER_SERIAL_RX=34 + -D BRIDGE_OVER_SERIAL_TX=25 + -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +lib_deps = + ${LilyGo_TLora_V2_1_1_6.lib_deps} + ${esp32_ota.lib_deps} + [env:LilyGo_TLora_V2_1_1_6_terminal_chat] extends = LilyGo_TLora_V2_1_1_6 build_flags = diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index b1e4714a..515f7020 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -35,14 +35,27 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -; -D BRIDGE_OVER_SERIAL=Serial2 -; -D BRIDGE_OVER_SERIAL_RX=9 -; -D BRIDGE_OVER_SERIAL_TX=8 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} +<../examples/simple_repeater> +[env:waveshare_rp2040_lora_Bridge] +extends = waveshare_rp2040_lora +build_flags = ${waveshare_rp2040_lora.build_flags} + -D ADVERT_NAME='"RP2040-LoRa Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D BRIDGE_OVER_SERIAL=Serial2 + -D BRIDGE_OVER_SERIAL_RX=9 + -D BRIDGE_OVER_SERIAL_TX=8 + -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${waveshare_rp2040_lora.build_src_filter} + +<../examples/simple_repeater> + [env:waveshare_rp2040_lora_room_server] extends = waveshare_rp2040_lora build_flags = ${waveshare_rp2040_lora.build_flags} From 24ef375fc764ee8ed5c97ceccb022dcbb19b9b34 Mon Sep 17 00:00:00 2001 From: Haylin Moore Date: Thu, 14 Aug 2025 15:51:02 -0700 Subject: [PATCH 007/546] default.nix: add python3 to the shell There are many systems that chose to not have a global python install, namely my own systems :p. I end up having to nix-shell -p python3 anytime I want to run the build.sh as the last step uf2conv depends on python --- default.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/default.nix b/default.nix index 828c0ee8..4f9e1c59 100644 --- a/default.nix +++ b/default.nix @@ -4,6 +4,7 @@ in pkgs.mkShell { buildInputs = [ pkgs.platformio + pkgs.python3 # optional: needed as a programmer i.e. for esp32 pkgs.avrdude ]; From f462113f4c442148924f924a7b61ab54f3291a05 Mon Sep 17 00:00:00 2001 From: uncle lit <43320854+LitBomb@users.noreply.github.com> Date: Wed, 20 Aug 2025 12:20:06 -0700 Subject: [PATCH 008/546] Update faq.md updated .mp3 file list for sound customization on t-deck added instruction to take a screenshot on t-deck Added link to MeshCore Ultra firmware user guide update github link from /meshcore to /meshcore-dev Update current hardware list to point to flasher.meshcore.co.uk --- docs/faq.md | 106 +++++++++++++++++++++++++++++++--------------------- 1 file changed, 63 insertions(+), 43 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 6dc8fe9e..c3a29462 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -1,7 +1,7 @@ **MeshCore-FAQ** A list of frequently-asked questions and answers for MeshCore -The current version of this MeshCore FAQ is at https://github.com/ripplebiz/MeshCore/blob/main/docs/faq.md. +The current version of this MeshCore FAQ is at https://github.com/meshcore-dev/MeshCore/blob/main/docs/faq.md. This MeshCore FAQ is also mirrored at https://github.com/LitBomb/MeshCore-FAQ and might have newer updates if pull requests on Scott's MeshCore repo are not approved yet. author: https://github.com/LitBomb @@ -27,18 +27,20 @@ author: https://github.com/LitBomb - [3.3. Q: What is the password to administer a repeater or a room server?](#33-q-what-is-the-password-to-administer-a-repeater-or-a-room-server) - [3.4. Q: What is the password to join a room server?](#34-q-what-is-the-password-to-join-a-room-server) - [4. T-Deck Related](#4-t-deck-related) - - [4.1. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode?](#41-q-what-are-the-steps-to-get-a-t-deck-into-dfu-device-firmware-update-mode) - - [4.2. Q: Why is my T-Deck Plus not getting any satellite lock?](#42-q-why-is-my-t-deck-plus-not-getting-any-satellite-lock) - - [4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?](#43-q-why-is-my-og-non-plus-t-deck-not-getting-any-satellite-lock) - - [4.4. Q: What size of SD card does the T-Deck support?](#44-q-what-size-of-sd-card-does-the-t-deck-support) - - [4.5. Q: What is the public key for the default public channel?](#45-q-what-is-the-public-key-for-the-default-public-channel) - - [4.6. Q: How do I get maps on T-Deck?](#46-q-how-do-i-get-maps-on-t-deck) - - [4.7. Q: Where do the map tiles go?](#47-q-where-do-the-map-tiles-go) - - [4.8. Q: How to unlock deeper map zoom and server management features on T-Deck?](#48-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck) - - [4.9. Q: How to decipher the diagnostics screen on T-Deck?](#49-q-how-to-decipher-the-diagnostics-screen-on-t-deck) - - [4.10. Q: The T-Deck sound is too loud?](#410-q-the-t-deck-sound-is-too-loud) - - [4.11. Q: Can you customize the sound?](#411-q-can-you-customize-the-sound) - - [4.12. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#412-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) + - [4.1. Q: Is there a user guide for T-Deck, T-Pager, T-Watch, or T-Display Pro?](#41-q-is-there-a-user-guide-for-t-deck-t-pager-t-watch-or-t-display-pro) + - [4.2. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode?](#42-q-what-are-the-steps-to-get-a-t-deck-into-dfu-device-firmware-update-mode) + - [4.3. Q: Why is my T-Deck Plus not getting any satellite lock?](#43-q-why-is-my-t-deck-plus-not-getting-any-satellite-lock) + - [4.4. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?](#44-q-why-is-my-og-non-plus-t-deck-not-getting-any-satellite-lock) + - [4.5. Q: What size of SD card does the T-Deck support?](#45-q-what-size-of-sd-card-does-the-t-deck-support) + - [4.6. Q: what is the public key for the default public channel?](#46-q-what-is-the-public-key-for-the-default-public-channel) + - [4.7. Q: How do I get maps on T-Deck?](#47-q-how-do-i-get-maps-on-t-deck) + - [4.8. Q: Where do the map tiles go?](#48-q-where-do-the-map-tiles-go) + - [4.9. Q: How to unlock deeper map zoom and server management features on T-Deck?](#49-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck) + - [4.10. Q: How to decipher the diagnostics screen on T-Deck?](#410-q-how-to-decipher-the-diagnostics-screen-on-t-deck) + - [4.11. Q: The T-Deck sound is too loud?](#411-q-the-t-deck-sound-is-too-loud) + - [4.12. Q: Can you customize the sound?](#412-q-can-you-customize-the-sound) + - [4.13. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts?](#413-q-what-is-the-import-from-clipboard-feature-on-the-t-deck-and-is-there-a-way-to-manually-add-nodes-without-having-to-receive-adverts) + - [4.14. Q: How to capture a screenshot on T-Deck?](#414-q-how-to-capture-a-screenshot-on-t-deck) - [5. General](#5-general) - [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr) - [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat) @@ -68,10 +70,10 @@ author: https://github.com/LitBomb - [6.6. Q: My RAK/T1000-E/xiao\_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?](#66-q-my-rakt1000-exiao_nrf52-device-seems-to-be-corrupted-how-do-i-wipe-it-clean-to-start-fresh) - [6.7. Q: WebFlasher fails on Linux with failed to open](#67-q-webflasher-fails-on-linux-with-failed-to-open) - [7. Other Questions:](#7-other-questions) - - [7.1 Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app) - - [7.2 Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air) - - [7.3 Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu) - - [7.4 Q are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) + - [7.1. Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app) + - [7.2. Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air) + - [7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu) + - [7.4. Q are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) ## 1. Introduction @@ -105,9 +107,11 @@ Anyone is able to build anything they like on top of MeshCore without paying any You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server). #### 1.2.1. Hardware -To use MeshCore without using a phone as the client interface, you can run MeshCore on a T-Deck or T-Deck Plus. It is a complete off-grid secure communication solution. +MeshCore is available on a variety of 433MHz, 868MHz and 915MHz LoRa devices. For example, Lilygo T-Deck, T-Pager, RAK Wireless WisBlock RAK4631 devices (e.g. 19003, 19007, 19026), Heltec V3, Xiao S3 WIO, Xiao C3, Heltec T114, Station G2, Nano G2 Ultra, Seeed Studio T1000-E. More devices are being added regularly. -MeshCore is also available on a variety of 868MHz and 915MHz LoRa devices. For example, RAK4631 devices (19003, 19007, 19026), Heltec V3, Xiao S3 WIO, Xiao C3, Heltec T114, Station G2, Seeed Studio T1000-E. More devices will be supported later. +For an up-to-date list of supported devices, please go to https://flasher.meshcore.co.uk/ + +To use MeshCore without using a phone as the client interface, you can run MeshCore on a LiLygo's T-Deck, T-Deck Plus, T-Pager, T-Watch, or T-Display Pro. MeshCore Ultra firmware running on these devices are a complete off-grid secure communication solution. #### 1.2.2. Firmware MeshCore has four firmware types that are not available on other LoRa systems. MeshCore has the following: @@ -116,7 +120,7 @@ MeshCore has four firmware types that are not available on other LoRa systems. M Companion radios are for connecting to the Android app or web app as a messenger client. There are two different companion radio firmware versions: 1. **BLE Companion** - BLE Companion firmware runs on a supported LoRa device and connects to a smart device running the Android MeshCore client over BLE (iOS MeshCore client will be available soon) + BLE Companion firmware runs on a supported LoRa device and connects to a smart device running the Android or iOS MeshCore client over BLE 2. **USB Serial Companion** @@ -147,7 +151,7 @@ A room server can also take on the repeater role. To enable repeater role on a ## 2. Initial Setup ### 2.1. Q: How many devices do I need to start using MeshCore? -**A:** If you have one supported device, flash the BLE Companion firmware and use your device as a client. You can connect to the device using the Android client via Bluetooth (iOS client will be available later). You can start communicating with other MeshCore users near you. +**A:** If you have one supported device, flash the BLE Companion firmware and use your device as a client. You can connect to the device using the Android or iOS client via Bluetooth. You can start communicating with other MeshCore users near you. If you have two supported devices, and there are not many MeshCore users near you, flash both to BLE Companion firmware so you can use your devices to communicate with your near-by friends and family. @@ -157,7 +161,7 @@ After you flashed the latest firmware onto your repeater device, keep the device `set freq {frequency}` -The repeater and room server CLI reference is here: https://github.com/ripplebiz/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference +The repeater and room server CLI reference is here: https://github.com/meshcore-dev/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference If you have more supported devices, you can use your additional devices with the room server firmware. @@ -255,7 +259,11 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo ## 4. T-Deck Related -### 4.1. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode? +### 4.1. Q: Is there a user guide for T-Deck, T-Pager, T-Watch, or T-Display Pro? + +**A:** Yes, it is available on https://buymeacoffee.com/ripplebiz/ultra-v7-7-guide-meshcore-users + +### 4.2. Q: What are the steps to get a T-Deck into DFU (Device Firmware Update) mode? **A:** 1. Device off 2. Connect USB cable to device @@ -266,20 +274,20 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo 7. T-Deck in DFU mode now 8. At this point you can begin flashing using -### 4.2. Q: Why is my T-Deck Plus not getting any satellite lock? +### 4.3. Q: Why is my T-Deck Plus not getting any satellite lock? **A:** For T-Deck Plus, the GPS baud rate should be set to **38400**. Also, some T-Deck Plus devices were found to have the GPS module installed upside down, with the GPS antenna facing down instead of up. If your T-Deck Plus still doesn't get any satellite lock after setting the baud rate to 38400, you might need to open the device to check the GPS orientation. GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-Deck will continue to try to get a GPS lock. You can go to the `GPS Info` screen; you should see the `Sentences:` counter increasing if the baud rate is correct. [Source](https://discord.com/channels/826570251612323860/1330643963501351004/1356609240302616689) -### 4.3. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? +### 4.4. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock? **A:** The OG (non-Plus) T-Deck doesn't come with a GPS. If you added a GPS to your OG T-Deck, please refer to the manual of your GPS to see what baud rate it requires. Alternatively, you can try to set the baud rate from 9600, 19200, etc., and up to 115200 to see which one works. -### 4.4. Q: What size of SD card does the T-Deck support? +### 4.5. Q: What size of SD card does the T-Deck support? **A:** Users have had no issues using 16GB or 32GB SD cards. Format the SD card to **FAT32**. -### 4.5. Q: what is the public key for the default public channel? +### 4.6. Q: what is the public key for the default public channel? **A:** T-Deck uses the same key the smartphone apps use but in base64 `izOH6cXN6mrJ5e26oRXNcg==` @@ -290,7 +298,7 @@ The smartphone app key is in hex: [Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354194409213792388) -### 4.6. Q: How do I get maps on T-Deck? +### 4.7. Q: How do I get maps on T-Deck? **A:** You need map tiles. You can get pre-downloaded map tiles here (a good way to support development): - (Europe) - (US) @@ -304,19 +312,20 @@ There is also a modified script that adds additional error handling and parallel UK map tiles are available separately from Andy Kirby on his discord server: -### 4.7. Q: Where do the map tiles go? +### 4.8. Q: Where do the map tiles go? Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card. -### 4.8. Q: How to unlock deeper map zoom and server management features on T-Deck? +### 4.9. Q: How to unlock deeper map zoom and server management features on T-Deck? **A:** You can download, install, and use the T-Deck firmware for free, but it has some features (map zoom, server administration) that are enabled if you purchase an unlock code for \$10 per T-Deck device. Unlock page: -### 4.9. Q: How to decipher the diagnostics screen on T-Deck? +### 4.10. Q: How to decipher the diagnostics screen on T-Deck? **A: ** Space is tight on T-Deck's screen, so the information is a bit cryptic. The format is : `{hops} l:{packet-length}({payload-len}) t:{packet-type} snr:{n} rssi:{n}` -See here for packet-type: [https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19](https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19 "https://github.com/ripplebiz/MeshCore/blob/main/src/Packet.h#L19") +See here for packet-type: +https://github.com/meshcore-dev/MeshCore/blob/main/src/Packet.h#L19 #define PAYLOAD_TYPE_REQ 0x00 // request (prefixed with dest/src hashes, MAC) (enc data: timestamp, blob) @@ -330,15 +339,25 @@ See here for packet-type: [https://github.com/ripplebiz/MeshCore/blob/main/src/P [Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350611321040932966) -### 4.10. Q: The T-Deck sound is too loud? -### 4.11. Q: Can you customize the sound? +### 4.11. Q: The T-Deck sound is too loud? +### 4.12. Q: Can you customize the sound? -**A:** You can customise the sounds on the T-Deck, just by placing `.mp3` files onto the `root` dir of the SD card. `startup.mp3`, `alert.mp3` and `new-advert.mp3` +**A:** You can customise the sounds on the T-Deck, by placing `.mp3` files onto the `root` dir of the SD card. The files are: -### 4.12. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? +* `startup.mp3` +* `error.mp3` +* `alert.mp3` +* `new-advert.mp3` +* `existing-advert.mp3` + +### 4.13. Q: What is the 'Import from Clipboard' feature on the t-deck and is there a way to manually add nodes without having to receive adverts? **A:** 'Import from Clipboard' is for importing a contact via a file named 'clipboard.txt' on the SD card. The opposite, is in the Identity screen, the 'Card to Clipboard' menu, which writes to 'clipboard.txt' so you can share yourself (call these 'biz cards', that start with "meshcore://...") +### 4.14. Q: How to capture a screenshot on T-Deck? + +**A:** To capture a screenshot on a T-Deck, long press the top-left corner of the screen. The screenshot is saved to the microSD card, if one is inserted into the device. + --- ## 5. General @@ -393,7 +412,7 @@ The third character is the capital letter 'O', not zero `0` ### 5.7. Q: Is MeshCore open source? **A:** Most of the firmware is freely available. Everything is open source except the T-Deck firmware and Liam's native mobile apps. -- Firmware repo: +- Firmware repo: https://github.com/meshcore-dev/MeshCore ### 5.8. Q: How can I support MeshCore? **A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at . @@ -521,7 +540,7 @@ To start managing your USB serial-connected device using picocom, use the follow - `picocom -b 115200 /dev/ttyUSB0 --imap lfcrlf` From here, reference repeater and room server command line commands on MeshCore github wiki here: - - https://github.com/ripplebiz/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference + - https://github.com/meshcore-dev/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference ### 5.14. Q: Are there are projects built around MeshCore? @@ -603,7 +622,7 @@ Allow the browser user on it: --- ## 7. Other Questions: -### 7.1 Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app? +### 7.1. Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app? **A:** The steps below work on both Android and iOS as nRF has made both apps' user interface the same on both platforms: @@ -622,7 +641,7 @@ Allow the browser user on it: 14. Wait for the update to complete. It can take a few minutes. -### 7.2 Q: How to update ESP32-based devices over the air? +### 7.2. Q: How to update ESP32-based devices over the air? **A:** For ESP32-based devices (e.g. Heltec V3): 1. On flasher.meshcore.co.uk, download the **non-merged** version of the firmware for your ESP32 device (e.g. `Heltec_v3_repeater-v1.6.2-4449fd3.bin`, no `"merged"` in the file name) @@ -634,7 +653,7 @@ Allow the browser user on it: 8. From a browser, go to http://192.168.4.1/update and upload the non-merged bin from the flasher -### 7.3 Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)? +### 7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)? **A:** Yes, developer `che aporeps` has an enhanced OTA DFU bootloader for nRF52 based devices. With this bootloader, if it detects that the application firmware is invalid, it falls back to OTA DFU mode so you can attempt to flash again to recover. This bootloader has other changes to make the OTA DFU process more fault tolerant. @@ -646,9 +665,10 @@ Currently, the following boards are supported: - Seeed Studio XIAO nRF52840 BLE SENSE - RAK 4631 -### 7.4 Q are the MeshCore logo and font available? +### 7.4. Q are the MeshCore logo and font available? -**A:** Yes, it is on the MeshCore github repo here: https://github.com/ripplebiz/MeshCore/tree/main/logo +**A:** Yes, it is on the MeshCore github repo here: +https://github.com/meshcore-dev/MeshCore/tree/main/logo --- From 82184c583635db7769af476a75fc22a4fc5fcd52 Mon Sep 17 00:00:00 2001 From: uncle lit <43320854+LitBomb@users.noreply.github.com> Date: Wed, 20 Aug 2025 22:08:19 -0700 Subject: [PATCH 009/546] Update faq.md update verbiage on room server's repeat functionality to discourage turning repeat on on room server. Update repeater flood advert to 3 hours and mentioning pending PR to change that to 12 hours. --- docs/faq.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index c3a29462..8f099bb7 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -142,9 +142,11 @@ A room server can be remotely administered using a T-Deck running the MeshCore f When a client logs into a room server, the client will receive the previously 32 unseen messages. -A room server can also take on the repeater role. To enable repeater role on a room server, use this command: +Although room server can also repeat with the command line command `set repeat on`, it is not recommended nor encouraged. A room server with repeat set to `on` lacks the full set of repeater and remote administration features that are only available in the repeater firmware. + +The recommendation is to run repeater and room server on separate devices for the best experience. + -`set repeat {on|off}` --- @@ -201,10 +203,12 @@ MeshCore allows you to manually broadcast your name, position and public encrypt * Zero hop means your advert is broadcasted out to anyone that can hear it, and that's it. * Flooded means it's broadcasted out and then repeated by all the repeaters that hear it. -MeshCore clients only advertise themselves when the user initiates it. A repeater (and room server?) advertises its presence once every 240 minutes. This interval can be configured using the following command: +MeshCore clients only advertise themselves when the user initiates it. A repeater sends a flood advert once every 3 hours by default. This interval can be configured using the following command: `set advert.interval {minutes}` +As of Aug 20 2025, a pending PR on github will change the flood advert to 12 hours to minimize airtime utilization caused by repeaters' flood adverts. + ### 2.5. Q: Is there a hop limit? **A:** Internally the firmware has maximum limit of 64 hops. In real world settings it will be difficult to get close to the limit due to the environments and timing as packets travel further and further. We want to hear how far your MeshCore conversations go. From 02ad2bed4d194036cf94041afb56651190dcb6d9 Mon Sep 17 00:00:00 2001 From: uncle lit <43320854+LitBomb@users.noreply.github.com> Date: Mon, 25 Aug 2025 17:20:13 -0700 Subject: [PATCH 010/546] Update faq.md update instruction to add a node to the Internet Map from the app, and note that you can use the same companion radio to remove the nodes you added previously add channel and contact QR code URL format --- docs/faq.md | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 8f099bb7..9f176d36 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -73,7 +73,8 @@ author: https://github.com/LitBomb - [7.1. Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app) - [7.2. Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air) - [7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu) - - [7.4. Q are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) + - [7.4. Q: are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) + - [7.5. Q: What is the format of a contact or channel QR code?](#75-q-what-is-the-format-of-a-contact-or-channel-qr-code) ## 1. Introduction @@ -480,10 +481,14 @@ This could change in the future if MeshCore develops a client firmware that repe [Source](https://discord.com/channels/826570251612323860/1330643963501351004/1354780032140054659) ### 5.12. Q: How do I add a node to the [MeshCore Map]([url](https://meshcore.co.uk/map.html)) -**A:** From the smartphone app, connect to a BLE Companion radio -- To add the BLE Companion radio your smartphone is connected to to the map, tap the `advert` icon, then tap `Advert (To Clipboard)`. -- To add a Repeater or Room Server to the map, tap the 3 dots next to the Repeater or Room Server you want to add to the map, then tap `Share (To Clipboard)`. -- Go to the [MeshCore Map web site]([url](https://meshcore.co.uk/map.html)), tap the plus sign on the lower right corner and paste in the meshcore://... blob, then tap `Add Node` +**A:** + +To add a BLE Companion radio, connect to the BLE Companion radio from the MeshCore smartphone app. In the app, tap the `3 dot` menu icon at the top right corner, then tap `Internet Map`. Tap the `3 dot` menu icon again and choose `Add me to the Map` + +To add a Repeater or Room Server to the map, go to the Contact List, tap the `3 dot` next to the Repeater or Room Server you want to add to the Internet Map, tap `Share`, then tap `Upload to Internet Map`. + +You can use the same companion (same public key) that you used to add your repeaters or room servers to remove them from the Internet Map. + ### 5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio? ** A:** Yes. @@ -669,10 +674,25 @@ Currently, the following boards are supported: - Seeed Studio XIAO nRF52840 BLE SENSE - RAK 4631 -### 7.4. Q are the MeshCore logo and font available? +### 7.4. Q: are the MeshCore logo and font available? **A:** Yes, it is on the MeshCore github repo here: https://github.com/meshcore-dev/MeshCore/tree/main/logo +### 7.5. Q: What is the format of a contact or channel QR code? + +**A:** +Channel: +`meshcore://channel/add?name=&secret=` + +Contact: +`meshcore://contact/add?name=&public_key=&type=` + +where `&type` is: +`chat = 1` +`repeater = 2` +`room = 3` +`sensor = 4` + --- From 2e2e677b0a51dd0e5a36f65619ebceb5ce37a4f9 Mon Sep 17 00:00:00 2001 From: kelsey hudson Date: Wed, 27 Aug 2025 00:37:16 -0700 Subject: [PATCH 011/546] Ikoka Stick: Board IDs, LED behavior Updates the manufacturer identifier with the EBYTE module. Makes the LED behave properly. Turns the bright blue LED off after the first time you transmit anything via LoRa. --- variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h | 10 +++++++++- variants/ikoka_stick_nrf/platformio.ini | 3 +++ variants/ikoka_stick_nrf/variant.h | 2 +- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h index c66f4827..08061c23 100644 --- a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h +++ b/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h @@ -16,9 +16,17 @@ public: #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + #if defined(LED_BLUE) + // turn off that annoying blue LED before transmitting + digitalWrite(LED_BLUE, HIGH); + #endif } void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + #if defined(LED_BLUE) + // do it after transmitting too, just in case + digitalWrite(LED_BLUE, HIGH); + #endif } #endif @@ -39,7 +47,7 @@ public: } const char* getManufacturerName() const override { - return "Ikoka Stick (Xiao-nrf52)"; + return MANUFACTURER_STRING; } void reboot() override { diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 6e6ae101..16bc1253 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -61,6 +61,7 @@ extends = ikoka_stick_nrf_baseboard ; No PA in this model, full 22dBm build_flags = ${ikoka_stick_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Stick-E22-22dBm (Xiao_nrf52)"' -D LORA_TX_POWER=22 build_src_filter = ${nrf52840_xiao.build_src_filter} + @@ -75,6 +76,7 @@ extends = ikoka_stick_nrf_baseboard ; cause distortion in the PA output. 20dBm in -> 30dBm out build_flags = ${ikoka_stick_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Stick-E22-30dBm (Xiao_nrf52)"' -D LORA_TX_POWER=20 build_src_filter = ${nrf52840_xiao.build_src_filter} + @@ -89,6 +91,7 @@ extends = ikoka_stick_nrf_baseboard ; to the rf amplifier frontend. 9dBm in -> 33dBm out build_flags = ${ikoka_stick_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Stick-E22-33dBm (Xiao_nrf52)"' -D LORA_TX_POWER=9 build_src_filter = ${nrf52840_xiao.build_src_filter} + diff --git a/variants/ikoka_stick_nrf/variant.h b/variants/ikoka_stick_nrf/variant.h index f94ebe49..ff5a41a6 100644 --- a/variants/ikoka_stick_nrf/variant.h +++ b/variants/ikoka_stick_nrf/variant.h @@ -35,7 +35,7 @@ extern "C" #define LED_GREEN (13) #define LED_BLUE (12) -#define LED_STATE_ON (1) // State when LED is litted +#define LED_STATE_ON (0) // State when LED is litted // Buttons #define PIN_BUTTON1 (PINS_COUNT) From b3adaa790abfb47c483d7877413eda5269a6cea0 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper Date: Sat, 30 Aug 2025 20:53:57 +0200 Subject: [PATCH 012/546] Update faq.md --- docs/faq.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/faq.md b/docs/faq.md index 9f176d36..09e83c29 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -75,6 +75,7 @@ author: https://github.com/LitBomb - [7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu) - [7.4. Q: are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) - [7.5. Q: What is the format of a contact or channel QR code?](#75-q-what-is-the-format-of-a-contact-or-channel-qr-code) + - [7.6. Q: How do I connect to the comnpanion via WIFI, e.g. using a heltec v3?](#76-q-how-do-i-connect-to-the-comnpanion-via-wifi-eg-using-a-heltec-v3) ## 1. Introduction @@ -694,5 +695,9 @@ where `&type` is: `room = 3` `sensor = 4` +### 7.6. Q: How do I connect to the comnpanion via WIFI, e.g. using a heltec v3? + **A:** +WiFi firmware requires you to compile it yourself, as you need to set the wifi ssid and password. +Edit WIFI_SSID and WIFI_PWD in `./variants/heltec_v3/platformio.ini` and then flash it to your device. --- From 9fd7e9427a528db1cca00cbad1b1d854f2dea6de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Mon, 1 Sep 2025 10:53:51 +0100 Subject: [PATCH 013/546] Add bridge support for WSL3 board --- variants/heltec_v3/platformio.ini | 23 ++++++++++++++++++- variants/lilygo_tlora_v2_1/platformio.ini | 2 +- variants/waveshare_rp2040_lora/platformio.ini | 2 +- 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 1d2baa2b..3e7524c9 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -49,7 +49,7 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 -[env:Heltec_v3_Bridge] +[env:Heltec_v3_repeater_bridge] extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} @@ -208,6 +208,27 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_WSL3_repeater_bridge] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D ADVERT_NAME='"Heltec WSL3 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D BRIDGE_OVER_SERIAL=Serial2 + -D BRIDGE_OVER_SERIAL_RX=5 + -D BRIDGE_OVER_SERIAL_TX=6 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + [env:Heltec_WSL3_room_server] extends = Heltec_lora32_v3 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 4e2078c1..4e146cf6 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -64,7 +64,7 @@ lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} ${esp32_ota.lib_deps} -[env:LilyGo_TLora_V2_1_1_6_Bridge] +[env:LilyGo_TLora_V2_1_1_6_repeater_bridge] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 60b39ad6..0ec745ff 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -40,7 +40,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} build_src_filter = ${waveshare_rp2040_lora.build_src_filter} +<../examples/simple_repeater> -[env:waveshare_rp2040_lora_Bridge] +[env:waveshare_rp2040_lora_repeater_bridge] extends = waveshare_rp2040_lora build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_NAME='"RP2040-LoRa Bridge"' From f3c52d84dbd0b7e2c874933c1a23cba28e176caf Mon Sep 17 00:00:00 2001 From: ripplebiz Date: Mon, 1 Sep 2025 22:00:06 +1000 Subject: [PATCH 014/546] Update README.md --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index fa5b3cd0..179553b0 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,15 @@ Here are some general principals you should try to adhere to: * No dynamic memory allocation, except during setup/begin functions. * Use the same brace and indenting style that's in the core source modules. (A .clang-format is prob going to be added soon, but please do NOT retroactively re-format existing code. This just creates unnecessary diffs that make finding problems harder) +## Road-Map / To-Do + +There are a number of fairly major feature features in the pipeline, with no particular time-frames attached yet. In very rough chronological order: +- [X] Companion radio: UI redesign +- [ ] Core: round-trip manual path support +- [ ] Companion + Apps: support for multiple sub-meshes (and 'off-grid' client repeat mode) +- [ ] Core: new framework for hosting multiple virtual nodes on one physical device +- [ ] V2 protocol spec: discussion and concensus around V2 packet protocol, including path hashes, new encryption specs, etc + ## 📞 Get Support - Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page. From 4849b863e9fc7345b8ae1fa12a2c8b3cc27483f5 Mon Sep 17 00:00:00 2001 From: ripplebiz Date: Mon, 1 Sep 2025 22:07:36 +1000 Subject: [PATCH 015/546] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 179553b0..cc94bbef 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,7 @@ Here are some general principals you should try to adhere to: There are a number of fairly major feature features in the pipeline, with no particular time-frames attached yet. In very rough chronological order: - [X] Companion radio: UI redesign +- [ ] Core + Repeater: enhanced zero-hop neighbour discovery - [ ] Core: round-trip manual path support - [ ] Companion + Apps: support for multiple sub-meshes (and 'off-grid' client repeat mode) - [ ] Core: new framework for hosting multiple virtual nodes on one physical device From c3fb3bcefe94cc58919a3c50dbb8e6e00230c888 Mon Sep 17 00:00:00 2001 From: ripplebiz Date: Mon, 1 Sep 2025 22:14:21 +1000 Subject: [PATCH 016/546] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc94bbef..cef44e0e 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ Here are some general principals you should try to adhere to: ## Road-Map / To-Do -There are a number of fairly major feature features in the pipeline, with no particular time-frames attached yet. In very rough chronological order: +There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order: - [X] Companion radio: UI redesign - [ ] Core + Repeater: enhanced zero-hop neighbour discovery - [ ] Core: round-trip manual path support From b64e78b7eb4f5c5a7881e6f6095d53191e78b102 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 2 Sep 2025 08:06:43 +1000 Subject: [PATCH 017/546] fix: Heltec Vision Master E290: rename companion target --- variants/heltec_vision_master_e290/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_vision_master_e290/platformio.ini b/variants/heltec_vision_master_e290/platformio.ini index 4150adc8..8ac01cfb 100644 --- a/variants/heltec_vision_master_e290/platformio.ini +++ b/variants/heltec_vision_master_e290/platformio.ini @@ -27,7 +27,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_Vision_Master_E290_radio_ble] +[env:Heltec_Vision_Master_E290_companion_radio_ble] extends = Heltec_Vision_Master_E290_base build_flags = ${Heltec_Vision_Master_E290_base.build_flags} From 1948d284a022b3951e5b6b607b13ca0eed71bc1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Thu, 4 Sep 2025 23:43:05 +0100 Subject: [PATCH 018/546] Extract serial bridge into dedicated classes This commit refactors the serial bridge functionality out of the `simple_repeater` example and into a more reusable, object-oriented structure. An `AbstractBridge` interface has been introduced, along with a concrete `SerialBridge` implementation. This encapsulates all the logic for packet framing, checksum calculation, and serial communication, cleaning up the main example file significantly. The `simple_repeater` example now instantiates and uses the `SerialBridge` class, resulting in better separation of concerns and improved code organization. --- examples/simple_repeater/main.cpp | 109 +++--------------------------- src/helpers/AbstractBridge.h | 32 +++++++++ src/helpers/SerialBridge.cpp | 98 +++++++++++++++++++++++++++ src/helpers/SerialBridge.h | 30 ++++++++ 4 files changed, 171 insertions(+), 98 deletions(-) create mode 100644 src/helpers/AbstractBridge.h create mode 100644 src/helpers/SerialBridge.cpp create mode 100644 src/helpers/SerialBridge.h diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7e44d40b..7150f0b2 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -79,7 +79,7 @@ /* ------------------------------ Code -------------------------------- */ #ifdef BRIDGE_OVER_SERIAL -#define SERIAL_PKT_MAGIC 0xcafe +#include "helpers/SerialBridge.h" #endif #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS @@ -118,6 +118,10 @@ struct ClientInfo { #define MAX_CLIENTS 32 #endif +#ifdef BRIDGE_OVER_SERIAL +AbstractBridge* bridge; +#endif + struct NeighbourInfo { mesh::Identity id; uint32_t advert_timestamp; @@ -256,89 +260,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif } -#ifdef BRIDGE_OVER_SERIAL - struct SerialPacket { - uint16_t magic, len, crc; - uint8_t payload[MAX_TRANS_UNIT]; - SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} - }; - - // Fletcher-16 - // https://en.wikipedia.org/wiki/Fletcher%27s_checksum - inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { - uint8_t sum1 = 0, sum2 = 0; - - for (size_t i = 0; i < len; i++) { - sum1 = (sum1 + bytes[i]) % 255; - sum2 = (sum2 + sum1) % 255; - } - - return (sum2 << 8) | sum1; - }; - - inline void serialBridgeSendPkt(const mesh::Packet *pkt) { - SerialPacket spkt; - spkt.len = pkt->writeTo(spkt.payload); - spkt.crc = fletcher16(spkt.payload, spkt.len); - BRIDGE_OVER_SERIAL.write((uint8_t *)&spkt, sizeof(SerialPacket)); - -#if MESH_PACKET_LOGGING - Serial.printf("%s: BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), spkt.len, spkt.crc); -#endif - } - - inline void serialBridgeReceivePkt() { - static constexpr uint16_t size = sizeof(SerialPacket) + 1; - static uint8_t buffer[size]; - static uint16_t tail = 0; - - while (BRIDGE_OVER_SERIAL.available()) { - buffer[tail] = (uint8_t)BRIDGE_OVER_SERIAL.read(); - MESH_DEBUG_PRINT("%02x ", buffer[tail]); - tail = (tail + 1) % size; - - // Check for complete packet by looking back to where the magic number should be - const uint16_t head = (tail - sizeof(SerialPacket) + size) % size; - if ((buffer[head] | (buffer[(head + 1) % size] << 8)) != SERIAL_PKT_MAGIC) { - return; - } - - uint8_t bytes[MAX_TRANS_UNIT]; - const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); - - if (len == 0 || len > sizeof(bytes)) { - MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, invalid packet len", getLogDateTime()); - return; - } - - for (size_t i = 0; i < len; i++) { - bytes[i] = buffer[(head + 6 + i) % size]; - } - - const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); - const uint16_t f16 = fletcher16(bytes, len); - -#if MESH_PACKET_LOGGING - Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, crc); -#endif - - if ((f16 != crc)) { - MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, invalid packet checksum", getLogDateTime()); - return; - } - - mesh::Packet *pkt = _mgr->allocNew(); - if (pkt == NULL) { - MESH_DEBUG_PRINTLN("%s: BRIDGE: RX, no unused packets available", getLogDateTime()); - return; - } - - pkt->readFrom(bytes, len); - _mgr->queueInbound(pkt, futureMillis(0)); - } - } -#endif - protected: float getAirtimeBudgetFactor() const override { return _prefs.airtime_factor; @@ -389,7 +310,7 @@ protected: void logTx(mesh::Packet* pkt, int len) override { #ifdef BRIDGE_OVER_SERIAL if (!pkt->isMarkedDoNotRetransmit()) { - serialBridgeSendPkt(pkt); + bridge->onPacketTransmitted(pkt); } #endif if (_logging) { @@ -657,6 +578,9 @@ public: : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { +#ifdef BRIDGE_OVER_SERIAL + bridge = new SerialBridge(BRIDGE_OVER_SERIAL, _mgr); +#endif memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; set_radio_at = revert_radio_at = 0; @@ -858,7 +782,7 @@ public: void loop() { #ifdef BRIDGE_OVER_SERIAL - serialBridgeReceivePkt(); + bridge->loop(); #endif mesh::Mesh::loop(); @@ -910,18 +834,7 @@ void setup() { delay(1000); #ifdef BRIDGE_OVER_SERIAL -#if defined(ESP32) - BRIDGE_OVER_SERIAL.setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); -#elif defined(RP2040_PLATFORM) - BRIDGE_OVER_SERIAL.setRX(BRIDGE_OVER_SERIAL_RX); - BRIDGE_OVER_SERIAL.setTX(BRIDGE_OVER_SERIAL_TX); -#elif defined(STM32_PLATFORM) - BRIDGE_OVER_SERIAL.setRx(BRIDGE_OVER_SERIAL_RX); - BRIDGE_OVER_SERIAL.setTx(BRIDGE_OVER_SERIAL_TX); -#else -#error SerialBridge was not tested on the current platform -#endif - BRIDGE_OVER_SERIAL.begin(115200); + bridge->begin(); #endif board.begin(); diff --git a/src/helpers/AbstractBridge.h b/src/helpers/AbstractBridge.h new file mode 100644 index 00000000..930eea65 --- /dev/null +++ b/src/helpers/AbstractBridge.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +class AbstractBridge { +public: + virtual ~AbstractBridge() {} + + /** + * @brief Initializes the bridge. + */ + virtual void begin() = 0; + + /** + * @brief A method to be called on every main loop iteration. + * Used for tasks like checking for incoming data. + */ + virtual void loop() = 0; + + /** + * @brief A callback that is triggered when the mesh transmits a packet. + * The bridge can use this to forward the packet. + * + * @param packet The packet that was transmitted. + */ + virtual void onPacketTransmitted(mesh::Packet* packet) = 0; + + /** + * @brief Processes a received packet from the bridge's medium. + */ + virtual void onPacketReceived() = 0; +}; diff --git a/src/helpers/SerialBridge.cpp b/src/helpers/SerialBridge.cpp new file mode 100644 index 00000000..235661aa --- /dev/null +++ b/src/helpers/SerialBridge.cpp @@ -0,0 +1,98 @@ +#include "SerialBridge.h" +#include + +#ifdef BRIDGE_OVER_SERIAL + +#define SERIAL_PKT_MAGIC 0xcafe + +struct SerialPacket { + uint16_t magic, len, crc; + uint8_t payload[MAX_TRANS_UNIT]; + SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} +}; + +// Fletcher-16 +// https://en.wikipedia.org/wiki/Fletcher%27s_checksum +inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { + uint8_t sum1 = 0, sum2 = 0; + + for (size_t i = 0; i < len; i++) { + sum1 = (sum1 + bytes[i]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; +}; + +SerialBridge::SerialBridge(Stream& serial, mesh::PacketManager* mgr) : _serial(&serial), _mgr(mgr) {} + +void SerialBridge::begin() { +#if defined(ESP32) + ((HardwareSerial*)_serial)->setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); +#elif defined(RP2040_PLATFORM) + ((HardwareSerial*)_serial)->setRX(BRIDGE_OVER_SERIAL_RX); + ((HardwareSerial*)_serial)->setTX(BRIDGE_OVER_SERIAL_TX); +#elif defined(STM32_PLATFORM) + ((HardwareSerial*)_serial)->setRx(BRIDGE_OVER_SERIAL_RX); + ((HardwareSerial*)_serial)->setTx(BRIDGE_OVER_SERIAL_TX); +#else +#error SerialBridge was not tested on the current platform +#endif + ((HardwareSerial*)_serial)->begin(115200); +} + +void SerialBridge::onPacketTransmitted(mesh::Packet* packet) { + SerialPacket spkt; + spkt.len = packet->writeTo(spkt.payload); + spkt.crc = fletcher16(spkt.payload, spkt.len); + _serial->write((uint8_t *)&spkt, sizeof(SerialPacket)); +} + +void SerialBridge::loop() { + while (_serial->available()) { + onPacketReceived(); + } +} + +void SerialBridge::onPacketReceived() { + static constexpr uint16_t size = sizeof(SerialPacket) + 1; + static uint8_t buffer[size]; + static uint16_t tail = 0; + + buffer[tail] = (uint8_t)_serial->read(); + tail = (tail + 1) % size; + + // Check for complete packet by looking back to where the magic number should be + const uint16_t head = (tail - sizeof(SerialPacket) + size) % size; + if ((buffer[head] | (buffer[(head + 1) % size] << 8)) != SERIAL_PKT_MAGIC) { + return; + } + + uint8_t bytes[MAX_TRANS_UNIT]; + const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); + + if (len == 0 || len > sizeof(bytes)) { + return; + } + + for (size_t i = 0; i < len; i++) { + bytes[i] = buffer[(head + 6 + i) % size]; + } + + const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); + const uint16_t f16 = fletcher16(bytes, len); + + if ((f16 != crc)) { + return; + } + + mesh::Packet *new_pkt = _mgr->allocNew(); + if (new_pkt == NULL) { + return; + } + + new_pkt->readFrom(bytes, len); + _mgr->queueInbound(new_pkt, 0); +} + +#endif diff --git a/src/helpers/SerialBridge.h b/src/helpers/SerialBridge.h new file mode 100644 index 00000000..3a4f0776 --- /dev/null +++ b/src/helpers/SerialBridge.h @@ -0,0 +1,30 @@ +#pragma once + +#include "helpers/AbstractBridge.h" +#include + +#ifdef BRIDGE_OVER_SERIAL + +/** + * @brief A bridge implementation that uses a serial port to connect two mesh networks. + */ +class SerialBridge : public AbstractBridge { +public: + /** + * @brief Construct a new Serial Bridge object + * + * @param serial The serial port to use for the bridge. + * @param mgr A pointer to the packet manager. + */ + SerialBridge(Stream& serial, mesh::PacketManager* mgr); + void begin() override; + void loop() override; + void onPacketTransmitted(mesh::Packet* packet) override; + void onPacketReceived() override; + +private: + Stream* _serial; + mesh::PacketManager* _mgr; +}; + +#endif From ee3c4baea5e420f8798ad313861fb1ed5bb2f730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Thu, 4 Sep 2025 23:50:13 +0100 Subject: [PATCH 019/546] Prevent packet loops and duplicates Implement a "seen packets" table to track packets that have already been processed by the serial bridge. This prevents packets from being re-transmitted over the serial link if they have already been seen, and it stops inbound packets from serial from being re-injected into the mesh if they are duplicates. Duplicate inbound packets are now freed to prevent memory leaks. --- src/helpers/SerialBridge.cpp | 16 +++++++++++----- src/helpers/SerialBridge.h | 2 ++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/helpers/SerialBridge.cpp b/src/helpers/SerialBridge.cpp index 235661aa..bbd2e0dd 100644 --- a/src/helpers/SerialBridge.cpp +++ b/src/helpers/SerialBridge.cpp @@ -42,10 +42,12 @@ void SerialBridge::begin() { } void SerialBridge::onPacketTransmitted(mesh::Packet* packet) { - SerialPacket spkt; - spkt.len = packet->writeTo(spkt.payload); - spkt.crc = fletcher16(spkt.payload, spkt.len); - _serial->write((uint8_t *)&spkt, sizeof(SerialPacket)); + if (!_seen_packets.hasSeen(packet)) { + SerialPacket spkt; + spkt.len = packet->writeTo(spkt.payload); + spkt.crc = fletcher16(spkt.payload, spkt.len); + _serial->write((uint8_t *)&spkt, sizeof(SerialPacket)); + } } void SerialBridge::loop() { @@ -92,7 +94,11 @@ void SerialBridge::onPacketReceived() { } new_pkt->readFrom(bytes, len); - _mgr->queueInbound(new_pkt, 0); + if (!_seen_packets.hasSeen(new_pkt)) { + _mgr->queueInbound(new_pkt, 0); + } else { + _mgr->free(new_pkt); + } } #endif diff --git a/src/helpers/SerialBridge.h b/src/helpers/SerialBridge.h index 3a4f0776..fe3c176f 100644 --- a/src/helpers/SerialBridge.h +++ b/src/helpers/SerialBridge.h @@ -1,6 +1,7 @@ #pragma once #include "helpers/AbstractBridge.h" +#include "helpers/SimpleMeshTables.h" #include #ifdef BRIDGE_OVER_SERIAL @@ -25,6 +26,7 @@ public: private: Stream* _serial; mesh::PacketManager* _mgr; + SimpleMeshTables _seen_packets; }; #endif From 2b920dfed32ae548812bba1383d3b2a734bfc8d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 5 Sep 2025 01:50:50 +0100 Subject: [PATCH 020/546] Rework packet serialization and parsing --- src/helpers/AbstractBridge.h | 4 +- src/helpers/SerialBridge.cpp | 106 +++++++++++++++++------------------ src/helpers/SerialBridge.h | 28 ++++++++- 3 files changed, 82 insertions(+), 56 deletions(-) diff --git a/src/helpers/AbstractBridge.h b/src/helpers/AbstractBridge.h index 930eea65..a348e933 100644 --- a/src/helpers/AbstractBridge.h +++ b/src/helpers/AbstractBridge.h @@ -27,6 +27,8 @@ public: /** * @brief Processes a received packet from the bridge's medium. + * + * @param packet The packet that was received. */ - virtual void onPacketReceived() = 0; + virtual void onPacketReceived(mesh::Packet* packet) = 0; }; diff --git a/src/helpers/SerialBridge.cpp b/src/helpers/SerialBridge.cpp index bbd2e0dd..c56645b6 100644 --- a/src/helpers/SerialBridge.cpp +++ b/src/helpers/SerialBridge.cpp @@ -3,14 +3,6 @@ #ifdef BRIDGE_OVER_SERIAL -#define SERIAL_PKT_MAGIC 0xcafe - -struct SerialPacket { - uint16_t magic, len, crc; - uint8_t payload[MAX_TRANS_UNIT]; - SerialPacket() : magic(SERIAL_PKT_MAGIC), len(0), crc(0) {} -}; - // Fletcher-16 // https://en.wikipedia.org/wiki/Fletcher%27s_checksum inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { @@ -43,61 +35,67 @@ void SerialBridge::begin() { void SerialBridge::onPacketTransmitted(mesh::Packet* packet) { if (!_seen_packets.hasSeen(packet)) { - SerialPacket spkt; - spkt.len = packet->writeTo(spkt.payload); - spkt.crc = fletcher16(spkt.payload, spkt.len); - _serial->write((uint8_t *)&spkt, sizeof(SerialPacket)); + uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; + uint16_t len = packet->writeTo(buffer + 4); + + buffer[0] = (SERIAL_PKT_MAGIC >> 8) & 0xFF; + buffer[1] = SERIAL_PKT_MAGIC & 0xFF; + buffer[2] = (len >> 8) & 0xFF; + buffer[3] = len & 0xFF; + + uint16_t checksum = fletcher16(buffer + 4, len); + buffer[4 + len] = (checksum >> 8) & 0xFF; + buffer[5 + len] = checksum & 0xFF; + + _serial->write(buffer, len + SERIAL_OVERHEAD); } } void SerialBridge::loop() { while (_serial->available()) { - onPacketReceived(); + uint8_t b = _serial->read(); + + if (_rx_buffer_pos < 2) { + // Waiting for magic word + if ((_rx_buffer_pos == 0 && b == ((SERIAL_PKT_MAGIC >> 8) & 0xFF)) || + (_rx_buffer_pos == 1 && b == (SERIAL_PKT_MAGIC & 0xFF))) { + _rx_buffer[_rx_buffer_pos++] = b; + } else { + _rx_buffer_pos = 0; + } + } else { + // Reading length, payload, and checksum + _rx_buffer[_rx_buffer_pos++] = b; + + if (_rx_buffer_pos >= 4) { + uint16_t len = (_rx_buffer[2] << 8) | _rx_buffer[3]; + if (len > MAX_PACKET_PAYLOAD) { + _rx_buffer_pos = 0; // Invalid length, reset + return; + } + + if (_rx_buffer_pos == len + SERIAL_OVERHEAD) { // Full packet received + uint16_t checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; + if (checksum == fletcher16(_rx_buffer + 4, len)) { + mesh::Packet *pkt = _mgr->allocNew(); + if (pkt) { + memcpy(pkt->payload, _rx_buffer + 4, len); + pkt->payload_len = len; + onPacketReceived(pkt); + } + } + _rx_buffer_pos = 0; // Reset for next packet + } + } + } } } -void SerialBridge::onPacketReceived() { - static constexpr uint16_t size = sizeof(SerialPacket) + 1; - static uint8_t buffer[size]; - static uint16_t tail = 0; - - buffer[tail] = (uint8_t)_serial->read(); - tail = (tail + 1) % size; - - // Check for complete packet by looking back to where the magic number should be - const uint16_t head = (tail - sizeof(SerialPacket) + size) % size; - if ((buffer[head] | (buffer[(head + 1) % size] << 8)) != SERIAL_PKT_MAGIC) { - return; - } - - uint8_t bytes[MAX_TRANS_UNIT]; - const uint16_t len = buffer[(head + 2) % size] | (buffer[(head + 3) % size] << 8); - - if (len == 0 || len > sizeof(bytes)) { - return; - } - - for (size_t i = 0; i < len; i++) { - bytes[i] = buffer[(head + 6 + i) % size]; - } - - const uint16_t crc = buffer[(head + 4) % size] | (buffer[(head + 5) % size] << 8); - const uint16_t f16 = fletcher16(bytes, len); - - if ((f16 != crc)) { - return; - } - - mesh::Packet *new_pkt = _mgr->allocNew(); - if (new_pkt == NULL) { - return; - } - - new_pkt->readFrom(bytes, len); - if (!_seen_packets.hasSeen(new_pkt)) { - _mgr->queueInbound(new_pkt, 0); +void SerialBridge::onPacketReceived(mesh::Packet* packet) { + if (!_seen_packets.hasSeen(packet)) { + _mgr->queueInbound(packet, 0); } else { - _mgr->free(new_pkt); + _mgr->free(packet); } } diff --git a/src/helpers/SerialBridge.h b/src/helpers/SerialBridge.h index fe3c176f..7b729870 100644 --- a/src/helpers/SerialBridge.h +++ b/src/helpers/SerialBridge.h @@ -21,12 +21,38 @@ public: void begin() override; void loop() override; void onPacketTransmitted(mesh::Packet* packet) override; - void onPacketReceived() override; + void onPacketReceived(mesh::Packet* packet) override; private: + /** + * @brief The 2-byte magic word used to signify the start of a packet. + */ + static constexpr uint16_t SERIAL_PKT_MAGIC = 0xCAFE; + + /** + * @brief The total overhead of the serial protocol in bytes. + * [MAGIC_WORD (2 bytes)] [LENGTH (2 bytes)] [PAYLOAD (variable)] [CHECKSUM (2 bytes)] + */ + static constexpr uint16_t SERIAL_OVERHEAD = 6; + + /** + * @brief The maximum size of a packet on the serial line. + * + * This is calculated as the sum of: + * - 1 byte for the packet header (from mesh::Packet) + * - 4 bytes for transport codes (from mesh::Packet) + * - 1 byte for the path length (from mesh::Packet) + * - MAX_PATH_SIZE for the path itself (from MeshCore.h) + * - MAX_PACKET_PAYLOAD for the payload (from MeshCore.h) + * - SERIAL_OVERHEAD for the serial framing + */ + static constexpr uint16_t MAX_SERIAL_PACKET_SIZE = (MAX_TRANS_UNIT + 1) + SERIAL_OVERHEAD; + Stream* _serial; mesh::PacketManager* _mgr; SimpleMeshTables _seen_packets; + uint8_t _rx_buffer[MAX_SERIAL_PACKET_SIZE]; // Buffer for serial data + uint16_t _rx_buffer_pos = 0; }; #endif From 77ab19153e9c36eddf7f4c3546e1c5c6f0ec7396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 5 Sep 2025 02:07:26 +0100 Subject: [PATCH 021/546] Add serial logging for TX/RX packets --- examples/simple_repeater/main.cpp | 2 +- src/helpers/SerialBridge.cpp | 18 +++++++++++++++++- src/helpers/SerialBridge.h | 5 ++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7150f0b2..93d3656f 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -579,7 +579,7 @@ public: _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { #ifdef BRIDGE_OVER_SERIAL - bridge = new SerialBridge(BRIDGE_OVER_SERIAL, _mgr); + bridge = new SerialBridge(BRIDGE_OVER_SERIAL, _mgr, &rtc); #endif memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; diff --git a/src/helpers/SerialBridge.cpp b/src/helpers/SerialBridge.cpp index c56645b6..f971e39c 100644 --- a/src/helpers/SerialBridge.cpp +++ b/src/helpers/SerialBridge.cpp @@ -1,5 +1,6 @@ #include "SerialBridge.h" #include +#include #ifdef BRIDGE_OVER_SERIAL @@ -16,7 +17,15 @@ inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { return (sum2 << 8) | sum1; }; -SerialBridge::SerialBridge(Stream& serial, mesh::PacketManager* mgr) : _serial(&serial), _mgr(mgr) {} +const char* SerialBridge::getLogDateTime() { + static char tmp[32]; + uint32_t now = _rtc->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), dt.year()); + return tmp; +} + +SerialBridge::SerialBridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc) : _serial(&serial), _mgr(mgr), _rtc(rtc) {} void SerialBridge::begin() { #if defined(ESP32) @@ -48,6 +57,10 @@ void SerialBridge::onPacketTransmitted(mesh::Packet* packet) { buffer[5 + len] = checksum & 0xFF; _serial->write(buffer, len + SERIAL_OVERHEAD); + +#if MESH_PACKET_LOGGING + Serial.printf("%s: BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); +#endif } } @@ -77,6 +90,9 @@ void SerialBridge::loop() { if (_rx_buffer_pos == len + SERIAL_OVERHEAD) { // Full packet received uint16_t checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; if (checksum == fletcher16(_rx_buffer + 4, len)) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); +#endif mesh::Packet *pkt = _mgr->allocNew(); if (pkt) { memcpy(pkt->payload, _rx_buffer + 4, len); diff --git a/src/helpers/SerialBridge.h b/src/helpers/SerialBridge.h index 7b729870..cc837d5e 100644 --- a/src/helpers/SerialBridge.h +++ b/src/helpers/SerialBridge.h @@ -16,14 +16,16 @@ public: * * @param serial The serial port to use for the bridge. * @param mgr A pointer to the packet manager. + * @param rtc A pointer to the RTC clock. */ - SerialBridge(Stream& serial, mesh::PacketManager* mgr); + SerialBridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc); void begin() override; void loop() override; void onPacketTransmitted(mesh::Packet* packet) override; void onPacketReceived(mesh::Packet* packet) override; private: + const char* getLogDateTime(); /** * @brief The 2-byte magic word used to signify the start of a packet. */ @@ -50,6 +52,7 @@ private: Stream* _serial; mesh::PacketManager* _mgr; + mesh::RTCClock* _rtc; SimpleMeshTables _seen_packets; uint8_t _rx_buffer[MAX_SERIAL_PACKET_SIZE]; // Buffer for serial data uint16_t _rx_buffer_pos = 0; From 375093f78df41e1632afb5f3362d71441442f2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 5 Sep 2025 09:22:06 +0100 Subject: [PATCH 022/546] Add nRF52 support and refactor packet handling This commit introduces several improvements to the SerialBridge helper: - Adds support for the nRF52 platform by implementing the `setPins` configuration. - Corrects the type cast for the RP2040 platform from `HardwareSerial` to `SerialUART`. - Refactors packet deserialization to use a new `Packet::readFrom()` method instead of a direct `memcpy`, improving encapsulation. - Updates the packet length validation to use the more appropriate `MAX_TRANS_UNIT` constant. --- src/helpers/SerialBridge.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/helpers/SerialBridge.cpp b/src/helpers/SerialBridge.cpp index f971e39c..eefd751a 100644 --- a/src/helpers/SerialBridge.cpp +++ b/src/helpers/SerialBridge.cpp @@ -29,13 +29,15 @@ SerialBridge::SerialBridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCCl void SerialBridge::begin() { #if defined(ESP32) - ((HardwareSerial*)_serial)->setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); + ((HardwareSerial *)_serial)->setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); +#elif defined(NRF52_PLATFORM) + ((HardwareSerial *)_serial)->setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); #elif defined(RP2040_PLATFORM) - ((HardwareSerial*)_serial)->setRX(BRIDGE_OVER_SERIAL_RX); - ((HardwareSerial*)_serial)->setTX(BRIDGE_OVER_SERIAL_TX); + ((SerialUART *)_serial)->setRX(BRIDGE_OVER_SERIAL_RX); + ((SerialUART *)_serial)->setTX(BRIDGE_OVER_SERIAL_TX); #elif defined(STM32_PLATFORM) - ((HardwareSerial*)_serial)->setRx(BRIDGE_OVER_SERIAL_RX); - ((HardwareSerial*)_serial)->setTx(BRIDGE_OVER_SERIAL_TX); + ((HardwareSerial *)_serial)->setRx(BRIDGE_OVER_SERIAL_RX); + ((HardwareSerial *)_serial)->setTx(BRIDGE_OVER_SERIAL_TX); #else #error SerialBridge was not tested on the current platform #endif @@ -82,21 +84,20 @@ void SerialBridge::loop() { if (_rx_buffer_pos >= 4) { uint16_t len = (_rx_buffer[2] << 8) | _rx_buffer[3]; - if (len > MAX_PACKET_PAYLOAD) { + if (len > (MAX_TRANS_UNIT + 1)) { _rx_buffer_pos = 0; // Invalid length, reset return; } - if (_rx_buffer_pos == len + SERIAL_OVERHEAD) { // Full packet received - uint16_t checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; - if (checksum == fletcher16(_rx_buffer + 4, len)) { + if (_rx_buffer_pos == len + SERIAL_OVERHEAD) { // Full packet received + uint16_t checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; + if (checksum == fletcher16(_rx_buffer + 4, len)) { #if MESH_PACKET_LOGGING - Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); + Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); #endif - mesh::Packet *pkt = _mgr->allocNew(); + mesh::Packet* pkt = _mgr->allocNew(); if (pkt) { - memcpy(pkt->payload, _rx_buffer + 4, len); - pkt->payload_len = len; + pkt->readFrom(_rx_buffer + 4, len); onPacketReceived(pkt); } } From 5843a12c71c094ef6e928ab714e253c8593c9c33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 5 Sep 2025 11:28:40 +0100 Subject: [PATCH 023/546] Rename `SerialBridge` to `RS232Bridge` --- examples/simple_repeater/main.cpp | 16 ++++----- .../RS232Bridge.cpp} | 34 +++++++++++-------- .../{SerialBridge.h => bridges/RS232Bridge.h} | 6 ++-- variants/heltec_v3/platformio.ini | 14 ++++---- variants/lilygo_tlora_v2_1/platformio.ini | 7 ++-- variants/waveshare_rp2040_lora/platformio.ini | 7 ++-- 6 files changed, 46 insertions(+), 38 deletions(-) rename src/helpers/{SerialBridge.cpp => bridges/RS232Bridge.cpp} (73%) rename src/helpers/{SerialBridge.h => bridges/RS232Bridge.h} (92%) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 93d3656f..7b16ab20 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -78,8 +78,8 @@ /* ------------------------------ Code -------------------------------- */ -#ifdef BRIDGE_OVER_SERIAL -#include "helpers/SerialBridge.h" +#ifdef WITH_RS232_BRIDGE +#include "helpers/bridges/RS232Bridge.h" #endif #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS @@ -118,7 +118,7 @@ struct ClientInfo { #define MAX_CLIENTS 32 #endif -#ifdef BRIDGE_OVER_SERIAL +#ifdef WITH_RS232_BRIDGE AbstractBridge* bridge; #endif @@ -308,7 +308,7 @@ protected: } } void logTx(mesh::Packet* pkt, int len) override { -#ifdef BRIDGE_OVER_SERIAL +#ifdef WITH_RS232_BRIDGE if (!pkt->isMarkedDoNotRetransmit()) { bridge->onPacketTransmitted(pkt); } @@ -578,8 +578,8 @@ public: : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { -#ifdef BRIDGE_OVER_SERIAL - bridge = new SerialBridge(BRIDGE_OVER_SERIAL, _mgr, &rtc); +#ifdef WITH_RS232_BRIDGE + bridge = new RS232Bridge(WITH_RS232_BRIDGE, _mgr, &rtc); #endif memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; @@ -781,7 +781,7 @@ public: } void loop() { -#ifdef BRIDGE_OVER_SERIAL +#ifdef WITH_RS232_BRIDGE bridge->loop(); #endif @@ -833,7 +833,7 @@ void setup() { Serial.begin(115200); delay(1000); -#ifdef BRIDGE_OVER_SERIAL +#ifdef WITH_RS232_BRIDGE bridge->begin(); #endif diff --git a/src/helpers/SerialBridge.cpp b/src/helpers/bridges/RS232Bridge.cpp similarity index 73% rename from src/helpers/SerialBridge.cpp rename to src/helpers/bridges/RS232Bridge.cpp index eefd751a..801bcc5c 100644 --- a/src/helpers/SerialBridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -1,8 +1,8 @@ -#include "SerialBridge.h" +#include "RS232Bridge.h" #include #include -#ifdef BRIDGE_OVER_SERIAL +#ifdef WITH_RS232_BRIDGE // Fletcher-16 // https://en.wikipedia.org/wiki/Fletcher%27s_checksum @@ -17,7 +17,7 @@ inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { return (sum2 << 8) | sum1; }; -const char* SerialBridge::getLogDateTime() { +const char* RS232Bridge::getLogDateTime() { static char tmp[32]; uint32_t now = _rtc->getCurrentTime(); DateTime dt = DateTime(now); @@ -25,26 +25,30 @@ const char* SerialBridge::getLogDateTime() { return tmp; } -SerialBridge::SerialBridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc) : _serial(&serial), _mgr(mgr), _rtc(rtc) {} +RS232Bridge::RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc) : _serial(&serial), _mgr(mgr), _rtc(rtc) {} + +void RS232Bridge::begin() { +#if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX) +#error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined" +#endif -void SerialBridge::begin() { #if defined(ESP32) - ((HardwareSerial *)_serial)->setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); + ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) - ((HardwareSerial *)_serial)->setPins(BRIDGE_OVER_SERIAL_RX, BRIDGE_OVER_SERIAL_TX); + ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) - ((SerialUART *)_serial)->setRX(BRIDGE_OVER_SERIAL_RX); - ((SerialUART *)_serial)->setTX(BRIDGE_OVER_SERIAL_TX); + ((SerialUART *)_serial)->setRX(WITH_RS232_BRIDGE_RX); + ((SerialUART *)_serial)->setTX(WITH_RS232_BRIDGE_TX); #elif defined(STM32_PLATFORM) - ((HardwareSerial *)_serial)->setRx(BRIDGE_OVER_SERIAL_RX); - ((HardwareSerial *)_serial)->setTx(BRIDGE_OVER_SERIAL_TX); + ((HardwareSerial *)_serial)->setRx(WITH_RS232_BRIDGE_RX); + ((HardwareSerial *)_serial)->setTx(WITH_RS232_BRIDGE_TX); #else -#error SerialBridge was not tested on the current platform +#error RS232Bridge was not tested on the current platform #endif ((HardwareSerial*)_serial)->begin(115200); } -void SerialBridge::onPacketTransmitted(mesh::Packet* packet) { +void RS232Bridge::onPacketTransmitted(mesh::Packet* packet) { if (!_seen_packets.hasSeen(packet)) { uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; uint16_t len = packet->writeTo(buffer + 4); @@ -66,7 +70,7 @@ void SerialBridge::onPacketTransmitted(mesh::Packet* packet) { } } -void SerialBridge::loop() { +void RS232Bridge::loop() { while (_serial->available()) { uint8_t b = _serial->read(); @@ -108,7 +112,7 @@ void SerialBridge::loop() { } } -void SerialBridge::onPacketReceived(mesh::Packet* packet) { +void RS232Bridge::onPacketReceived(mesh::Packet* packet) { if (!_seen_packets.hasSeen(packet)) { _mgr->queueInbound(packet, 0); } else { diff --git a/src/helpers/SerialBridge.h b/src/helpers/bridges/RS232Bridge.h similarity index 92% rename from src/helpers/SerialBridge.h rename to src/helpers/bridges/RS232Bridge.h index cc837d5e..0e99040f 100644 --- a/src/helpers/SerialBridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -4,12 +4,12 @@ #include "helpers/SimpleMeshTables.h" #include -#ifdef BRIDGE_OVER_SERIAL +#ifdef WITH_RS232_BRIDGE /** * @brief A bridge implementation that uses a serial port to connect two mesh networks. */ -class SerialBridge : public AbstractBridge { +class RS232Bridge : public AbstractBridge { public: /** * @brief Construct a new Serial Bridge object @@ -18,7 +18,7 @@ public: * @param mgr A pointer to the packet manager. * @param rtc A pointer to the RTC clock. */ - SerialBridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc); + RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc); void begin() override; void loop() override; void onPacketTransmitted(mesh::Packet* packet) override; diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 3e7524c9..4ffb11ba 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -59,13 +59,14 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 - -D BRIDGE_OVER_SERIAL=Serial2 - -D BRIDGE_OVER_SERIAL_RX=5 - -D BRIDGE_OVER_SERIAL_TX=6 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=5 + -D WITH_RS232_BRIDGE_TX=6 -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + +<../examples/simple_repeater> lib_deps = ${Heltec_lora32_v3.lib_deps} @@ -217,12 +218,13 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 - -D BRIDGE_OVER_SERIAL=Serial2 - -D BRIDGE_OVER_SERIAL_RX=5 - -D BRIDGE_OVER_SERIAL_TX=6 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=5 + -D WITH_RS232_BRIDGE_TX=6 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + +<../examples/simple_repeater> lib_deps = ${Heltec_lora32_v3.lib_deps} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 4e146cf6..313b9844 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -68,6 +68,7 @@ lib_deps = extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + + +<../examples/simple_repeater> build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} @@ -76,9 +77,9 @@ build_flags = -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 - -D BRIDGE_OVER_SERIAL=Serial2 - -D BRIDGE_OVER_SERIAL_RX=34 - -D BRIDGE_OVER_SERIAL_TX=25 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=34 + -D WITH_RS232_BRIDGE_TX=25 -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 0ec745ff..8824ddbd 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -48,12 +48,13 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 - -D BRIDGE_OVER_SERIAL=Serial2 - -D BRIDGE_OVER_SERIAL_RX=9 - -D BRIDGE_OVER_SERIAL_TX=8 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=8 -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} + + +<../examples/simple_repeater> [env:waveshare_rp2040_lora_room_server] From 2d651221c471dfb8b98b2e6de54759d5a723ccb1 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Fri, 5 Sep 2025 15:20:52 +0200 Subject: [PATCH 024/546] ui: sensors page --- examples/companion_radio/ui-new/UITask.cpp | 54 +++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 34ba60e5..efa31332 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -75,6 +75,7 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, + SENSORS, SHUTDOWN, Count // keep as last }; @@ -113,9 +114,32 @@ class HomeScreen : public UIScreen { display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); } + DynamicJsonDocument _sensors_doc; + JsonArray _sensors_arr; + bool scroll = false; + int scroll_offset = 0; + int next_sensors_refresh = 0; + + void refresh_sensors() { + CayenneLPP lpp(200); + if (millis() > next_sensors_refresh) { + lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + sensors.querySensors(0xFF, lpp); + _sensors_arr.clear(); + lpp.decode(lpp.getBuffer(), lpp.getSize(), _sensors_arr); + scroll = _sensors_arr.size() > UI_RECENT_LIST_SIZE; // there is a status line +#if AUTO_OFF_MILLIS > 0 + next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec +#else + next_sensors_refresh = millis() + 60000; // refresh sensor values every 1 min +#endif + } + } + public: HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) - : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), _shutdown_init(false) { } + : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), + _shutdown_init(false), _sensors_doc(2048) { _sensors_arr=_sensors_doc.to(); } void poll() override { if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released @@ -211,6 +235,34 @@ public: 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::SENSORS) { + int y = 18; + refresh_sensors(); + char buf[100]; + int s_size = _sensors_arr.size(); + for (int i = 0; i < (scroll?UI_RECENT_LIST_SIZE:s_size); i++) { + JsonObject v = _sensors_arr[(i+scroll_offset)%s_size]; + display.setCursor(0, y); + switch (v["type"].as()) { + case 136: // GPS + sprintf(buf, "%.4f %.4f", + v["value"]["latitude"].as(), + v["value"]["longitude"].as()); + break; + default: // will be a float for now + sprintf(buf, "%.02f", + v["value"].as()); + } + display.setCursor(0, y); + display.print(v["name"].as().c_str()); + display.setCursor( + display.width()-display.getTextWidth(buf)-1, y + ); + display.print(buf); + y = y + 12; + } + if (scroll) scroll_offset = (scroll_offset+1)%s_size; + else scroll_offset = 0; } else if (_page == HomePage::SHUTDOWN) { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); From f974cb2a4fbe2a93607a4737c3e08a6807f386e0 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Fri, 5 Sep 2025 15:32:02 +0200 Subject: [PATCH 025/546] ui: ENTER on SENSORS page toggles gps --- examples/companion_radio/ui-new/UITask.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index efa31332..93a7da1b 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -307,6 +307,10 @@ public: } return true; } + if (c == KEY_ENTER && _page == HomePage::SENSORS) { + _task->toggleGPS(); + return true; + } if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { _shutdown_init = true; // need to wait for button to be released return true; From 8fdaaceb1c4586bfe4477d608f50d23808c8f415 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte Date: Fri, 5 Sep 2025 15:35:04 +0200 Subject: [PATCH 026/546] ui: refresh sensors on gps toggle --- examples/companion_radio/ui-new/UITask.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 93a7da1b..1adabffa 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -309,6 +309,7 @@ public: } if (c == KEY_ENTER && _page == HomePage::SENSORS) { _task->toggleGPS(); + next_sensors_refresh=0; return true; } if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { From cb99eb4ae8db2a243b56d27e8e679fd222d09859 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Fri, 5 Sep 2025 14:49:06 +0100 Subject: [PATCH 027/546] Remove retransmit check for RS232 bridge in logTx Since the flag is preserved and respected by the mesh processing on the receiving end, there's no risk of these packets being retransmitted endlessly. --- examples/simple_repeater/main.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7b16ab20..3e2795d6 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -309,9 +309,7 @@ protected: } void logTx(mesh::Packet* pkt, int len) override { #ifdef WITH_RS232_BRIDGE - if (!pkt->isMarkedDoNotRetransmit()) { - bridge->onPacketTransmitted(pkt); - } + bridge->onPacketTransmitted(pkt); #endif if (_logging) { File f = openAppend(PACKET_LOG_FILE); From c10c010736a1b11e34873945f9dfe99217e754e9 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Sat, 6 Sep 2025 22:06:47 +0200 Subject: [PATCH 028/546] fix: only hide pin after successful authentication in SerialBLEInterface --- src/helpers/esp32/SerialBLEInterface.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index 1be703a8..7ec93723 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -66,7 +66,7 @@ bool SerialBLEInterface::onSecurityRequest() { void SerialBLEInterface::onAuthenticationComplete(esp_ble_auth_cmpl_t cmpl) { if (cmpl.success) { BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Success"); - //deviceConnected = true; + deviceConnected = true; } else { BLE_DEBUG_PRINTLN(" - SecurityCallback - Authentication Failure*"); @@ -88,8 +88,6 @@ void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { BLE_DEBUG_PRINTLN("onMtuChanged(), mtu=%d", pServer->getPeerMTU(param->mtu.conn_id)); - - deviceConnected = true; } void SerialBLEInterface::onDisconnect(BLEServer* pServer) { From 5b9d11ac8f9c91431ef417ac16280440d1920ec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 7 Sep 2025 21:39:54 +0100 Subject: [PATCH 029/546] Support ESPNow and improve documentation --- examples/simple_repeater/main.cpp | 62 +++++--- src/helpers/bridges/ESPNowBridge.cpp | 184 ++++++++++++++++++++++ src/helpers/bridges/ESPNowBridge.h | 170 ++++++++++++++++++++ src/helpers/bridges/RS232Bridge.cpp | 5 +- src/helpers/bridges/RS232Bridge.h | 77 +++++++-- variants/lilygo_tlora_v2_1/platformio.ini | 71 ++++++--- 6 files changed, 510 insertions(+), 59 deletions(-) create mode 100644 src/helpers/bridges/ESPNowBridge.cpp create mode 100644 src/helpers/bridges/ESPNowBridge.h diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 3e2795d6..b78481d0 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -80,30 +80,36 @@ #ifdef WITH_RS232_BRIDGE #include "helpers/bridges/RS232Bridge.h" +#define WITH_BRIDGE #endif -#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS -#define REQ_TYPE_KEEP_ALIVE 0x02 -#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 +#ifdef WITH_ESPNOW_BRIDGE +#include "helpers/bridges/ESPNowBridge.h" +#define WITH_BRIDGE +#endif -#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ +#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS +#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 -struct RepeaterStats { - uint16_t batt_milli_volts; - uint16_t curr_tx_queue_len; - int16_t noise_floor; - int16_t last_rssi; - uint32_t n_packets_recv; - uint32_t n_packets_sent; - uint32_t total_air_time_secs; - uint32_t total_up_time_secs; - uint32_t n_sent_flood, n_sent_direct; - uint32_t n_recv_flood, n_recv_direct; - uint16_t err_events; // was 'n_full_events' - int16_t last_snr; // x 4 - uint16_t n_direct_dups, n_flood_dups; - uint32_t total_rx_air_time_secs; -}; +#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ + + struct RepeaterStats { + uint16_t batt_milli_volts; + uint16_t curr_tx_queue_len; + int16_t noise_floor; + int16_t last_rssi; + uint32_t n_packets_recv; + uint32_t n_packets_sent; + uint32_t total_air_time_secs; + uint32_t total_up_time_secs; + uint32_t n_sent_flood, n_sent_direct; + uint32_t n_recv_flood, n_recv_direct; + uint16_t err_events; // was 'n_full_events' + int16_t last_snr; // x 4 + uint16_t n_direct_dups, n_flood_dups; + uint32_t total_rx_air_time_secs; + }; struct ClientInfo { mesh::Identity id; @@ -118,7 +124,7 @@ struct ClientInfo { #define MAX_CLIENTS 32 #endif -#ifdef WITH_RS232_BRIDGE +#ifdef WITH_BRIDGE AbstractBridge* bridge; #endif @@ -308,7 +314,7 @@ protected: } } void logTx(mesh::Packet* pkt, int len) override { -#ifdef WITH_RS232_BRIDGE +#ifdef WITH_BRIDGE bridge->onPacketTransmitted(pkt); #endif if (_logging) { @@ -576,8 +582,14 @@ public: : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { -#ifdef WITH_RS232_BRIDGE +#ifdef WITH_BRIDGE +#if defined(WITH_RS232_BRIDGE) bridge = new RS232Bridge(WITH_RS232_BRIDGE, _mgr, &rtc); +#elif defined(WITH_ESPNOW_BRIDGE) + bridge = new ESPNowBridge(_mgr, &rtc); +#else +#error "You must choose either RS232 or ESPNow bridge" +#endif #endif memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; @@ -779,7 +791,7 @@ public: } void loop() { -#ifdef WITH_RS232_BRIDGE +#ifdef WITH_BRIDGE bridge->loop(); #endif @@ -831,7 +843,7 @@ void setup() { Serial.begin(115200); delay(1000); -#ifdef WITH_RS232_BRIDGE +#ifdef WITH_BRIDGE bridge->begin(); #endif diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp new file mode 100644 index 00000000..a470d521 --- /dev/null +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -0,0 +1,184 @@ +#include "ESPNowBridge.h" + +#include +#include +#include + +#ifdef WITH_ESPNOW_BRIDGE + +// Static member to handle callbacks +ESPNowBridge *ESPNowBridge::_instance = nullptr; + +// Static callback wrappers +void ESPNowBridge::recv_cb(const uint8_t *mac, const uint8_t *data, int len) { + if (_instance) { + _instance->onDataRecv(mac, data, len); + } +} + +void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) { + if (_instance) { + _instance->onDataSent(mac, status); + } +} + +// Fletcher16 checksum calculation +static uint16_t fletcher16(const uint8_t *data, size_t len) { + uint16_t sum1 = 0; + uint16_t sum2 = 0; + + while (len--) { + sum1 = (sum1 + *data++) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; +} + +ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc) + : _mgr(mgr), _rtc(rtc), _rx_buffer_pos(0) { + _instance = this; +} + +void ESPNowBridge::begin() { + // Initialize WiFi in station mode + WiFi.mode(WIFI_STA); + + // Initialize ESP-NOW + if (esp_now_init() != ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: Error initializing ESP-NOW\n", getLogDateTime()); + return; + } + + // Register callbacks + esp_now_register_recv_cb(recv_cb); + esp_now_register_send_cb(send_cb); + + // Add broadcast peer + esp_now_peer_info_t peerInfo = {}; + memset(&peerInfo, 0, sizeof(peerInfo)); + memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address + peerInfo.channel = 0; + peerInfo.encrypt = false; + + if (esp_now_add_peer(&peerInfo) != ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime()); + return; + } +} + +void ESPNowBridge::loop() { + // Nothing to do here - ESP-NOW is callback based +} + +void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) { + size_t keyLen = strlen(_secret); + for (size_t i = 0; i < len; i++) { + data[i] ^= _secret[i % keyLen]; + } +} + +void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int len) { + // Ignore packets that are too small + if (len < 3) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len); +#endif + return; + } + + // Check packet header magic + if (data[0] != ESPNOW_HEADER_MAGIC) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%02X\n", getLogDateTime(), data[0]); +#endif + return; + } + + // Make a copy we can decrypt + uint8_t decrypted[MAX_ESPNOW_PACKET_SIZE]; + memcpy(decrypted, data + 1, len - 1); // Skip magic byte + + // Try to decrypt + xorCrypt(decrypted, len - 1); + + // Validate checksum + uint16_t received_checksum = (decrypted[0] << 8) | decrypted[1]; + uint16_t calculated_checksum = fletcher16(decrypted + 2, len - 3); + + if (received_checksum != calculated_checksum) { + // Failed to decrypt - likely from a different network +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X calc=0x%04X\n", getLogDateTime(), + received_checksum, calculated_checksum); +#endif + return; + } + +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: RX, len=%d\n", getLogDateTime(), len - 3); +#endif + + // Create mesh packet + mesh::Packet *pkt = _instance->_mgr->allocNew(); + if (!pkt) return; + + if (pkt->readFrom(decrypted + 2, len - 3)) { + _instance->onPacketReceived(pkt); + } else { + _instance->_mgr->free(pkt); + } +} + +void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status) { + // Could add transmission error handling here if needed +} + +void ESPNowBridge::onPacketReceived(mesh::Packet *packet) { + if (!_seen_packets.hasSeen(packet)) { + _mgr->queueInbound(packet, 0); + } else { + _mgr->free(packet); + } +} + +void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { + if (!_seen_packets.hasSeen(packet)) { + uint8_t buffer[MAX_ESPNOW_PACKET_SIZE]; + buffer[0] = ESPNOW_HEADER_MAGIC; + + // Write packet to buffer starting after magic byte and checksum + uint16_t len = packet->writeTo(buffer + 3); + + // Calculate and add checksum + uint16_t checksum = fletcher16(buffer + 3, len); + buffer[1] = (checksum >> 8) & 0xFF; + buffer[2] = checksum & 0xFF; + + // Encrypt payload (not including magic byte) + xorCrypt(buffer + 1, len + 2); + + // Broadcast using ESP-NOW + uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + esp_err_t result = esp_now_send(broadcastAddress, buffer, len + 3); + +#if MESH_PACKET_LOGGING + if (result == ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), len); + } else { + Serial.printf("%s: ESPNOW BRIDGE: TX FAILED!\n", getLogDateTime()); + } +#endif + } +} + +const char *ESPNowBridge::getLogDateTime() { + static char tmp[32]; + uint32_t now = _rtc->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), + dt.year()); + return tmp; +} + +#endif diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h new file mode 100644 index 00000000..7d2dbb0b --- /dev/null +++ b/src/helpers/bridges/ESPNowBridge.h @@ -0,0 +1,170 @@ +#pragma once + +#include "MeshCore.h" +#include "esp_now.h" +#include "helpers/AbstractBridge.h" +#include "helpers/SimpleMeshTables.h" + +#ifdef WITH_ESPNOW_BRIDGE + +#ifndef WITH_ESPNOW_BRIDGE_SECRET +#error WITH_ESPNOW_BRIDGE_SECRET must be defined to use ESPNowBridge +#endif + +/** + * @brief Bridge implementation using ESP-NOW protocol for packet transport + * + * This bridge enables mesh packet transport over ESP-NOW, a connectionless communication + * protocol provided by Espressif that allows ESP32 devices to communicate directly + * without WiFi router infrastructure. + * + * Features: + * - Broadcast-based communication (all bridges receive all packets) + * - Network isolation using XOR encryption with shared secret + * - Duplicate packet detection using SimpleMeshTables tracking + * - Maximum packet size of 250 bytes (ESP-NOW limitation) + * + * Packet Structure: + * [1 byte] Magic Header (0xAB) - Used to identify ESPNowBridge packets + * [2 bytes] Fletcher-16 checksum of encrypted payload (calculated over payload only) + * [n bytes] Encrypted payload containing the mesh packet + * + * The Fletcher-16 checksum is used to validate packet integrity and detect + * corrupted or tampered packets. It's calculated over the encrypted payload + * and provides a simple but effective way to verify packets are both + * uncorrupted and from the same network (since the checksum is calculated + * after encryption). + * + * Configuration: + * - Define WITH_ESPNOW_BRIDGE to enable this bridge + * - Define WITH_ESPNOW_BRIDGE_SECRET with a string to set the network encryption key + * + * Network Isolation: + * Multiple independent mesh networks can coexist by using different + * WITH_ESPNOW_BRIDGE_SECRET values. Packets encrypted with a different key will + * fail the checksum validation and be discarded. + */ +class ESPNowBridge : public AbstractBridge { +private: + static ESPNowBridge *_instance; + static void recv_cb(const uint8_t *mac, const uint8_t *data, int len); + static void send_cb(const uint8_t *mac, esp_now_send_status_t status); + + /** Packet manager for allocating and queuing mesh packets */ + mesh::PacketManager *_mgr; + + /** RTC clock for timestamping debug messages */ + mesh::RTCClock *_rtc; + + /** Tracks seen packets to prevent loops in broadcast communications */ + SimpleMeshTables _seen_packets; + + /** + * Maximum ESP-NOW packet size (250 bytes) + * This is a hardware limitation of ESP-NOW protocol: + * - ESP-NOW header: 20 bytes + * - Max payload: 250 bytes + * Source: ESP-NOW API documentation + */ + static const size_t MAX_ESPNOW_PACKET_SIZE = 250; + + /** + * Magic byte to identify ESPNowBridge packets (0xAB) + */ + static const uint8_t ESPNOW_HEADER_MAGIC = 0xAB; + + /** Buffer for receiving ESP-NOW packets */ + uint8_t _rx_buffer[MAX_ESPNOW_PACKET_SIZE]; + + /** Current position in receive buffer */ + size_t _rx_buffer_pos; + + /** + * Network encryption key from build define + * Must be defined with WITH_ESPNOW_BRIDGE_SECRET + * Used for XOR encryption to isolate different mesh networks + */ + const char *_secret = WITH_ESPNOW_BRIDGE_SECRET; + + /** + * Performs XOR encryption/decryption of data + * + * Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation. + * The same operation is used for both encryption and decryption. + * While not cryptographically secure, it provides basic network isolation. + * + * @param data Pointer to data to encrypt/decrypt + * @param len Length of data in bytes + */ + void xorCrypt(uint8_t *data, size_t len); + + /** + * ESP-NOW receive callback + * Called by ESP-NOW when a packet is received + * + * @param mac Source MAC address + * @param data Received data + * @param len Length of received data + */ + void onDataRecv(const uint8_t *mac, const uint8_t *data, int len); + + /** + * ESP-NOW send callback + * Called by ESP-NOW after a transmission attempt + * + * @param mac_addr Destination MAC address + * @param status Transmission status + */ + void onDataSent(const uint8_t *mac_addr, esp_now_send_status_t status); + +public: + /** + * Constructs an ESPNowBridge instance + * + * @param mgr PacketManager for allocating and queuing packets + * @param rtc RTCClock for timestamping debug messages + */ + ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc); + + /** + * Initializes the ESP-NOW bridge + * + * - Configures WiFi in station mode + * - Initializes ESP-NOW protocol + * - Registers callbacks + * - Sets up broadcast peer + */ + void begin() override; + + /** + * Main loop handler + * ESP-NOW is callback-based, so this is currently empty + */ + void loop() override; + + /** + * Called when a packet is received via ESP-NOW + * Queues the packet for mesh processing if not seen before + * + * @param packet The received mesh packet + */ + void onPacketReceived(mesh::Packet *packet) override; + + /** + * Called when a packet needs to be transmitted via ESP-NOW + * Encrypts and broadcasts the packet if not seen before + * + * @param packet The mesh packet to transmit + */ + void onPacketTransmitted(mesh::Packet *packet) override; + + /** + * Gets formatted date/time string for logging + * Format: "HH:MM:SS - DD/MM/YYYY U" + * + * @return Formatted date/time string + */ + const char *getLogDateTime(); +}; + +#endif diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 801bcc5c..5c3b8caa 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -4,8 +4,9 @@ #ifdef WITH_RS232_BRIDGE -// Fletcher-16 -// https://en.wikipedia.org/wiki/Fletcher%27s_checksum +// Static Fletcher-16 checksum calculation +// Based on: https://en.wikipedia.org/wiki/Fletcher%27s_checksum +// Used to verify data integrity of received packets inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { uint8_t sum1 = 0, sum2 = 0; diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 0e99040f..2adeb503 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -7,28 +7,87 @@ #ifdef WITH_RS232_BRIDGE /** - * @brief A bridge implementation that uses a serial port to connect two mesh networks. + * @brief Bridge implementation using RS232/UART protocol for packet transport + * + * This bridge enables mesh packet transport over serial/UART connections, + * allowing nodes to communicate over wired serial links. It implements a simple + * packet framing protocol with checksums for reliable transfer. + * + * Features: + * - Point-to-point communication over hardware UART + * - Fletcher-16 checksum for data integrity verification + * - Magic header for packet synchronization + * - Configurable RX/TX pins via build defines + * - Baud rate fixed at 115200 + * + * Packet Structure: + * [2 bytes] Magic Header (0xCAFE) - Used to identify start of packet + * [2 bytes] Fletcher-16 checksum - Data integrity check + * [1 byte] Payload length + * [n bytes] Packet payload + * + * The Fletcher-16 checksum is used to validate packet integrity and detect + * corrupted or malformed packets. It provides error detection capabilities + * suitable for serial communication where noise or timing issues could + * corrupt data. + * + * Configuration: + * - Define WITH_RS232_BRIDGE to enable this bridge + * - Define WITH_RS232_BRIDGE_RX with the RX pin number + * - Define WITH_RS232_BRIDGE_TX with the TX pin number + * + * Platform Support: + * - ESP32 targets + * - NRF52 targets + * - RP2040 targets + * - STM32 targets */ class RS232Bridge : public AbstractBridge { public: /** - * @brief Construct a new Serial Bridge object - * - * @param serial The serial port to use for the bridge. - * @param mgr A pointer to the packet manager. - * @param rtc A pointer to the RTC clock. + * @brief Constructs an RS232Bridge instance + * + * @param serial The hardware serial port to use + * @param mgr PacketManager for allocating and queuing packets + * @param rtc RTCClock for timestamping debug messages */ RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc); + + /** + * Initializes the RS232 bridge + * + * - Configures UART pins based on platform + * - Sets baud rate to 115200 + */ void begin() override; + + /** + * @brief Main loop handler + * Processes incoming serial data and builds packets + */ void loop() override; + + /** + * @brief Called when a packet needs to be transmitted over serial + * Formats and sends the packet with proper framing + * + * @param packet The mesh packet to transmit + */ void onPacketTransmitted(mesh::Packet* packet) override; + + /** + * @brief Called when a complete packet has been received from serial + * Queues the packet for mesh processing if checksum is valid + * + * @param packet The received mesh packet + */ void onPacketReceived(mesh::Packet* packet) override; private: + /** Helper function to get formatted timestamp for logging */ const char* getLogDateTime(); - /** - * @brief The 2-byte magic word used to signify the start of a packet. - */ + + /** Magic number to identify start of RS232 packets (0xCAFE) */ static constexpr uint16_t SERIAL_PKT_MAGIC = 0xCAFE; /** diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 313b9844..5e9874c9 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -64,29 +64,6 @@ lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} ${esp32_ota.lib_deps} -[env:LilyGo_TLora_V2_1_1_6_repeater_bridge] -extends = LilyGo_TLora_V2_1_1_6 -build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - + - + - +<../examples/simple_repeater> -build_flags = - ${LilyGo_TLora_V2_1_1_6.build_flags} - -D ADVERT_NAME='"TLora-V2.1-1.6 Bridge"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 - -D WITH_RS232_BRIDGE=Serial2 - -D WITH_RS232_BRIDGE_RX=34 - -D WITH_RS232_BRIDGE_TX=25 - -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -; -D CORE_DEBUG_LEVEL=3 -lib_deps = - ${LilyGo_TLora_V2_1_1_6.lib_deps} - ${esp32_ota.lib_deps} - [env:LilyGo_TLora_V2_1_1_6_terminal_chat] extends = LilyGo_TLora_V2_1_1_6 build_flags = @@ -179,3 +156,51 @@ build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 + +; +; Repeater Bridges +; +[env:LilyGo_TLora_V2_1_1_6_bridge_rs232] +extends = LilyGo_TLora_V2_1_1_6 +build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + + + + +<../examples/simple_repeater> +build_flags = + ${LilyGo_TLora_V2_1_1_6.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=34 + -D WITH_RS232_BRIDGE_TX=25 + -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +lib_deps = + ${LilyGo_TLora_V2_1_1_6.lib_deps} + ${esp32_ota.lib_deps} + +[env:LilyGo_TLora_V2_1_1_6_bridge_espnow] +extends = LilyGo_TLora_V2_1_1_6 +build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + + + + + +<../examples/simple_repeater> +build_flags = + ${LilyGo_TLora_V2_1_1_6.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' + -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +lib_deps = + ${LilyGo_TLora_V2_1_1_6.lib_deps} + ${esp32_ota.lib_deps} \ No newline at end of file From 04e70829a454703638e3740dc017cbf995d9f23a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 7 Sep 2025 21:46:51 +0100 Subject: [PATCH 030/546] Rename RS232 bridge environments --- variants/heltec_v3/platformio.ini | 55 +++++++++++++++++-- variants/lilygo_tlora_v2_1/platformio.ini | 4 +- variants/waveshare_rp2040_lora/platformio.ini | 6 +- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 4ffb11ba..c7ab86d8 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -49,12 +49,12 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 -[env:Heltec_v3_repeater_bridge] +[env:Heltec_v3_repeater_bridge_rs232] extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -D DISPLAY_CLASS=SSD1306Display - -D ADVERT_NAME='"Heltec Bridge"' + -D ADVERT_NAME='"RS232 Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -62,11 +62,33 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=5 -D WITH_RS232_BRIDGE_TX=6 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} - + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + ${esp32_ota.lib_deps} + +[env:Heltec_v3_repeater_bridge_espnow] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + + +<../examples/simple_repeater> lib_deps = ${Heltec_lora32_v3.lib_deps} @@ -209,11 +231,11 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 -[env:Heltec_WSL3_repeater_bridge] +[env:Heltec_WSL3_repeater_bridge_rs232] extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D ADVERT_NAME='"Heltec WSL3 Bridge"' + -D ADVERT_NAME='"RS232 Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -231,6 +253,27 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Heltec_WSL3_repeater_bridge_espnow] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + [env:Heltec_WSL3_room_server] extends = Heltec_lora32_v3 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 5e9874c9..aa957fba 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -176,7 +176,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=34 -D WITH_RS232_BRIDGE_TX=25 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 lib_deps = @@ -198,7 +198,7 @@ build_flags = -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 lib_deps = diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 8824ddbd..0f333069 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -40,10 +40,10 @@ build_flags = ${waveshare_rp2040_lora.build_flags} build_src_filter = ${waveshare_rp2040_lora.build_src_filter} +<../examples/simple_repeater> -[env:waveshare_rp2040_lora_repeater_bridge] +[env:waveshare_rp2040_lora_repeater_bridge_rs232] extends = waveshare_rp2040_lora build_flags = ${waveshare_rp2040_lora.build_flags} - -D ADVERT_NAME='"RP2040-LoRa Bridge"' + -D ADVERT_NAME='"RS232 Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -51,7 +51,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=9 -D WITH_RS232_BRIDGE_TX=8 - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} + From 537449e6af5895bd9aa107ddd416f1b3fbb216d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Sun, 7 Sep 2025 22:00:06 +0100 Subject: [PATCH 031/546] Refactor ESPNowBridge packet handling to use 2-byte magic header and improve packet size validation --- src/helpers/bridges/ESPNowBridge.cpp | 88 ++++++++++++++++++++-------- src/helpers/bridges/ESPNowBridge.h | 33 +++++++---- src/helpers/bridges/RS232Bridge.h | 6 +- 3 files changed, 89 insertions(+), 38 deletions(-) diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index a470d521..aed63a6b 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -10,7 +10,7 @@ ESPNowBridge *ESPNowBridge::_instance = nullptr; // Static callback wrappers -void ESPNowBridge::recv_cb(const uint8_t *mac, const uint8_t *data, int len) { +void ESPNowBridge::recv_cb(const uint8_t *mac, const uint8_t *data, int32_t len) { if (_instance) { _instance->onDataRecv(mac, data, len); } @@ -78,33 +78,44 @@ void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) { } } -void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int len) { - // Ignore packets that are too small - if (len < 3) { +void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len) { + // Ignore packets that are too small to contain header + checksum + if (len < (MAGIC_HEADER_SIZE + CHECKSUM_SIZE)) { #if MESH_PACKET_LOGGING Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len); #endif return; } - // Check packet header magic - if (data[0] != ESPNOW_HEADER_MAGIC) { + // Validate total packet size + if (len > MAX_ESPNOW_PACKET_SIZE) { #if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%02X\n", getLogDateTime(), data[0]); + Serial.printf("%s: ESPNOW BRIDGE: RX packet too large, len=%d\n", getLogDateTime(), len); +#endif + return; + } + + // Check packet header magic + uint16_t received_magic = (data[0] << 8) | data[1]; + if (received_magic != ESPNOW_HEADER_MAGIC) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%04X\n", getLogDateTime(), received_magic); #endif return; } // Make a copy we can decrypt uint8_t decrypted[MAX_ESPNOW_PACKET_SIZE]; - memcpy(decrypted, data + 1, len - 1); // Skip magic byte + const size_t encryptedDataLen = len - MAGIC_HEADER_SIZE; + memcpy(decrypted, data + MAGIC_HEADER_SIZE, encryptedDataLen); - // Try to decrypt - xorCrypt(decrypted, len - 1); + // Try to decrypt (checksum + payload) + xorCrypt(decrypted, encryptedDataLen); // Validate checksum uint16_t received_checksum = (decrypted[0] << 8) | decrypted[1]; - uint16_t calculated_checksum = fletcher16(decrypted + 2, len - 3); + const size_t payloadLen = encryptedDataLen - CHECKSUM_SIZE; + uint16_t calculated_checksum = fletcher16(decrypted + CHECKSUM_SIZE, payloadLen); if (received_checksum != calculated_checksum) { // Failed to decrypt - likely from a different network @@ -116,14 +127,14 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int len) } #if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX, len=%d\n", getLogDateTime(), len - 3); + Serial.printf("%s: ESPNOW BRIDGE: RX, payload_len=%d\n", getLogDateTime(), payloadLen); #endif // Create mesh packet mesh::Packet *pkt = _instance->_mgr->allocNew(); if (!pkt) return; - if (pkt->readFrom(decrypted + 2, len - 3)) { + if (pkt->readFrom(decrypted + CHECKSUM_SIZE, payloadLen)) { _instance->onPacketReceived(pkt); } else { _instance->_mgr->free(pkt); @@ -144,27 +155,56 @@ void ESPNowBridge::onPacketReceived(mesh::Packet *packet) { void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { if (!_seen_packets.hasSeen(packet)) { + + // First validate the packet pointer + if (!packet) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: TX invalid packet pointer\n", getLogDateTime()); +#endif + return; + } + + // Create a temporary buffer just for size calculation and reuse for actual writing + uint8_t sizingBuffer[MAX_PAYLOAD_SIZE]; + uint16_t meshPacketLen = packet->writeTo(sizingBuffer); + + // Check if packet fits within our maximum payload size + if (meshPacketLen > MAX_PAYLOAD_SIZE) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: ESPNOW BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), + meshPacketLen, MAX_PAYLOAD_SIZE); +#endif + return; + } + uint8_t buffer[MAX_ESPNOW_PACKET_SIZE]; - buffer[0] = ESPNOW_HEADER_MAGIC; - // Write packet to buffer starting after magic byte and checksum - uint16_t len = packet->writeTo(buffer + 3); + // Write magic header (2 bytes) + buffer[0] = (ESPNOW_HEADER_MAGIC >> 8) & 0xFF; + buffer[1] = ESPNOW_HEADER_MAGIC & 0xFF; - // Calculate and add checksum - uint16_t checksum = fletcher16(buffer + 3, len); - buffer[1] = (checksum >> 8) & 0xFF; - buffer[2] = checksum & 0xFF; + // Write packet payload starting after magic header and checksum + const size_t packetOffset = MAGIC_HEADER_SIZE + CHECKSUM_SIZE; + memcpy(buffer + packetOffset, sizingBuffer, meshPacketLen); - // Encrypt payload (not including magic byte) - xorCrypt(buffer + 1, len + 2); + // Calculate and add checksum (only of the payload) + uint16_t checksum = fletcher16(buffer + packetOffset, meshPacketLen); + buffer[2] = (checksum >> 8) & 0xFF; // High byte + buffer[3] = checksum & 0xFF; // Low byte + + // Encrypt payload and checksum (not including magic header) + xorCrypt(buffer + MAGIC_HEADER_SIZE, meshPacketLen + CHECKSUM_SIZE); + + // Total packet size: magic header + checksum + payload + const size_t totalPacketSize = MAGIC_HEADER_SIZE + CHECKSUM_SIZE + meshPacketLen; // Broadcast using ESP-NOW uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; - esp_err_t result = esp_now_send(broadcastAddress, buffer, len + 3); + esp_err_t result = esp_now_send(broadcastAddress, buffer, totalPacketSize); #if MESH_PACKET_LOGGING if (result == ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), len); + Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), meshPacketLen); } else { Serial.printf("%s: ESPNOW BRIDGE: TX FAILED!\n", getLogDateTime()); } diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index 7d2dbb0b..5c771471 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/src/helpers/bridges/ESPNowBridge.h @@ -25,9 +25,9 @@ * - Maximum packet size of 250 bytes (ESP-NOW limitation) * * Packet Structure: - * [1 byte] Magic Header (0xAB) - Used to identify ESPNowBridge packets + * [2 bytes] Magic Header - Used to identify ESPNowBridge packets * [2 bytes] Fletcher-16 checksum of encrypted payload (calculated over payload only) - * [n bytes] Encrypted payload containing the mesh packet + * [246 bytes max] Encrypted payload containing the mesh packet * * The Fletcher-16 checksum is used to validate packet integrity and detect * corrupted or tampered packets. It's calculated over the encrypted payload @@ -47,7 +47,7 @@ class ESPNowBridge : public AbstractBridge { private: static ESPNowBridge *_instance; - static void recv_cb(const uint8_t *mac, const uint8_t *data, int len); + static void recv_cb(const uint8_t *mac, const uint8_t *data, int32_t len); static void send_cb(const uint8_t *mac, esp_now_send_status_t status); /** Packet manager for allocating and queuing mesh packets */ @@ -60,18 +60,29 @@ private: SimpleMeshTables _seen_packets; /** - * Maximum ESP-NOW packet size (250 bytes) - * This is a hardware limitation of ESP-NOW protocol: - * - ESP-NOW header: 20 bytes - * - Max payload: 250 bytes - * Source: ESP-NOW API documentation + * ESP-NOW Protocol Structure: + * - ESP-NOW header: 20 bytes (handled by ESP-NOW protocol) + * - ESP-NOW payload: 250 bytes maximum + * Total ESP-NOW packet: 270 bytes + * + * Our Bridge Packet Structure (must fit in ESP-NOW payload): + * - Magic header: 2 bytes + * - Checksum: 2 bytes + * - Available payload: 246 bytes */ static const size_t MAX_ESPNOW_PACKET_SIZE = 250; /** - * Magic byte to identify ESPNowBridge packets (0xAB) + * Size constants for packet parsing */ - static const uint8_t ESPNOW_HEADER_MAGIC = 0xAB; + static const size_t MAGIC_HEADER_SIZE = 2; + static const size_t CHECKSUM_SIZE = 2; + static const size_t MAX_PAYLOAD_SIZE = MAX_ESPNOW_PACKET_SIZE - (MAGIC_HEADER_SIZE + CHECKSUM_SIZE); + + /** + * Magic bytes to identify ESPNowBridge packets + */ + static const uint16_t ESPNOW_HEADER_MAGIC = 0xC03E; /** Buffer for receiving ESP-NOW packets */ uint8_t _rx_buffer[MAX_ESPNOW_PACKET_SIZE]; @@ -106,7 +117,7 @@ private: * @param data Received data * @param len Length of received data */ - void onDataRecv(const uint8_t *mac, const uint8_t *data, int len); + void onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len); /** * ESP-NOW send callback diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 2adeb503..49c781cb 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -21,7 +21,7 @@ * - Baud rate fixed at 115200 * * Packet Structure: - * [2 bytes] Magic Header (0xCAFE) - Used to identify start of packet + * [2 bytes] Magic Header - Used to identify start of packet * [2 bytes] Fletcher-16 checksum - Data integrity check * [1 byte] Payload length * [n bytes] Packet payload @@ -87,8 +87,8 @@ private: /** Helper function to get formatted timestamp for logging */ const char* getLogDateTime(); - /** Magic number to identify start of RS232 packets (0xCAFE) */ - static constexpr uint16_t SERIAL_PKT_MAGIC = 0xCAFE; + /** Magic number to identify start of RS232 packets */ + static constexpr uint16_t SERIAL_PKT_MAGIC = 0xC03E; /** * @brief The total overhead of the serial protocol in bytes. From 0051ccef2603bcd207217036930654d7b660ccb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Mon, 8 Sep 2025 02:03:08 +0100 Subject: [PATCH 032/546] Refactor bridge implementations to inherit from BridgeBase --- platformio.ini | 1 + src/helpers/bridges/BridgeBase.cpp | 34 +++++++ src/helpers/bridges/BridgeBase.h | 84 +++++++++++++++++ src/helpers/bridges/ESPNowBridge.cpp | 52 +++-------- src/helpers/bridges/ESPNowBridge.h | 22 +---- src/helpers/bridges/RS232Bridge.cpp | 115 ++++++++++++++---------- src/helpers/bridges/RS232Bridge.h | 129 +++++++++++++++++---------- 7 files changed, 282 insertions(+), 155 deletions(-) create mode 100644 src/helpers/bridges/BridgeBase.cpp create mode 100644 src/helpers/bridges/BridgeBase.h diff --git a/platformio.ini b/platformio.ini index 1c89465f..d21f513e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -47,6 +47,7 @@ build_src_filter = +<*.cpp> + + + + + ; ----------------- ESP32 --------------------- diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp new file mode 100644 index 00000000..20551190 --- /dev/null +++ b/src/helpers/bridges/BridgeBase.cpp @@ -0,0 +1,34 @@ +#include "BridgeBase.h" + +const char *BridgeBase::getLogDateTime() { + static char tmp[32]; + uint32_t now = _rtc->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), + dt.year()); + return tmp; +} + +uint16_t BridgeBase::fletcher16(const uint8_t *data, size_t len) { + uint8_t sum1 = 0, sum2 = 0; + + for (size_t i = 0; i < len; i++) { + sum1 = (sum1 + data[i]) % 255; + sum2 = (sum2 + sum1) % 255; + } + + return (sum2 << 8) | sum1; +} + +bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t received_checksum) { + uint16_t calculated_checksum = fletcher16(data, len); + return received_checksum == calculated_checksum; +} + +void BridgeBase::handleReceivedPacket(mesh::Packet *packet) { + if (!_seen_packets.hasSeen(packet)) { + _mgr->queueInbound(packet, 0); + } else { + _mgr->free(packet); + } +} diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h new file mode 100644 index 00000000..2aff2306 --- /dev/null +++ b/src/helpers/bridges/BridgeBase.h @@ -0,0 +1,84 @@ +#pragma once + +#include "helpers/AbstractBridge.h" +#include "helpers/SimpleMeshTables.h" + +#include + +/** + * @brief Base class implementing common bridge functionality + * + * This class provides common functionality used by different bridge implementations + * like packet tracking, checksum calculation, timestamping, and duplicate detection. + * + * Features: + * - Fletcher-16 checksum calculation for data integrity + * - Packet duplicate detection using SimpleMeshTables + * - Common timestamp formatting for debug logging + * - Shared packet management and queuing logic + */ +class BridgeBase : public AbstractBridge { +public: + virtual ~BridgeBase() = default; + +protected: + /** Packet manager for allocating and queuing mesh packets */ + mesh::PacketManager *_mgr; + + /** RTC clock for timestamping debug messages */ + mesh::RTCClock *_rtc; + + /** Tracks seen packets to prevent loops in broadcast communications */ + SimpleMeshTables _seen_packets; + + /** + * @brief Constructs a BridgeBase instance + * + * @param mgr PacketManager for allocating and queuing packets + * @param rtc RTCClock for timestamping debug messages + */ + BridgeBase(mesh::PacketManager *mgr, mesh::RTCClock *rtc) : _mgr(mgr), _rtc(rtc) {} + + /** + * @brief Gets formatted date/time string for logging + * + * Format: "HH:MM:SS - DD/MM/YYYY U" + * + * @return Formatted date/time string + */ + const char *getLogDateTime(); + + /** + * @brief Calculate Fletcher-16 checksum + * + * Based on: https://en.wikipedia.org/wiki/Fletcher%27s_checksum + * Used to verify data integrity of received packets + * + * @param data Pointer to data to calculate checksum for + * @param len Length of data in bytes + * @return Calculated Fletcher-16 checksum + */ + static uint16_t fletcher16(const uint8_t *data, size_t len); + + /** + * @brief Validate received checksum against calculated checksum + * + * @param data Pointer to data to validate + * @param len Length of data in bytes + * @param received_checksum Checksum received with data + * @return true if checksum is valid, false otherwise + */ + bool validateChecksum(const uint8_t *data, size_t len, uint16_t received_checksum); + + /** + * @brief Common packet handling for received packets + * + * Implements the standard pattern used by all bridges: + * - Check if packet was seen before using _seen_packets.hasSeen() + * - Queue packet for mesh processing if not seen before + * - Free packet if already seen to prevent duplicates + * + * @param packet The received mesh packet + */ + void handleReceivedPacket(mesh::Packet *packet); +}; diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index aed63a6b..baf683ca 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -1,6 +1,5 @@ #include "ESPNowBridge.h" -#include #include #include @@ -22,21 +21,8 @@ void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) { } } -// Fletcher16 checksum calculation -static uint16_t fletcher16(const uint8_t *data, size_t len) { - uint16_t sum1 = 0; - uint16_t sum2 = 0; - - while (len--) { - sum1 = (sum1 + *data++) % 255; - sum2 = (sum2 + sum1) % 255; - } - - return (sum2 << 8) | sum1; -} - ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc) - : _mgr(mgr), _rtc(rtc), _rx_buffer_pos(0) { + : BridgeBase(mgr, rtc), _rx_buffer_pos(0) { _instance = this; } @@ -115,13 +101,12 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l // Validate checksum uint16_t received_checksum = (decrypted[0] << 8) | decrypted[1]; const size_t payloadLen = encryptedDataLen - CHECKSUM_SIZE; - uint16_t calculated_checksum = fletcher16(decrypted + CHECKSUM_SIZE, payloadLen); - if (received_checksum != calculated_checksum) { + if (!validateChecksum(decrypted + CHECKSUM_SIZE, payloadLen, received_checksum)) { // Failed to decrypt - likely from a different network #if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X calc=0x%04X\n", getLogDateTime(), - received_checksum, calculated_checksum); + Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X\n", getLogDateTime(), + received_checksum); #endif return; } @@ -146,23 +131,19 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta } void ESPNowBridge::onPacketReceived(mesh::Packet *packet) { - if (!_seen_packets.hasSeen(packet)) { - _mgr->queueInbound(packet, 0); - } else { - _mgr->free(packet); - } + handleReceivedPacket(packet); } void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { - if (!_seen_packets.hasSeen(packet)) { - - // First validate the packet pointer - if (!packet) { + // First validate the packet pointer + if (!packet) { #if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: TX invalid packet pointer\n", getLogDateTime()); + Serial.printf("%s: ESPNOW BRIDGE: TX invalid packet pointer\n", getLogDateTime()); #endif - return; - } + return; + } + + if (!_seen_packets.hasSeen(packet)) { // Create a temporary buffer just for size calculation and reuse for actual writing uint8_t sizingBuffer[MAX_PAYLOAD_SIZE]; @@ -212,13 +193,4 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { } } -const char *ESPNowBridge::getLogDateTime() { - static char tmp[32]; - uint32_t now = _rtc->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), - dt.year()); - return tmp; -} - #endif diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index 5c771471..d9962c72 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/src/helpers/bridges/ESPNowBridge.h @@ -2,8 +2,7 @@ #include "MeshCore.h" #include "esp_now.h" -#include "helpers/AbstractBridge.h" -#include "helpers/SimpleMeshTables.h" +#include "helpers/bridges/BridgeBase.h" #ifdef WITH_ESPNOW_BRIDGE @@ -44,21 +43,12 @@ * WITH_ESPNOW_BRIDGE_SECRET values. Packets encrypted with a different key will * fail the checksum validation and be discarded. */ -class ESPNowBridge : public AbstractBridge { +class ESPNowBridge : public BridgeBase { private: static ESPNowBridge *_instance; static void recv_cb(const uint8_t *mac, const uint8_t *data, int32_t len); static void send_cb(const uint8_t *mac, esp_now_send_status_t status); - /** Packet manager for allocating and queuing mesh packets */ - mesh::PacketManager *_mgr; - - /** RTC clock for timestamping debug messages */ - mesh::RTCClock *_rtc; - - /** Tracks seen packets to prevent loops in broadcast communications */ - SimpleMeshTables _seen_packets; - /** * ESP-NOW Protocol Structure: * - ESP-NOW header: 20 bytes (handled by ESP-NOW protocol) @@ -168,14 +158,6 @@ public: * @param packet The mesh packet to transmit */ void onPacketTransmitted(mesh::Packet *packet) override; - - /** - * Gets formatted date/time string for logging - * Format: "HH:MM:SS - DD/MM/YYYY U" - * - * @return Formatted date/time string - */ - const char *getLogDateTime(); }; #endif diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 5c3b8caa..39a0968a 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -1,32 +1,11 @@ #include "RS232Bridge.h" + #include -#include #ifdef WITH_RS232_BRIDGE -// Static Fletcher-16 checksum calculation -// Based on: https://en.wikipedia.org/wiki/Fletcher%27s_checksum -// Used to verify data integrity of received packets -inline static uint16_t fletcher16(const uint8_t *bytes, const size_t len) { - uint8_t sum1 = 0, sum2 = 0; - - for (size_t i = 0; i < len; i++) { - sum1 = (sum1 + bytes[i]) % 255; - sum2 = (sum2 + sum1) % 255; - } - - return (sum2 << 8) | sum1; -}; - -const char* RS232Bridge::getLogDateTime() { - static char tmp[32]; - uint32_t now = _rtc->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), dt.year()); - return tmp; -} - -RS232Bridge::RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc) : _serial(&serial), _mgr(mgr), _rtc(rtc) {} +RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc) + : BridgeBase(mgr, rtc), _serial(&serial) {} void RS232Bridge::begin() { #if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX) @@ -46,27 +25,48 @@ void RS232Bridge::begin() { #else #error RS232Bridge was not tested on the current platform #endif - ((HardwareSerial*)_serial)->begin(115200); + ((HardwareSerial *)_serial)->begin(115200); } -void RS232Bridge::onPacketTransmitted(mesh::Packet* packet) { +void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) { + // First validate the packet pointer + if (!packet) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime()); +#endif + return; + } + if (!_seen_packets.hasSeen(packet)) { + uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; uint16_t len = packet->writeTo(buffer + 4); - buffer[0] = (SERIAL_PKT_MAGIC >> 8) & 0xFF; - buffer[1] = SERIAL_PKT_MAGIC & 0xFF; - buffer[2] = (len >> 8) & 0xFF; - buffer[3] = len & 0xFF; + // Check if packet fits within our maximum payload size + if (len > (MAX_TRANS_UNIT + 1)) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len, + MAX_TRANS_UNIT + 1); +#endif + return; + } + // Build packet header + buffer[0] = (SERIAL_PKT_MAGIC >> 8) & 0xFF; // Magic high byte + buffer[1] = SERIAL_PKT_MAGIC & 0xFF; // Magic low byte + buffer[2] = (len >> 8) & 0xFF; // Length high byte + buffer[3] = len & 0xFF; // Length low byte + + // Calculate checksum over the payload uint16_t checksum = fletcher16(buffer + 4, len); - buffer[4 + len] = (checksum >> 8) & 0xFF; - buffer[5 + len] = checksum & 0xFF; + buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte + buffer[5 + len] = checksum & 0xFF; // Checksum low byte + // Send complete packet _serial->write(buffer, len + SERIAL_OVERHEAD); #if MESH_PACKET_LOGGING - Serial.printf("%s: BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); + Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); #endif } } @@ -81,7 +81,12 @@ void RS232Bridge::loop() { (_rx_buffer_pos == 1 && b == (SERIAL_PKT_MAGIC & 0xFF))) { _rx_buffer[_rx_buffer_pos++] = b; } else { + // Invalid magic byte, reset and start over _rx_buffer_pos = 0; + // Check if this byte could be the start of a new magic word + if (b == ((SERIAL_PKT_MAGIC >> 8) & 0xFF)) { + _rx_buffer[_rx_buffer_pos++] = b; + } } } else { // Reading length, payload, and checksum @@ -89,22 +94,44 @@ void RS232Bridge::loop() { if (_rx_buffer_pos >= 4) { uint16_t len = (_rx_buffer[2] << 8) | _rx_buffer[3]; + + // Validate length field if (len > (MAX_TRANS_UNIT + 1)) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: RX invalid length %d, resetting\n", getLogDateTime(), len); +#endif _rx_buffer_pos = 0; // Invalid length, reset - return; + continue; } if (_rx_buffer_pos == len + SERIAL_OVERHEAD) { // Full packet received - uint16_t checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; - if (checksum == fletcher16(_rx_buffer + 4, len)) { + uint16_t received_checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; + + if (validateChecksum(_rx_buffer + 4, len, received_checksum)) { #if MESH_PACKET_LOGGING - Serial.printf("%s: BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); + Serial.printf("%s: RS232 BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, + received_checksum); #endif - mesh::Packet* pkt = _mgr->allocNew(); + mesh::Packet *pkt = _mgr->allocNew(); if (pkt) { - pkt->readFrom(_rx_buffer + 4, len); - onPacketReceived(pkt); + if (pkt->readFrom(_rx_buffer + 4, len)) { + onPacketReceived(pkt); + } else { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: RX failed to parse packet\n", getLogDateTime()); +#endif + _mgr->free(pkt); + } + } else { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: RX failed to allocate packet\n", getLogDateTime()); +#endif } + } else { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: RX checksum mismatch, rcv=0x%04x\n", getLogDateTime(), + received_checksum); +#endif } _rx_buffer_pos = 0; // Reset for next packet } @@ -113,12 +140,8 @@ void RS232Bridge::loop() { } } -void RS232Bridge::onPacketReceived(mesh::Packet* packet) { - if (!_seen_packets.hasSeen(packet)) { - _mgr->queueInbound(packet, 0); - } else { - _mgr->free(packet); - } +void RS232Bridge::onPacketReceived(mesh::Packet *packet) { + handleReceivedPacket(packet); } #endif diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 49c781cb..32fad17f 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -1,7 +1,7 @@ #pragma once -#include "helpers/AbstractBridge.h" -#include "helpers/SimpleMeshTables.h" +#include "helpers/bridges/BridgeBase.h" + #include #ifdef WITH_RS232_BRIDGE @@ -15,21 +15,22 @@ * * Features: * - Point-to-point communication over hardware UART - * - Fletcher-16 checksum for data integrity verification - * - Magic header for packet synchronization + * - Fletcher-16 checksum for data integrity verification + * - Magic header for packet synchronization and frame alignment + * - Duplicate packet detection using SimpleMeshTables tracking * - Configurable RX/TX pins via build defines - * - Baud rate fixed at 115200 + * - Fixed baud rate at 115200 for consistent timing * * Packet Structure: - * [2 bytes] Magic Header - Used to identify start of packet - * [2 bytes] Fletcher-16 checksum - Data integrity check - * [1 byte] Payload length - * [n bytes] Packet payload + * [2 bytes] Magic Header (0xC03E) - Used to identify start of RS232Bridge packets + * [2 bytes] Payload Length - Length of the mesh packet payload + * [n bytes] Mesh Packet Payload - The actual mesh packet data + * [2 bytes] Fletcher-16 Checksum - Calculated over the payload for integrity verification * - * The Fletcher-16 checksum is used to validate packet integrity and detect - * corrupted or malformed packets. It provides error detection capabilities - * suitable for serial communication where noise or timing issues could - * corrupt data. + * The Fletcher-16 checksum is calculated over the mesh packet payload and provides + * error detection capabilities suitable for serial communication where electrical + * noise, timing issues, or hardware problems could corrupt data. The checksum + * validation ensures only valid packets are forwarded to the mesh. * * Configuration: * - Define WITH_RS232_BRIDGE to enable this bridge @@ -37,12 +38,13 @@ * - Define WITH_RS232_BRIDGE_TX with the TX pin number * * Platform Support: - * - ESP32 targets - * - NRF52 targets - * - RP2040 targets - * - STM32 targets + * Different platforms require different pin configuration methods: + * - ESP32: Uses HardwareSerial::setPins(rx, tx) + * - NRF52: Uses HardwareSerial::setPins(rx, tx) + * - RP2040: Uses SerialUART::setRX(rx) and SerialUART::setTX(tx) + * - STM32: Uses HardwareSerial::setRx(rx) and HardwareSerial::setTx(tx) */ -class RS232Bridge : public AbstractBridge { +class RS232Bridge : public BridgeBase { public: /** * @brief Constructs an RS232Bridge instance @@ -51,69 +53,98 @@ public: * @param mgr PacketManager for allocating and queuing packets * @param rtc RTCClock for timestamping debug messages */ - RS232Bridge(Stream& serial, mesh::PacketManager* mgr, mesh::RTCClock* rtc); + RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc); /** * Initializes the RS232 bridge - * - * - Configures UART pins based on platform - * - Sets baud rate to 115200 + * + * - Validates that RX/TX pins are defined + * - Configures UART pins based on target platform + * - Sets baud rate to 115200 for consistent communication + * - Platform-specific pin configuration methods are used */ void begin() override; /** - * @brief Main loop handler - * Processes incoming serial data and builds packets + * @brief Main loop handler for processing incoming serial data + * + * Implements a state machine for packet reception: + * 1. Searches for magic header bytes for packet synchronization + * 2. Reads length field to determine expected packet size + * 3. Validates packet length against maximum allowed size + * 4. Receives complete packet payload and checksum + * 5. Validates Fletcher-16 checksum for data integrity + * 6. Creates mesh packet and forwards if valid */ void loop() override; /** * @brief Called when a packet needs to be transmitted over serial - * Formats and sends the packet with proper framing + * + * Formats the mesh packet with RS232 framing protocol: + * - Adds magic header for synchronization + * - Includes payload length field + * - Calculates Fletcher-16 checksum over payload + * - Transmits complete framed packet + * - Uses duplicate detection to prevent retransmission * * @param packet The mesh packet to transmit */ - void onPacketTransmitted(mesh::Packet* packet) override; + void onPacketTransmitted(mesh::Packet *packet) override; /** - * @brief Called when a complete packet has been received from serial - * Queues the packet for mesh processing if checksum is valid - * - * @param packet The received mesh packet + * @brief Called when a complete valid packet has been received from serial + * + * Forwards the received packet to the mesh for processing. + * The packet has already been validated for checksum integrity + * and parsed successfully at this point. + * + * @param packet The received mesh packet ready for processing */ - void onPacketReceived(mesh::Packet* packet) override; + void onPacketReceived(mesh::Packet *packet) override; private: - /** Helper function to get formatted timestamp for logging */ - const char* getLogDateTime(); + /** + * RS232 Protocol Structure: + * - Magic header: 2 bytes (packet identification) + * - Length field: 2 bytes (payload length) + * - Payload: variable bytes (mesh packet data) + * - Checksum: 2 bytes (Fletcher-16 over payload) + * Total overhead: 6 bytes + */ - /** Magic number to identify start of RS232 packets */ + /** Magic number to identify start of RS232Bridge packets */ static constexpr uint16_t SERIAL_PKT_MAGIC = 0xC03E; /** - * @brief The total overhead of the serial protocol in bytes. - * [MAGIC_WORD (2 bytes)] [LENGTH (2 bytes)] [PAYLOAD (variable)] [CHECKSUM (2 bytes)] + * Size constants for packet parsing and construction */ - static constexpr uint16_t SERIAL_OVERHEAD = 6; + static constexpr uint16_t MAGIC_HEADER_SIZE = 2; + static constexpr uint16_t LENGTH_FIELD_SIZE = 2; + static constexpr uint16_t CHECKSUM_SIZE = 2; /** - * @brief The maximum size of a packet on the serial line. + * @brief The total overhead of the serial protocol in bytes. + * Includes: MAGIC_WORD (2) + LENGTH (2) + CHECKSUM (2) = 6 bytes + */ + static constexpr uint16_t SERIAL_OVERHEAD = MAGIC_HEADER_SIZE + LENGTH_FIELD_SIZE + CHECKSUM_SIZE; + + /** + * @brief The maximum size of a complete packet on the serial line. * * This is calculated as the sum of: - * - 1 byte for the packet header (from mesh::Packet) - * - 4 bytes for transport codes (from mesh::Packet) - * - 1 byte for the path length (from mesh::Packet) - * - MAX_PATH_SIZE for the path itself (from MeshCore.h) - * - MAX_PACKET_PAYLOAD for the payload (from MeshCore.h) - * - SERIAL_OVERHEAD for the serial framing + * - MAX_TRANS_UNIT + 1 for the maximum mesh packet size + * - SERIAL_OVERHEAD for the framing (magic + length + checksum) */ static constexpr uint16_t MAX_SERIAL_PACKET_SIZE = (MAX_TRANS_UNIT + 1) + SERIAL_OVERHEAD; - Stream* _serial; - mesh::PacketManager* _mgr; - mesh::RTCClock* _rtc; - SimpleMeshTables _seen_packets; - uint8_t _rx_buffer[MAX_SERIAL_PACKET_SIZE]; // Buffer for serial data + /** Hardware serial port interface */ + Stream *_serial; + + /** Buffer for building received packets */ + uint8_t _rx_buffer[MAX_SERIAL_PACKET_SIZE]; + + /** Current position in the receive buffer */ uint16_t _rx_buffer_pos = 0; }; From 7b08acf56d820d9811bab51cfa5597f99c3a1774 Mon Sep 17 00:00:00 2001 From: kelsey hudson Date: Sun, 7 Sep 2025 21:29:10 -0700 Subject: [PATCH 033/546] Ikoka Stick: Move to unified code naming conventions --- ...stick_nrf_board.cpp => IkokaStickNRFBoard.cpp} | 15 +++++---------- ...oka_stick_nrf_board.h => IkokaStickNRFBoard.h} | 6 +++--- variants/ikoka_stick_nrf/target.cpp | 2 +- variants/ikoka_stick_nrf/target.h | 4 ++-- 4 files changed, 11 insertions(+), 16 deletions(-) rename variants/ikoka_stick_nrf/{ikoka_stick_nrf_board.cpp => IkokaStickNRFBoard.cpp} (91%) rename variants/ikoka_stick_nrf/{ikoka_stick_nrf_board.h => IkokaStickNRFBoard.h} (89%) diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp similarity index 91% rename from variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp rename to variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 8634cda1..6b660383 100644 --- a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -1,28 +1,26 @@ #ifdef XIAO_NRF52 #include -#include "ikoka_stick_nrf_board.h" +#include "IkokaStickNRFBoard.h" #include #include static BLEDfu bledfu; -static void connect_callback(uint16_t conn_handle) -{ +static void connect_callback(uint16_t conn_handle) { (void)conn_handle; MESH_DEBUG_PRINTLN("BLE client connected"); } -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) -{ +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { (void)conn_handle; (void)reason; MESH_DEBUG_PRINTLN("BLE client disconnected"); } -void ikoka_stick_nrf_board::begin() { +void IkokaStickNRFBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; @@ -50,7 +48,7 @@ void ikoka_stick_nrf_board::begin() { delay(10); // give sx1262 some time to power up } -bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) { +bool IkokaStickNRFBoard::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice // Note: All config***() function must be called before begin() @@ -91,9 +89,6 @@ bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) { strcpy(reply, "OK - started"); return true; - - - return false; } #endif diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h similarity index 89% rename from variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h rename to variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 08061c23..4a061d42 100644 --- a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -5,7 +5,7 @@ #ifdef XIAO_NRF52 -class ikoka_stick_nrf_board : public mesh::MainBoard { +class IkokaStickNRFBoard : public mesh::MainBoard { protected: uint8_t startup_reason; @@ -46,7 +46,7 @@ public: return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; } - const char* getManufacturerName() const override { + const char *getManufacturerName() const override { return MANUFACTURER_STRING; } @@ -54,7 +54,7 @@ public: NVIC_SystemReset(); } - bool startOTAUpdate(const char* id, char reply[]) override; + bool startOTAUpdate(const char *id, char reply[]) override; }; #endif diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index c2712761..bd803399 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -2,7 +2,7 @@ #include "target.h" #include -ikoka_stick_nrf_board board; +IkokaStickNRFBoard board; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h index 8311503a..c276e89f 100644 --- a/variants/ikoka_stick_nrf/target.h +++ b/variants/ikoka_stick_nrf/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include #include -#include +#include #include #include #include @@ -16,7 +16,7 @@ extern MomentaryButton user_btn; #endif -extern ikoka_stick_nrf_board board; +extern IkokaStickNRFBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern EnvironmentSensorManager sensors; From 74dea260e54b429762c191cef351ffb146ab91b7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 8 Sep 2025 19:22:59 +1000 Subject: [PATCH 034/546] * proposed change for re-trying reciprocal path transmit --- examples/companion_radio/MyMesh.cpp | 5 +++-- examples/companion_radio/MyMesh.h | 3 ++- examples/simple_secure_chat/main.cpp | 6 +++--- src/helpers/BaseChatMesh.cpp | 29 +++++++++++++++++++++++----- src/helpers/BaseChatMesh.h | 5 +++-- 5 files changed, 35 insertions(+), 13 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7847d652..04d5577e 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -294,7 +294,7 @@ void MyMesh::onContactPathUpdated(const ContactInfo &contact) { dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } -bool MyMesh::processAck(const uint8_t *data) { +ContactInfo* MyMesh::processAck(const uint8_t *data) { // see if matches any in a table for (int i = 0; i < EXPECTED_ACK_TABLE_SIZE; i++) { if (memcmp(data, &expected_ack_table[i].ack, 4) == 0) { // got an ACK from recipient @@ -306,7 +306,7 @@ bool MyMesh::processAck(const uint8_t *data) { // NOTE: the same ACK can be received multiple times! expected_ack_table[i].ack = 0; // clear expected hash, now that we have received ACK - return true; + return expected_ack_table[i].contact; } } return checkConnectionsAck(data); @@ -825,6 +825,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (expected_ack) { expected_ack_table[next_ack_idx].msg_sent = _ms->getMillis(); // add to circular table expected_ack_table[next_ack_idx].ack = expected_ack; + expected_ack_table[next_ack_idx].contact = recipient; next_ack_idx = (next_ack_idx + 1) % EXPECTED_ACK_TABLE_SIZE; } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e3235128..0dc9ad61 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -112,7 +112,7 @@ protected: bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override; void onContactPathUpdated(const ContactInfo &contact) override; - bool processAck(const uint8_t *data) override; + ContactInfo* processAck(const uint8_t *data) override; void queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packet *pkt, uint32_t sender_timestamp, const uint8_t *extra, int extra_len, const char *text); @@ -205,6 +205,7 @@ private: struct AckTableEntry { unsigned long msg_sent; uint32_t ack; + ContactInfo* contact; }; #define EXPECTED_ACK_TABLE_SIZE 8 AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index a6b048a1..eac35898 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -217,18 +217,18 @@ protected: saveContacts(); } - bool processAck(const uint8_t *data) override { + ContactInfo* processAck(const uint8_t *data) override { if (memcmp(data, &expected_ack_crc, 4) == 0) { // got an ACK from recipient Serial.printf(" Got ACK! (round trip: %d millis)\n", _ms->getMillis() - last_msg_sent); // NOTE: the same ACK can be received multiple times! expected_ack_crc = 0; // reset our expected hash, now that we have received ACK - return true; + return NULL; // TODO: really should return ContactInfo pointer } //uint32_t crc; //memcpy(&crc, data, 4); //MESH_DEBUG_PRINTLN("unknown ACK received: %08X (expected: %08X)", crc, expected_ack_crc); - return false; + return NULL; } void onMessageRecv(const ContactInfo& from, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) override { diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 60366c65..eb199ef5 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -223,6 +223,10 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } } else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) { onContactResponse(from, data, len); + if (packet->isRouteFlood() && from.out_path_len >= 0) { + // we have direct path, but other node is still sending flood response, so maybe they didn't receive reciprocal path properly(?) + handleReturnPathRetry(from, packet->path, packet->path_len); + } } } @@ -248,7 +252,7 @@ bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_ if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) { // also got an encoded ACK! - if (processAck(extra)) { + if (processAck(extra) != NULL) { txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer } } else if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 0) { @@ -258,12 +262,25 @@ bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_ } void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { - if (processAck((uint8_t *)&ack_crc)) { + ContactInfo* from; + if ((from = processAck((uint8_t *)&ack_crc)) != NULL) { txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit + + if (packet->isRouteFlood() && from->out_path_len >= 0) { + // we have direct path, but other node is still sending flood, so maybe they didn't receive reciprocal path properly(?) + handleReturnPathRetry(*from, packet->path, packet->path_len); + } } } +void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { + // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) + // override this method in various firmwares, if there's a better strategy + mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); + if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay +} + #ifdef MAX_GROUP_CHANNELS int BaseChatMesh::searchChannelsByHash(const uint8_t* hash, mesh::GroupChannel dest[], int max_matches) { int n = 0; @@ -550,7 +567,7 @@ void BaseChatMesh::markConnectionActive(const ContactInfo& contact) { } } -bool BaseChatMesh::checkConnectionsAck(const uint8_t* data) { +ContactInfo* BaseChatMesh::checkConnectionsAck(const uint8_t* data) { for (int i = 0; i < MAX_CONNECTIONS; i++) { if (connections[i].keep_alive_millis > 0 && memcmp(&connections[i].expected_ack, data, 4) == 0) { // yes, got an ack for our keep_alive request! @@ -559,10 +576,12 @@ bool BaseChatMesh::checkConnectionsAck(const uint8_t* data) { // re-schedule next KEEP_ALIVE, now that we have heard from server connections[i].next_ping = futureMillis(connections[i].keep_alive_millis); - return true; // yes, a match + + auto id = &connections[i].server_id; + return lookupContactByPubKey(id->pub_key, PUB_KEY_SIZE); // yes, a match } } - return false; /// no match + return NULL; /// no match } void BaseChatMesh::checkConnections() { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 9a4aa810..9392001e 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -93,7 +93,7 @@ protected: // 'UI' concepts, for sub-classes to implement virtual bool isAutoAddEnabled() const { return true; } virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; - virtual bool processAck(const uint8_t *data) = 0; + virtual ContactInfo* processAck(const uint8_t *data) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0; virtual bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len); virtual void onMessageRecv(const ContactInfo& contact, mesh::Packet* pkt, uint32_t sender_timestamp, const char *text) = 0; @@ -105,6 +105,7 @@ protected: virtual void onChannelMessageRecv(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t timestamp, const char *text) = 0; virtual uint8_t onContactRequest(const ContactInfo& contact, uint32_t sender_timestamp, const uint8_t* data, uint8_t len, uint8_t* reply) = 0; virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; + virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len); // storage concepts, for sub-classes to override/implement virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented @@ -127,7 +128,7 @@ protected: void stopConnection(const uint8_t* pub_key); bool hasConnectionTo(const uint8_t* pub_key); void markConnectionActive(const ContactInfo& contact); - bool checkConnectionsAck(const uint8_t* data); + ContactInfo* checkConnectionsAck(const uint8_t* data); void checkConnections(); public: From f2e8fb0259f2100571f49ebdf0ecc6f50333c0be Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Mon, 8 Sep 2025 21:46:19 +1000 Subject: [PATCH 035/546] * refactor: MyMesh class extracted --- examples/simple_repeater/MyMesh.cpp | 684 ++++++++++++++++++++++++ examples/simple_repeater/MyMesh.h | 173 ++++++ examples/simple_repeater/main.cpp | 795 +--------------------------- 3 files changed, 861 insertions(+), 791 deletions(-) create mode 100644 examples/simple_repeater/MyMesh.cpp create mode 100644 examples/simple_repeater/MyMesh.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp new file mode 100644 index 00000000..6e789f95 --- /dev/null +++ b/examples/simple_repeater/MyMesh.cpp @@ -0,0 +1,684 @@ +#include "MyMesh.h" + +/* ------------------------------ Config -------------------------------- */ + +#ifndef LORA_FREQ + #define LORA_FREQ 915.0 +#endif +#ifndef LORA_BW + #define LORA_BW 250 +#endif +#ifndef LORA_SF + #define LORA_SF 10 +#endif +#ifndef LORA_CR + #define LORA_CR 5 +#endif +#ifndef LORA_TX_POWER + #define LORA_TX_POWER 20 +#endif + +#ifndef ADVERT_NAME + #define ADVERT_NAME "repeater" +#endif +#ifndef ADVERT_LAT + #define ADVERT_LAT 0.0 +#endif +#ifndef ADVERT_LON + #define ADVERT_LON 0.0 +#endif + +#ifndef ADMIN_PASSWORD + #define ADMIN_PASSWORD "password" +#endif + +#ifndef SERVER_RESPONSE_DELAY + #define SERVER_RESPONSE_DELAY 300 +#endif + +#ifndef TXT_ACK_DELAY + #define TXT_ACK_DELAY 200 +#endif + +#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS +#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 + +#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ + +#define CLI_REPLY_DELAY_MILLIS 600 + + +ClientInfo *MyMesh::putClient(const mesh::Identity &id) { + uint32_t min_time = 0xFFFFFFFF; + ClientInfo *oldest = &known_clients[0]; + for (int i = 0; i < MAX_CLIENTS; i++) { + if (known_clients[i].last_activity < min_time) { + oldest = &known_clients[i]; + min_time = oldest->last_activity; + } + if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known + } + + oldest->id = id; + oldest->out_path_len = -1; // initially out_path is unknown + oldest->last_timestamp = 0; + return oldest; +} + +void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) { +#if MAX_NEIGHBOURS // check if neighbours enabled + // find existing neighbour, else use least recently updated + uint32_t oldest_timestamp = 0xFFFFFFFF; + NeighbourInfo *neighbour = &neighbours[0]; + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + // if neighbour already known, we should update it + if (id.matches(neighbours[i].id)) { + neighbour = &neighbours[i]; + break; + } + + // otherwise we should update the least recently updated neighbour + if (neighbours[i].heard_timestamp < oldest_timestamp) { + neighbour = &neighbours[i]; + oldest_timestamp = neighbour->heard_timestamp; + } + } + + // update neighbour info + neighbour->id = id; + neighbour->advert_timestamp = timestamp; + neighbour->heard_timestamp = getRTCClock()->getCurrentTime(); + neighbour->snr = (int8_t)(snr * 4); +#endif +} + +int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, + size_t payload_len) { + // uint32_t now = getRTCClock()->getCurrentTimeUnique(); + // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp + memcpy(reply_data, &sender_timestamp, + 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') + + switch (payload[0]) { + case REQ_TYPE_GET_STATUS: { // guests can also access this now + RepeaterStats stats; + stats.batt_milli_volts = board.getBattMilliVolts(); + stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); + stats.noise_floor = (int16_t)_radio->getNoiseFloor(); + stats.last_rssi = (int16_t)radio_driver.getLastRSSI(); + stats.n_packets_recv = radio_driver.getPacketsRecv(); + stats.n_packets_sent = radio_driver.getPacketsSent(); + stats.total_air_time_secs = getTotalAirTime() / 1000; + stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.n_sent_flood = getNumSentFlood(); + stats.n_sent_direct = getNumSentDirect(); + stats.n_recv_flood = getNumRecvFlood(); + stats.n_recv_direct = getNumRecvDirect(); + stats.err_events = _err_flags; + stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); + stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); + stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; + + memcpy(&reply_data[4], &stats, sizeof(stats)); + + return 4 + sizeof(stats); // reply_len + } + case REQ_TYPE_GET_TELEMETRY_DATA: { + uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + + telemetry.reset(); + telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + // query other sensors -- target specific + sensors.querySensors((sender->is_admin ? 0xFF : 0x00) & perm_mask, telemetry); + + uint8_t tlen = telemetry.getSize(); + memcpy(&reply_data[4], telemetry.getBuffer(), tlen); + return 4 + tlen; // reply_len + } + } + return 0; // unknown command +} + +mesh::Packet *MyMesh::createSelfAdvert() { + uint8_t app_data[MAX_ADVERT_DATA_SIZE]; + uint8_t app_data_len; + { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } + + return createAdvert(self_id, app_data, app_data_len); +} + +File MyMesh::openAppend(const char *fname) { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + return _fs->open(fname, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + return _fs->open(fname, "a"); +#else + return _fs->open(fname, "a", true); +#endif +} + +bool MyMesh::allowPacketForward(const mesh::Packet *packet) { + if (_prefs.disable_fwd) return false; + if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + return true; +} + +const char *MyMesh::getLogDateTime() { + static char tmp[32]; + uint32_t now = getRTCClock()->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), + dt.year()); + return tmp; +} + +void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { +#if MESH_PACKET_LOGGING + Serial.print(getLogDateTime()); + Serial.print(" RAW: "); + mesh::Utils::printHex(Serial, raw, len); + Serial.println(); +#endif +} + +void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { + if (_logging) { + File f = openAppend(PACKET_LOG_FILE); + if (f) { + f.print(getLogDateTime()); + f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", len, + pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score * 1000)); + + if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || + pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { + f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + } else { + f.printf("\n"); + } + f.close(); + } + } +} + +void MyMesh::logTx(mesh::Packet *pkt, int len) { + if (_logging) { + File f = openAppend(PACKET_LOG_FILE); + if (f) { + f.print(getLogDateTime()); + f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", len, pkt->getPayloadType(), + pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + + if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || + pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { + f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + } else { + f.printf("\n"); + } + f.close(); + } + } +} + +void MyMesh::logTxFail(mesh::Packet *pkt, int len) { + if (_logging) { + File f = openAppend(PACKET_LOG_FILE); + if (f) { + f.print(getLogDateTime()); + f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", len, pkt->getPayloadType(), + pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + f.close(); + } + } +} + +int MyMesh::calcRxDelay(float score, uint32_t air_time) const { + if (_prefs.rx_delay_base <= 0.0f) return 0; + return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); +} + +uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + return getRNG()->nextInt(0, 6) * t; +} +uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + return getRNG()->nextInt(0, 6) * t; +} + +void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, + uint8_t *data, size_t len) { + if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin + // client (unknown at this stage) + uint32_t timestamp; + memcpy(×tamp, data, 4); + + bool is_admin; + data[len] = 0; // ensure null terminator + if (strcmp((char *)&data[4], _prefs.password) == 0) { // check for valid password + is_admin = true; + } else if (strcmp((char *)&data[4], _prefs.guest_password) == 0) { // check guest password + is_admin = false; + } else { +#if MESH_DEBUG + MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); +#endif + return; + } + + auto client = putClient(sender); // add to known clients (if not already known) + if (timestamp <= client->last_timestamp) { + MESH_DEBUG_PRINTLN("Possible login replay attack!"); + return; // FATAL: client table is full -OR- replay attack + } + + MESH_DEBUG_PRINTLN("Login success!"); + client->last_timestamp = timestamp; + client->last_activity = getRTCClock()->getCurrentTime(); + client->is_admin = is_admin; + memcpy(client->secret, secret, PUB_KEY_SIZE); + + uint32_t now = getRTCClock()->getCurrentTimeUnique(); + memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp +#if 0 + memcpy(&reply_data[4], "OK", 2); // legacy response +#else + reply_data[4] = RESP_SERVER_LOGIN_OK; + reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) + reply_data[6] = is_admin ? 1 : 0; + reply_data[7] = 0; // FUTURE: reserved + getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness +#endif + + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response + mesh::Packet *path = createPathReturn(sender, client->secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, 12); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 12); + if (reply) { + if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + } else { + sendFlood(reply, SERVER_RESPONSE_DELAY); + } + } + } + } +} + +int MyMesh::searchPeersByHash(const uint8_t *hash) { + int n = 0; + for (int i = 0; i < MAX_CLIENTS; i++) { + if (known_clients[i].id.isHashMatch(hash)) { + matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) + } + } + return n; +} + +void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { + int i = matching_peer_indexes[peer_idx]; + if (i >= 0 && i < MAX_CLIENTS) { + // lookup pre-calculated shared_secret + memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE); + } else { + MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); + } +} + +void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp, + const uint8_t *app_data, size_t app_data_len) { + mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl + + // if this a zero hop advert, add it to neighbours + if (packet->path_len == 0) { + AdvertDataParser parser(app_data, app_data_len); + if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters + putNeighbour(id, timestamp, packet->getSNR()); + } + } +} + +void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, + uint8_t *data, size_t len) { + int i = matching_peer_indexes[sender_idx]; + if (i < 0 || + i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) + MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); + return; + } + auto client = &known_clients[i]; + if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!) + uint32_t timestamp; + memcpy(×tamp, data, 4); + + if (timestamp > client->last_timestamp) { // prevent replay attacks + int reply_len = handleRequest(client, timestamp, &data[4], len - 4); + if (reply_len == 0) return; // invalid command + + client->last_timestamp = timestamp; + client->last_activity = getRTCClock()->getCurrentTime(); + + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response + mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet *reply = + createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + if (reply) { + if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + } else { + sendFlood(reply, SERVER_RESPONSE_DELAY); + } + } + } + } else { + MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); + } + } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->is_admin) { // a CLI command + uint32_t sender_timestamp; + memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) + uint flags = (data[4] >> 2); // message attempt number, and other flags + + if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { + MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); + } else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks + bool is_retry = (sender_timestamp == client->last_timestamp); + client->last_timestamp = sender_timestamp; + client->last_activity = getRTCClock()->getCurrentTime(); + + // len can be > original length, but 'text' will be padded with zeroes + data[len] = 0; // need to make a C string again, with null terminator + + if (flags == TXT_TYPE_PLAIN) { // for legacy CLI, send Acks + uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove + // to sender that we got it + mesh::Utils::sha256((uint8_t *)&ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, + PUB_KEY_SIZE); + + mesh::Packet *ack = createAck(ack_hash); + if (ack) { + if (client->out_path_len < 0) { + sendFlood(ack, TXT_ACK_DELAY); + } else { + sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); + } + } + } + + uint8_t temp[166]; + char *command = (char *)&data[5]; + char *reply = (char *)&temp[5]; + if (is_retry) { + *reply = 0; + } else { + handleCommand(sender_timestamp, command, reply); + } + int text_len = strlen(reply); + if (text_len > 0) { + uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); + if (timestamp == sender_timestamp) { + // WORKAROUND: the two timestamps need to be different, in the CLI view + timestamp++; + } + memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique + temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN + + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + if (reply) { + if (client->out_path_len < 0) { + sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + } else { + sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS); + } + } + } + } else { + MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); + } + } +} + +bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t *secret, uint8_t *path, + uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len) { + // TODO: prevent replay attacks + int i = matching_peer_indexes[sender_idx]; + + if (i >= 0 && + i < MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) + MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); + auto client = &known_clients[i]; + memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + } else { + MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); + } + + // NOTE: no reciprocal path send!! + return false; +} + +MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, + mesh::RTCClock &rtc, mesh::MeshTables &tables) + : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), + _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + memset(known_clients, 0, sizeof(known_clients)); + next_local_advert = next_flood_advert = 0; + set_radio_at = revert_radio_at = 0; + _logging = false; + +#if MAX_NEIGHBOURS + memset(neighbours, 0, sizeof(neighbours)); +#endif + + // defaults + memset(&_prefs, 0, sizeof(_prefs)); + _prefs.airtime_factor = 1.0; // one half + _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; + _prefs.tx_delay_factor = 0.5f; // was 0.25f + StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); + _prefs.node_lat = ADVERT_LAT; + _prefs.node_lon = ADVERT_LON; + StrHelper::strncpy(_prefs.password, ADMIN_PASSWORD, sizeof(_prefs.password)); + _prefs.freq = LORA_FREQ; + _prefs.sf = LORA_SF; + _prefs.bw = LORA_BW; + _prefs.cr = LORA_CR; + _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.advert_interval = 1; // default to 2 minutes for NEW installs + _prefs.flood_advert_interval = 12; // 12 hours + _prefs.flood_max = 64; + _prefs.interference_threshold = 0; // disabled +} + +void MyMesh::begin(FILESYSTEM *fs) { + mesh::Mesh::begin(); + _fs = fs; + // load persisted prefs + _cli.loadPrefs(_fs); + + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + radio_set_tx_power(_prefs.tx_power_dbm); + + updateAdvertTimer(); + updateFloodAdvertTimer(); +} + +void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { + set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params + pending_freq = freq; + pending_bw = bw; + pending_sf = sf; + pending_cr = cr; + + revert_radio_at = futureMillis(2000 + timeout_mins * 60 * 1000); // schedule when to revert radio params +} + +bool MyMesh::formatFileSystem() { +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + return InternalFS.format(); +#elif defined(RP2040_PLATFORM) + return LittleFS.format(); +#elif defined(ESP32) + return SPIFFS.format(); +#else +#error "need to implement file system erase" + return false; +#endif +} + +void MyMesh::sendSelfAdvertisement(int delay_millis) { + mesh::Packet *pkt = createSelfAdvert(); + if (pkt) { + sendFlood(pkt, delay_millis); + } else { + MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); + } +} + +void MyMesh::updateAdvertTimer() { + if (_prefs.advert_interval > 0) { // schedule local advert timer + next_local_advert = futureMillis(((uint32_t)_prefs.advert_interval) * 2 * 60 * 1000); + } else { + next_local_advert = 0; // stop the timer + } +} + +void MyMesh::updateFloodAdvertTimer() { + if (_prefs.flood_advert_interval > 0) { // schedule flood advert timer + next_flood_advert = futureMillis(((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000); + } else { + next_flood_advert = 0; // stop the timer + } +} + +void MyMesh::dumpLogFile() { +#if defined(RP2040_PLATFORM) + File f = _fs->open(PACKET_LOG_FILE, "r"); +#else + File f = _fs->open(PACKET_LOG_FILE); +#endif + if (f) { + while (f.available()) { + int c = f.read(); + if (c < 0) break; + Serial.print((char)c); + } + f.close(); + } +} + +void MyMesh::setTxPower(uint8_t power_dbm) { + radio_set_tx_power(power_dbm); +} + +void MyMesh::formatNeighborsReply(char *reply) { + char *dp = reply; + +#if MAX_NEIGHBOURS + for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 134; i++) { + NeighbourInfo *neighbour = &neighbours[i]; + if (neighbour->heard_timestamp == 0) continue; // skip empty slots + + // add new line if not first item + if (i > 0) *dp++ = '\n'; + + char hex[10]; + // get 4 bytes of neighbour id as hex + mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); + + // add next neighbour + uint32_t secs_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; + sprintf(dp, "%s:%d:%d", hex, secs_ago, neighbour->snr); + while (*dp) + dp++; // find end of string + } +#endif + if (dp == reply) { // no neighbours, need empty response + strcpy(dp, "-none-"); + dp += 6; + } + *dp = 0; // null terminator +} + +void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { +#if MAX_NEIGHBOURS + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + NeighbourInfo *neighbour = &neighbours[i]; + if (memcmp(neighbour->id.pub_key, pubkey, key_len) == 0) { + neighbours[i] = NeighbourInfo(); // clear neighbour entry + } + } +#endif +} + +void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { + self_id = new_id; +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + IdentityStore store(*_fs, ""); +#elif defined(ESP32) + IdentityStore store(*_fs, "/identity"); +#elif defined(RP2040_PLATFORM) + IdentityStore store(*_fs, "/identity"); +#else +#error "need to define saveIdentity()" +#endif + store.save("_main", self_id); +} + +void MyMesh::clearStats() { + radio_driver.resetStats(); + resetStats(); + ((SimpleMeshTables *)getTables())->resetStats(); +} + +void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { + while (*command == ' ') + command++; // skip leading spaces + + if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) + memcpy(reply, command, 3); // reflect the prefix back + reply += 3; + command += 3; + } + + _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands +} + +void MyMesh::loop() { + mesh::Mesh::loop(); + + if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { + mesh::Packet *pkt = createSelfAdvert(); + if (pkt) sendFlood(pkt); + + updateFloodAdvertTimer(); // schedule next flood advert + updateAdvertTimer(); // also schedule local advert (so they don't overlap) + } else if (next_local_advert && millisHasNowPassed(next_local_advert)) { + mesh::Packet *pkt = createSelfAdvert(); + if (pkt) sendZeroHop(pkt); + + updateAdvertTimer(); // schedule next local advert + } + + if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params + set_radio_at = 0; // clear timer + radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr); + MESH_DEBUG_PRINTLN("Temp radio params"); + } + + if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig + revert_radio_at = 0; // clear timer + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + MESH_DEBUG_PRINTLN("Radio params restored"); + } +} diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h new file mode 100644 index 00000000..46039e81 --- /dev/null +++ b/examples/simple_repeater/MyMesh.h @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include + +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + #include +#elif defined(RP2040_PLATFORM) + #include +#elif defined(ESP32) + #include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +struct RepeaterStats { + uint16_t batt_milli_volts; + uint16_t curr_tx_queue_len; + int16_t noise_floor; + int16_t last_rssi; + uint32_t n_packets_recv; + uint32_t n_packets_sent; + uint32_t total_air_time_secs; + uint32_t total_up_time_secs; + uint32_t n_sent_flood, n_sent_direct; + uint32_t n_recv_flood, n_recv_direct; + uint16_t err_events; // was 'n_full_events' + int16_t last_snr; // x 4 + uint16_t n_direct_dups, n_flood_dups; + uint32_t total_rx_air_time_secs; +}; + +struct ClientInfo { + mesh::Identity id; + uint32_t last_timestamp, last_activity; + uint8_t secret[PUB_KEY_SIZE]; + bool is_admin; + int8_t out_path_len; + uint8_t out_path[MAX_PATH_SIZE]; +}; + +#ifndef MAX_CLIENTS + #define MAX_CLIENTS 32 +#endif + +struct NeighbourInfo { + mesh::Identity id; + uint32_t advert_timestamp; + uint32_t heard_timestamp; + int8_t snr; // multiplied by 4, user should divide to get float value +}; + +#ifndef FIRMWARE_BUILD_DATE + #define FIRMWARE_BUILD_DATE "1 Sep 2025" +#endif + +#ifndef FIRMWARE_VERSION + #define FIRMWARE_VERSION "v1.8.1" +#endif + +#define FIRMWARE_ROLE "repeater" + +#define PACKET_LOG_FILE "/packet_log" + +class MyMesh : public mesh::Mesh, public CommonCLICallbacks { + FILESYSTEM* _fs; + unsigned long next_local_advert, next_flood_advert; + bool _logging; + NodePrefs _prefs; + CommonCLI _cli; + uint8_t reply_data[MAX_PACKET_PAYLOAD]; + ClientInfo known_clients[MAX_CLIENTS]; +#if MAX_NEIGHBOURS + NeighbourInfo neighbours[MAX_NEIGHBOURS]; +#endif + CayenneLPP telemetry; + unsigned long set_radio_at, revert_radio_at; + float pending_freq; + float pending_bw; + uint8_t pending_sf; + uint8_t pending_cr; + int matching_peer_indexes[MAX_CLIENTS]; + + ClientInfo* putClient(const mesh::Identity& id); + void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); + int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); + mesh::Packet* createSelfAdvert(); + + File openAppend(const char* fname); + +protected: + float getAirtimeBudgetFactor() const override { + return _prefs.airtime_factor; + } + + bool allowPacketForward(const mesh::Packet* packet) override; + const char* getLogDateTime() override; + void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; + + void logRx(mesh::Packet* pkt, int len, float score) override; + void logTx(mesh::Packet* pkt, int len) override; + void logTxFail(mesh::Packet* pkt, int len) override; + int calcRxDelay(float score, uint32_t air_time) const override; + + uint32_t getRetransmitDelay(const mesh::Packet* packet) override; + uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override; + + int getInterferenceThreshold() const override { + return _prefs.interference_threshold; + } + int getAGCResetInterval() const override { + return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds + } + uint8_t getExtraAckTransmitCount() const override { + return _prefs.multi_acks; + } + + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + int searchPeersByHash(const uint8_t* hash) override; + void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; + void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len); + void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; + bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + +public: + MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); + + void begin(FILESYSTEM* fs); + + const char* getFirmwareVer() override { return FIRMWARE_VERSION; } + const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } + const char* getRole() override { return FIRMWARE_ROLE; } + const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } + + void savePrefs() override { + _cli.savePrefs(_fs); + } + + void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; + bool formatFileSystem() override; + void sendSelfAdvertisement(int delay_millis) override; + void updateAdvertTimer() override; + void updateFloodAdvertTimer() override; + + void setLoggingOn(bool enable) override { _logging = enable; } + + void eraseLogFile() override { + _fs->remove(PACKET_LOG_FILE); + } + + void dumpLogFile() override; + void setTxPower(uint8_t power_dbm) override; + void formatNeighborsReply(char *reply) override; + void removeNeighbor(const uint8_t* pubkey, int key_len) override; + + mesh::LocalIdentity& getSelfId() override { return self_id; } + + void saveIdentity(const mesh::LocalIdentity& new_id) override; + void clearStats() override; + void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void loop(); +}; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 3f0c0ebe..64e92aa5 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -1,803 +1,13 @@ #include // needed for PlatformIO #include -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - #include -#elif defined(RP2040_PLATFORM) - #include -#elif defined(ESP32) - #include -#endif - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* ------------------------------ Config -------------------------------- */ - -#ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "1 Sep 2025" -#endif - -#ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.8.1" -#endif - -#ifndef LORA_FREQ - #define LORA_FREQ 915.0 -#endif -#ifndef LORA_BW - #define LORA_BW 250 -#endif -#ifndef LORA_SF - #define LORA_SF 10 -#endif -#ifndef LORA_CR - #define LORA_CR 5 -#endif -#ifndef LORA_TX_POWER - #define LORA_TX_POWER 20 -#endif - -#ifndef ADVERT_NAME - #define ADVERT_NAME "repeater" -#endif -#ifndef ADVERT_LAT - #define ADVERT_LAT 0.0 -#endif -#ifndef ADVERT_LON - #define ADVERT_LON 0.0 -#endif - -#ifndef ADMIN_PASSWORD - #define ADMIN_PASSWORD "password" -#endif - -#ifndef SERVER_RESPONSE_DELAY - #define SERVER_RESPONSE_DELAY 300 -#endif - -#ifndef TXT_ACK_DELAY - #define TXT_ACK_DELAY 200 -#endif +#include "MyMesh.h" #ifdef DISPLAY_CLASS #include "UITask.h" static UITask ui_task(display); #endif -#define FIRMWARE_ROLE "repeater" - -#define PACKET_LOG_FILE "/packet_log" - -/* ------------------------------ Code -------------------------------- */ - -#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS -#define REQ_TYPE_KEEP_ALIVE 0x02 -#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 - -#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ - -struct RepeaterStats { - uint16_t batt_milli_volts; - uint16_t curr_tx_queue_len; - int16_t noise_floor; - int16_t last_rssi; - uint32_t n_packets_recv; - uint32_t n_packets_sent; - uint32_t total_air_time_secs; - uint32_t total_up_time_secs; - uint32_t n_sent_flood, n_sent_direct; - uint32_t n_recv_flood, n_recv_direct; - uint16_t err_events; // was 'n_full_events' - int16_t last_snr; // x 4 - uint16_t n_direct_dups, n_flood_dups; - uint32_t total_rx_air_time_secs; -}; - -struct ClientInfo { - mesh::Identity id; - uint32_t last_timestamp, last_activity; - uint8_t secret[PUB_KEY_SIZE]; - bool is_admin; - int8_t out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; -}; - -#ifndef MAX_CLIENTS - #define MAX_CLIENTS 32 -#endif - -struct NeighbourInfo { - mesh::Identity id; - uint32_t advert_timestamp; - uint32_t heard_timestamp; - int8_t snr; // multiplied by 4, user should divide to get float value -}; - -#define CLI_REPLY_DELAY_MILLIS 600 - -class MyMesh : public mesh::Mesh, public CommonCLICallbacks { - FILESYSTEM* _fs; - unsigned long next_local_advert, next_flood_advert; - bool _logging; - NodePrefs _prefs; - CommonCLI _cli; - uint8_t reply_data[MAX_PACKET_PAYLOAD]; - ClientInfo known_clients[MAX_CLIENTS]; -#if MAX_NEIGHBOURS - NeighbourInfo neighbours[MAX_NEIGHBOURS]; -#endif - CayenneLPP telemetry; - unsigned long set_radio_at, revert_radio_at; - float pending_freq; - float pending_bw; - uint8_t pending_sf; - uint8_t pending_cr; - - ClientInfo* putClient(const mesh::Identity& id) { - uint32_t min_time = 0xFFFFFFFF; - ClientInfo* oldest = &known_clients[0]; - for (int i = 0; i < MAX_CLIENTS; i++) { - if (known_clients[i].last_activity < min_time) { - oldest = &known_clients[i]; - min_time = oldest->last_activity; - } - if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known - } - - oldest->id = id; - oldest->out_path_len = -1; // initially out_path is unknown - oldest->last_timestamp = 0; - return oldest; - } - - void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr) { - #if MAX_NEIGHBOURS // check if neighbours enabled - // find existing neighbour, else use least recently updated - uint32_t oldest_timestamp = 0xFFFFFFFF; - NeighbourInfo* neighbour = &neighbours[0]; - for (int i = 0; i < MAX_NEIGHBOURS; i++) { - // if neighbour already known, we should update it - if (id.matches(neighbours[i].id)) { - neighbour = &neighbours[i]; - break; - } - - // otherwise we should update the least recently updated neighbour - if (neighbours[i].heard_timestamp < oldest_timestamp) { - neighbour = &neighbours[i]; - oldest_timestamp = neighbour->heard_timestamp; - } - } - - // update neighbour info - neighbour->id = id; - neighbour->advert_timestamp = timestamp; - neighbour->heard_timestamp = getRTCClock()->getCurrentTime(); - neighbour->snr = (int8_t) (snr * 4); - #endif - } - - int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) { - // uint32_t now = getRTCClock()->getCurrentTimeUnique(); - // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') - - switch (payload[0]) { - case REQ_TYPE_GET_STATUS: { // guests can also access this now - RepeaterStats stats; - stats.batt_milli_volts = board.getBattMilliVolts(); - stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); - stats.noise_floor = (int16_t)_radio->getNoiseFloor(); - stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); - stats.n_packets_recv = radio_driver.getPacketsRecv(); - stats.n_packets_sent = radio_driver.getPacketsSent(); - stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; - stats.n_sent_flood = getNumSentFlood(); - stats.n_sent_direct = getNumSentDirect(); - stats.n_recv_flood = getNumRecvFlood(); - stats.n_recv_direct = getNumRecvDirect(); - stats.err_events = _err_flags; - stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); - stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); - stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); - stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; - - memcpy(&reply_data[4], &stats, sizeof(stats)); - - return 4 + sizeof(stats); // reply_len - } - case REQ_TYPE_GET_TELEMETRY_DATA: { - uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions - - telemetry.reset(); - telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - // query other sensors -- target specific - sensors.querySensors((sender->is_admin ? 0xFF : 0x00) & perm_mask, telemetry); - - uint8_t tlen = telemetry.getSize(); - memcpy(&reply_data[4], telemetry.getBuffer(), tlen); - return 4 + tlen; // reply_len - } - } - return 0; // unknown command - } - - mesh::Packet* createSelfAdvert() { - uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - - return createAdvert(self_id, app_data, app_data_len); - } - - File openAppend(const char* fname) { - #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - return _fs->open(fname, FILE_O_WRITE); - #elif defined(RP2040_PLATFORM) - return _fs->open(fname, "a"); - #else - return _fs->open(fname, "a", true); - #endif - } - -protected: - float getAirtimeBudgetFactor() const override { - return _prefs.airtime_factor; - } - - bool allowPacketForward(const mesh::Packet* packet) override { - if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; - return true; - } - - const char* getLogDateTime() override { - static char tmp[32]; - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), dt.year()); - return tmp; - } - - void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override { - #if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.print(" RAW: "); - mesh::Utils::printHex(Serial, raw, len); - Serial.println(); - #endif - } - - void logRx(mesh::Packet* pkt, int len, float score) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); - - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ - || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); - } else { - f.printf("\n"); - } - f.close(); - } - } - } - void logTx(mesh::Packet* pkt, int len) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); - - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ - || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); - } else { - f.printf("\n"); - } - f.close(); - } - } - } - void logTxFail(mesh::Packet* pkt, int len) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); - f.close(); - } - } - } - - int calcRxDelay(float score, uint32_t air_time) const override { - if (_prefs.rx_delay_base <= 0.0f) return 0; - return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); - } - - uint32_t getRetransmitDelay(const mesh::Packet* packet) override { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; - } - uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; - } - int getInterferenceThreshold() const override { - return _prefs.interference_threshold; - } - int getAGCResetInterval() const override { - return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds - } - uint8_t getExtraAckTransmitCount() const override { - return _prefs.multi_acks; - } - - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override { - if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) - uint32_t timestamp; - memcpy(×tamp, data, 4); - - bool is_admin; - data[len] = 0; // ensure null terminator - if (strcmp((char *) &data[4], _prefs.password) == 0) { // check for valid password - is_admin = true; - } else if (strcmp((char *) &data[4], _prefs.guest_password) == 0) { // check guest password - is_admin = false; - } else { - #if MESH_DEBUG - MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); - #endif - return; - } - - auto client = putClient(sender); // add to known clients (if not already known) - if (timestamp <= client->last_timestamp) { - MESH_DEBUG_PRINTLN("Possible login replay attack!"); - return; // FATAL: client table is full -OR- replay attack - } - - MESH_DEBUG_PRINTLN("Login success!"); - client->last_timestamp = timestamp; - client->last_activity = getRTCClock()->getCurrentTime(); - client->is_admin = is_admin; - memcpy(client->secret, secret, PUB_KEY_SIZE); - - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - #if 0 - memcpy(&reply_data[4], "OK", 2); // legacy response - #else - reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) - reply_data[6] = is_admin ? 1 : 0; - reply_data[7] = 0; // FUTURE: reserved - getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness - #endif - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet* path = createPathReturn(sender, client->secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 12); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 12); - if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); - } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); - } - } - } - } - } - - int matching_peer_indexes[MAX_CLIENTS]; - - int searchPeersByHash(const uint8_t* hash) override { - int n = 0; - for (int i = 0; i < MAX_CLIENTS; i++) { - if (known_clients[i].id.isHashMatch(hash)) { - matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) - } - } - return n; - } - - void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override { - int i = matching_peer_indexes[peer_idx]; - if (i >= 0 && i < MAX_CLIENTS) { - // lookup pre-calculated shared_secret - memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE); - } else { - MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); - } - } - - void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { - mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl - - // if this a zero hop advert, add it to neighbours - if (packet->path_len == 0) { - AdvertDataParser parser(app_data, app_data_len); - if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters - putNeighbour(id, timestamp, packet->getSNR()); - } - } - } - - void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override { - int i = matching_peer_indexes[sender_idx]; - if (i < 0 || i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) - MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); - return; - } - auto client = &known_clients[i]; - if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!) - uint32_t timestamp; - memcpy(×tamp, data, 4); - - if (timestamp > client->last_timestamp) { // prevent replay attacks - int reply_len = handleRequest(client, timestamp, &data[4], len - 4); - if (reply_len == 0) return; // invalid command - - client->last_timestamp = timestamp; - client->last_activity = getRTCClock()->getCurrentTime(); - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); - if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); - } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); - } - } - } - } else { - MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); - } - } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->is_admin) { // a CLI command - uint32_t sender_timestamp; - memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags - - if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { - MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); - } else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks - bool is_retry = (sender_timestamp == client->last_timestamp); - client->last_timestamp = sender_timestamp; - client->last_activity = getRTCClock()->getCurrentTime(); - - // len can be > original length, but 'text' will be padded with zeroes - data[len] = 0; // need to make a C string again, with null terminator - - if (flags == TXT_TYPE_PLAIN) { // for legacy CLI, send Acks - uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, PUB_KEY_SIZE); - - mesh::Packet* ack = createAck(ack_hash); - if (ack) { - if (client->out_path_len < 0) { - sendFlood(ack, TXT_ACK_DELAY); - } else { - sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); - } - } - } - - uint8_t temp[166]; - char *command = (char *) &data[5]; - char *reply = (char *) &temp[5]; - if (is_retry) { - *reply = 0; - } else { - handleCommand(sender_timestamp, command, reply); - } - int text_len = strlen(reply); - if (text_len > 0) { - uint32_t timestamp = getRTCClock()->getCurrentTimeUnique(); - if (timestamp == sender_timestamp) { - // WORKAROUND: the two timestamps need to be different, in the CLI view - timestamp++; - } - memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique - temp[4] = (TXT_TYPE_CLI_DATA << 2); // NOTE: legacy was: TXT_TYPE_PLAIN - - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); - if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply, CLI_REPLY_DELAY_MILLIS); - } else { - sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS); - } - } - } - } else { - MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); - } - } - } - - bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override { - // TODO: prevent replay attacks - int i = matching_peer_indexes[sender_idx]; - - if (i >= 0 && i < MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) - MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t) path_len); - auto client = &known_clients[i]; - memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() - } else { - MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); - } - - // NOTE: no reciprocal path send!! - return false; - } - -public: - MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) - : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) - { - memset(known_clients, 0, sizeof(known_clients)); - next_local_advert = next_flood_advert = 0; - set_radio_at = revert_radio_at = 0; - _logging = false; - - #if MAX_NEIGHBOURS - memset(neighbours, 0, sizeof(neighbours)); - #endif - - // defaults - memset(&_prefs, 0, sizeof(_prefs)); - _prefs.airtime_factor = 1.0; // one half - _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; - _prefs.tx_delay_factor = 0.5f; // was 0.25f - StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); - _prefs.node_lat = ADVERT_LAT; - _prefs.node_lon = ADVERT_LON; - StrHelper::strncpy(_prefs.password, ADMIN_PASSWORD, sizeof(_prefs.password)); - _prefs.freq = LORA_FREQ; - _prefs.sf = LORA_SF; - _prefs.bw = LORA_BW; - _prefs.cr = LORA_CR; - _prefs.tx_power_dbm = LORA_TX_POWER; - _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 12; // 12 hours - _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled - } - - void begin(FILESYSTEM* fs) { - mesh::Mesh::begin(); - _fs = fs; - // load persisted prefs - _cli.loadPrefs(_fs); - - radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); - radio_set_tx_power(_prefs.tx_power_dbm); - - updateAdvertTimer(); - updateFloodAdvertTimer(); - } - - const char* getFirmwareVer() override { return FIRMWARE_VERSION; } - const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } - const char* getRole() override { return FIRMWARE_ROLE; } - const char* getNodeName() { return _prefs.node_name; } - NodePrefs* getNodePrefs() { - return &_prefs; - } - - void savePrefs() override { - _cli.savePrefs(_fs); - } - - void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override { - set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params - pending_freq = freq; - pending_bw = bw; - pending_sf = sf; - pending_cr = cr; - - revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params - } - - bool formatFileSystem() override { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - return InternalFS.format(); -#elif defined(RP2040_PLATFORM) - return LittleFS.format(); -#elif defined(ESP32) - return SPIFFS.format(); -#else - #error "need to implement file system erase" - return false; -#endif - } - - void sendSelfAdvertisement(int delay_millis) override { - mesh::Packet* pkt = createSelfAdvert(); - if (pkt) { - sendFlood(pkt, delay_millis); - } else { - MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); - } - } - - void updateAdvertTimer() override { - if (_prefs.advert_interval > 0) { // schedule local advert timer - next_local_advert = futureMillis( ((uint32_t)_prefs.advert_interval) * 2 * 60 * 1000); - } else { - next_local_advert = 0; // stop the timer - } - } - void updateFloodAdvertTimer() override { - if (_prefs.flood_advert_interval > 0) { // schedule flood advert timer - next_flood_advert = futureMillis( ((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000); - } else { - next_flood_advert = 0; // stop the timer - } - } - - void setLoggingOn(bool enable) override { _logging = enable; } - - void eraseLogFile() override { - _fs->remove(PACKET_LOG_FILE); - } - - void dumpLogFile() override { -#if defined(RP2040_PLATFORM) - File f = _fs->open(PACKET_LOG_FILE, "r"); -#else - File f = _fs->open(PACKET_LOG_FILE); -#endif - if (f) { - while (f.available()) { - int c = f.read(); - if (c < 0) break; - Serial.print((char)c); - } - f.close(); - } - } - - void setTxPower(uint8_t power_dbm) override { - radio_set_tx_power(power_dbm); - } - - void formatNeighborsReply(char *reply) override { - char *dp = reply; - -#if MAX_NEIGHBOURS - for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 134; i++) { - NeighbourInfo* neighbour = &neighbours[i]; - if (neighbour->heard_timestamp == 0) continue; // skip empty slots - - // add new line if not first item - if (i > 0) *dp++ = '\n'; - - char hex[10]; - // get 4 bytes of neighbour id as hex - mesh::Utils::toHex(hex, neighbour->id.pub_key, 4); - - // add next neighbour - uint32_t secs_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; - sprintf(dp, "%s:%d:%d", hex, secs_ago, neighbour->snr); - while (*dp) dp++; // find end of string - } -#endif - if (dp == reply) { // no neighbours, need empty response - strcpy(dp, "-none-"); dp += 6; - } - *dp = 0; // null terminator - } - - void removeNeighbor(const uint8_t* pubkey, int key_len) override { -#if MAX_NEIGHBOURS - for (int i = 0; i < MAX_NEIGHBOURS; i++) { - NeighbourInfo* neighbour = &neighbours[i]; - if(memcmp(neighbour->id.pub_key, pubkey, key_len) == 0){ - neighbours[i] = NeighbourInfo(); // clear neighbour entry - } - } -#endif - } - - mesh::LocalIdentity& getSelfId() override { return self_id; } - - void saveIdentity(const mesh::LocalIdentity& new_id) override { - self_id = new_id; -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - IdentityStore store(*_fs, ""); -#elif defined(ESP32) - IdentityStore store(*_fs, "/identity"); -#elif defined(RP2040_PLATFORM) - IdentityStore store(*_fs, "/identity"); -#else - #error "need to define saveIdentity()" -#endif - store.save("_main", self_id); - } - - void clearStats() override { - radio_driver.resetStats(); - resetStats(); - ((SimpleMeshTables *)getTables())->resetStats(); - } - - void handleCommand(uint32_t sender_timestamp, char* command, char* reply) { - while (*command == ' ') command++; // skip leading spaces - - if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) - memcpy(reply, command, 3); // reflect the prefix back - reply += 3; - command += 3; - } - - _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands - } - - void loop() { - mesh::Mesh::loop(); - - if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { - mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendFlood(pkt); - - updateFloodAdvertTimer(); // schedule next flood advert - updateAdvertTimer(); // also schedule local advert (so they don't overlap) - } else if (next_local_advert && millisHasNowPassed(next_local_advert)) { - mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendZeroHop(pkt); - - updateAdvertTimer(); // schedule next local advert - } - - if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params - set_radio_at = 0; // clear timer - radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr); - MESH_DEBUG_PRINTLN("Temp radio params"); - } - - if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig - revert_radio_at = 0; // clear timer - radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); - MESH_DEBUG_PRINTLN("Radio params restored"); - } - - #ifdef DISPLAY_CLASS - ui_task.loop(); - #endif - } -}; - StdRNG fast_rng; SimpleMeshTables tables; @@ -899,4 +109,7 @@ void loop() { the_mesh.loop(); sensors.loop(); +#ifdef DISPLAY_CLASS + ui_task.loop(); +#endif } From 1d25c87c57ddca51eb0abbad272b5d45951c64e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Mon, 8 Sep 2025 11:15:28 +0100 Subject: [PATCH 036/546] Refactor bridge packet handling to use common magic number and size constants --- src/helpers/bridges/BridgeBase.h | 20 +++++++++++++++++++ src/helpers/bridges/ESPNowBridge.cpp | 24 +++++++++++------------ src/helpers/bridges/ESPNowBridge.h | 9 +-------- src/helpers/bridges/RS232Bridge.cpp | 10 +++++----- src/helpers/bridges/RS232Bridge.h | 12 +----------- variants/lilygo_tlora_v2_1/platformio.ini | 2 +- 6 files changed, 40 insertions(+), 37 deletions(-) diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index 2aff2306..c1764ae3 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/src/helpers/bridges/BridgeBase.h @@ -21,6 +21,26 @@ class BridgeBase : public AbstractBridge { public: virtual ~BridgeBase() = default; + /** + * @brief Common magic number used by all bridge implementations for packet identification + * + * This magic number is placed at the beginning of bridge packets to identify + * them as mesh bridge packets and provide frame synchronization. + */ + static constexpr uint16_t BRIDGE_PACKET_MAGIC = 0xC03E; + + /** + * @brief Common field sizes used by bridge implementations + * + * These constants define the size of common packet fields used across bridges. + * BRIDGE_MAGIC_SIZE is used by all bridges for packet identification. + * BRIDGE_LENGTH_SIZE is used by bridges that need explicit length fields (like RS232). + * BRIDGE_CHECKSUM_SIZE is used by all bridges for Fletcher-16 checksums. + */ + static constexpr uint16_t BRIDGE_MAGIC_SIZE = sizeof(BRIDGE_PACKET_MAGIC); + static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t); + static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t); + protected: /** Packet manager for allocating and queuing mesh packets */ mesh::PacketManager *_mgr; diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index baf683ca..a02f804e 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -66,7 +66,7 @@ void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) { void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len) { // Ignore packets that are too small to contain header + checksum - if (len < (MAGIC_HEADER_SIZE + CHECKSUM_SIZE)) { + if (len < (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE)) { #if MESH_PACKET_LOGGING Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len); #endif @@ -83,7 +83,7 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l // Check packet header magic uint16_t received_magic = (data[0] << 8) | data[1]; - if (received_magic != ESPNOW_HEADER_MAGIC) { + if (received_magic != BRIDGE_PACKET_MAGIC) { #if MESH_PACKET_LOGGING Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%04X\n", getLogDateTime(), received_magic); #endif @@ -92,17 +92,17 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l // Make a copy we can decrypt uint8_t decrypted[MAX_ESPNOW_PACKET_SIZE]; - const size_t encryptedDataLen = len - MAGIC_HEADER_SIZE; - memcpy(decrypted, data + MAGIC_HEADER_SIZE, encryptedDataLen); + const size_t encryptedDataLen = len - BRIDGE_MAGIC_SIZE; + memcpy(decrypted, data + BRIDGE_MAGIC_SIZE, encryptedDataLen); // Try to decrypt (checksum + payload) xorCrypt(decrypted, encryptedDataLen); // Validate checksum uint16_t received_checksum = (decrypted[0] << 8) | decrypted[1]; - const size_t payloadLen = encryptedDataLen - CHECKSUM_SIZE; + const size_t payloadLen = encryptedDataLen - BRIDGE_CHECKSUM_SIZE; - if (!validateChecksum(decrypted + CHECKSUM_SIZE, payloadLen, received_checksum)) { + if (!validateChecksum(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen, received_checksum)) { // Failed to decrypt - likely from a different network #if MESH_PACKET_LOGGING Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X\n", getLogDateTime(), @@ -119,7 +119,7 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l mesh::Packet *pkt = _instance->_mgr->allocNew(); if (!pkt) return; - if (pkt->readFrom(decrypted + CHECKSUM_SIZE, payloadLen)) { + if (pkt->readFrom(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen)) { _instance->onPacketReceived(pkt); } else { _instance->_mgr->free(pkt); @@ -161,11 +161,11 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { uint8_t buffer[MAX_ESPNOW_PACKET_SIZE]; // Write magic header (2 bytes) - buffer[0] = (ESPNOW_HEADER_MAGIC >> 8) & 0xFF; - buffer[1] = ESPNOW_HEADER_MAGIC & 0xFF; + buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; + buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Write packet payload starting after magic header and checksum - const size_t packetOffset = MAGIC_HEADER_SIZE + CHECKSUM_SIZE; + const size_t packetOffset = BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE; memcpy(buffer + packetOffset, sizingBuffer, meshPacketLen); // Calculate and add checksum (only of the payload) @@ -174,10 +174,10 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { buffer[3] = checksum & 0xFF; // Low byte // Encrypt payload and checksum (not including magic header) - xorCrypt(buffer + MAGIC_HEADER_SIZE, meshPacketLen + CHECKSUM_SIZE); + xorCrypt(buffer + BRIDGE_MAGIC_SIZE, meshPacketLen + BRIDGE_CHECKSUM_SIZE); // Total packet size: magic header + checksum + payload - const size_t totalPacketSize = MAGIC_HEADER_SIZE + CHECKSUM_SIZE + meshPacketLen; + const size_t totalPacketSize = BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE + meshPacketLen; // Broadcast using ESP-NOW uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index d9962c72..b43f1744 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/src/helpers/bridges/ESPNowBridge.h @@ -65,14 +65,7 @@ private: /** * Size constants for packet parsing */ - static const size_t MAGIC_HEADER_SIZE = 2; - static const size_t CHECKSUM_SIZE = 2; - static const size_t MAX_PAYLOAD_SIZE = MAX_ESPNOW_PACKET_SIZE - (MAGIC_HEADER_SIZE + CHECKSUM_SIZE); - - /** - * Magic bytes to identify ESPNowBridge packets - */ - static const uint16_t ESPNOW_HEADER_MAGIC = 0xC03E; + static const size_t MAX_PAYLOAD_SIZE = MAX_ESPNOW_PACKET_SIZE - (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE); /** Buffer for receiving ESP-NOW packets */ uint8_t _rx_buffer[MAX_ESPNOW_PACKET_SIZE]; diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 39a0968a..b209a6da 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -52,8 +52,8 @@ void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) { } // Build packet header - buffer[0] = (SERIAL_PKT_MAGIC >> 8) & 0xFF; // Magic high byte - buffer[1] = SERIAL_PKT_MAGIC & 0xFF; // Magic low byte + buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte + buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte buffer[2] = (len >> 8) & 0xFF; // Length high byte buffer[3] = len & 0xFF; // Length low byte @@ -77,14 +77,14 @@ void RS232Bridge::loop() { if (_rx_buffer_pos < 2) { // Waiting for magic word - if ((_rx_buffer_pos == 0 && b == ((SERIAL_PKT_MAGIC >> 8) & 0xFF)) || - (_rx_buffer_pos == 1 && b == (SERIAL_PKT_MAGIC & 0xFF))) { + if ((_rx_buffer_pos == 0 && b == ((BRIDGE_PACKET_MAGIC >> 8) & 0xFF)) || + (_rx_buffer_pos == 1 && b == (BRIDGE_PACKET_MAGIC & 0xFF))) { _rx_buffer[_rx_buffer_pos++] = b; } else { // Invalid magic byte, reset and start over _rx_buffer_pos = 0; // Check if this byte could be the start of a new magic word - if (b == ((SERIAL_PKT_MAGIC >> 8) & 0xFF)) { + if (b == ((BRIDGE_PACKET_MAGIC >> 8) & 0xFF)) { _rx_buffer[_rx_buffer_pos++] = b; } } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 32fad17f..3b09de75 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -113,21 +113,11 @@ private: * Total overhead: 6 bytes */ - /** Magic number to identify start of RS232Bridge packets */ - static constexpr uint16_t SERIAL_PKT_MAGIC = 0xC03E; - - /** - * Size constants for packet parsing and construction - */ - static constexpr uint16_t MAGIC_HEADER_SIZE = 2; - static constexpr uint16_t LENGTH_FIELD_SIZE = 2; - static constexpr uint16_t CHECKSUM_SIZE = 2; - /** * @brief The total overhead of the serial protocol in bytes. * Includes: MAGIC_WORD (2) + LENGTH (2) + CHECKSUM (2) = 6 bytes */ - static constexpr uint16_t SERIAL_OVERHEAD = MAGIC_HEADER_SIZE + LENGTH_FIELD_SIZE + CHECKSUM_SIZE; + static constexpr uint16_t SERIAL_OVERHEAD = BRIDGE_MAGIC_SIZE + BRIDGE_LENGTH_SIZE + BRIDGE_CHECKSUM_SIZE; /** * @brief The maximum size of a complete packet on the serial line. diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index aa957fba..d9ff1700 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -198,7 +198,7 @@ build_flags = -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' -; -D MESH_PACKET_LOGGING=1 + -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 lib_deps = From 1c93c162a18bcf61bedbafe343d680e06333aa40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Mon, 8 Sep 2025 18:49:33 +0100 Subject: [PATCH 037/546] Add ESPNow bridge configurations for all ESP32 targets --- variants/generic-e22/platformio.ini | 94 ++++++++++++++++++ variants/heltec_ct62/platformio.ini | 41 ++++++++ variants/heltec_e213/platformio.ini | 45 +++++++++ variants/heltec_e290/platformio.ini | 45 +++++++++ variants/heltec_t190/platformio.ini | 41 ++++++++ variants/heltec_tracker/platformio.ini | 47 +++++++++ variants/heltec_v2/platformio.ini | 47 +++++++++ variants/heltec_wireless_paper/platformio.ini | 45 +++++++++ variants/lilygo_t3s3/platformio.ini | 45 +++++++++ variants/lilygo_t3s3_sx1276/platformio.ini | 45 +++++++++ variants/lilygo_tbeam_SX1262/platformio.ini | 41 ++++++++ variants/lilygo_tbeam_SX1276/platformio.ini | 43 ++++++++ .../platformio.ini | 41 ++++++++ variants/lilygo_tlora_v2_1/platformio.ini | 2 +- variants/meshadventurer/platformio.ini | 98 +++++++++++++++++++ variants/station_g2/platformio.ini | 84 ++++++++++++++++ variants/tenstar_c3/platformio.ini | 96 ++++++++++++++++++ variants/xiao_s3_wio/platformio.ini | 41 ++++++++ 18 files changed, 940 insertions(+), 1 deletion(-) diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 8b2c293b..c9a67220 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -47,6 +47,53 @@ lib_deps = ${Generic_E22.lib_deps} ${esp32_ota.lib_deps} +; [env:Generic_E22_sx1262_repeater_bridge_rs232] +; extends = Generic_E22 +; build_src_filter = ${Generic_E22.build_src_filter} +; + +; +<../examples/simple_repeater/main.cpp> +; build_flags = +; ${Generic_E22.build_flags} +; -D RADIO_CLASS=CustomSX1262 +; -D WRAPPER_CLASS=CustomSX1262Wrapper +; -D LORA_TX_POWER=22 +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Generic_E22.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Generic_E22_sx1262_repeater_bridge_espnow] +extends = Generic_E22 +build_src_filter = ${Generic_E22.build_src_filter} + + + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Generic_E22.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Generic_E22.lib_deps} + ${esp32_ota.lib_deps} + [env:Generic_E22_sx1268_repeater] extends = Generic_E22 build_src_filter = ${Generic_E22.build_src_filter} @@ -66,3 +113,50 @@ build_flags = lib_deps = ${Generic_E22.lib_deps} ${esp32_ota.lib_deps} + +; [env:Generic_E22_sx1268_repeater_bridge_rs232] +; extends = Generic_E22 +; build_src_filter = ${Generic_E22.build_src_filter} +; + +; +<../examples/simple_repeater/main.cpp> +; build_flags = +; ${Generic_E22.build_flags} +; -D RADIO_CLASS=CustomSX1268 +; -D WRAPPER_CLASS=CustomSX1268Wrapper +; -D LORA_TX_POWER=22 +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Generic_E22.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Generic_E22_sx1268_repeater_bridge_espnow] +extends = Generic_E22 +build_src_filter = ${Generic_E22.build_src_filter} + + + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Generic_E22.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Generic_E22.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 0dc512b9..1b83adbf 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -49,6 +49,47 @@ lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_ct62_repeater_bridge_rs232] +; extends = Heltec_ct62 +; build_flags = +; ${Heltec_ct62.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_ct62.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_ct62.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_ct62_repeater_bridge_espnow] +extends = Heltec_ct62 +build_flags = + ${Heltec_ct62.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_ct62.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_ct62.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_ct62_companion_radio_usb] extends = Heltec_ct62 build_flags = diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index c8efc819..a6fe2560 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -93,6 +93,51 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_E213_repeater_bridge_rs232] +; extends = Heltec_E213_base +; build_flags = +; ${Heltec_E213_base.build_flags} +; -D DISPLAY_CLASS=E213Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_E213_base.build_src_filter} +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_E213_base.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_E213_repeater_bridge_espnow] +extends = Heltec_E213_base +build_flags = + ${Heltec_E213_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_E213_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_E213_base.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_E213_room_server] extends = Heltec_E213_base build_flags = diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 377162f4..0223b30c 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -89,6 +89,51 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_E290_repeater_bridge_rs232] +; extends = Heltec_E290_base +; build_flags = +; ${Heltec_E290_base.build_flags} +; -D DISPLAY_CLASS=E290Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_E290_base.build_src_filter} +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_E290_base.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_E290_repeater_bridge_espnow] +extends = Heltec_E290_base +build_flags = + ${Heltec_E290_base.build_flags} + -D DISPLAY_CLASS=E290Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_E290_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_E290_base.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_E290_room_server] extends = Heltec_E290_base build_flags = diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 7debe178..52bb79e0 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -94,6 +94,47 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_T190_repeater_bridge_rs232] +; extends = Heltec_T190_base +; build_flags = +; ${Heltec_T190_base.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_T190_base.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_T190_base.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_T190_repeater_bridge_espnow] +extends = Heltec_T190_base +build_flags = + ${Heltec_T190_base.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_T190_base.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_T190_base.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_T190_room_server] extends = Heltec_T190_base build_flags = diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index f1477e9f..5c0df007 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -80,6 +80,53 @@ lib_deps = ${Heltec_tracker_base.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_Wireless_Tracker_repeater_bridge_rs232] +; extends = Heltec_tracker_base +; build_flags = +; ${Heltec_tracker_base.build_flags} +; -D DISPLAY_ROTATION=1 +; -D DISPLAY_CLASS=ST7735Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_tracker_base.build_src_filter} +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_tracker_base.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_Wireless_Tracker_repeater_bridge_espnow] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_tracker_base.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_Wireless_Tracker_room_server] extends = Heltec_tracker_base build_flags = diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 352ea34d..d2afe4db 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -40,6 +40,53 @@ lib_deps = ${Heltec_lora32_v2.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_v2_repeater_bridge_rs232] +; extends = Heltec_lora32_v2 +; build_flags = +; ${Heltec_lora32_v2.build_flags} +; -D DISPLAY_CLASS=SSD1306Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_lora32_v2.build_src_filter} +; + +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_lora32_v2.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_v2_repeater_bridge_espnow] +extends = Heltec_lora32_v2 +build_flags = + ${Heltec_lora32_v2.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v2.build_src_filter} + + + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v2.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_v2_room_server] extends = Heltec_lora32_v2 build_flags = diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 8de826e4..43ac2a82 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -68,6 +68,51 @@ lib_deps = ${Heltec_Wireless_Paper_base.lib_deps} ${esp32_ota.lib_deps} +; [env:Heltec_Wireless_Paper_repeater_bridge_rs232] +; extends = Heltec_Wireless_Paper_base +; build_flags = +; ${Heltec_Wireless_Paper_base.build_flags} +; -D DISPLAY_CLASS=E213Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Heltec_Wireless_Paper_base.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Heltec_Wireless_Paper_repeater_bridge_espnow] +extends = Heltec_Wireless_Paper_base +build_flags = + ${Heltec_Wireless_Paper_base.build_flags} + -D DISPLAY_CLASS=E213Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_Wireless_Paper_base.lib_deps} + ${esp32_ota.lib_deps} + [env:Heltec_Wireless_Paper_room_server] extends = Heltec_Wireless_Paper_base build_flags = diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 637cc123..ca221108 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -52,6 +52,51 @@ lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} ${esp32_ota.lib_deps} +; [env:LilyGo_T3S3_sx1262_Repeater_bridge_rs232] +; extends = LilyGo_T3S3_sx1262 +; build_flags = +; ${LilyGo_T3S3_sx1262.build_flags} +; -D DISPLAY_CLASS=SSD1306Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${LilyGo_T3S3_sx1262.lib_deps} +; ${esp32_ota.lib_deps} + +[env:LilyGo_T3S3_sx1262_Repeater_bridge_espnow] +extends = LilyGo_T3S3_sx1262 +build_flags = + ${LilyGo_T3S3_sx1262.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_T3S3_sx1262.lib_deps} + ${esp32_ota.lib_deps} + [env:LilyGo_T3S3_sx1262_terminal_chat] extends = LilyGo_T3S3_sx1262 build_flags = diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 23c58fb8..1c0d5cf1 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -50,6 +50,51 @@ lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} ${esp32_ota.lib_deps} +; [env:LilyGo_T3S3_sx1276_Repeater_bridge_rs232] +; extends = LilyGo_T3S3_sx1276 +; build_flags = +; ${LilyGo_T3S3_sx1276.build_flags} +; -D DISPLAY_CLASS=SSD1306Display +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} +; + +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${LilyGo_T3S3_sx1276.lib_deps} +; ${esp32_ota.lib_deps} + +[env:LilyGo_T3S3_sx1276_Repeater_bridge_espnow] +extends = LilyGo_T3S3_sx1276 +build_flags = + ${LilyGo_T3S3_sx1276.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + + + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_T3S3_sx1276.lib_deps} + ${esp32_ota.lib_deps} + [env:LilyGo_T3S3_sx1276_terminal_chat] extends = LilyGo_T3S3_sx1276 build_flags = diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index ea8872de..f7d1a764 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -74,6 +74,47 @@ lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} ${esp32_ota.lib_deps} +; [env:Tbeam_SX1262_repeater_bridge_rs232] +; extends = LilyGo_TBeam_SX1262 +; build_flags = +; ${LilyGo_TBeam_SX1262.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${LilyGo_TBeam_SX1262.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Tbeam_SX1262_repeater_bridge_espnow] +extends = LilyGo_TBeam_SX1262 +build_flags = + ${LilyGo_TBeam_SX1262.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1262.lib_deps} + ${esp32_ota.lib_deps} + [env:Tbeam_SX1262_room_server] extends = LilyGo_TBeam_SX1262 build_flags = diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 782b74c7..d7e119ef 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -72,6 +72,49 @@ lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} +; [env:Tbeam_SX1276_repeater_bridge_rs232] +; extends = LilyGo_TBeam_SX1276 +; build_flags = +; ${LilyGo_TBeam_SX1276.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D PERSISTANT_GPS=1 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${LilyGo_TBeam_SX1276.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Tbeam_SX1276_repeater_bridge_espnow] +extends = LilyGo_TBeam_SX1276 +build_flags = + ${LilyGo_TBeam_SX1276.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D PERSISTANT_GPS=1 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_SX1276.lib_deps} + ${esp32_ota.lib_deps} + [env:Tbeam_SX1276_room_server] extends = LilyGo_TBeam_SX1276 build_flags = diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index e6135872..328ebf07 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -52,6 +52,47 @@ lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} ${esp32_ota.lib_deps} +; [env:T_Beam_S3_Supreme_SX1262_repeater_bridge_rs232] +; extends = T_Beam_S3_Supreme_SX1262 +; build_flags = +; ${T_Beam_S3_Supreme_SX1262.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0 +; -D ADVERT_LON=0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${T_Beam_S3_Supreme_SX1262.lib_deps} +; ${esp32_ota.lib_deps} + +[env:T_Beam_S3_Supreme_SX1262_repeater_bridge_espnow] +extends = T_Beam_S3_Supreme_SX1262 +build_flags = + ${T_Beam_S3_Supreme_SX1262.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0 + -D ADVERT_LON=0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${T_Beam_S3_Supreme_SX1262.lib_deps} + ${esp32_ota.lib_deps} + [env:T_Beam_S3_Supreme_SX1262_room_server] extends = T_Beam_S3_Supreme_SX1262 build_flags = diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index d9ff1700..aa957fba 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -198,7 +198,7 @@ build_flags = -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' - -D MESH_PACKET_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 lib_deps = diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index 1b881c1a..be3b4943 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -54,6 +54,55 @@ lib_deps = ${Meshadventurer.lib_deps} ${esp32_ota.lib_deps} +; [env:Meshadventurer_sx1262_repeater_bridge_rs232] +; extends = Meshadventurer +; build_src_filter = ${Meshadventurer.build_src_filter} +; + +; +<../examples/simple_repeater> +; + +; build_flags = +; ${Meshadventurer.build_flags} +; -D RADIO_CLASS=CustomSX1262 +; -D WRAPPER_CLASS=CustomSX1262Wrapper +; -D LORA_TX_POWER=22 +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Meshadventurer.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Meshadventurer_sx1262_repeater_bridge_espnow] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + + + +<../examples/simple_repeater> + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + ${esp32_ota.lib_deps} + [env:Meshadventurer_sx1268_repeater] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} @@ -75,6 +124,55 @@ lib_deps = ${Meshadventurer.lib_deps} ${esp32_ota.lib_deps} +; [env:Meshadventurer_sx1268_repeater_bridge_rs232] +; extends = Meshadventurer +; build_src_filter = ${Meshadventurer.build_src_filter} +; + +; +<../examples/simple_repeater> +; + +; build_flags = +; ${Meshadventurer.build_flags} +; -D RADIO_CLASS=CustomSX1268 +; -D WRAPPER_CLASS=CustomSX1268Wrapper +; -D LORA_TX_POWER=22 +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Meshadventurer.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Meshadventurer_sx1268_repeater_bridge_espnow] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + + + +<../examples/simple_repeater> + + +build_flags = + ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Meshadventurer.lib_deps} + ${esp32_ota.lib_deps} + [env:Meshadventurer_sx1262_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 0e1631a8..908d6443 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -45,6 +45,47 @@ lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps} +; [env:Station_G2_repeater_bridge_rs232] +; extends = Station_G2 +; build_flags = +; ${Station_G2.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Station_G2.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Station_G2.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Station_G2_repeater_bridge_espnow] +extends = Station_G2 +build_flags = + ${Station_G2.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Station_G2.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Station_G2.lib_deps} + ${esp32_ota.lib_deps} + [env:Station_G2_logging_repeater] extends = Station_G2 build_flags = @@ -64,6 +105,49 @@ lib_deps = ${Station_G2.lib_deps} ${esp32_ota.lib_deps} +; [env:Station_G2_logging_repeater_bridge_rs232] +; extends = Station_G2 +; build_flags = +; ${Station_G2.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D SX126X_RX_BOOSTED_GAIN=1 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${Station_G2.build_src_filter} +; + +; +<../examples/simple_repeater> +; lib_deps = +; ${Station_G2.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Station_G2_logging_repeater_bridge_espnow] +extends = Station_G2 +build_flags = + ${Station_G2.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D MESH_PACKET_LOGGING=1 + -D SX126X_RX_BOOSTED_GAIN=1 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_DEBUG=1 +build_src_filter = ${Station_G2.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Station_G2.lib_deps} + ${esp32_ota.lib_deps} + [env:Station_G2_room_server] extends = Station_G2 build_src_filter = ${Station_G2.build_src_filter} diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 4967ec55..c22b3771 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -44,6 +44,55 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} +; [env:Tenstar_C3_Repeater_sx1262_bridge_rs232] +; extends = Tenstar_esp32_C3 +; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +; + +; +<../examples/simple_repeater/main.cpp> +; build_flags = +; ${Tenstar_esp32_C3.build_flags} +; -D RADIO_CLASS=CustomSX1262 +; -D WRAPPER_CLASS=CustomSX1262Wrapper +; -D SX126X_RX_BOOSTED_GAIN=1 +; -D LORA_TX_POWER=22 +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Tenstar_esp32_C3.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Tenstar_C3_Repeater_sx1262_bridge_espnow] +extends = Tenstar_esp32_C3 +build_src_filter = ${Tenstar_esp32_C3.build_src_filter} + + + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Tenstar_esp32_C3.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Tenstar_esp32_C3.lib_deps} + ${esp32_ota.lib_deps} + [env:Tenstar_C3_Repeater_sx1268] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} @@ -63,3 +112,50 @@ build_flags = lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} + +; [env:Tenstar_C3_Repeater_sx1268_bridge_rs232] +; extends = Tenstar_esp32_C3 +; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +; + +; +<../examples/simple_repeater/main.cpp> +; build_flags = +; ${Tenstar_esp32_C3.build_flags} +; -D RADIO_CLASS=CustomSX1268 +; -D WRAPPER_CLASS=CustomSX1268Wrapper +; -D LORA_TX_POWER=22 +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Tenstar_esp32_C3.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Tenstar_C3_Repeater_sx1268_bridge_espnow] +extends = Tenstar_esp32_C3 +build_src_filter = ${Tenstar_esp32_C3.build_src_filter} + + + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Tenstar_esp32_C3.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +lib_deps = + ${Tenstar_esp32_C3.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index b4f25e53..7408f85d 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -44,6 +44,47 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} ${esp32_ota.lib_deps} +; [env:Xiao_S3_WIO_Repeater_bridge_rs232] +; extends = Xiao_S3_WIO +; build_src_filter = ${Xiao_S3_WIO.build_src_filter} +; + +; +<../examples/simple_repeater/main.cpp> +; build_flags = +; ${Xiao_S3_WIO.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${Xiao_S3_WIO.lib_deps} +; ${esp32_ota.lib_deps} + +[env:Xiao_S3_WIO_Repeater_bridge_espnow] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + + + +<../examples/simple_repeater/main.cpp> +build_flags = + ${Xiao_S3_WIO.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_S3_WIO.lib_deps} + ${esp32_ota.lib_deps} + [env:Xiao_S3_WIO_room_server] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} From a55fa8d8ecc987f2fbdf2f792a6fb62e24099c54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= Date: Mon, 8 Sep 2025 20:21:33 +0100 Subject: [PATCH 038/546] Add BRIDGE_DELAY as a buffer to prevent immediate processing conflicts in the mesh network --- src/helpers/bridges/BridgeBase.cpp | 4 +++- src/helpers/bridges/BridgeBase.h | 12 ++++++++++-- src/helpers/bridges/RS232Bridge.cpp | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp index 20551190..03871418 100644 --- a/src/helpers/bridges/BridgeBase.cpp +++ b/src/helpers/bridges/BridgeBase.cpp @@ -1,5 +1,7 @@ #include "BridgeBase.h" +#include + const char *BridgeBase::getLogDateTime() { static char tmp[32]; uint32_t now = _rtc->getCurrentTime(); @@ -27,7 +29,7 @@ bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t rece void BridgeBase::handleReceivedPacket(mesh::Packet *packet) { if (!_seen_packets.hasSeen(packet)) { - _mgr->queueInbound(packet, 0); + _mgr->queueInbound(packet, millis() + BRIDGE_DELAY); } else { _mgr->free(packet); } diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index c1764ae3..ab62619f 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/src/helpers/bridges/BridgeBase.h @@ -23,7 +23,7 @@ public: /** * @brief Common magic number used by all bridge implementations for packet identification - * + * * This magic number is placed at the beginning of bridge packets to identify * them as mesh bridge packets and provide frame synchronization. */ @@ -31,7 +31,7 @@ public: /** * @brief Common field sizes used by bridge implementations - * + * * These constants define the size of common packet fields used across bridges. * BRIDGE_MAGIC_SIZE is used by all bridges for packet identification. * BRIDGE_LENGTH_SIZE is used by bridges that need explicit length fields (like RS232). @@ -41,6 +41,14 @@ public: static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t); static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t); + /** + * @brief Default delay in milliseconds for scheduling inbound packet processing + * + * It provides a buffer to prevent immediate processing conflicts in the mesh network. + * Used in handleReceivedPacket() as: millis() + BRIDGE_DELAY + */ + static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ? + protected: /** Packet manager for allocating and queuing mesh packets */ mesh::PacketManager *_mgr; diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index b209a6da..d182aea6 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -54,8 +54,8 @@ void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) { // Build packet header buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte - buffer[2] = (len >> 8) & 0xFF; // Length high byte - buffer[3] = len & 0xFF; // Length low byte + buffer[2] = (len >> 8) & 0xFF; // Length high byte + buffer[3] = len & 0xFF; // Length low byte // Calculate checksum over the payload uint16_t checksum = fletcher16(buffer + 4, len); From 0dfd2bcbb89f8d7f250dd84887a394e603f039e5 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf Date: Mon, 8 Sep 2025 22:57:48 +0200 Subject: [PATCH 039/546] README.md: Explain that companion nodes do not repeat messages This is a key difference compared to other systems and I see people asking this a lot. It is mentioned in the FAQ but let's make it more prominent in the README. --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cef44e0e..c14f20fd 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,10 @@ MeshCore provides the ability to create wireless mesh networks, similar to Mesht ## ⚡ Key Features -* Multi-Hop Packet Routing – Devices can forward messages across multiple nodes, extending range beyond a single radio's reach. MeshCore supports up to a configurable number of hops to balance network efficiency and prevent excessive traffic. +* Multi-Hop Packet Routing + * Devices can forward messages across multiple nodes, extending range beyond a single radio's reach. + * Supports up to a configurable number of hops to balance network efficiency and prevent excessive traffic. + * Nodes use fixed roles where "Companion" nodes are not repeating messages at all to prevent adverse routing paths from being used. * Supports LoRa Radios – Works with Heltec, RAK Wireless, and other LoRa-based hardware. * Decentralized & Resilient – No central server or internet required; the network is self-healing. * Low Power Consumption – Ideal for battery-powered or solar-powered devices. From bb29b66b29c7f957d68600c343e8a53232aeb258 Mon Sep 17 00:00:00 2001 From: ripplebiz Date: Tue, 9 Sep 2025 14:05:07 +1000 Subject: [PATCH 040/546] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index cef44e0e..7d5186ec 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,14 @@ Here are some general principals you should try to adhere to: There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order: - [X] Companion radio: UI redesign +- [ ] Repeater + Room Server: add ACL's (like Sensor Node has) +- [ ] Standardise Bridge mode for repeaters +- [ ] Repeater/Bridge: Standardise the Transport Codes for zoning/filtering - [ ] Core + Repeater: enhanced zero-hop neighbour discovery - [ ] Core: round-trip manual path support - [ ] Companion + Apps: support for multiple sub-meshes (and 'off-grid' client repeat mode) +- [ ] Core + Apps: support for LZW message compression +- [ ] Core: dynamic CR (Coding Rate) for weak vs strong hops - [ ] Core: new framework for hosting multiple virtual nodes on one physical device - [ ] V2 protocol spec: discussion and concensus around V2 packet protocol, including path hashes, new encryption specs, etc From e8314c9c8c6deecc2af1ecf01ed31dcf471322b1 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 9 Sep 2025 16:55:46 +1000 Subject: [PATCH 041/546] new ldscript for extrafs nrf companion envs --- boards/nrf52840_s140_v6_extrafs.ld | 38 ++++++++++++++++++++++ boards/nrf52840_s140_v7_extrafs.ld | 38 ++++++++++++++++++++++ variants/heltec_mesh_solar/platformio.ini | 4 +++ variants/heltec_t114/platformio.ini | 8 +++++ variants/ikoka_stick_nrf/platformio.ini | 4 +++ variants/lilygo_techo/platformio.ini | 4 +++ variants/mesh_pocket/platformio.ini | 4 +++ variants/minewsemi_me25ls01/platformio.ini | 4 +++ variants/nano_g2_ultra/platformio.ini | 4 +++ variants/promicro/platformio.ini | 4 +++ variants/rak4631/platformio.ini | 4 +++ variants/rak_wismesh_tag/platformio.ini | 4 +++ variants/sensecap_solar/platformio.ini | 4 +++ variants/t1000-e/platformio.ini | 4 +++ variants/thinknode_m1/platformio.ini | 4 +++ variants/wio-tracker-l1/platformio.ini | 4 +++ variants/xiao_nrf52/platformio.ini | 4 +++ 17 files changed, 140 insertions(+) create mode 100644 boards/nrf52840_s140_v6_extrafs.ld create mode 100644 boards/nrf52840_s140_v7_extrafs.ld diff --git a/boards/nrf52840_s140_v6_extrafs.ld b/boards/nrf52840_s140_v6_extrafs.ld new file mode 100644 index 00000000..35261067 --- /dev/null +++ b/boards/nrf52840_s140_v6_extrafs.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0xD4000 - 0x26000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/boards/nrf52840_s140_v7_extrafs.ld b/boards/nrf52840_s140_v7_extrafs.ld new file mode 100644 index 00000000..5956183a --- /dev/null +++ b/boards/nrf52840_s140_v7_extrafs.ld @@ -0,0 +1,38 @@ +/* Linker script to configure memory regions. */ + +SEARCH_DIR(.) +GROUP(-lgcc -lc -lnosys) + +MEMORY +{ + FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xD4000 - 0x27000 + + /* SRAM required by Softdevice depend on + * - Attribute Table Size (Number of Services and Characteristics) + * - Vendor UUID count + * - Max ATT MTU + * - Concurrent connection peripheral + central + secure links + * - Event Len, HVN queue, Write CMD queue + */ + RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000 +} + +SECTIONS +{ + . = ALIGN(4); + .svc_data : + { + PROVIDE(__start_svc_data = .); + KEEP(*(.svc_data)) + PROVIDE(__stop_svc_data = .); + } > RAM + + .fs_data : + { + PROVIDE(__start_fs_data = .); + KEEP(*(.fs_data)) + PROVIDE(__stop_fs_data = .); + } > RAM +} INSERT AFTER .data; + +INCLUDE "nrf52_common.ld" diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 18c4ac73..c5d8c3e8 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -57,6 +57,8 @@ build_flags = [env:Heltec_mesh_solar_companion_radio_ble] extends = Heltec_mesh_solar +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Heltec_mesh_solar.build_flags} -D MAX_CONTACTS=350 @@ -75,6 +77,8 @@ lib_deps = [env:Heltec_mesh_solar_companion_radio_usb] extends = Heltec_mesh_solar +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Heltec_mesh_solar.build_flags} -D MAX_CONTACTS=350 diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index dec3282d..4bbc05b1 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -70,6 +70,8 @@ build_flags = [env:Heltec_t114_without_display_companion_radio_ble] extends = Heltec_t114 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Heltec_t114.build_flags} -I examples/companion_radio/ui-new @@ -90,6 +92,8 @@ lib_deps = [env:Heltec_t114_without_display_companion_radio_usb] extends = Heltec_t114 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Heltec_t114.build_flags} -I examples/companion_radio/ui-new @@ -158,6 +162,8 @@ build_flags = [env:Heltec_t114_companion_radio_ble] extends = Heltec_t114_with_display +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Heltec_t114_with_display.build_flags} -I examples/companion_radio/ui-new @@ -178,6 +184,8 @@ lib_deps = [env:Heltec_t114_companion_radio_usb] extends = Heltec_t114_with_display +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Heltec_t114_with_display.build_flags} -I examples/companion_radio/ui-new diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 1f2bbfe9..071d2d4f 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -101,6 +101,8 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} [ikoka_stick_nrf_companion_radio_ble] extends = ikoka_stick_nrf_baseboard +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${ikoka_stick_nrf_baseboard.build_flags} -D MAX_CONTACTS=350 @@ -121,6 +123,8 @@ lib_deps = [ikoka_stick_nrf_companion_radio_usb] extends = ikoka_stick_nrf_baseboard +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${ikoka_stick_nrf_baseboard.build_flags} -D MAX_CONTACTS=350 diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index 7d64fad7..fb25326f 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -78,6 +78,8 @@ build_flags = [env:LilyGo_T-Echo_companion_radio_ble] extends = LilyGo_T-Echo +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${LilyGo_T-Echo.build_flags} -I src/helpers/ui @@ -101,6 +103,8 @@ lib_deps = [env:LilyGo_T-Echo_companion_radio_usb] extends = LilyGo_T-Echo +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${LilyGo_T-Echo.build_flags} -I src/helpers/ui diff --git a/variants/mesh_pocket/platformio.ini b/variants/mesh_pocket/platformio.ini index 7c996157..1ed0d1ec 100644 --- a/variants/mesh_pocket/platformio.ini +++ b/variants/mesh_pocket/platformio.ini @@ -67,6 +67,8 @@ build_flags = [env:Mesh_pocket_companion_radio_ble] extends = Mesh_pocket +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Mesh_pocket.build_flags} -I examples/companion_radio/ui-new @@ -89,6 +91,8 @@ lib_deps = [env:Mesh_pocket_companion_radio_usb] extends = Mesh_pocket +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Mesh_pocket.build_flags} -I examples/companion_radio/ui-new diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index 71887002..da234dd2 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -50,6 +50,8 @@ lib_deps = ${nrf52840_me25ls01.lib_deps} [env:Minewsemi_me25ls01_companion_radio_ble] extends = me25ls01 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${me25ls01.build_flags} -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 @@ -147,6 +149,8 @@ lib_deps = ${me25ls01.lib_deps} [env:Minewsemi_me25ls01_companion_radio_usb] extends = me25ls01 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${me25ls01.build_flags} -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 163f4311..1a9a7001 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -31,6 +31,8 @@ upload_protocol = nrfutil [env:Nano_G2_Ultra_companion_radio_ble] extends = Nano_G2_Ultra +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Nano_G2_Ultra.build_flags} -I src/helpers/ui @@ -62,6 +64,8 @@ lib_deps = [env:Nano_G2_Ultra_companion_radio_usb] extends = Nano_G2_Ultra +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Nano_G2_Ultra.build_flags} -I src/helpers/ui diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 5a70f7ba..a65d3c69 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -86,6 +86,8 @@ lib_deps = ${Faketec.lib_deps} [env:Faketec_companion_radio_usb] extends = Faketec +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Faketec.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 @@ -104,6 +106,8 @@ lib_deps = ${Faketec.lib_deps} [env:Faketec_companion_radio_ble] extends = Faketec +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${Faketec.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 7e7d2234..01f4d088 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -64,6 +64,8 @@ build_src_filter = ${rak4631.build_src_filter} [env:RAK_4631_companion_radio_usb] extends = rak4631 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${rak4631.build_flags} -I examples/companion_radio/ui-new @@ -83,6 +85,8 @@ lib_deps = [env:RAK_4631_companion_radio_ble] extends = rak4631 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${rak4631.build_flags} -I examples/companion_radio/ui-new diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 572919eb..ab14a689 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -66,6 +66,8 @@ build_src_filter = ${rak_wismesh_tag.build_src_filter} [env:RAK_WisMesh_Tag_companion_radio_usb] extends = rak_wismesh_tag +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${rak_wismesh_tag.build_flags} -I examples/companion_radio/ui-orig @@ -83,6 +85,8 @@ lib_deps = [env:RAK_WisMesh_Tag_companion_radio_ble] extends = rak_wismesh_tag +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${rak_wismesh_tag.build_flags} -I examples/companion_radio/ui-orig diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 649ace84..0c8d5e12 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -74,6 +74,8 @@ build_src_filter = ${SenseCap_Solar.build_src_filter} [env:SenseCap_Solar_companion_radio_ble] extends = SenseCap_Solar +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${SenseCap_Solar.build_flags} -D MAX_CONTACTS=350 @@ -92,6 +94,8 @@ lib_deps = [env:SenseCap_Solar_companion_radio_usb] extends = SenseCap_Solar +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${SenseCap_Solar.build_flags} -D MAX_CONTACTS=350 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 1f7d60dd..4f635f4d 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -68,6 +68,8 @@ lib_deps = ${t1000-e.lib_deps} [env:t1000e_companion_radio_usb] extends = t1000-e +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${t1000-e.build_flags} -I examples/companion_radio/ui-orig -D MAX_CONTACTS=100 @@ -89,6 +91,8 @@ lib_deps = ${t1000-e.lib_deps} [env:t1000e_companion_radio_ble] extends = t1000-e +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${t1000-e.build_flags} -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index eeeb692e..97f2370e 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -67,6 +67,8 @@ lib_deps = [env:ThinkNode_M1_companion_radio_ble] extends = ThinkNode_M1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${ThinkNode_M1.build_flags} -I src/helpers/ui @@ -99,6 +101,8 @@ lib_deps = [env:ThinkNode_M1_companion_radio_usb] extends = ThinkNode_M1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 build_flags = ${ThinkNode_M1.build_flags} -I src/helpers/ui diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 87670bd0..a5debd72 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -55,6 +55,8 @@ lib_deps = ${WioTrackerL1.lib_deps} [env:WioTrackerL1_companion_radio_usb] extends = WioTrackerL1 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${WioTrackerL1.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=100 @@ -77,6 +79,8 @@ lib_deps = ${WioTrackerL1.lib_deps} [env:WioTrackerL1_companion_radio_ble] extends = WioTrackerL1 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${WioTrackerL1.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 10807476..1ce7186c 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -57,6 +57,8 @@ upload_protocol = nrfutil [env:Xiao_nrf52_companion_radio_ble] extends = Xiao_nrf52 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} -D MAX_CONTACTS=350 @@ -76,6 +78,8 @@ lib_deps = [env:Xiao_nrf52_companion_radio_usb] extends = Xiao_nrf52 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} -D MAX_CONTACTS=350 From f92bd0db9e9229f6e82add6e9036663f6fdb0977 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 9 Sep 2025 17:00:29 +1000 Subject: [PATCH 042/546] fix inconsistencies across nrf companion roles --- variants/lilygo_techo/platformio.ini | 1 + variants/nano_g2_ultra/platformio.ini | 1 + variants/t1000-e/platformio.ini | 4 ++-- variants/wio-tracker-l1/platformio.ini | 5 +++-- variants/xiao_nrf52/platformio.ini | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index fb25326f..6a71a7bf 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -114,6 +114,7 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 -D UI_RECENT_LIST_SIZE=9 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D QSPIFLASH=1 build_src_filter = ${LilyGo_T-Echo.build_src_filter} +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 1a9a7001..116a1f25 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -72,6 +72,7 @@ build_flags = -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D QSPIFLASH=1 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display -D PIN_BUZZER=4 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 4f635f4d..69d9dccf 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -72,8 +72,8 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${t1000-e.build_flags} -I examples/companion_radio/ui-orig - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index a5debd72..585e0ba7 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -59,11 +59,12 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${WioTrackerL1.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SH1106Display -D OFFLINE_QUEUE_SIZE=256 -D PIN_BUZZER=12 + -D QSPIFLASH=1 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 1ce7186c..74ac6a56 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -84,6 +84,7 @@ build_flags = ${Xiao_nrf52.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 + -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} From 5344f04d899cb0fadf5a36ef28b89af312ebd53f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 9 Sep 2025 18:46:30 +1000 Subject: [PATCH 043/546] * Repeater: slight refactor of 'bridge' instantiation --- examples/simple_repeater/main.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 4862d7d0..e4d8a454 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -124,10 +124,6 @@ struct ClientInfo { #define MAX_CLIENTS 32 #endif -#ifdef WITH_BRIDGE -AbstractBridge* bridge; -#endif - struct NeighbourInfo { mesh::Identity id; uint32_t advert_timestamp; @@ -154,6 +150,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { float pending_bw; uint8_t pending_sf; uint8_t pending_cr; +#if defined(WITH_RS232_BRIDGE) + RS232Bridge bridge; +#elif defined(WITH_ESPNOW_BRIDGE) + ESPNowBridge bridge; +#endif ClientInfo* putClient(const mesh::Identity& id) { uint32_t min_time = 0xFFFFFFFF; @@ -315,7 +316,7 @@ protected: } void logTx(mesh::Packet* pkt, int len) override { #ifdef WITH_BRIDGE - bridge->onPacketTransmitted(pkt); + bridge.onPacketTransmitted(pkt); #endif if (_logging) { File f = openAppend(PACKET_LOG_FILE); @@ -581,16 +582,12 @@ public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) - { -#ifdef WITH_BRIDGE #if defined(WITH_RS232_BRIDGE) - bridge = new RS232Bridge(WITH_RS232_BRIDGE, _mgr, &rtc); + , bridge(WITH_RS232_BRIDGE, _mgr, &rtc) #elif defined(WITH_ESPNOW_BRIDGE) - bridge = new ESPNowBridge(_mgr, &rtc); -#else -#error "You must choose either RS232 or ESPNow bridge" -#endif + , bridge(_mgr, &rtc) #endif + { memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; set_radio_at = revert_radio_at = 0; @@ -626,6 +623,10 @@ public: // load persisted prefs _cli.loadPrefs(_fs); + #ifdef WITH_BRIDGE + bridge.begin(); + #endif + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); @@ -792,7 +793,7 @@ public: void loop() { #ifdef WITH_BRIDGE - bridge->loop(); + bridge.loop(); #endif mesh::Mesh::loop(); @@ -843,10 +844,6 @@ void setup() { Serial.begin(115200); delay(1000); -#ifdef WITH_BRIDGE - bridge->begin(); -#endif - board.begin(); #ifdef DISPLAY_CLASS From 3666cd72e5f583f2fdf8b5f0e2e41570c5835e2e Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Tue, 9 Sep 2025 20:52:19 +1000 Subject: [PATCH 044/546] * room refactor: extracted MyMesh class --- examples/simple_room_server/MyMesh.cpp | 809 ++++++++++++++++++++ examples/simple_room_server/MyMesh.h | 220 ++++++ examples/simple_room_server/main.cpp | 971 +------------------------ 3 files changed, 1033 insertions(+), 967 deletions(-) create mode 100644 examples/simple_room_server/MyMesh.cpp create mode 100644 examples/simple_room_server/MyMesh.h diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp new file mode 100644 index 00000000..83d18137 --- /dev/null +++ b/examples/simple_room_server/MyMesh.cpp @@ -0,0 +1,809 @@ +#include "MyMesh.h" + +#define REPLY_DELAY_MILLIS 1500 +#define PUSH_NOTIFY_DELAY_MILLIS 2000 +#define SYNC_PUSH_INTERVAL 1200 + +#define PUSH_ACK_TIMEOUT_FLOOD 12000 +#define PUSH_TIMEOUT_BASE 4000 +#define PUSH_ACK_TIMEOUT_FACTOR 2000 + +#define POST_SYNC_DELAY_SECS 6 + +#define CLIENT_KEEP_ALIVE_SECS 0 // Now Disabled (was 128) + +#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS +#define REQ_TYPE_KEEP_ALIVE 0x02 +#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 + +#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ + +struct ServerStats { + uint16_t batt_milli_volts; + uint16_t curr_tx_queue_len; + int16_t noise_floor; + int16_t last_rssi; + uint32_t n_packets_recv; + uint32_t n_packets_sent; + uint32_t total_air_time_secs; + uint32_t total_up_time_secs; + uint32_t n_sent_flood, n_sent_direct; + uint32_t n_recv_flood, n_recv_direct; + uint16_t err_events; // was 'n_full_events' + int16_t last_snr; // x 4 + uint16_t n_direct_dups, n_flood_dups; + uint16_t n_posted, n_post_push; +}; + +ClientInfo *MyMesh::putClient(const mesh::Identity &id) { + for (int i = 0; i < num_clients; i++) { + if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known + } + ClientInfo *newClient; + if (num_clients < MAX_CLIENTS) { + newClient = &known_clients[num_clients++]; + } else { // table is currently full + // evict least active client + uint32_t oldest_timestamp = 0xFFFFFFFF; + newClient = &known_clients[0]; + for (int i = 0; i < num_clients; i++) { + auto c = &known_clients[i]; + if (c->last_activity < oldest_timestamp) { + oldest_timestamp = c->last_activity; + newClient = c; + } + } + } + newClient->id = id; + newClient->out_path_len = -1; // initially out_path is unknown + newClient->last_timestamp = 0; + return newClient; +} + +void MyMesh::evict(ClientInfo *client) { + client->last_activity = 0; // this slot will now be re-used (will be oldest) + memset(client->id.pub_key, 0, sizeof(client->id.pub_key)); + memset(client->secret, 0, sizeof(client->secret)); + client->pending_ack = 0; +} + +void MyMesh::addPost(ClientInfo *client, const char *postData) { + // TODO: suggested postData format: /<descrption> + posts[next_post_idx].author = client->id; // add to cyclic queue + StrHelper::strncpy(posts[next_post_idx].text, postData, MAX_POST_TEXT_LEN); + + posts[next_post_idx].post_timestamp = getRTCClock()->getCurrentTimeUnique(); + next_post_idx = (next_post_idx + 1) % MAX_UNSYNCED_POSTS; + + next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); + _num_posted++; // stats +} + +void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { + int len = 0; + memcpy(&reply_data[len], &post.post_timestamp, 4); + len += 4; // this is a PAST timestamp... but should be accepted by client + + uint8_t attempt; + getRNG()->random(&attempt, 1); // need this for re-tries, so packet hash (and ACK) will be different + reply_data[len++] = (TXT_TYPE_SIGNED_PLAIN << 2) | (attempt & 3); // 'signed' plain text + + // encode prefix of post.author.pub_key + memcpy(&reply_data[len], post.author.pub_key, 4); + len += 4; // just first 4 bytes + + int text_len = strlen(post.text); + memcpy(&reply_data[len], post.text, text_len); + len += text_len; + + // calc expected ACK reply + mesh::Utils::sha256((uint8_t *)&client->pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); + client->push_post_timestamp = post.post_timestamp; + + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->secret, reply_data, len); + if (reply) { + if (client->out_path_len < 0) { + sendFlood(reply); + client->ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); + } else { + sendDirect(reply, client->out_path, client->out_path_len); + client->ack_timeout = + futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); + } + _num_post_pushes++; // stats + } else { + client->pending_ack = 0; + MESH_DEBUG_PRINTLN("Unable to push post to client"); + } +} + +uint8_t MyMesh::getUnsyncedCount(ClientInfo *client) { + uint8_t count = 0; + for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) { + if (posts[k].post_timestamp > client->sync_since // is new post for this Client? + && !posts[k].author.matches(client->id)) { // don't push posts to the author + count++; + } + } + return count; +} + +bool MyMesh::processAck(const uint8_t *data) { + for (int i = 0; i < num_clients; i++) { + auto client = &known_clients[i]; + if (client->pending_ack && memcmp(data, &client->pending_ack, 4) == 0) { // got an ACK from Client! + client->pending_ack = 0; // clear this, so next push can happen + client->push_failures = 0; + client->sync_since = client->push_post_timestamp; // advance Client's SINCE timestamp, to sync next post + return true; + } + } + return false; +} + +mesh::Packet *MyMesh::createSelfAdvert() { + uint8_t app_data[MAX_ADVERT_DATA_SIZE]; + uint8_t app_data_len; + { + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } + + return createAdvert(self_id, app_data, app_data_len); +} + +File MyMesh::openAppend(const char *fname) { +#if defined(NRF52_PLATFORM) + return _fs->open(fname, FILE_O_WRITE); +#elif defined(RP2040_PLATFORM) + return _fs->open(fname, "a"); +#else + return _fs->open(fname, "a", true); +#endif +} + +int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, + size_t payload_len) { + // uint32_t now = getRTCClock()->getCurrentTimeUnique(); + // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp + memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') + + switch (payload[0]) { + case REQ_TYPE_GET_STATUS: { + ServerStats stats; + stats.batt_milli_volts = board.getBattMilliVolts(); + stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); + stats.noise_floor = (int16_t)_radio->getNoiseFloor(); + stats.last_rssi = (int16_t)radio_driver.getLastRSSI(); + stats.n_packets_recv = radio_driver.getPacketsRecv(); + stats.n_packets_sent = radio_driver.getPacketsSent(); + stats.total_air_time_secs = getTotalAirTime() / 1000; + stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.n_sent_flood = getNumSentFlood(); + stats.n_sent_direct = getNumSentDirect(); + stats.n_recv_flood = getNumRecvFlood(); + stats.n_recv_direct = getNumRecvDirect(); + stats.err_events = _err_flags; + stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); + stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); + stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); + stats.n_posted = _num_posted; + stats.n_post_push = _num_post_pushes; + + memcpy(&reply_data[4], &stats, sizeof(stats)); + return 4 + sizeof(stats); + } + + case REQ_TYPE_GET_TELEMETRY_DATA: { + uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions + + telemetry.reset(); + telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + // query other sensors -- target specific + sensors.querySensors((sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00) & perm_mask, telemetry); + + uint8_t tlen = telemetry.getSize(); + memcpy(&reply_data[4], telemetry.getBuffer(), tlen); + return 4 + tlen; // reply_len + } + } + return 0; // unknown command +} + +void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { +#if MESH_PACKET_LOGGING + Serial.print(getLogDateTime()); + Serial.print(" RAW: "); + mesh::Utils::printHex(Serial, raw, len); + Serial.println(); +#endif +} + +void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { + if (_logging) { + File f = openAppend(PACKET_LOG_FILE); + if (f) { + f.print(getLogDateTime()); + f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", len, + pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, + (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score * 1000)); + + if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || + pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { + f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + } else { + f.printf("\n"); + } + f.close(); + } + } +} +void MyMesh::logTx(mesh::Packet *pkt, int len) { + if (_logging) { + File f = openAppend(PACKET_LOG_FILE); + if (f) { + f.print(getLogDateTime()); + f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", len, pkt->getPayloadType(), + pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + + if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ || + pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { + f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); + } else { + f.printf("\n"); + } + f.close(); + } + } +} +void MyMesh::logTxFail(mesh::Packet *pkt, int len) { + if (_logging) { + File f = openAppend(PACKET_LOG_FILE); + if (f) { + f.print(getLogDateTime()); + f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", len, pkt->getPayloadType(), + pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); + f.close(); + } + } +} + +int MyMesh::calcRxDelay(float score, uint32_t air_time) const { + if (_prefs.rx_delay_base <= 0.0f) return 0; + return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); +} + +const char *MyMesh::getLogDateTime() { + static char tmp[32]; + uint32_t now = getRTCClock()->getCurrentTime(); + DateTime dt = DateTime(now); + sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), + dt.year()); + return tmp; +} + +uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + return getRNG()->nextInt(0, 6) * t; +} +uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + return getRNG()->nextInt(0, 6) * t; +} + +bool MyMesh::allowPacketForward(const mesh::Packet *packet) { + if (_prefs.disable_fwd) return false; + if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + return true; +} + +void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, + uint8_t *data, size_t len) { + if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin + // client (unknown at this stage) + uint32_t sender_timestamp, sender_sync_since; + memcpy(&sender_timestamp, data, 4); + memcpy(&sender_sync_since, &data[4], 4); // sender's "sync messags SINCE x" timestamp + + RoomPermission perm; + data[len] = 0; // ensure null terminator + if (strcmp((char *)&data[8], _prefs.password) == 0) { // check for valid admin password + perm = RoomPermission::ADMIN; + } else { + if (strcmp((char *)&data[8], _prefs.guest_password) == 0) { // check the room/public password + perm = RoomPermission::GUEST; + } else if (_prefs.allow_read_only) { + perm = RoomPermission::READ_ONLY; + } else { + MESH_DEBUG_PRINTLN("Incorrect room password"); + return; // no response. Client will timeout + } + } + + auto client = putClient(sender); // add to known clients (if not already known) + if (sender_timestamp <= client->last_timestamp) { + MESH_DEBUG_PRINTLN("possible replay attack!"); + return; + } + + MESH_DEBUG_PRINTLN("Login success!"); + client->permission = perm; + client->last_timestamp = sender_timestamp; + client->sync_since = sender_sync_since; + client->pending_ack = 0; + client->push_failures = 0; + memcpy(client->secret, secret, PUB_KEY_SIZE); + + uint32_t now = getRTCClock()->getCurrentTime(); + client->last_activity = now; + + now = getRTCClock()->getCurrentTimeUnique(); + memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp + // TODO: maybe reply with count of messages waiting to be synced for THIS client? + reply_data[4] = RESP_SERVER_LOGIN_OK; + reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16) + reply_data[6] = (perm == RoomPermission::ADMIN ? 1 : (perm == RoomPermission::GUEST ? 0 : 2)); + reply_data[7] = getUnsyncedCount(client); // NEW + memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed + + next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first + + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response + mesh::Packet *path = createPathReturn(sender, client->secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, 8 + 2); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 8 + 2); + if (reply) { + if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + } else { + sendFlood(reply, SERVER_RESPONSE_DELAY); + } + } + } + } +} + +int MyMesh::searchPeersByHash(const uint8_t *hash) { + int n = 0; + for (int i = 0; i < num_clients; i++) { + if (known_clients[i].id.isHashMatch(hash)) { + matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) + } + } + return n; +} + +void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { + int i = matching_peer_indexes[peer_idx]; + if (i >= 0 && i < num_clients) { + // lookup pre-calculated shared_secret + memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE); + } else { + MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); + } +} + +void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, + uint8_t *data, size_t len) { + int i = matching_peer_indexes[sender_idx]; + if (i < 0 || i >= num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) + MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); + return; + } + auto client = &known_clients[i]; + if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post + uint32_t sender_timestamp; + memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) + uint flags = (data[4] >> 2); // message attempt number, and other flags + + if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { + MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags); + } else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks, but send Acks for retries + bool is_retry = (sender_timestamp == client->last_timestamp); + client->last_timestamp = sender_timestamp; + + uint32_t now = getRTCClock()->getCurrentTimeUnique(); + client->last_activity = now; + client->push_failures = 0; // reset so push can resume (if prev failed) + + // len can be > original length, but 'text' will be padded with zeroes + data[len] = 0; // need to make a C string again, with null terminator + + uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to + // sender that we got it + mesh::Utils::sha256((uint8_t *)&ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, + PUB_KEY_SIZE); + + uint8_t temp[166]; + bool send_ack; + if (flags == TXT_TYPE_CLI_DATA) { + if (client->permission == RoomPermission::ADMIN) { + if (is_retry) { + temp[5] = 0; // no reply + } else { + handleCommand(sender_timestamp, (char *)&data[5], (char *)&temp[5]); + temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN) + } + send_ack = false; + } else { + temp[5] = 0; // no reply + send_ack = false; // and no ACK... user shoudn't be sending these + } + } else { // TXT_TYPE_PLAIN + if (client->permission == RoomPermission::READ_ONLY) { + temp[5] = 0; // no reply + send_ack = false; // no ACK + } else { + if (!is_retry) { + addPost(client, (const char *)&data[5]); + } + temp[5] = 0; // no reply (ACK is enough) + send_ack = true; + } + } + + uint32_t delay_millis; + if (send_ack) { + if (client->out_path_len < 0) { + mesh::Packet *ack = createAck(ack_hash); + if (ack) sendFlood(ack, TXT_ACK_DELAY); + delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; + } else { + uint32_t d = TXT_ACK_DELAY; + if (getExtraAckTransmitCount() > 0) { + mesh::Packet *a1 = createMultiAck(ack_hash, 1); + if (a1) sendDirect(a1, client->out_path, client->out_path_len, d); + d += 300; + } + + mesh::Packet *a2 = createAck(ack_hash); + if (a2) sendDirect(a2, client->out_path, client->out_path_len, d); + delay_millis = d + REPLY_DELAY_MILLIS; + } + } else { + delay_millis = 0; + } + + int text_len = strlen((char *)&temp[5]); + if (text_len > 0) { + if (now == sender_timestamp) { + // WORKAROUND: the two timestamps need to be different, in the CLI view + now++; + } + memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique + + // calc expected ACK reply + // mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, + // PUB_KEY_SIZE); + + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); + if (reply) { + if (client->out_path_len < 0) { + sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY); + } else { + sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY); + } + } + } + } else { + MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); + } + } else if (type == PAYLOAD_TYPE_REQ && len >= 5) { + uint32_t sender_timestamp; + memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) + if (sender_timestamp < client->last_timestamp) { // prevent replay attacks + MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); + } else { + client->last_timestamp = sender_timestamp; + + uint32_t now = getRTCClock()->getCurrentTime(); + client->last_activity = now; // <-- THIS will keep client connection alive + client->push_failures = 0; // reset so push can resume (if prev failed) + + if (data[4] == REQ_TYPE_KEEP_ALIVE && packet->isRouteDirect()) { // request type + uint32_t forceSince = 0; + if (len >= 9) { // optional - last post_timestamp client received + memcpy(&forceSince, &data[5], 4); // NOTE: this may be 0, if part of decrypted PADDING! + } else { + memcpy(&data[5], &forceSince, 4); // make sure there are zeroes in payload (for ack_hash calc below) + } + if (forceSince > 0) { + client->sync_since = forceSince; // force-update the 'sync since' + } + + client->pending_ack = 0; + + // TODO: Throttle KEEP_ALIVE requests! + // if client sends too quickly, evict() + + // RULE: only send keep_alive response DIRECT! + if (client->out_path_len >= 0) { + uint32_t ack_hash; // calc ACK to prove to sender that we got request + mesh::Utils::sha256((uint8_t *)&ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE); + + auto reply = createAck(ack_hash); + if (reply) { + reply->payload[reply->payload_len++] = getUnsyncedCount(client); // NEW: add unsynced counter to end of ACK packet + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + } + } + } else { + int reply_len = handleRequest(client, sender_timestamp, &data[4], len - 4); + if (reply_len > 0) { // valid command + if (packet->isRouteFlood()) { + // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response + mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); + if (reply) { + if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + } else { + sendFlood(reply, SERVER_RESPONSE_DELAY); + } + } + } + } + } + } + } +} + +bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t *secret, uint8_t *path, + uint8_t path_len, uint8_t extra_type, uint8_t *extra, uint8_t extra_len) { + // TODO: prevent replay attacks + int i = matching_peer_indexes[sender_idx]; + + if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) + MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); + auto client = &known_clients[i]; + memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + } else { + MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); + } + + if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) { + // also got an encoded ACK! + processAck(extra); + } + + // NOTE: no reciprocal path send!! + return false; +} + +void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) { + if (processAck((uint8_t *)&ack_crc)) { + packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit + } +} + +MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, + mesh::RTCClock &rtc, mesh::MeshTables &tables) + : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), + _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + next_local_advert = next_flood_advert = 0; + _logging = false; + set_radio_at = revert_radio_at = 0; + + // defaults + memset(&_prefs, 0, sizeof(_prefs)); + _prefs.airtime_factor = 1.0; // one half + _prefs.rx_delay_base = 0.0f; // off by default, was 10.0 + _prefs.tx_delay_factor = 0.5f; // was 0.25f; + StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); + _prefs.node_lat = ADVERT_LAT; + _prefs.node_lon = ADVERT_LON; + StrHelper::strncpy(_prefs.password, ADMIN_PASSWORD, sizeof(_prefs.password)); + _prefs.freq = LORA_FREQ; + _prefs.sf = LORA_SF; + _prefs.bw = LORA_BW; + _prefs.cr = LORA_CR; + _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.disable_fwd = 1; + _prefs.advert_interval = 1; // default to 2 minutes for NEW installs + _prefs.flood_advert_interval = 12; // 12 hours + _prefs.flood_max = 64; + _prefs.interference_threshold = 0; // disabled +#ifdef ROOM_PASSWORD + StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); +#endif + + num_clients = 0; + next_post_idx = 0; + next_client_idx = 0; + next_push = 0; + memset(posts, 0, sizeof(posts)); + _num_posted = _num_post_pushes = 0; +} + +void MyMesh::begin(FILESYSTEM *fs) { + mesh::Mesh::begin(); + _fs = fs; + // load persisted prefs + _cli.loadPrefs(_fs); + + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + radio_set_tx_power(_prefs.tx_power_dbm); + + updateAdvertTimer(); + updateFloodAdvertTimer(); +} + +void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { + set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params + pending_freq = freq; + pending_bw = bw; + pending_sf = sf; + pending_cr = cr; + + revert_radio_at = futureMillis(2000 + timeout_mins * 60 * 1000); // schedule when to revert radio params +} + +bool MyMesh::formatFileSystem() { +#if defined(NRF52_PLATFORM) + return InternalFS.format(); +#elif defined(RP2040_PLATFORM) + return LittleFS.format(); +#elif defined(ESP32) + return SPIFFS.format(); +#else +#error "need to implement file system erase" + return false; +#endif +} + +void MyMesh::sendSelfAdvertisement(int delay_millis) { + mesh::Packet *pkt = createSelfAdvert(); + if (pkt) { + sendFlood(pkt, delay_millis); + } else { + MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); + } +} + +void MyMesh::updateAdvertTimer() { + if (_prefs.advert_interval > 0) { // schedule local advert timer + next_local_advert = futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); + } else { + next_local_advert = 0; // stop the timer + } +} +void MyMesh::updateFloodAdvertTimer() { + if (_prefs.flood_advert_interval > 0) { // schedule flood advert timer + next_flood_advert = futureMillis(((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000); + } else { + next_flood_advert = 0; // stop the timer + } +} + +void MyMesh::dumpLogFile() { +#if defined(RP2040_PLATFORM) + File f = _fs->open(PACKET_LOG_FILE, "r"); +#else + File f = _fs->open(PACKET_LOG_FILE); +#endif + if (f) { + while (f.available()) { + int c = f.read(); + if (c < 0) break; + Serial.print((char)c); + } + f.close(); + } +} + +void MyMesh::setTxPower(uint8_t power_dbm) { + radio_set_tx_power(power_dbm); +} + +void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { + self_id = new_id; +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + IdentityStore store(*_fs, ""); +#elif defined(ESP32) + IdentityStore store(*_fs, "/identity"); +#elif defined(RP2040_PLATFORM) + IdentityStore store(*_fs, "/identity"); +#else +#error "need to define saveIdentity()" +#endif + store.save("_main", self_id); +} + +void MyMesh::clearStats() { + radio_driver.resetStats(); + resetStats(); + ((SimpleMeshTables *)getTables())->resetStats(); +} + +void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { + while (*command == ' ') + command++; // skip leading spaces + + if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) + memcpy(reply, command, 3); // reflect the prefix back + reply += 3; + command += 3; + } + + _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands +} + +void MyMesh::loop() { + mesh::Mesh::loop(); + + if (millisHasNowPassed(next_push) && num_clients > 0) { + // check for ACK timeouts + for (int i = 0; i < num_clients; i++) { + auto c = &known_clients[i]; + if (c->pending_ack && millisHasNowPassed(c->ack_timeout)) { + c->push_failures++; + c->pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) + MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); + } + } + // check next Round-Robin client, and sync next new post + auto client = &known_clients[next_client_idx]; + bool did_push = false; + if (client->pending_ack == 0 && client->last_activity != 0 && + client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max + MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t)client->id.pub_key[0]); + uint32_t now = getRTCClock()->getCurrentTime(); + for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) { + auto p = &posts[idx]; + if (now >= p->post_timestamp + POST_SYNC_DELAY_SECS && + p->post_timestamp > client->sync_since // is new post for this Client? + && !p->author.matches(client->id)) { // don't push posts to the author + // push this post to Client, then wait for ACK + pushPostToClient(client, *p); + did_push = true; + MESH_DEBUG_PRINTLN("loop - pushed to client %02X: %s", (uint32_t)client->id.pub_key[0], p->text); + break; + } + idx = (idx + 1) % MAX_UNSYNCED_POSTS; // wrap to start of cyclic queue + } + } else { + MESH_DEBUG_PRINTLN("loop - skipping busy (or evicted) client %02X", (uint32_t)client->id.pub_key[0]); + } + next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client + + if (did_push) { + next_push = futureMillis(SYNC_PUSH_INTERVAL); + } else { + // were no unsynced posts for curr client, so proccess next client much quicker! (in next loop()) + next_push = futureMillis(SYNC_PUSH_INTERVAL / 8); + } + } + + if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { + mesh::Packet *pkt = createSelfAdvert(); + if (pkt) sendFlood(pkt); + + updateFloodAdvertTimer(); // schedule next flood advert + updateAdvertTimer(); // also schedule local advert (so they don't overlap) + } else if (next_local_advert && millisHasNowPassed(next_local_advert)) { + mesh::Packet *pkt = createSelfAdvert(); + if (pkt) sendZeroHop(pkt); + + updateAdvertTimer(); // schedule next local advert + } + + if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params + set_radio_at = 0; // clear timer + radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr); + MESH_DEBUG_PRINTLN("Temp radio params"); + } + + if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig + revert_radio_at = 0; // clear timer + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + MESH_DEBUG_PRINTLN("Radio params restored"); + } + + // TODO: periodically check for OLD/inactive entries in known_clients[], and evict +} diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h new file mode 100644 index 00000000..945cca7d --- /dev/null +++ b/examples/simple_room_server/MyMesh.h @@ -0,0 +1,220 @@ +#pragma once + +#include <Arduino.h> // needed for PlatformIO +#include <Mesh.h> + +#if defined(NRF52_PLATFORM) + #include <InternalFileSystem.h> +#elif defined(RP2040_PLATFORM) + #include <LittleFS.h> +#elif defined(ESP32) + #include <SPIFFS.h> +#endif + +#include <helpers/ArduinoHelpers.h> +#include <helpers/StaticPoolPacketManager.h> +#include <helpers/SimpleMeshTables.h> +#include <helpers/IdentityStore.h> +#include <helpers/AdvertDataHelpers.h> +#include <helpers/TxtDataHelpers.h> +#include <helpers/CommonCLI.h> +#include <RTClib.h> +#include <target.h> + +/* ------------------------------ Config -------------------------------- */ + +#ifndef FIRMWARE_BUILD_DATE + #define FIRMWARE_BUILD_DATE "1 Sep 2025" +#endif + +#ifndef FIRMWARE_VERSION + #define FIRMWARE_VERSION "v1.8.1" +#endif + +#ifndef LORA_FREQ + #define LORA_FREQ 915.0 +#endif +#ifndef LORA_BW + #define LORA_BW 250 +#endif +#ifndef LORA_SF + #define LORA_SF 10 +#endif +#ifndef LORA_CR + #define LORA_CR 5 +#endif +#ifndef LORA_TX_POWER + #define LORA_TX_POWER 20 +#endif + +#ifndef ADVERT_NAME + #define ADVERT_NAME "Test BBS" +#endif +#ifndef ADVERT_LAT + #define ADVERT_LAT 0.0 +#endif +#ifndef ADVERT_LON + #define ADVERT_LON 0.0 +#endif + +#ifndef ADMIN_PASSWORD + #define ADMIN_PASSWORD "password" +#endif + +#ifndef MAX_CLIENTS + #define MAX_CLIENTS 32 +#endif + +#ifndef MAX_UNSYNCED_POSTS + #define MAX_UNSYNCED_POSTS 32 +#endif + +#ifndef SERVER_RESPONSE_DELAY + #define SERVER_RESPONSE_DELAY 300 +#endif + +#ifndef TXT_ACK_DELAY + #define TXT_ACK_DELAY 200 +#endif + +#define FIRMWARE_ROLE "room_server" + +#define PACKET_LOG_FILE "/packet_log" + +enum RoomPermission { + ADMIN, + GUEST, + READ_ONLY +}; + +struct ClientInfo { + mesh::Identity id; + uint32_t last_timestamp; // by THEIR clock + uint32_t last_activity; // by OUR clock + uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock) + uint32_t pending_ack; + uint32_t push_post_timestamp; + unsigned long ack_timeout; + RoomPermission permission; + uint8_t push_failures; + uint8_t secret[PUB_KEY_SIZE]; + int out_path_len; + uint8_t out_path[MAX_PATH_SIZE]; +}; + +#define MAX_POST_TEXT_LEN (160-9) + +struct PostInfo { + mesh::Identity author; + uint32_t post_timestamp; // by OUR clock + char text[MAX_POST_TEXT_LEN+1]; +}; + +class MyMesh : public mesh::Mesh, public CommonCLICallbacks { + FILESYSTEM* _fs; + unsigned long next_local_advert, next_flood_advert; + bool _logging; + NodePrefs _prefs; + CommonCLI _cli; + uint8_t reply_data[MAX_PACKET_PAYLOAD]; + int num_clients; + ClientInfo known_clients[MAX_CLIENTS]; + unsigned long next_push; + uint16_t _num_posted, _num_post_pushes; + int next_client_idx; // for round-robin polling + int next_post_idx; + PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue + CayenneLPP telemetry; + unsigned long set_radio_at, revert_radio_at; + float pending_freq; + float pending_bw; + uint8_t pending_sf; + uint8_t pending_cr; + int matching_peer_indexes[MAX_CLIENTS]; + + ClientInfo* putClient(const mesh::Identity& id); + void evict(ClientInfo* client); + void addPost(ClientInfo* client, const char* postData); + void pushPostToClient(ClientInfo* client, PostInfo& post); + uint8_t getUnsyncedCount(ClientInfo* client); + bool processAck(const uint8_t *data); + mesh::Packet* createSelfAdvert(); + File openAppend(const char* fname); + int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); + +protected: + float getAirtimeBudgetFactor() const override { + return _prefs.airtime_factor; + } + + void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; + void logRx(mesh::Packet* pkt, int len, float score) override; + void logTx(mesh::Packet* pkt, int len) override; + void logTxFail(mesh::Packet* pkt, int len) override; + + int calcRxDelay(float score, uint32_t air_time) const override; + const char* getLogDateTime() override; + uint32_t getRetransmitDelay(const mesh::Packet* packet) override; + uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override; + + int getInterferenceThreshold() const override { + return _prefs.interference_threshold; + } + int getAGCResetInterval() const override { + return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds + } + uint8_t getExtraAckTransmitCount() const override { + return _prefs.multi_acks; + } + + bool allowPacketForward(const mesh::Packet* packet) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; + int searchPeersByHash(const uint8_t* hash) override ; + void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; + void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; + bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; + +public: + MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); + + void begin(FILESYSTEM* fs); + + const char* getFirmwareVer() override { return FIRMWARE_VERSION; } + const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } + const char* getRole() override { return FIRMWARE_ROLE; } + const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } + + void savePrefs() override { + _cli.savePrefs(_fs); + } + + void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; + bool formatFileSystem() override; + void sendSelfAdvertisement(int delay_millis) override; + void updateAdvertTimer() override; + void updateFloodAdvertTimer() override; + + void setLoggingOn(bool enable) override { _logging = enable; } + + void eraseLogFile() override { + _fs->remove(PACKET_LOG_FILE); + } + + void dumpLogFile() override; + void setTxPower(uint8_t power_dbm) override; + + void formatNeighborsReply(char *reply) override { + strcpy(reply, "not supported"); + } + + mesh::LocalIdentity& getSelfId() override { return self_id; } + + void saveIdentity(const mesh::LocalIdentity& new_id) override; + void clearStats() override; + void handleCommand(uint32_t sender_timestamp, char* command, char* reply); + void loop(); +}; diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index aa9c8e37..8f6b6d58 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -1,979 +1,13 @@ #include <Arduino.h> // needed for PlatformIO #include <Mesh.h> -#if defined(NRF52_PLATFORM) - #include <InternalFileSystem.h> -#elif defined(RP2040_PLATFORM) - #include <LittleFS.h> -#elif defined(ESP32) - #include <SPIFFS.h> -#endif - -#include <helpers/ArduinoHelpers.h> -#include <helpers/StaticPoolPacketManager.h> -#include <helpers/SimpleMeshTables.h> -#include <helpers/IdentityStore.h> -#include <helpers/AdvertDataHelpers.h> -#include <helpers/TxtDataHelpers.h> -#include <helpers/CommonCLI.h> -#include <RTClib.h> -#include <target.h> - -/* ------------------------------ Config -------------------------------- */ - -#ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "1 Sep 2025" -#endif - -#ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.8.1" -#endif - -#ifndef LORA_FREQ - #define LORA_FREQ 915.0 -#endif -#ifndef LORA_BW - #define LORA_BW 250 -#endif -#ifndef LORA_SF - #define LORA_SF 10 -#endif -#ifndef LORA_CR - #define LORA_CR 5 -#endif -#ifndef LORA_TX_POWER - #define LORA_TX_POWER 20 -#endif - -#ifndef ADVERT_NAME - #define ADVERT_NAME "Test BBS" -#endif -#ifndef ADVERT_LAT - #define ADVERT_LAT 0.0 -#endif -#ifndef ADVERT_LON - #define ADVERT_LON 0.0 -#endif - -#ifndef ADMIN_PASSWORD - #define ADMIN_PASSWORD "password" -#endif - -#ifndef MAX_CLIENTS - #define MAX_CLIENTS 32 -#endif - -#ifndef MAX_UNSYNCED_POSTS - #define MAX_UNSYNCED_POSTS 32 -#endif - -#ifndef SERVER_RESPONSE_DELAY - #define SERVER_RESPONSE_DELAY 300 -#endif - -#ifndef TXT_ACK_DELAY - #define TXT_ACK_DELAY 200 -#endif +#include "MyMesh.h" #ifdef DISPLAY_CLASS #include "UITask.h" static UITask ui_task(display); #endif -#define FIRMWARE_ROLE "room_server" - -#define PACKET_LOG_FILE "/packet_log" - -/* ------------------------------ Code -------------------------------- */ - -enum RoomPermission { - ADMIN, - GUEST, - READ_ONLY -}; - -struct ClientInfo { - mesh::Identity id; - uint32_t last_timestamp; // by THEIR clock - uint32_t last_activity; // by OUR clock - uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock) - uint32_t pending_ack; - uint32_t push_post_timestamp; - unsigned long ack_timeout; - RoomPermission permission; - uint8_t push_failures; - uint8_t secret[PUB_KEY_SIZE]; - int out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; -}; - -#define MAX_POST_TEXT_LEN (160-9) - -struct PostInfo { - mesh::Identity author; - uint32_t post_timestamp; // by OUR clock - char text[MAX_POST_TEXT_LEN+1]; -}; - -#define REPLY_DELAY_MILLIS 1500 -#define PUSH_NOTIFY_DELAY_MILLIS 2000 -#define SYNC_PUSH_INTERVAL 1200 - -#define PUSH_ACK_TIMEOUT_FLOOD 12000 -#define PUSH_TIMEOUT_BASE 4000 -#define PUSH_ACK_TIMEOUT_FACTOR 2000 - -#define POST_SYNC_DELAY_SECS 6 - -#define CLIENT_KEEP_ALIVE_SECS 0 // Now Disabled (was 128) - -#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS -#define REQ_TYPE_KEEP_ALIVE 0x02 -#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 - -#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ - -struct ServerStats { - uint16_t batt_milli_volts; - uint16_t curr_tx_queue_len; - int16_t noise_floor; - int16_t last_rssi; - uint32_t n_packets_recv; - uint32_t n_packets_sent; - uint32_t total_air_time_secs; - uint32_t total_up_time_secs; - uint32_t n_sent_flood, n_sent_direct; - uint32_t n_recv_flood, n_recv_direct; - uint16_t err_events; // was 'n_full_events' - int16_t last_snr; // x 4 - uint16_t n_direct_dups, n_flood_dups; - uint16_t n_posted, n_post_push; -}; - -class MyMesh : public mesh::Mesh, public CommonCLICallbacks { - FILESYSTEM* _fs; - unsigned long next_local_advert, next_flood_advert; - bool _logging; - NodePrefs _prefs; - CommonCLI _cli; - uint8_t reply_data[MAX_PACKET_PAYLOAD]; - int num_clients; - ClientInfo known_clients[MAX_CLIENTS]; - unsigned long next_push; - uint16_t _num_posted, _num_post_pushes; - int next_client_idx; // for round-robin polling - int next_post_idx; - PostInfo posts[MAX_UNSYNCED_POSTS]; // cyclic queue - CayenneLPP telemetry; - unsigned long set_radio_at, revert_radio_at; - float pending_freq; - float pending_bw; - uint8_t pending_sf; - uint8_t pending_cr; - - ClientInfo* putClient(const mesh::Identity& id) { - for (int i = 0; i < num_clients; i++) { - if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known - } - ClientInfo* newClient; - if (num_clients < MAX_CLIENTS) { - newClient = &known_clients[num_clients++]; - } else { // table is currently full - // evict least active client - uint32_t oldest_timestamp = 0xFFFFFFFF; - newClient = &known_clients[0]; - for (int i = 0; i < num_clients; i++) { - auto c = &known_clients[i]; - if (c->last_activity < oldest_timestamp) { - oldest_timestamp = c->last_activity; - newClient = c; - } - } - } - newClient->id = id; - newClient->out_path_len = -1; // initially out_path is unknown - newClient->last_timestamp = 0; - return newClient; - } - - void evict(ClientInfo* client) { - client->last_activity = 0; // this slot will now be re-used (will be oldest) - memset(client->id.pub_key, 0, sizeof(client->id.pub_key)); - memset(client->secret, 0, sizeof(client->secret)); - client->pending_ack = 0; - } - - void addPost(ClientInfo* client, const char* postData) { - // TODO: suggested postData format: <title>/<descrption> - posts[next_post_idx].author = client->id; // add to cyclic queue - StrHelper::strncpy(posts[next_post_idx].text, postData, MAX_POST_TEXT_LEN); - - posts[next_post_idx].post_timestamp = getRTCClock()->getCurrentTimeUnique(); - next_post_idx = (next_post_idx + 1) % MAX_UNSYNCED_POSTS; - - next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); - _num_posted++; // stats - } - - void pushPostToClient(ClientInfo* client, PostInfo& post) { - int len = 0; - memcpy(&reply_data[len], &post.post_timestamp, 4); len += 4; // this is a PAST timestamp... but should be accepted by client - - uint8_t attempt; - getRNG()->random(&attempt, 1); // need this for re-tries, so packet hash (and ACK) will be different - reply_data[len++] = (TXT_TYPE_SIGNED_PLAIN << 2) | (attempt & 3); // 'signed' plain text - - // encode prefix of post.author.pub_key - memcpy(&reply_data[len], post.author.pub_key, 4); len += 4; // just first 4 bytes - - int text_len = strlen(post.text); - memcpy(&reply_data[len], post.text, text_len); len += text_len; - - // calc expected ACK reply - mesh::Utils::sha256((uint8_t *)&client->pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); - client->push_post_timestamp = post.post_timestamp; - - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->secret, reply_data, len); - if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply); - client->ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); - } else { - sendDirect(reply, client->out_path, client->out_path_len); - client->ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); - } - _num_post_pushes++; // stats - } else { - client->pending_ack = 0; - MESH_DEBUG_PRINTLN("Unable to push post to client"); - } - } - - uint8_t getUnsyncedCount(ClientInfo* client) { - uint8_t count = 0; - for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) { - if (posts[k].post_timestamp > client->sync_since // is new post for this Client? - && !posts[k].author.matches(client->id)) { // don't push posts to the author - count++; - } - } - return count; - } - - bool processAck(const uint8_t *data) { - for (int i = 0; i < num_clients; i++) { - auto client = &known_clients[i]; - if (client->pending_ack && memcmp(data, &client->pending_ack, 4) == 0) { // got an ACK from Client! - client->pending_ack = 0; // clear this, so next push can happen - client->push_failures = 0; - client->sync_since = client->push_post_timestamp; // advance Client's SINCE timestamp, to sync next post - return true; - } - } - return false; - } - - mesh::Packet* createSelfAdvert() { - uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - - return createAdvert(self_id, app_data, app_data_len); - } - - File openAppend(const char* fname) { - #if defined(NRF52_PLATFORM) - return _fs->open(fname, FILE_O_WRITE); - #elif defined(RP2040_PLATFORM) - return _fs->open(fname, "a"); - #else - return _fs->open(fname, "a", true); - #endif - } - - int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len) { - // uint32_t now = getRTCClock()->getCurrentTimeUnique(); - // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') - - switch (payload[0]) { - case REQ_TYPE_GET_STATUS: { - ServerStats stats; - stats.batt_milli_volts = board.getBattMilliVolts(); - stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); - stats.noise_floor = (int16_t)_radio->getNoiseFloor(); - stats.last_rssi = (int16_t) radio_driver.getLastRSSI(); - stats.n_packets_recv = radio_driver.getPacketsRecv(); - stats.n_packets_sent = radio_driver.getPacketsSent(); - stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; - stats.n_sent_flood = getNumSentFlood(); - stats.n_sent_direct = getNumSentDirect(); - stats.n_recv_flood = getNumRecvFlood(); - stats.n_recv_direct = getNumRecvDirect(); - stats.err_events = _err_flags; - stats.last_snr = (int16_t)(radio_driver.getLastSNR() * 4); - stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); - stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); - stats.n_posted = _num_posted; - stats.n_post_push = _num_post_pushes; - - memcpy(&reply_data[4], &stats, sizeof(stats)); - return 4 + sizeof(stats); - } - - case REQ_TYPE_GET_TELEMETRY_DATA: { - uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions - - telemetry.reset(); - telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - // query other sensors -- target specific - sensors.querySensors((sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00) & perm_mask, telemetry); - - uint8_t tlen = telemetry.getSize(); - memcpy(&reply_data[4], telemetry.getBuffer(), tlen); - return 4 + tlen; // reply_len - } - } - return 0; // unknown command - } - -protected: - float getAirtimeBudgetFactor() const override { - return _prefs.airtime_factor; - } - - void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override { - #if MESH_PACKET_LOGGING - Serial.print(getLogDateTime()); - Serial.print(" RAW: "); - mesh::Utils::printHex(Serial, raw, len); - Serial.println(); - #endif - } - - void logRx(mesh::Packet* pkt, int len, float score) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": RX, len=%d (type=%d, route=%s, payload_len=%d) SNR=%d RSSI=%d score=%d", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len, - (int)_radio->getLastSNR(), (int)_radio->getLastRSSI(), (int)(score*1000)); - - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ - || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); - } else { - f.printf("\n"); - } - f.close(); - } - } - } - void logTx(mesh::Packet* pkt, int len) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": TX, len=%d (type=%d, route=%s, payload_len=%d)", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); - - if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH || pkt->getPayloadType() == PAYLOAD_TYPE_REQ - || pkt->getPayloadType() == PAYLOAD_TYPE_RESPONSE || pkt->getPayloadType() == PAYLOAD_TYPE_TXT_MSG) { - f.printf(" [%02X -> %02X]\n", (uint32_t)pkt->payload[1], (uint32_t)pkt->payload[0]); - } else { - f.printf("\n"); - } - f.close(); - } - } - } - void logTxFail(mesh::Packet* pkt, int len) override { - if (_logging) { - File f = openAppend(PACKET_LOG_FILE); - if (f) { - f.print(getLogDateTime()); - f.printf(": TX FAIL!, len=%d (type=%d, route=%s, payload_len=%d)\n", - len, pkt->getPayloadType(), pkt->isRouteDirect() ? "D" : "F", pkt->payload_len); - f.close(); - } - } - } - - int calcRxDelay(float score, uint32_t air_time) const override { - if (_prefs.rx_delay_base <= 0.0f) return 0; - return (int) ((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); - } - - const char* getLogDateTime() override { - static char tmp[32]; - uint32_t now = getRTCClock()->getCurrentTime(); - DateTime dt = DateTime(now); - sprintf(tmp, "%02d:%02d:%02d - %d/%d/%d U", dt.hour(), dt.minute(), dt.second(), dt.day(), dt.month(), dt.year()); - return tmp; - } - - uint32_t getRetransmitDelay(const mesh::Packet* packet) override { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; - } - uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6)*t; - } - int getInterferenceThreshold() const override { - return _prefs.interference_threshold; - } - int getAGCResetInterval() const override { - return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds - } - uint8_t getExtraAckTransmitCount() const override { - return _prefs.multi_acks; - } - - bool allowPacketForward(const mesh::Packet* packet) override { - if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; - return true; - } - - void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override { - if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin client (unknown at this stage) - uint32_t sender_timestamp, sender_sync_since; - memcpy(&sender_timestamp, data, 4); - memcpy(&sender_sync_since, &data[4], 4); // sender's "sync messags SINCE x" timestamp - - RoomPermission perm; - data[len] = 0; // ensure null terminator - if (strcmp((char *) &data[8], _prefs.password) == 0) { // check for valid admin password - perm = RoomPermission::ADMIN; - } else { - if (strcmp((char *) &data[8], _prefs.guest_password) == 0) { // check the room/public password - perm = RoomPermission::GUEST; - } else if (_prefs.allow_read_only) { - perm = RoomPermission::READ_ONLY; - } else { - MESH_DEBUG_PRINTLN("Incorrect room password"); - return; // no response. Client will timeout - } - } - - auto client = putClient(sender); // add to known clients (if not already known) - if (sender_timestamp <= client->last_timestamp) { - MESH_DEBUG_PRINTLN("possible replay attack!"); - return; - } - - MESH_DEBUG_PRINTLN("Login success!"); - client->permission = perm; - client->last_timestamp = sender_timestamp; - client->sync_since = sender_sync_since; - client->pending_ack = 0; - client->push_failures = 0; - memcpy(client->secret, secret, PUB_KEY_SIZE); - - uint32_t now = getRTCClock()->getCurrentTime(); - client->last_activity = now; - - now = getRTCClock()->getCurrentTimeUnique(); - memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - // TODO: maybe reply with count of messages waiting to be synced for THIS client? - reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16) - reply_data[6] = (perm == RoomPermission::ADMIN ? 1 : (perm == RoomPermission::GUEST ? 0 : 2)); - reply_data[7] = getUnsyncedCount(client); // NEW - memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed - - next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first - - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet* path = createPathReturn(sender, client->secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 8 + 2); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 8 + 2); - if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); - } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); - } - } - } - } - } - - int matching_peer_indexes[MAX_CLIENTS]; - - int searchPeersByHash(const uint8_t* hash) override { - int n = 0; - for (int i = 0; i < num_clients; i++) { - if (known_clients[i].id.isHashMatch(hash)) { - matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) - } - } - return n; - } - - void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override { - int i = matching_peer_indexes[peer_idx]; - if (i >= 0 && i < num_clients) { - // lookup pre-calculated shared_secret - memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE); - } else { - MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); - } - } - - void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override { - int i = matching_peer_indexes[sender_idx]; - if (i < 0 || i >= num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) - MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); - return; - } - auto client = &known_clients[i]; - if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post - uint32_t sender_timestamp; - memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags - - if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { - MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags); - } else if (sender_timestamp >= client->last_timestamp) { // prevent replay attacks, but send Acks for retries - bool is_retry = (sender_timestamp == client->last_timestamp); - client->last_timestamp = sender_timestamp; - - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - client->last_activity = now; - client->push_failures = 0; // reset so push can resume (if prev failed) - - // len can be > original length, but 'text' will be padded with zeroes - data[len] = 0; // need to make a C string again, with null terminator - - uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), client->id.pub_key, PUB_KEY_SIZE); - - uint8_t temp[166]; - bool send_ack; - if (flags == TXT_TYPE_CLI_DATA) { - if (client->permission == RoomPermission::ADMIN) { - if (is_retry) { - temp[5] = 0; // no reply - } else { - handleCommand(sender_timestamp, (char *) &data[5], (char *) &temp[5]); - temp[4] = (TXT_TYPE_CLI_DATA << 2); // attempt and flags, (NOTE: legacy was: TXT_TYPE_PLAIN) - } - send_ack = false; - } else { - temp[5] = 0; // no reply - send_ack = false; // and no ACK... user shoudn't be sending these - } - } else { // TXT_TYPE_PLAIN - if (client->permission == RoomPermission::READ_ONLY) { - temp[5] = 0; // no reply - send_ack = false; // no ACK - } else { - if (!is_retry) { - addPost(client, (const char *) &data[5]); - } - temp[5] = 0; // no reply (ACK is enough) - send_ack = true; - } - } - - uint32_t delay_millis; - if (send_ack) { - if (client->out_path_len < 0) { - mesh::Packet* ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); - delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; - } else { - uint32_t d = TXT_ACK_DELAY; - if (getExtraAckTransmitCount() > 0) { - mesh::Packet* a1 = createMultiAck(ack_hash, 1); - if (a1) sendDirect(a1, client->out_path, client->out_path_len, d); - d += 300; - } - - mesh::Packet* a2 = createAck(ack_hash); - if (a2) sendDirect(a2, client->out_path, client->out_path_len, d); - delay_millis = d + REPLY_DELAY_MILLIS; - } - } else { - delay_millis = 0; - } - - int text_len = strlen((char *) &temp[5]); - if (text_len > 0) { - if (now == sender_timestamp) { - // WORKAROUND: the two timestamps need to be different, in the CLI view - now++; - } - memcpy(temp, &now, 4); // mostly an extra blob to help make packet_hash unique - - // calc expected ACK reply - //mesh::Utils::sha256((uint8_t *)&expected_ack_crc, 4, temp, 5 + text_len, self_id.pub_key, PUB_KEY_SIZE); - - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); - if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY); - } else { - sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY); - } - } - } - } else { - MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); - } - } else if (type == PAYLOAD_TYPE_REQ && len >= 5) { - uint32_t sender_timestamp; - memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - if (sender_timestamp < client->last_timestamp) { // prevent replay attacks - MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); - } else { - client->last_timestamp = sender_timestamp; - - uint32_t now = getRTCClock()->getCurrentTime(); - client->last_activity = now; // <-- THIS will keep client connection alive - client->push_failures = 0; // reset so push can resume (if prev failed) - - if (data[4] == REQ_TYPE_KEEP_ALIVE && packet->isRouteDirect()) { // request type - uint32_t forceSince = 0; - if (len >= 9) { // optional - last post_timestamp client received - memcpy(&forceSince, &data[5], 4); // NOTE: this may be 0, if part of decrypted PADDING! - } else { - memcpy(&data[5], &forceSince, 4); // make sure there are zeroes in payload (for ack_hash calc below) - } - if (forceSince > 0) { - client->sync_since = forceSince; // force-update the 'sync since' - } - - client->pending_ack = 0; - - // TODO: Throttle KEEP_ALIVE requests! - // if client sends too quickly, evict() - - // RULE: only send keep_alive response DIRECT! - if (client->out_path_len >= 0) { - uint32_t ack_hash; // calc ACK to prove to sender that we got request - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE); - - auto reply = createAck(ack_hash); - if (reply) { - reply->payload[reply->payload_len++] = getUnsyncedCount(client); // NEW: add unsynced counter to end of ACK packet - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); - } - } - } else { - int reply_len = handleRequest(client, sender_timestamp, &data[4], len - 4); - if (reply_len > 0) { // valid command - if (packet->isRouteFlood()) { - // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet* path = createPathReturn(client->id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); - if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); - } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); - } - } - } - } - } - } - } - } - - bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override { - // TODO: prevent replay attacks - int i = matching_peer_indexes[sender_idx]; - - if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) - MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t) path_len); - auto client = &known_clients[i]; - memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() - } else { - MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); - } - - if (extra_type == PAYLOAD_TYPE_ACK && extra_len >= 4) { - // also got an encoded ACK! - processAck(extra); - } - - // NOTE: no reciprocal path send!! - return false; - } - - void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override { - if (processAck((uint8_t *)&ack_crc)) { - packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit - } - } - -public: - MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) - : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) - { - next_local_advert = next_flood_advert = 0; - _logging = false; - set_radio_at = revert_radio_at = 0; - - // defaults - memset(&_prefs, 0, sizeof(_prefs)); - _prefs.airtime_factor = 1.0; // one half - _prefs.rx_delay_base = 0.0f; // off by default, was 10.0 - _prefs.tx_delay_factor = 0.5f; // was 0.25f; - StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); - _prefs.node_lat = ADVERT_LAT; - _prefs.node_lon = ADVERT_LON; - StrHelper::strncpy(_prefs.password, ADMIN_PASSWORD, sizeof(_prefs.password)); - _prefs.freq = LORA_FREQ; - _prefs.sf = LORA_SF; - _prefs.bw = LORA_BW; - _prefs.cr = LORA_CR; - _prefs.tx_power_dbm = LORA_TX_POWER; - _prefs.disable_fwd = 1; - _prefs.advert_interval = 1; // default to 2 minutes for NEW installs - _prefs.flood_advert_interval = 12; // 12 hours - _prefs.flood_max = 64; - _prefs.interference_threshold = 0; // disabled - #ifdef ROOM_PASSWORD - StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); - #endif - - num_clients = 0; - next_post_idx = 0; - next_client_idx = 0; - next_push = 0; - memset(posts, 0, sizeof(posts)); - _num_posted = _num_post_pushes = 0; - } - - void begin(FILESYSTEM* fs) { - mesh::Mesh::begin(); - _fs = fs; - // load persisted prefs - _cli.loadPrefs(_fs); - - radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); - radio_set_tx_power(_prefs.tx_power_dbm); - - updateAdvertTimer(); - updateFloodAdvertTimer(); - } - - const char* getFirmwareVer() override { return FIRMWARE_VERSION; } - const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } - const char* getRole() override { return FIRMWARE_ROLE; } - const char* getNodeName() { return _prefs.node_name; } - NodePrefs* getNodePrefs() { - return &_prefs; - } - - void savePrefs() override { - _cli.savePrefs(_fs); - } - - void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override { - set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params - pending_freq = freq; - pending_bw = bw; - pending_sf = sf; - pending_cr = cr; - - revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params - } - - bool formatFileSystem() override { - #if defined(NRF52_PLATFORM) - return InternalFS.format(); - #elif defined(RP2040_PLATFORM) - return LittleFS.format(); - #elif defined(ESP32) - return SPIFFS.format(); - #else - #error "need to implement file system erase" - return false; - #endif - } - - void sendSelfAdvertisement(int delay_millis) override { - mesh::Packet* pkt = createSelfAdvert(); - if (pkt) { - sendFlood(pkt, delay_millis); - } else { - MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); - } - } - - void updateAdvertTimer() override { - if (_prefs.advert_interval > 0) { // schedule local advert timer - next_local_advert = futureMillis((uint32_t)_prefs.advert_interval * 2 * 60 * 1000); - } else { - next_local_advert = 0; // stop the timer - } - } - void updateFloodAdvertTimer() override { - if (_prefs.flood_advert_interval > 0) { // schedule flood advert timer - next_flood_advert = futureMillis( ((uint32_t)_prefs.flood_advert_interval) * 60 * 60 * 1000); - } else { - next_flood_advert = 0; // stop the timer - } - } - - void setLoggingOn(bool enable) override { _logging = enable; } - - void eraseLogFile() override { - _fs->remove(PACKET_LOG_FILE); - } - - void dumpLogFile() override { - #if defined(RP2040_PLATFORM) - File f = _fs->open(PACKET_LOG_FILE, "r"); - #else - File f = _fs->open(PACKET_LOG_FILE); - #endif - if (f) { - while (f.available()) { - int c = f.read(); - if (c < 0) break; - Serial.print((char)c); - } - f.close(); - } - } - - void setTxPower(uint8_t power_dbm) override { - radio_set_tx_power(power_dbm); - } - - void formatNeighborsReply(char *reply) override { - strcpy(reply, "not supported"); - } - - mesh::LocalIdentity& getSelfId() override { return self_id; } - - void saveIdentity(const mesh::LocalIdentity& new_id) override { - self_id = new_id; -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - IdentityStore store(*_fs, ""); -#elif defined(ESP32) - IdentityStore store(*_fs, "/identity"); -#elif defined(RP2040_PLATFORM) - IdentityStore store(*_fs, "/identity"); -#else - #error "need to define saveIdentity()" -#endif - store.save("_main", self_id); - } - - void clearStats() override { - radio_driver.resetStats(); - resetStats(); - ((SimpleMeshTables *)getTables())->resetStats(); - } - - void handleCommand(uint32_t sender_timestamp, char* command, char* reply) { - while (*command == ' ') command++; // skip leading spaces - - if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) - memcpy(reply, command, 3); // reflect the prefix back - reply += 3; - command += 3; - } - - _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands - } - - void loop() { - mesh::Mesh::loop(); - - if (millisHasNowPassed(next_push) && num_clients > 0) { - // check for ACK timeouts - for (int i = 0; i < num_clients; i++) { - auto c = &known_clients[i]; - if (c->pending_ack && millisHasNowPassed(c->ack_timeout)) { - c->push_failures++; - c->pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) - MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); - } - } - // check next Round-Robin client, and sync next new post - auto client = &known_clients[next_client_idx]; - bool did_push = false; - if (client->pending_ack == 0 && client->last_activity != 0 && client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max - MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t) client->id.pub_key[0]); - uint32_t now = getRTCClock()->getCurrentTime(); - for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) { - auto p = &posts[idx]; - if (now >= p->post_timestamp + POST_SYNC_DELAY_SECS && p->post_timestamp > client->sync_since // is new post for this Client? - && !p->author.matches(client->id)) { // don't push posts to the author - // push this post to Client, then wait for ACK - pushPostToClient(client, *p); - did_push = true; - MESH_DEBUG_PRINTLN("loop - pushed to client %02X: %s", (uint32_t) client->id.pub_key[0], p->text); - break; - } - idx = (idx + 1) % MAX_UNSYNCED_POSTS; // wrap to start of cyclic queue - } - } else { - MESH_DEBUG_PRINTLN("loop - skipping busy (or evicted) client %02X", (uint32_t) client->id.pub_key[0]); - } - next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client - - if (did_push) { - next_push = futureMillis(SYNC_PUSH_INTERVAL); - } else { - // were no unsynced posts for curr client, so proccess next client much quicker! (in next loop()) - next_push = futureMillis(SYNC_PUSH_INTERVAL / 8); - } - } - - if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { - mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendFlood(pkt); - - updateFloodAdvertTimer(); // schedule next flood advert - updateAdvertTimer(); // also schedule local advert (so they don't overlap) - } else if (next_local_advert && millisHasNowPassed(next_local_advert)) { - mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendZeroHop(pkt); - - updateAdvertTimer(); // schedule next local advert - } - - if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params - set_radio_at = 0; // clear timer - radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr); - MESH_DEBUG_PRINTLN("Temp radio params"); - } - - if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig - revert_radio_at = 0; // clear timer - radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); - MESH_DEBUG_PRINTLN("Radio params restored"); - } - - #ifdef DISPLAY_CLASS - ui_task.loop(); - #endif - - // TODO: periodically check for OLD/inactive entries in known_clients[], and evict - } -}; - StdRNG fast_rng; SimpleMeshTables tables; MyMesh the_mesh(board, radio_driver, *new ArduinoMillis(), fast_rng, rtc_clock, tables); @@ -1073,4 +107,7 @@ void loop() { the_mesh.loop(); sensors.loop(); +#ifdef DISPLAY_CLASS + ui_task.loop(); +#endif } From cf93109cd5563f10e1b220b20a8b82d723c13405 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Tue, 9 Sep 2025 19:20:39 +0800 Subject: [PATCH 045/546] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20altitude=20su?= =?UTF-8?q?pport=20to=20environment=20sensor=20node=20telemetry?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Include actual node altitude in GPS telemetry instead of hardcoded 0.0f - Extract altitude data from both ublox_GNSS and serial GPS sources - Update debug logging to display altitude alongside lat/lon coordinates --- src/helpers/sensors/EnvironmentSensorManager.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index df08ed78..6b1b9e47 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -252,7 +252,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen next_available_channel = TELEM_CHANNEL_SELF + 1; if (requester_permissions & TELEM_PERM_LOCATION && gps_active) { - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, 0.0f); // allow lat/lon via telemetry even if no GPS is detected + telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); // allow lat/lon via telemetry even if no GPS is detected } if (requester_permissions & TELEM_PERM_ENVIRONMENT) { @@ -577,17 +577,23 @@ void EnvironmentSensorManager::loop() { node_lat = ((double)ublox_GNSS.getLatitude())/10000000.; node_lon = ((double)ublox_GNSS.getLongitude())/10000000.; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + node_altitude = ((double)ublox_GNSS.getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); } else if (serialGPSFlag && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); } #else if (_location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); } #endif } From d83cdc501f7e38ba9d8e439d28f500eae24ea7e8 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Tue, 9 Sep 2025 16:32:41 +0200 Subject: [PATCH 046/546] ui: use LPPDataHelper and conditionals for sensors page --- examples/companion_radio/ui-new/UITask.cpp | 105 +++++++--- examples/companion_radio/ui-new/UITask.h | 1 + src/helpers/sensors/LPPDataHelpers.h | 223 +++++++++++++++++++++ variants/lilygo_techo/platformio.ini | 3 + 4 files changed, 306 insertions(+), 26 deletions(-) create mode 100644 src/helpers/sensors/LPPDataHelpers.h diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 0236a381..1e03f086 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -75,7 +75,9 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, +#if UI_SENSORS_PAGE == 1 SENSORS, +#endif SHUTDOWN, Count // keep as last }; @@ -114,20 +116,25 @@ class HomeScreen : public UIScreen { display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); } - DynamicJsonDocument _sensors_doc; - JsonArray _sensors_arr; - bool scroll = false; - int scroll_offset = 0; + CayenneLPP sensors_lpp; + int sensors_nb = 0; + bool sensors_scroll = false; + int sensors_scroll_offset = 0; int next_sensors_refresh = 0; void refresh_sensors() { - CayenneLPP lpp(200); if (millis() > next_sensors_refresh) { - lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - sensors.querySensors(0xFF, lpp); - _sensors_arr.clear(); - lpp.decode(lpp.getBuffer(), lpp.getSize(), _sensors_arr); - scroll = _sensors_arr.size() > UI_RECENT_LIST_SIZE; // there is a status line + sensors_lpp.reset(); + sensors_nb = 0; + sensors_lpp.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + sensors.querySensors(0xFF, sensors_lpp); + LPPReader reader (sensors_lpp.getBuffer(), sensors_lpp.getSize()); + uint8_t channel, type; + while(reader.readHeader(channel, type)) { + reader.skipData(type); + sensors_nb ++; + } + sensors_scroll = sensors_nb > UI_RECENT_LIST_SIZE; #if AUTO_OFF_MILLIS > 0 next_sensors_refresh = millis() + 5000; // refresh sensor values every 5 sec #else @@ -139,7 +146,7 @@ class HomeScreen : public UIScreen { public: HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), - _shutdown_init(false), _sensors_doc(2048) { _sensors_arr=_sensors_doc.to<JsonArray>(); } + _shutdown_init(false), sensors_lpp(200) { } void poll() override { if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released @@ -235,34 +242,78 @@ public: 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); +#if UI_SENSORS_PAGE == 1 } else if (_page == HomePage::SENSORS) { int y = 18; refresh_sensors(); - char buf[100]; - int s_size = _sensors_arr.size(); - for (int i = 0; i < (scroll?UI_RECENT_LIST_SIZE:s_size); i++) { - JsonObject v = _sensors_arr[(i+scroll_offset)%s_size]; + char buf[30]; + char name[30]; + LPPReader r(sensors_lpp.getBuffer(), sensors_lpp.getSize()); + + for (int i = 0; i < sensors_scroll_offset; i++) { + uint8_t channel, type; + r.readHeader(channel, type); + r.skipData(type); + } + + for (int i = 0; i < (sensors_scroll?UI_RECENT_LIST_SIZE:sensors_nb); i++) { + uint8_t channel, type; + if (!r.readHeader(channel, type)) { // reached end, reset + r.reset(); + r.readHeader(channel, type); + } + display.setCursor(0, y); - switch (v["type"].as<int>()) { - case 136: // GPS - sprintf(buf, "%.4f %.4f", - v["value"]["latitude"].as<float>(), - v["value"]["longitude"].as<float>()); + float v; + switch (type) { + case LPP_GPS: // GPS + float lat, lon, alt; + r.readGPS(lat, lon, alt); + strcpy(name, "gps"); sprintf(buf, "%.4f %.4f", lat, lon); break; - default: // will be a float for now - sprintf(buf, "%.02f", - v["value"].as<float>()); + case LPP_VOLTAGE: + r.readVoltage(v); + strcpy(name, "voltage"); sprintf(buf, "%6.2f", v); + break; + case LPP_CURRENT: + r.readCurrent(v); + strcpy(name, "current"); sprintf(buf, "%.3f", v); + break; + case LPP_TEMPERATURE: + r.readTemperature(v); + strcpy(name, "temperature"); sprintf(buf, "%.2f", v); + break; + case LPP_RELATIVE_HUMIDITY: + r.readRelativeHumidity(v); + strcpy(name, "humidity"); sprintf(buf, "%.2f", v); + break; + case LPP_BAROMETRIC_PRESSURE: + r.readPressure(v); + strcpy(name, "pressure"); sprintf(buf, "%.2f", v); + break; + case LPP_ALTITUDE: + r.readAltitude(v); + strcpy(name, "altitude"); sprintf(buf, "%.0f", v); + break; + case LPP_POWER: + r.readPower(v); + strcpy(name, "power"); sprintf(buf, "%6.2f", v); + break; + default: + r.skipData(type); + strcpy(name, "unk"); sprintf(buf, ""); } display.setCursor(0, y); - display.print(v["name"].as<JsonString>().c_str()); + display.print(name); display.setCursor( display.width()-display.getTextWidth(buf)-1, y ); display.print(buf); y = y + 12; } - if (scroll) scroll_offset = (scroll_offset+1)%s_size; - else scroll_offset = 0; + if (sensors_scroll) sensors_scroll_offset = (sensors_scroll_offset+1)%sensors_nb; + else sensors_scroll_offset = 0; +#endif } else if (_page == HomePage::SHUTDOWN) { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); @@ -307,11 +358,13 @@ public: } return true; } +#if UI_SENSORS_PAGE == 1 if (c == KEY_ENTER && _page == HomePage::SENSORS) { _task->toggleGPS(); next_sensors_refresh=0; return true; } +#endif if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) { _shutdown_init = true; // need to wait for button to be released return true; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 46024b1f..769b2c64 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -6,6 +6,7 @@ #include <helpers/SensorManager.h> #include <helpers/BaseSerialInterface.h> #include <Arduino.h> +#include <helpers/sensors/LPPDataHelpers.h> #ifdef PIN_BUZZER #include <helpers/ui/buzzer.h> diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h new file mode 100644 index 00000000..b9025de4 --- /dev/null +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -0,0 +1,223 @@ +#pragma once + +#include <stdint.h> + +#define LPP_DIGITAL_INPUT 0 // 1 byte +#define LPP_DIGITAL_OUTPUT 1 // 1 byte +#define LPP_ANALOG_INPUT 2 // 2 bytes, 0.01 signed +#define LPP_ANALOG_OUTPUT 3 // 2 bytes, 0.01 signed +#define LPP_GENERIC_SENSOR 100 // 4 bytes, unsigned +#define LPP_LUMINOSITY 101 // 2 bytes, 1 lux unsigned +#define LPP_PRESENCE 102 // 1 byte, bool +#define LPP_TEMPERATURE 103 // 2 bytes, 0.1°C signed +#define LPP_RELATIVE_HUMIDITY 104 // 1 byte, 0.5% unsigned +#define LPP_ACCELEROMETER 113 // 2 bytes per axis, 0.001G +#define LPP_BAROMETRIC_PRESSURE 115 // 2 bytes 0.1hPa unsigned +#define LPP_VOLTAGE 116 // 2 bytes 0.01V unsigned +#define LPP_CURRENT 117 // 2 bytes 0.001A unsigned +#define LPP_FREQUENCY 118 // 4 bytes 1Hz unsigned +#define LPP_PERCENTAGE 120 // 1 byte 1-100% unsigned +#define LPP_ALTITUDE 121 // 2 byte 1m signed +#define LPP_CONCENTRATION 125 // 2 bytes, 1 ppm unsigned +#define LPP_POWER 128 // 2 byte, 1W, unsigned +#define LPP_DISTANCE 130 // 4 byte, 0.001m, unsigned +#define LPP_ENERGY 131 // 4 byte, 0.001kWh, unsigned +#define LPP_DIRECTION 132 // 2 bytes, 1deg, unsigned +#define LPP_UNIXTIME 133 // 4 bytes, unsigned +#define LPP_GYROMETER 134 // 2 bytes per axis, 0.01 °/s +#define LPP_COLOUR 135 // 1 byte per RGB Color +#define LPP_GPS 136 // 3 byte lon/lat 0.0001 °, 3 bytes alt 0.01 meter +#define LPP_SWITCH 142 // 1 byte, 0/1 +#define LPP_POLYLINE 240 // 1 byte size, 1 byte delta factor, 3 byte lon/lat 0.0001° * factor, n (size-8) bytes deltas + +// Multipliers +#define LPP_DIGITAL_INPUT_MULT 1 +#define LPP_DIGITAL_OUTPUT_MULT 1 +#define LPP_ANALOG_INPUT_MULT 100 +#define LPP_ANALOG_OUTPUT_MULT 100 +#define LPP_GENERIC_SENSOR_MULT 1 +#define LPP_LUMINOSITY_MULT 1 +#define LPP_PRESENCE_MULT 1 +#define LPP_TEMPERATURE_MULT 10 +#define LPP_RELATIVE_HUMIDITY_MULT 2 +#define LPP_ACCELEROMETER_MULT 1000 +#define LPP_BAROMETRIC_PRESSURE_MULT 10 +#define LPP_VOLTAGE_MULT 100 +#define LPP_CURRENT_MULT 1000 +#define LPP_FREQUENCY_MULT 1 +#define LPP_PERCENTAGE_MULT 1 +#define LPP_ALTITUDE_MULT 1 +#define LPP_POWER_MULT 1 +#define LPP_DISTANCE_MULT 1000 +#define LPP_ENERGY_MULT 1000 +#define LPP_DIRECTION_MULT 1 +#define LPP_UNIXTIME_MULT 1 +#define LPP_GYROMETER_MULT 100 +#define LPP_GPS_LAT_LON_MULT 10000 +#define LPP_GPS_ALT_MULT 100 +#define LPP_SWITCH_MULT 1 +#define LPP_CONCENTRATION_MULT 1 +#define LPP_COLOUR_MULT 1 + +#define LPP_ERROR_OK 0 +#define LPP_ERROR_OVERFLOW 1 +#define LPP_ERROR_UNKOWN_TYPE 2 + +class LPPReader { + const uint8_t* _buf; + uint8_t _len; + uint8_t _pos; + + float getFloat(const uint8_t * buffer, uint8_t size, uint32_t multiplier, bool is_signed) { + uint32_t value = 0; + for (uint8_t i = 0; i < size; i++) { + value = (value << 8) + buffer[i]; + } + + int sign = 1; + if (is_signed) { + uint32_t bit = 1ul << ((size * 8) - 1); + if ((value & bit) == bit) { + value = (bit << 1) - value; + sign = -1; + } + } + return sign * ((float) value / multiplier); + } + +public: + LPPReader(const uint8_t buf[], uint8_t len) : _buf(buf), _len(len), _pos(0) { } + + void reset() { + _pos = 0; + } + + bool readHeader(uint8_t& channel, uint8_t& type) { + if (_pos + 2 < _len) { + channel = _buf[_pos++]; + type = _buf[_pos++]; + + return channel != 0; // channel 0 is End-of-data + } + return false; // end-of-buffer + } + + bool readGPS(float& lat, float& lon, float& alt) { + lat = getFloat(&_buf[_pos], 3, 10000, true); _pos += 3; + lon = getFloat(&_buf[_pos], 3, 10000, true); _pos += 3; + alt = getFloat(&_buf[_pos], 3, 100, true); _pos += 3; + return _pos <= _len; + } + bool readVoltage(float& voltage) { + voltage = getFloat(&_buf[_pos], 2, 100, false); _pos += 2; + return _pos <= _len; + } + bool readCurrent(float& amps) { + amps = getFloat(&_buf[_pos], 2, 1000, false); _pos += 2; + return _pos <= _len; + } + bool readPower(float& watts) { + watts = getFloat(&_buf[_pos], 2, 1, false); _pos += 2; + return _pos <= _len; + } + bool readTemperature(float& degrees_c) { + degrees_c = getFloat(&_buf[_pos], 2, 10, true); _pos += 2; + return _pos <= _len; + } + bool readPressure(float& pa) { + pa = getFloat(&_buf[_pos], 2, 10, false); _pos += 2; + return _pos <= _len; + } + bool readRelativeHumidity(float& pct) { + pct = getFloat(&_buf[_pos], 1, 2, false); _pos += 1; + return _pos <= _len; + } + bool readAltitude(float& m) { + m = getFloat(&_buf[_pos], 2, 1, true); _pos += 2; + return _pos <= _len; + } + + void skipData(uint8_t type) { + switch (type) { + case LPP_GPS: + _pos += 9; break; + case LPP_POLYLINE: + _pos += 8; break; // TODO: this is MINIMIUM + case LPP_GYROMETER: + case LPP_ACCELEROMETER: + _pos += 6; break; + case LPP_GENERIC_SENSOR: + case LPP_FREQUENCY: + case LPP_DISTANCE: + case LPP_ENERGY: + case LPP_UNIXTIME: + _pos += 4; break; + case LPP_COLOUR: + _pos += 3; break; + case LPP_ANALOG_INPUT: + case LPP_ANALOG_OUTPUT: + case LPP_LUMINOSITY: + case LPP_TEMPERATURE: + case LPP_CONCENTRATION: + case LPP_BAROMETRIC_PRESSURE: + case LPP_ALTITUDE: + case LPP_VOLTAGE: + case LPP_CURRENT: + case LPP_DIRECTION: + case LPP_POWER: + _pos += 2; break; + default: + _pos++; + } + } +}; + +class LPPWriter { + uint8_t* _buf; + uint8_t _max_len; + uint8_t _len; + + void write(uint16_t value) { + _buf[_len++] = (value >> 8) & 0xFF; // MSB + _buf[_len++] = value & 0xFF; // LSB + } + +public: + LPPWriter(uint8_t buf[], uint8_t max_len): _buf(buf), _max_len(max_len), _len(0) { } + + bool writeVoltage(uint8_t channel, float voltage) { + if (_len + 4 <= _max_len) { + _buf[_len++] = channel; + _buf[_len++] = LPP_VOLTAGE; + uint16_t value = voltage * 100; + write(value); + return true; + } + return false; + } + + bool writeGPS(uint8_t channel, float lat, float lon, float alt) { + if (_len + 11 <= _max_len) { + _buf[_len++] = channel; + _buf[_len++] = LPP_GPS; + + int32_t lati = lat * 10000; // we lose some precision :-( + int32_t loni = lon * 10000; + int32_t alti = alt * 100; + + _buf[_len++] = lati >> 16; + _buf[_len++] = lati >> 8; + _buf[_len++] = lati; + _buf[_len++] = loni >> 16; + _buf[_len++] = loni >> 8; + _buf[_len++] = loni; + _buf[_len++] = alti >> 16; + _buf[_len++] = alti >> 8; + _buf[_len++] = alti; + return true; + } + return false; + } + + uint8_t length() { return _len; } +}; diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index 7d64fad7..36617495 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -88,6 +88,8 @@ build_flags = -D BLE_PIN_CODE=123456 ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 @@ -109,6 +111,7 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 build_src_filter = ${LilyGo_T-Echo.build_src_filter} +<../examples/companion_radio/*.cpp> From 80d5e2d8bcabdcdc512e48a239b325ddaa4531c3 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Wed, 10 Sep 2025 17:04:03 +1200 Subject: [PATCH 047/546] fix wismesh tag power off and wake up --- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 22af6f74..e5104a58 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -62,7 +62,8 @@ public: digitalWrite(LED_PIN, HIGH); #endif #ifdef BUTTON_PIN - while(digitalRead(BUTTON_PIN)); + // wismesh tag uses LOW to indicate button is pressed, wait until it goes HIGH to indicate it was released + while(digitalRead(BUTTON_PIN) == LOW); #endif #ifdef LED_GREEN digitalWrite(LED_GREEN, LOW); @@ -72,7 +73,8 @@ public: #endif #ifdef BUTTON_PIN - nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_HIGH); + // configure button press to wake up when in powered off state + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(BUTTON_PIN), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif sd_power_system_off(); From 65ef6c2fd0db17a103c9b61708da6ba78f5632a1 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 10 Sep 2025 17:04:58 +1000 Subject: [PATCH 048/546] * repeater and room server build_src_filter fixes --- variants/generic-e22/platformio.ini | 12 ++++++------ variants/generic_espnow/platformio.ini | 4 ++-- variants/lilygo_tlora_c6/platformio.ini | 2 +- variants/rak3x72/platformio.ini | 2 +- variants/sensecap_solar/platformio.ini | 4 ++-- variants/tenstar_c3/platformio.ini | 12 ++++++------ variants/thinknode_m1/platformio.ini | 4 ++-- variants/wio-e5-dev/platformio.ini | 2 +- variants/wio-e5-mini/platformio.ini | 2 +- variants/xiao_c3/platformio.ini | 2 +- variants/xiao_c6/platformio.ini | 6 +++--- variants/xiao_nrf52/platformio.ini | 4 ++-- variants/xiao_s3_wio/platformio.ini | 6 +++--- 13 files changed, 31 insertions(+), 31 deletions(-) diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index c9a67220..2f61f412 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -30,7 +30,7 @@ lib_deps = [env:Generic_E22_sx1262_repeater] extends = Generic_E22 build_src_filter = ${Generic_E22.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Generic_E22.build_flags} -D RADIO_CLASS=CustomSX1262 @@ -51,7 +51,7 @@ lib_deps = ; extends = Generic_E22 ; build_src_filter = ${Generic_E22.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> -; +<../examples/simple_repeater/main.cpp> +; +<../examples/simple_repeater/*.cpp> ; build_flags = ; ${Generic_E22.build_flags} ; -D RADIO_CLASS=CustomSX1262 @@ -75,7 +75,7 @@ lib_deps = extends = Generic_E22 build_src_filter = ${Generic_E22.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Generic_E22.build_flags} -D RADIO_CLASS=CustomSX1262 @@ -97,7 +97,7 @@ lib_deps = [env:Generic_E22_sx1268_repeater] extends = Generic_E22 build_src_filter = ${Generic_E22.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Generic_E22.build_flags} -D RADIO_CLASS=CustomSX1268 @@ -118,7 +118,7 @@ lib_deps = ; extends = Generic_E22 ; build_src_filter = ${Generic_E22.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> -; +<../examples/simple_repeater/main.cpp> +; +<../examples/simple_repeater/*.cpp> ; build_flags = ; ${Generic_E22.build_flags} ; -D RADIO_CLASS=CustomSX1268 @@ -142,7 +142,7 @@ lib_deps = extends = Generic_E22 build_src_filter = ${Generic_E22.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Generic_E22.build_flags} -D RADIO_CLASS=CustomSX1268 diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index 1c14dfee..db87a81b 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -44,7 +44,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 build_src_filter = ${Generic_ESPNOW.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> lib_deps = ${Generic_ESPNOW.lib_deps} ${esp32_ota.lib_deps} @@ -75,7 +75,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' build_src_filter = ${Generic_ESPNOW.build_src_filter} - +<../examples/simple_room_server/main.cpp> + +<../examples/simple_room_server/*.cpp> lib_deps = ${Generic_ESPNOW.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 76a897d6..f8a935cd 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -33,7 +33,7 @@ build_src_filter = ${esp32c6_base.build_src_filter} [env:LilyGo_Tlora_C6_repeater] extends = tlora_c6 build_src_filter = ${tlora_c6.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${tlora_c6.build_flags} -D ADVERT_NAME='"Tlora C6 Repeater"' diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index 2fee1a76..da9b2f22 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -19,7 +19,7 @@ build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Repeater"' -D ADMIN_PASSWORD='"password"' build_src_filter = ${rak3x72.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> [env:rak3x72-sensor] extends = rak3x72 diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 649ace84..ee9acf0d 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -57,7 +57,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> [env:SenseCap_Solar_room_server] extends = SenseCap_Solar @@ -70,7 +70,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} - +<../examples/simple_room_server/main.cpp> + +<../examples/simple_room_server/*.cpp> [env:SenseCap_Solar_companion_radio_ble] extends = SenseCap_Solar diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index c22b3771..25bf6713 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter} [env:Tenstar_C3_Repeater_sx1262] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Tenstar_esp32_C3.build_flags} -D RADIO_CLASS=CustomSX1262 @@ -48,7 +48,7 @@ lib_deps = ; extends = Tenstar_esp32_C3 ; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> -; +<../examples/simple_repeater/main.cpp> +; +<../examples/simple_repeater/*.cpp> ; build_flags = ; ${Tenstar_esp32_C3.build_flags} ; -D RADIO_CLASS=CustomSX1262 @@ -73,7 +73,7 @@ lib_deps = extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Tenstar_esp32_C3.build_flags} -D RADIO_CLASS=CustomSX1262 @@ -96,7 +96,7 @@ lib_deps = [env:Tenstar_C3_Repeater_sx1268] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Tenstar_esp32_C3.build_flags} -D RADIO_CLASS=CustomSX1268 @@ -117,7 +117,7 @@ lib_deps = ; extends = Tenstar_esp32_C3 ; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> -; +<../examples/simple_repeater/main.cpp> +; +<../examples/simple_repeater/*.cpp> ; build_flags = ; ${Tenstar_esp32_C3.build_flags} ; -D RADIO_CLASS=CustomSX1268 @@ -141,7 +141,7 @@ lib_deps = extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Tenstar_esp32_C3.build_flags} -D RADIO_CLASS=CustomSX1268 diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index eeeb692e..7a8f5a3c 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -46,7 +46,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M1.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> lib_deps = ${ThinkNode_M1.lib_deps} @@ -61,7 +61,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M1.build_src_filter} - +<../examples/simple_room_server/main.cpp> + +<../examples/simple_room_server/*.cpp> lib_deps = ${ThinkNode_M1.lib_deps} diff --git a/variants/wio-e5-dev/platformio.ini b/variants/wio-e5-dev/platformio.ini index ce96ad2a..350d79fb 100644 --- a/variants/wio-e5-dev/platformio.ini +++ b/variants/wio-e5-dev/platformio.ini @@ -20,7 +20,7 @@ build_flags = ${lora_e5.build_flags} -D ADVERT_NAME='"WIO-E5 Repeater"' -D ADMIN_PASSWORD='"password"' build_src_filter = ${lora_e5.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> [env:wio-e5_companion_radio_usb] extends = lora_e5 diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 3d98d93e..30c8ef87 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -23,7 +23,7 @@ build_flags = ${lora_e5_mini.build_flags} -D ADVERT_NAME='"wio-e5-mini Repeater"' -D ADMIN_PASSWORD='"password"' build_src_filter = ${lora_e5_mini.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> [env:wio-e5-mini-sensor] extends = lora_e5_mini diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index de79fb10..e139fef3 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -22,7 +22,7 @@ build_src_filter = ${esp32_base.build_src_filter} [env:Xiao_C3_Repeater_sx1262] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} -D RADIO_CLASS=CustomSX1262 diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 24a17e06..83698d0a 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -33,7 +33,7 @@ build_src_filter = ${esp32c6_base.build_src_filter} [env:Xiao_C6_Repeater] extends = Xiao_C6 build_src_filter = ${Xiao_C6.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Xiao_C6.build_flags} -D ADVERT_NAME='"Xiao C6 Repeater"' @@ -93,7 +93,7 @@ build_flags = [env:Meshimi_Repeater] extends = Meshimi build_src_filter = ${Meshimi.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Meshimi.build_flags} -D ADVERT_NAME='"Meshimi Repeater"' @@ -150,7 +150,7 @@ build_flags = [env:WHY2025_badge_Repeater] extends = WHY2025_badge build_src_filter = ${WHY2025_badge.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${WHY2025_badge.build_flags} -D ADVERT_NAME='"WHY2025 Badge Repeater"' diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 10807476..ea1cdd3d 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -107,7 +107,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> [env:Xiao_nrf52_alt_pinout_repeater] extends = env:Xiao_nrf52_repeater @@ -126,4 +126,4 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} - +<../examples/simple_room_server/main.cpp> \ No newline at end of file + +<../examples/simple_room_server/*.cpp> \ No newline at end of file diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 7408f85d..e6d2357d 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -30,7 +30,7 @@ build_src_filter = ${esp32_base.build_src_filter} [env:Xiao_S3_WIO_Repeater] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Xiao_S3_WIO.build_flags} -D ADVERT_NAME='"XiaoS3 Repeater"' @@ -48,7 +48,7 @@ lib_deps = ; extends = Xiao_S3_WIO ; build_src_filter = ${Xiao_S3_WIO.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> -; +<../examples/simple_repeater/main.cpp> +; +<../examples/simple_repeater/*.cpp> ; build_flags = ; ${Xiao_S3_WIO.build_flags} ; -D ADVERT_NAME='"RS232 Bridge"' @@ -69,7 +69,7 @@ lib_deps = extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Xiao_S3_WIO.build_flags} -D ADVERT_NAME='"ESPNow Bridge"' From c8a10cc3b30814b44658e61eb8f3cd88a7e93818 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 10 Sep 2025 17:34:06 +1000 Subject: [PATCH 049/546] * RAK wishmesh tag: build fix --- variants/rak_wismesh_tag/platformio.ini | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 572919eb..6b505073 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -35,6 +35,7 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} + end2endzone/NonBlockingRTTTL@^1.3.0 [env:RAK_WisMesh_Tag_Repeater] extends = rak_wismesh_tag @@ -79,7 +80,6 @@ build_src_filter = ${rak_wismesh_tag.build_src_filter} lib_deps = ${rak_wismesh_tag.lib_deps} densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 [env:RAK_WisMesh_Tag_companion_radio_ble] extends = rak_wismesh_tag @@ -100,7 +100,6 @@ build_src_filter = ${rak_wismesh_tag.build_src_filter} lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 [env:RAK_WisMesh_Tag_sensor] extends = rak4631 From e42ecc3bb3d0c32570474153575eed584a0c9a0b Mon Sep 17 00:00:00 2001 From: 446564 <robjdev@mailbox.org> Date: Wed, 10 Sep 2025 09:44:58 -0700 Subject: [PATCH 050/546] fix nano g2 notification revert change to disable buzzer before hibernate needs more work as the buzzer pin is a macro and can't be changed at runtime --- variants/nano_g2_ultra/nano-g2.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 69df0c65..5cedb0f9 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -52,8 +52,8 @@ public: void powerOff() override { // put GPS chip to sleep digitalWrite(PIN_GPS_STANDBY, LOW); -// unset buzzer to prevent notification circuit activating on hibernate -#undef PIN_BUZZER + // TODO: unset buzzer to prevent notification circuit activating on hibernate + // needs to be set as silent or somehow stop using macros for pins nrf_gpio_cfg_sense_input(digitalPinToInterrupt(PIN_USER_BTN), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); From 510472bfa017b130fc0a71f2327df19d109f46e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Wed, 10 Sep 2025 23:56:07 +0100 Subject: [PATCH 051/546] Normalize repeater target names --- variants/lilygo_t3s3/platformio.ini | 6 +++--- variants/lilygo_t3s3_sx1276/platformio.ini | 6 +++--- variants/lilygo_tlora_v2_1/platformio.ini | 6 +++--- variants/promicro/platformio.ini | 2 +- variants/rak4631/platformio.ini | 2 +- variants/rak_wismesh_tag/platformio.ini | 2 +- variants/rpi_picow/platformio.ini | 2 +- variants/tenstar_c3/platformio.ini | 12 ++++++------ variants/waveshare_rp2040_lora/platformio.ini | 2 +- variants/wio-tracker-l1/platformio.ini | 2 +- variants/xiao_c3/platformio.ini | 2 +- variants/xiao_c6/platformio.ini | 6 +++--- variants/xiao_rp2040/platformio.ini | 2 +- variants/xiao_s3_wio/platformio.ini | 6 +++--- 14 files changed, 29 insertions(+), 29 deletions(-) diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index ca221108..9ca7f5ef 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -33,7 +33,7 @@ lib_deps = adafruit/Adafruit SSD1306 @ ^2.5.13 ; === LilyGo T3S3 with SX1262 environments === -[env:LilyGo_T3S3_sx1262_Repeater] +[env:LilyGo_T3S3_sx1262_repeater] extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} @@ -52,7 +52,7 @@ lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} ${esp32_ota.lib_deps} -; [env:LilyGo_T3S3_sx1262_Repeater_bridge_rs232] +; [env:LilyGo_T3S3_sx1262_repeater_bridge_rs232] ; extends = LilyGo_T3S3_sx1262 ; build_flags = ; ${LilyGo_T3S3_sx1262.build_flags} @@ -75,7 +75,7 @@ lib_deps = ; ${LilyGo_T3S3_sx1262.lib_deps} ; ${esp32_ota.lib_deps} -[env:LilyGo_T3S3_sx1262_Repeater_bridge_espnow] +[env:LilyGo_T3S3_sx1262_repeater_bridge_espnow] extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 1c0d5cf1..9df1fe0c 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -31,7 +31,7 @@ lib_deps = adafruit/Adafruit SSD1306 @ ^2.5.13 ; === LilyGo T3S3 with SX1276 environments === -[env:LilyGo_T3S3_sx1276_Repeater] +[env:LilyGo_T3S3_sx1276_repeater] extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} @@ -50,7 +50,7 @@ lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} ${esp32_ota.lib_deps} -; [env:LilyGo_T3S3_sx1276_Repeater_bridge_rs232] +; [env:LilyGo_T3S3_sx1276_repeater_bridge_rs232] ; extends = LilyGo_T3S3_sx1276 ; build_flags = ; ${LilyGo_T3S3_sx1276.build_flags} @@ -73,7 +73,7 @@ lib_deps = ; ${LilyGo_T3S3_sx1276.lib_deps} ; ${esp32_ota.lib_deps} -[env:LilyGo_T3S3_sx1276_Repeater_bridge_espnow] +[env:LilyGo_T3S3_sx1276_repeater_bridge_espnow] extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index aa957fba..0f62243c 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -45,7 +45,7 @@ lib_deps = adafruit/Adafruit BMP280 Library @ ^2.6.8 ; === LILYGO T-LoRa V2.1-1.6 with SX1276 environments === -[env:LilyGo_TLora_V2_1_1_6_Repeater] +[env:LilyGo_TLora_V2_1_1_6_repeater] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} +<helpers/ui/SSD1306Display.cpp> @@ -160,7 +160,7 @@ lib_deps = ; ; Repeater Bridges ; -[env:LilyGo_TLora_V2_1_1_6_bridge_rs232] +[env:LilyGo_TLora_V2_1_1_6_repeater_bridge_rs232] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} +<helpers/ui/SSD1306Display.cpp> @@ -183,7 +183,7 @@ lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} ${esp32_ota.lib_deps} -[env:LilyGo_TLora_V2_1_1_6_bridge_espnow] +[env:LilyGo_TLora_V2_1_1_6_repeater_bridge_espnow] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} +<helpers/ui/SSD1306Display.cpp> diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index a65d3c69..afd3bbcf 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -34,7 +34,7 @@ lib_deps= ${nrf52_base.lib_deps} adafruit/Adafruit BMP280 Library@^2.6.8 stevemarple/MicroNMEA @ ^2.0.6 -[env:Faketec_Repeater] +[env:Faketec_repeater] extends = Faketec build_src_filter = ${Faketec.build_src_filter} +<../examples/simple_repeater> diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 01f4d088..a1ccdc29 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -30,7 +30,7 @@ lib_deps = adafruit/Adafruit SSD1306 @ ^2.5.13 sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 -[env:RAK_4631_Repeater] +[env:RAK_4631_repeater] extends = rak4631 build_flags = ${rak4631.build_flags} diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 59d55175..f3ef451e 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -37,7 +37,7 @@ lib_deps = ${sensor_base.lib_deps} end2endzone/NonBlockingRTTTL@^1.3.0 -[env:RAK_WisMesh_Tag_Repeater] +[env:RAK_WisMesh_Tag_repeater] extends = rak_wismesh_tag build_flags = ${rak_wismesh_tag.build_flags} diff --git a/variants/rpi_picow/platformio.ini b/variants/rpi_picow/platformio.ini index adec5d77..a0a80f76 100644 --- a/variants/rpi_picow/platformio.ini +++ b/variants/rpi_picow/platformio.ini @@ -27,7 +27,7 @@ build_src_filter = ${rp2040_base.build_src_filter} +<../variants/rpi_picow> lib_deps = ${rp2040_base.lib_deps} -[env:PicoW_Repeater] +[env:PicoW_repeater] extends = rpi_picow build_flags = ${rpi_picow.build_flags} -D ADVERT_NAME='"PicoW Repeater"' diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 25bf6713..04a1f20b 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -23,7 +23,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/tenstar_c3> -[env:Tenstar_C3_Repeater_sx1262] +[env:Tenstar_C3_repeater_sx1262] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -44,7 +44,7 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} -; [env:Tenstar_C3_Repeater_sx1262_bridge_rs232] +; [env:Tenstar_C3_repeater_sx1262_bridge_rs232] ; extends = Tenstar_esp32_C3 ; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> @@ -69,7 +69,7 @@ lib_deps = ; ${Tenstar_esp32_C3.lib_deps} ; ${esp32_ota.lib_deps} -[env:Tenstar_C3_Repeater_sx1262_bridge_espnow] +[env:Tenstar_C3_repeater_sx1262_bridge_espnow] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> @@ -93,7 +93,7 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} -[env:Tenstar_C3_Repeater_sx1268] +[env:Tenstar_C3_repeater_sx1268] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -113,7 +113,7 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} -; [env:Tenstar_C3_Repeater_sx1268_bridge_rs232] +; [env:Tenstar_C3_repeater_sx1268_bridge_rs232] ; extends = Tenstar_esp32_C3 ; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> @@ -137,7 +137,7 @@ lib_deps = ; ${Tenstar_esp32_C3.lib_deps} ; ${esp32_ota.lib_deps} -[env:Tenstar_C3_Repeater_sx1268_bridge_espnow] +[env:Tenstar_C3_repeater_sx1268_bridge_espnow] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index f2f180aa..cf113189 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -33,7 +33,7 @@ build_src_filter = ${rp2040_base.build_src_filter} +<../variants/waveshare_rp2040_lora> lib_deps = ${rp2040_base.lib_deps} -[env:waveshare_rp2040_lora_Repeater] +[env:waveshare_rp2040_lora_repeater] extends = waveshare_rp2040_lora build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_NAME='"RP2040-LoRa Repeater"' diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 585e0ba7..1a6c6bf9 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -24,7 +24,7 @@ lib_deps= ${nrf52_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 stevemarple/MicroNMEA @ ^2.0.6 -[env:WioTrackerL1_Repeater] +[env:WioTrackerL1_repeater] extends = WioTrackerL1 build_src_filter = ${WioTrackerL1.build_src_filter} +<../examples/simple_repeater> diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index e139fef3..84c6d0f7 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -19,7 +19,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_c3> -[env:Xiao_C3_Repeater_sx1262] +[env:Xiao_C3_repeater_sx1262] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 83698d0a..ab032224 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -30,7 +30,7 @@ build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/xiao_c6> +<XiaoC6Board.cpp> -[env:Xiao_C6_Repeater] +[env:Xiao_C6_repeater] extends = Xiao_C6 build_src_filter = ${Xiao_C6.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -90,7 +90,7 @@ build_flags = -D SX126X_RX_BOOSTED_GAIN=1 -D USE_XIAO_ESP32C6_EXTERNAL_ANTENNA=1 -[env:Meshimi_Repeater] +[env:Meshimi_repeater] extends = Meshimi build_src_filter = ${Meshimi.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -147,7 +147,7 @@ build_flags = -USX126X_DIO2_AS_RF_SWITCH -USX126X_DIO3_TCXO_VOLTAGE -[env:WHY2025_badge_Repeater] +[env:WHY2025_badge_repeater] extends = WHY2025_badge build_src_filter = ${WHY2025_badge.build_src_filter} +<../examples/simple_repeater/*.cpp> diff --git a/variants/xiao_rp2040/platformio.ini b/variants/xiao_rp2040/platformio.ini index 6c9c70f6..4a1ee4ef 100644 --- a/variants/xiao_rp2040/platformio.ini +++ b/variants/xiao_rp2040/platformio.ini @@ -29,7 +29,7 @@ build_src_filter = ${rp2040_base.build_src_filter} +<../variants/xiao_rp2040> lib_deps = ${rp2040_base.lib_deps} -[env:Xiao_rp2040_Repeater] +[env:Xiao_rp2040_repeater] extends = Xiao_rp2040 build_flags = ${Xiao_rp2040.build_flags} -D ADVERT_NAME='"Xiao Repeater"' diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index e6d2357d..69cb5c1f 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -27,7 +27,7 @@ build_flags = ${esp32_base.build_flags} build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_s3_wio> -[env:Xiao_S3_WIO_Repeater] +[env:Xiao_S3_WIO_repeater] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -44,7 +44,7 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} ${esp32_ota.lib_deps} -; [env:Xiao_S3_WIO_Repeater_bridge_rs232] +; [env:Xiao_S3_WIO_repeater_bridge_rs232] ; extends = Xiao_S3_WIO ; build_src_filter = ${Xiao_S3_WIO.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> @@ -65,7 +65,7 @@ lib_deps = ; ${Xiao_S3_WIO.lib_deps} ; ${esp32_ota.lib_deps} -[env:Xiao_S3_WIO_Repeater_bridge_espnow] +[env:Xiao_S3_WIO_repeater_bridge_espnow] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> From 281591f147444871f429d0c2cfc5d1805b041b93 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 12 Sep 2025 15:35:31 +1000 Subject: [PATCH 052/546] * refactor: moved ACL out of SensorMesh -> ClientACL --- examples/simple_sensor/SensorMesh.cpp | 228 ++++++-------------------- examples/simple_sensor/SensorMesh.h | 35 +--- src/Identity.cpp | 2 +- src/Identity.h | 2 +- src/helpers/ClientACL.cpp | 128 +++++++++++++++ src/helpers/ClientACL.h | 47 ++++++ 6 files changed, 232 insertions(+), 210 deletions(-) create mode 100644 src/helpers/ClientACL.cpp create mode 100644 src/helpers/ClientACL.h diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 72c0d97b..0508713c 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -71,78 +71,6 @@ static File openAppend(FILESYSTEM* _fs, const char* fname) { #endif } -static File openWrite(FILESYSTEM* _fs, const char* filename) { - #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - _fs->remove(filename); - return _fs->open(filename, FILE_O_WRITE); - #elif defined(RP2040_PLATFORM) - return _fs->open(filename, "w"); - #else - return _fs->open(filename, "w", true); - #endif -} - -void SensorMesh::loadContacts() { - num_contacts = 0; - if (_fs->exists("/s_contacts")) { - #if defined(RP2040_PLATFORM) - File file = _fs->open("/s_contacts", "r"); - #else - File file = _fs->open("/s_contacts"); - #endif - if (file) { - bool full = false; - while (!full) { - ContactInfo c; - uint8_t pub_key[32]; - uint8_t unused[6]; - - bool success = (file.read(pub_key, 32) == 32); - success = success && (file.read((uint8_t *) &c.permissions, 1) == 1); - success = success && (file.read(unused, 6) == 6); - success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); - success = success && (file.read(c.out_path, 64) == 64); - success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); - c.last_timestamp = 0; // transient - c.last_activity = 0; - - if (!success) break; // EOF - - c.id = mesh::Identity(pub_key); - if (num_contacts < MAX_CONTACTS) { - contacts[num_contacts++] = c; - } else { - full = true; - } - } - file.close(); - } - } -} - -void SensorMesh::saveContacts() { - File file = openWrite(_fs, "/s_contacts"); - if (file) { - uint8_t unused[5]; - memset(unused, 0, sizeof(unused)); - - for (int i = 0; i < num_contacts; i++) { - auto c = &contacts[i]; - if (c->permissions == 0) continue; // skip deleted entries - - bool success = (file.write(c->id.pub_key, 32) == 32); - success = success && (file.write((uint8_t *) &c->permissions, 1) == 1); - success = success && (file.write(unused, 6) == 6); - success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1); - success = success && (file.write(c->out_path, 64) == 64); - success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); - - if (!success) break; // write failed - } - file.close(); - } -} - static uint8_t getDataSize(uint8_t type) { switch (type) { case LPP_GPS: @@ -295,8 +223,8 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint uint8_t res2 = payload[1]; if (res1 == 0 && res2 == 0) { uint8_t ofs = 4; - for (int i = 0; i < num_contacts && ofs + 7 <= sizeof(reply_data) - 4; i++) { - auto c = &contacts[i]; + for (int i = 0; i < acl.getNumClients() && ofs + 7 <= sizeof(reply_data) - 4; i++) { + auto c = acl.getClientByIdx(i); if (c->permissions == 0) continue; // skip deleted entries memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix reply_data[ofs++] = c->permissions; @@ -318,63 +246,7 @@ mesh::Packet* SensorMesh::createSelfAdvert() { return createAdvert(self_id, app_data, app_data_len); } -ContactInfo* SensorMesh::getContact(const uint8_t* pubkey, int key_len) { - for (int i = 0; i < num_contacts; i++) { - if (memcmp(pubkey, contacts[i].id.pub_key, key_len) == 0) return &contacts[i]; // already known - } - return NULL; // not found -} - -ContactInfo* SensorMesh::putContact(const mesh::Identity& id, uint8_t init_perms) { - uint32_t min_time = 0xFFFFFFFF; - ContactInfo* oldest = &contacts[MAX_CONTACTS - 1]; - for (int i = 0; i < num_contacts; i++) { - if (id.matches(contacts[i].id)) return &contacts[i]; // already known - if (!contacts[i].isAdmin() && contacts[i].last_activity < min_time) { - oldest = &contacts[i]; - min_time = oldest->last_activity; - } - } - - ContactInfo* c; - if (num_contacts < MAX_CONTACTS) { - c = &contacts[num_contacts++]; - } else { - c = oldest; // evict least active contact - } - memset(c, 0, sizeof(*c)); - c->permissions = init_perms; - c->id = id; - c->out_path_len = -1; // initially out_path is unknown - return c; -} - -bool SensorMesh::applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms) { - ContactInfo* c; - if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts - c = getContact(pubkey, key_len); - if (c == NULL) return false; // partial pubkey not found - - num_contacts--; // delete from contacts[] - int i = c - contacts; - while (i < num_contacts) { - contacts[i] = contacts[i + 1]; - i++; - } - } else { - if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying - - mesh::Identity id(pubkey); - c = putContact(id, 0); - - c->permissions = perms; // update their permissions - self_id.calcSharedSecret(c->shared_secret, pubkey); - } - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger saveContacts() - return true; -} - -void SensorMesh::sendAlert(ContactInfo* c, Trigger* t) { +void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { int text_len = strlen(t->text); uint8_t data[MAX_PACKET_PAYLOAD]; @@ -457,9 +329,9 @@ int SensorMesh::getAGCResetInterval() const { } uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { - ContactInfo* client; + ClientInfo* client; if (data[0] == 0) { // blank password, just check if sender is in ACL - client = getContact(sender.pub_key, PUB_KEY_SIZE); + client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { #if MESH_DEBUG MESH_DEBUG_PRINTLN("Login, sender not in ACL"); @@ -474,7 +346,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* return 0; } - client = putContact(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known) + client = acl.putClient(sender, PERM_RECV_ALERTS_HI | PERM_RECV_ALERTS_LO); // add to contacts (if not already known) if (sender_timestamp <= client->last_timestamp) { MESH_DEBUG_PRINTLN("Possible login replay attack!"); return 0; // FATAL: client table is full -OR- replay attack @@ -527,7 +399,8 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r int hex_len = min(sp - hex, PUB_KEY_SIZE*2); if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) { uint8_t perms = atoi(sp); - if (applyContactPermissions(pubkey, hex_len / 2, perms)) { + if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) { + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger acl.save() strcpy(reply, "OK"); } else { strcpy(reply, "Err - invalid params"); @@ -538,8 +411,8 @@ void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* r } } else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) { Serial.println("ACL:"); - for (int i = 0; i < num_contacts; i++) { - auto c = &contacts[i]; + for (int i = 0; i < acl.getNumClients(); i++) { + auto c = acl.getClientByIdx(i); if (c->permissions == 0) continue; // skip deleted entries Serial.printf("%02X ", c->permissions); @@ -595,8 +468,8 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con int SensorMesh::searchPeersByHash(const uint8_t* hash) { int n = 0; - for (int i = 0; i < num_contacts && n < MAX_SEARCH_RESULTS; i++) { - if (contacts[i].id.isHashMatch(hash)) { + for (int i = 0; i < acl.getNumClients() && n < MAX_SEARCH_RESULTS; i++) { + if (acl.getClientByIdx(i)->id.isHashMatch(hash)) { matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) } } @@ -605,15 +478,15 @@ int SensorMesh::searchPeersByHash(const uint8_t* hash) { void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; - if (i >= 0 && i < num_contacts) { + if (i >= 0 && i < acl.getNumClients()) { // lookup pre-calculated shared_secret - memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); + memcpy(dest_secret, acl.getClientByIdx(i)->shared_secret, PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); } } -void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { +void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash) { if (dest.out_path_len < 0) { mesh::Packet* ack = createAck(ack_hash); if (ack) sendFlood(ack, TXT_ACK_DELAY); @@ -632,34 +505,34 @@ void SensorMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) { int i = matching_peer_indexes[sender_idx]; - if (i < 0 || i >= num_contacts) { + if (i < 0 || i >= acl.getNumClients()) { MESH_DEBUG_PRINTLN("onPeerDataRecv: Invalid sender idx: %d", i); return; } - ContactInfo& from = contacts[i]; + ClientInfo* from = acl.getClientByIdx(i); if (type == PAYLOAD_TYPE_REQ) { // request (from a known contact) uint32_t timestamp; memcpy(×tamp, data, 4); - if (timestamp > from.last_timestamp) { // prevent replay attacks - uint8_t reply_len = handleRequest(from.isAdmin() ? 0xFF : from.permissions, timestamp, data[4], &data[5], len - 5); + if (timestamp > from->last_timestamp) { // prevent replay attacks + uint8_t reply_len = handleRequest(from->isAdmin() ? 0xFF : from->permissions, timestamp, data[4], &data[5], len - 5); if (reply_len == 0) return; // invalid command - from.last_timestamp = timestamp; - from.last_activity = getRTCClock()->getCurrentTime(); + from->last_timestamp = timestamp; + from->last_activity = getRTCClock()->getCurrentTime(); if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, + mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, reply_data, reply_len); + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len); if (reply) { - if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); + if (from->out_path_len >= 0) { // we have an out_path, so send DIRECT + sendDirect(reply, from->out_path, from->out_path_len, SERVER_RESPONSE_DELAY); } else { sendFlood(reply, SERVER_RESPONSE_DELAY); } @@ -668,30 +541,30 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } else { MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); } - } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from.isAdmin()) { // a CLI command + } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) uint flags = (data[4] >> 2); // message attempt number, and other flags - if (sender_timestamp > from.last_timestamp) { // prevent replay attacks + if (sender_timestamp > from->last_timestamp) { // prevent replay attacks if (flags == TXT_TYPE_PLAIN) { - bool handled = handleIncomingMsg(from, sender_timestamp, &data[5], flags, len - 5); + bool handled = handleIncomingMsg(*from, sender_timestamp, &data[5], flags, len - 5); if (handled) { // if msg was handled then send an ack uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it - mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from.id.pub_key, PUB_KEY_SIZE); + mesh::Utils::sha256((uint8_t *) &ack_hash, 4, data, 5 + strlen((char *)&data[5]), from->id.pub_key, PUB_KEY_SIZE); if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK - mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, - PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); + mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); if (path) sendFlood(path, TXT_ACK_DELAY); } else { - sendAckTo(from, ack_hash); - } + sendAckTo(*from, ack_hash); + } } } else if (flags == TXT_TYPE_CLI_DATA) { - from.last_timestamp = sender_timestamp; - from.last_activity = getRTCClock()->getCurrentTime(); + from->last_timestamp = sender_timestamp; + from->last_activity = getRTCClock()->getCurrentTime(); // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator @@ -711,12 +584,12 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i memcpy(temp, ×tamp, 4); // mostly an extra blob to help make packet_hash unique temp[4] = (TXT_TYPE_CLI_DATA << 2); - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from.id, secret, temp, 5 + text_len); + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len); if (reply) { - if (from.out_path_len < 0) { + if (from->out_path_len < 0) { sendFlood(reply, CLI_REPLY_DELAY_MILLIS); } else { - sendDirect(reply, from.out_path, from.out_path_len, CLI_REPLY_DELAY_MILLIS); + sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS); } } } @@ -729,7 +602,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } } -bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) { +bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) { MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from "); #ifdef MESH_DEBUG mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE); @@ -740,21 +613,21 @@ bool SensorMesh::handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { int i = matching_peer_indexes[sender_idx]; - if (i < 0 || i >= num_contacts) { + if (i < 0 || i >= acl.getNumClients()) { MESH_DEBUG_PRINTLN("onPeerPathRecv: Invalid sender idx: %d", i); return false; } - ContactInfo& from = contacts[i]; + ClientInfo* from = acl.getClientByIdx(i); MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len); // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from.out_path, path, from.out_path_len = path_len); // store a copy of path, for sendDirect() - from.last_activity = getRTCClock()->getCurrentTime(); + memcpy(from->out_path, path, from->out_path_len = path_len); // store a copy of path, for sendDirect() + from->last_activity = getRTCClock()->getCurrentTime(); // REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes?? - if (from.isAdmin()) { + if (from->isAdmin()) { // only do saveContacts() (of this out_path change) if this is an admin dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } @@ -781,7 +654,6 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { - num_contacts = 0; next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; last_read_time = 0; @@ -815,7 +687,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { // load persisted prefs _cli.loadPrefs(_fs); - loadContacts(); + acl.load(_fs); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); @@ -967,13 +839,13 @@ void SensorMesh::loop() { if (millisHasNowPassed(t->send_expiry)) { // next send needed? if (t->attempt >= 4) { // max attempts reached, try next contact t->curr_contact_idx++; - if (t->curr_contact_idx >= num_contacts) { // no more contacts to try? + if (t->curr_contact_idx >= acl.getNumClients()) { // no more contacts to try? num_alert_tasks--; // remove t from queue for (int i = 0; i < num_alert_tasks; i++) { alert_tasks[i] = alert_tasks[i + 1]; } } else { - auto c = &contacts[t->curr_contact_idx]; + auto c = acl.getClientByIdx(t->curr_contact_idx); uint16_t pri_mask = (t->pri == HIGH_PRI_ALERT) ? PERM_RECV_ALERTS_HI : PERM_RECV_ALERTS_LO; if (c->permissions & pri_mask) { // contact wants alert @@ -986,8 +858,8 @@ void SensorMesh::loop() { // next contact tested in next ::loop() } } - } else if (t->curr_contact_idx < num_contacts) { - auto c = &contacts[t->curr_contact_idx]; // send next attempt + } else if (t->curr_contact_idx < acl.getNumClients()) { + auto c = acl.getClientByIdx(t->curr_contact_idx); // send next attempt sendAlert(c, t); // NOTE: modifies attempt, expected_acks[] and send_expiry } else { // contact list has likely been modified while waiting for alert ACK, cancel this task @@ -998,7 +870,7 @@ void SensorMesh::loop() { // is there are pending dirty contacts write needed? if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) { - saveContacts(); + acl.save(_fs); dirty_contacts_expiry = 0; } } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 7b3b3954..7f7ee794 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -20,15 +20,10 @@ #include <helpers/AdvertDataHelpers.h> #include <helpers/TxtDataHelpers.h> #include <helpers/CommonCLI.h> +#include <helpers/ClientACL.h> #include <RTClib.h> #include <target.h> -#define PERM_ACL_ROLE_MASK 3 // lower 2 bits -#define PERM_ACL_GUEST 0 -#define PERM_ACL_READ_ONLY 1 -#define PERM_ACL_READ_WRITE 2 -#define PERM_ACL_ADMIN 3 - #define PERM_RESERVED1 (1 << 2) #define PERM_RESERVED2 (1 << 3) #define PERM_RESERVED3 (1 << 4) @@ -36,18 +31,6 @@ #define PERM_RECV_ALERTS_LO (1 << 6) // low priority alerts #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts -struct ContactInfo { - mesh::Identity id; - uint8_t permissions; - int8_t out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; - uint8_t shared_secret[PUB_KEY_SIZE]; - uint32_t last_timestamp; // by THEIR clock (transient) - uint32_t last_activity; // by OUR clock (transient) - - bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; } -}; - #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "1 Sep 2025" #endif @@ -58,8 +41,6 @@ struct ContactInfo { #define FIRMWARE_ROLE "sensor" -#define MAX_CONTACTS 20 - #define MAX_SEARCH_RESULTS 8 #define MAX_CONCURRENT_ALERTS 4 @@ -141,16 +122,15 @@ protected: void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; - virtual bool handleIncomingMsg(ContactInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); - void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); + virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); + void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); private: FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; NodePrefs _prefs; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; - ContactInfo contacts[MAX_CONTACTS]; - int num_contacts; + ClientACL acl; unsigned long dirty_contacts_expiry; CayenneLPP telemetry; uint32_t last_read_time; @@ -163,15 +143,10 @@ private: uint8_t pending_sf; uint8_t pending_cr; - void loadContacts(); - void saveContacts(); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); - ContactInfo* getContact(const uint8_t* pubkey, int key_len); - ContactInfo* putContact(const mesh::Identity& id, uint8_t init_perms); - bool applyContactPermissions(const uint8_t* pubkey, int key_len, uint8_t perms); - void sendAlert(ContactInfo* c, Trigger* t); + void sendAlert(const ClientInfo* c, Trigger* t); }; diff --git a/src/Identity.cpp b/src/Identity.cpp index 138c66b7..83298928 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -92,7 +92,7 @@ void LocalIdentity::sign(uint8_t* sig, const uint8_t* message, int msg_len) cons ed25519_sign(sig, message, msg_len, pub_key, prv_key); } -void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) { +void LocalIdentity::calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const { ed25519_key_exchange(secret, other_pub_key, prv_key); } diff --git a/src/Identity.h b/src/Identity.h index e84c1934..24a7c0ff 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -71,7 +71,7 @@ public: * \param secret OUT - the 'shared secret' (must be PUB_KEY_SIZE bytes) * \param other_pub_key IN - the public key of second party in the exchange (must be PUB_KEY_SIZE bytes) */ - void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key); + void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const; bool readFrom(Stream& s); bool writeTo(Stream& s) const; diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp new file mode 100644 index 00000000..23f7b337 --- /dev/null +++ b/src/helpers/ClientACL.cpp @@ -0,0 +1,128 @@ +#include "ClientACL.h" + +static File openWrite(FILESYSTEM* _fs, const char* filename) { + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(filename); + return _fs->open(filename, FILE_O_WRITE); + #elif defined(RP2040_PLATFORM) + return _fs->open(filename, "w"); + #else + return _fs->open(filename, "w", true); + #endif +} + +void ClientACL::load(FILESYSTEM* _fs) { + num_clients = 0; + if (_fs->exists("/s_contacts")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/s_contacts", "r"); + #else + File file = _fs->open("/s_contacts"); + #endif + if (file) { + bool full = false; + while (!full) { + ClientInfo c; + uint8_t pub_key[32]; + uint8_t unused[6]; + + bool success = (file.read(pub_key, 32) == 32); + success = success && (file.read((uint8_t *) &c.permissions, 1) == 1); + success = success && (file.read(unused, 6) == 6); + success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); + success = success && (file.read(c.out_path, 64) == 64); + success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); + c.last_timestamp = 0; // transient + c.last_activity = 0; + + if (!success) break; // EOF + + c.id = mesh::Identity(pub_key); + if (num_clients < MAX_CLIENTS) { + clients[num_clients++] = c; + } else { + full = true; + } + } + file.close(); + } + } +} + +void ClientACL::save(FILESYSTEM* _fs) { + File file = openWrite(_fs, "/s_contacts"); + if (file) { + uint8_t unused[6]; + memset(unused, 0, sizeof(unused)); + + for (int i = 0; i < num_clients; i++) { + auto c = &clients[i]; + if (c->permissions == 0) continue; // skip deleted entries + + bool success = (file.write(c->id.pub_key, 32) == 32); + success = success && (file.write((uint8_t *) &c->permissions, 1) == 1); + success = success && (file.write(unused, 6) == 6); + success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1); + success = success && (file.write(c->out_path, 64) == 64); + success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); + + if (!success) break; // write failed + } + file.close(); + } +} + +ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) { + for (int i = 0; i < num_clients; i++) { + if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known + } + return NULL; // not found +} + +ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) { + uint32_t min_time = 0xFFFFFFFF; + ClientInfo* oldest = &clients[MAX_CLIENTS - 1]; + for (int i = 0; i < num_clients; i++) { + if (id.matches(clients[i].id)) return &clients[i]; // already known + if (!clients[i].isAdmin() && clients[i].last_activity < min_time) { + oldest = &clients[i]; + min_time = oldest->last_activity; + } + } + + ClientInfo* c; + if (num_clients < MAX_CLIENTS) { + c = &clients[num_clients++]; + } else { + c = oldest; // evict least active contact + } + memset(c, 0, sizeof(*c)); + c->permissions = init_perms; + c->id = id; + c->out_path_len = -1; // initially out_path is unknown + return c; +} + +bool ClientACL::applyPermissions(const mesh::LocalIdentity& self_id, const uint8_t* pubkey, int key_len, uint8_t perms) { + ClientInfo* c; + if ((perms & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { // guest role is not persisted in contacts + c = getClient(pubkey, key_len); + if (c == NULL) return false; // partial pubkey not found + + num_clients--; // delete from contacts[] + int i = c - clients; + while (i < num_clients) { + clients[i] = clients[i + 1]; + i++; + } + } else { + if (key_len < PUB_KEY_SIZE) return false; // need complete pubkey when adding/modifying + + mesh::Identity id(pubkey); + c = putClient(id, 0); + + c->permissions = perms; // update their permissions + self_id.calcSharedSecret(c->shared_secret, pubkey); + } + return true; +} diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h new file mode 100644 index 00000000..f8cc1233 --- /dev/null +++ b/src/helpers/ClientACL.h @@ -0,0 +1,47 @@ +#pragma once + +#include <Arduino.h> // needed for PlatformIO +#include <Mesh.h> +#include <helpers/IdentityStore.h> + +#define PERM_ACL_ROLE_MASK 3 // lower 2 bits +#define PERM_ACL_GUEST 0 +#define PERM_ACL_READ_ONLY 1 +#define PERM_ACL_READ_WRITE 2 +#define PERM_ACL_ADMIN 3 + +struct ClientInfo { + mesh::Identity id; + uint8_t permissions; + int8_t out_path_len; + uint8_t out_path[MAX_PATH_SIZE]; + uint8_t shared_secret[PUB_KEY_SIZE]; + uint32_t last_timestamp; // by THEIR clock (transient) + uint32_t last_activity; // by OUR clock (transient) + + bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; } +}; + +#ifndef MAX_CLIENTS + #define MAX_CLIENTS 20 +#endif + +class ClientACL { + ClientInfo clients[MAX_CLIENTS]; + int num_clients; + +public: + ClientACL() { + memset(clients, 0, sizeof(clients)); + num_clients = 0; + } + void load(FILESYSTEM* _fs); + void save(FILESYSTEM* _fs); + + ClientInfo* getClient(const uint8_t* pubkey, int key_len); + ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms); + bool applyPermissions(const mesh::LocalIdentity& self_id, const uint8_t* pubkey, int key_len, uint8_t perms); + + int getNumClients() const { return num_clients; } + ClientInfo* getClientByIdx(int idx) { return &clients[idx]; } +}; From 25ea953cc3d744fde55b0cd78ffe222b4af5715f Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Fri, 12 Sep 2025 20:23:21 +1200 Subject: [PATCH 053/546] don't mark as connected until connection secured --- src/helpers/nrf52/SerialBLEInterface.cpp | 14 ++++++++++---- src/helpers/nrf52/SerialBLEInterface.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 4bc9d10a..ad5823f1 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -4,10 +4,7 @@ static SerialBLEInterface* instance; void SerialBLEInterface::onConnect(uint16_t connection_handle) { BLE_DEBUG_PRINTLN("SerialBLEInterface: connected"); - if(instance){ - instance->_isDeviceConnected = true; - // no need to stop advertising on connect, as the ble stack does this automatically - } + // we now set _isDeviceConnected=true in onSecured callback instead } void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) { @@ -18,6 +15,14 @@ void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason } } +void SerialBLEInterface::onSecured(uint16_t connection_handle) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured"); + if(instance){ + instance->_isDeviceConnected = true; + // no need to stop advertising on connect, as the ble stack does this automatically + } +} + void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { instance = this; @@ -36,6 +41,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); + Bluefruit.Security.setSecuredCallback(onSecured); // To be consistent OTA DFU should be added first if it exists //bledfu.begin(); diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index 239cf6c4..bf29892d 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -25,6 +25,7 @@ class SerialBLEInterface : public BaseSerialInterface { void clearBuffers() { send_queue_len = 0; } static void onConnect(uint16_t connection_handle); static void onDisconnect(uint16_t connection_handle, uint8_t reason); + static void onSecured(uint16_t connection_handle); public: SerialBLEInterface() { From b5820b1ce0ae8fb00baf8b51ae4111c614d769c9 Mon Sep 17 00:00:00 2001 From: Bryant Kelley <bryant@bryantkelley.com> Date: Fri, 12 Sep 2025 11:22:53 -0700 Subject: [PATCH 054/546] Add companion not showing up over BLE to FAQ --- docs/faq.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 6dc8fe9e..391ceb5b 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -63,10 +63,11 @@ author: https://github.com/LitBomb<!-- omit from toc --> - [6.1. Q: My client says another client or a repeater or a room server was last seen many, many days ago.](#61-q-my-client-says-another-client-or-a-repeater-or-a-room-server-was-last-seen-many-many-days-ago) - [6.2. Q: A repeater or a client or a room server I expect to see on my discover list (on T-Deck) or contact list (on a smart device client) are not listed.](#62-q-a-repeater-or-a-client-or-a-room-server-i-expect-to-see-on-my-discover-list-on-t-deck-or-contact-list-on-a-smart-device-client-are-not-listed) - [6.3. Q: How to connect to a repeater via BLE (Bluetooth)?](#63-q-how-to-connect-to-a-repeater-via-ble-bluetooth) - - [6.4. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) - - [6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection) - - [6.6. Q: My RAK/T1000-E/xiao\_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?](#66-q-my-rakt1000-exiao_nrf52-device-seems-to-be-corrupted-how-do-i-wipe-it-clean-to-start-fresh) - - [6.7. Q: WebFlasher fails on Linux with failed to open](#67-q-webflasher-fails-on-linux-with-failed-to-open) + - [6.4. Q: My companion isn't showing up over Bluetooth?](#64-q-my-companion-isnt-showing-up-over-bluetooth) + - [6.5. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code?](#64-q-i-cant-connect-via-bluetooth-what-is-the-bluetooth-pairing-code) + - [6.6. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection.](#65-q-my-heltec-v3-keeps-disconnecting-from-my-smartphone--it-cant-hold-a-solid-bluetooth-connection) + - [6.7. Q: My RAK/T1000-E/xiao\_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh?](#66-q-my-rakt1000-exiao_nrf52-device-seems-to-be-corrupted-how-do-i-wipe-it-clean-to-start-fresh) + - [6.8. Q: WebFlasher fails on Linux with failed to open](#67-q-webflasher-fails-on-linux-with-failed-to-open) - [7. Other Questions:](#7-other-questions) - [7.1 Q: How to update nRF (RAK, T114, Seed XIAO) repeater and room server firmware over the air using the new simpler DFU app?](#71-q-how-to-update-nrf-rak-t114-seed-xiao-repeater-and-room-server-firmware-over-the-air-using-the-new-simpler-dfu-app) - [7.2 Q: How to update ESP32-based devices over the air?](#72-q-how-to-update-esp32-based-devices-over-the-air) @@ -563,15 +564,19 @@ You can get the epoch time on <https://www.epochconverter.com/> and use it to se ### 6.3. Q: How to connect to a repeater via BLE (Bluetooth)? **A:** You can't connect to a device running repeater firmware via Bluetooth. Devices running the BLE companion firmware you can connect to it via Bluetooth using the android app -### 6.4. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code? +### 6.4. Q: My companion isn't showing up over Bluetooth? + +**A:** make sure that you flashed the Bluetooth companion firmware and not the USB-only companion firmware. + +### 6.5. Q: I can't connect via Bluetooth, what is the Bluetooth pairing code? **A:** the default Bluetooth pairing code is `123456` -### 6.5. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. +### 6.6. Q: My Heltec V3 keeps disconnecting from my smartphone. It can't hold a solid Bluetooth connection. **A:** Heltec V3 has a very small coil antenna on its PCB for Wi-Fi and Bluetooth connectivity. It has a very short range, only a few feet. It is possible to remove the coil antenna and replace it with a 31mm wire. The BT range is much improved with the modification. -### 6.6. Q: My RAK/T1000-E/xiao_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh? +### 6.7. Q: My RAK/T1000-E/xiao_nRF52 device seems to be corrupted, how do I wipe it clean to start fresh? **A:** 1. Connect USB-C cable to your device, per your device's instruction, get it to flash mode: @@ -591,8 +596,7 @@ You can get the epoch time on <https://www.epochconverter.com/> and use it to se Separately, starting in firmware version 1.7.0, there is a CLI Rescue mode. If your device has a user button (e.g. some RAK, T114), you can activate the rescue mode by hold down the user button of the device within 8 seconds of boot. Then you can use the 'Console' on flasher.meshcore.co.uk - -### 6.7. Q: WebFlasher fails on Linux with failed to open +### 6.8. Q: WebFlasher fails on Linux with failed to open **A:** If the usb port doesn't have the right ownership for this task, the process fails with the following error: `NetworkError: Failed to execute 'open' on 'SerialPort': Failed to open serial port.` From de2e0cf59c7661ee5cc3b624cc9cdbe1dc5e1018 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 13 Sep 2025 19:37:15 +1000 Subject: [PATCH 055/546] * repeater now using ClientACL class --- examples/simple_repeater/MyMesh.cpp | 216 +++++++++++++++++----------- examples/simple_repeater/MyMesh.h | 15 +- 2 files changed, 137 insertions(+), 94 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4265ca6a..dad2dd83 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -43,28 +43,13 @@ #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 +#define REQ_TYPE_GET_ACCESS_LIST 0x05 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define CLI_REPLY_DELAY_MILLIS 600 - -ClientInfo *MyMesh::putClient(const mesh::Identity &id) { - uint32_t min_time = 0xFFFFFFFF; - ClientInfo *oldest = &known_clients[0]; - for (int i = 0; i < MAX_CLIENTS; i++) { - if (known_clients[i].last_activity < min_time) { - oldest = &known_clients[i]; - min_time = oldest->last_activity; - } - if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known - } - - oldest->id = id; - oldest->out_path_len = -1; // initially out_path is unknown - oldest->last_timestamp = 0; - return oldest; -} +#define LAZY_CONTACTS_WRITE_DELAY 5000 void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float snr) { #if MAX_NEIGHBOURS // check if neighbours enabled @@ -93,15 +78,61 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn #endif } -int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, - size_t payload_len) { +uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { + ClientInfo* client; + if (data[0] == 0) { // blank password, just check if sender is in ACL + client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); + if (client == NULL) { + #if MESH_DEBUG + MESH_DEBUG_PRINTLN("Login, sender not in ACL"); + #endif + return 0; + } + } else { + uint8_t perms; + if (strcmp((char *)data, _prefs.password) == 0) { // check for valid admin password + perms = PERM_ACL_ADMIN; + } else if (strcmp((char *)data, _prefs.guest_password) == 0) { // check guest password + perms = PERM_ACL_GUEST; + } else { +#if MESH_DEBUG + MESH_DEBUG_PRINTLN("Invalid password: %s", data); +#endif + return 0; + } + + client = acl.putClient(sender, 0); // add to contacts (if not already known) + if (sender_timestamp <= client->last_timestamp) { + MESH_DEBUG_PRINTLN("Possible login replay attack!"); + return 0; // FATAL: client table is full -OR- replay attack + } + + MESH_DEBUG_PRINTLN("Login success!"); + client->last_timestamp = sender_timestamp; + client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions |= perms; + memcpy(client->shared_secret, secret, PUB_KEY_SIZE); + + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + } + + uint32_t now = getRTCClock()->getCurrentTimeUnique(); + memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp + reply_data[4] = RESP_SERVER_LOGIN_OK; + reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) + reply_data[6] = client->isAdmin() ? 1 : 0; + reply_data[7] = client->permissions; + getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness + + return 12; // reply length +} + +int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp - memcpy(reply_data, &sender_timestamp, - 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') + memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') - switch (payload[0]) { - case REQ_TYPE_GET_STATUS: { // guests can also access this now + if (payload[0] == REQ_TYPE_GET_STATUS) { // guests can also access this now RepeaterStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); @@ -125,18 +156,31 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return 4 + sizeof(stats); // reply_len } - case REQ_TYPE_GET_TELEMETRY_DATA: { + if (payload[0] == REQ_TYPE_GET_TELEMETRY_DATA) { uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors((sender->is_admin ? 0xFF : 0x00) & perm_mask, telemetry); + sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); return 4 + tlen; // reply_len } + if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && (sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN) { + uint8_t res1 = payload[1]; // reserved for future (extra query params) + uint8_t res2 = payload[2]; + if (res1 == 0 && res2 == 0) { + uint8_t ofs = 4; + for (int i = 0; i < acl.getNumClients() && ofs + 7 <= sizeof(reply_data) - 4; i++) { + auto c = acl.getClientByIdx(i); + if (c->permissions == 0) continue; // skip deleted entries + memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix + reply_data[ofs++] = c->permissions; + } + return ofs; + } } return 0; // unknown command } @@ -261,65 +305,26 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m uint32_t timestamp; memcpy(×tamp, data, 4); - bool is_admin; - data[len] = 0; // ensure null terminator - if (strcmp((char *)&data[4], _prefs.password) == 0) { // check for valid password - is_admin = true; - } else if (strcmp((char *)&data[4], _prefs.guest_password) == 0) { // check guest password - is_admin = false; - } else { -#if MESH_DEBUG - MESH_DEBUG_PRINTLN("Invalid password: %s", &data[4]); -#endif - return; - } + uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); - auto client = putClient(sender); // add to known clients (if not already known) - if (timestamp <= client->last_timestamp) { - MESH_DEBUG_PRINTLN("Possible login replay attack!"); - return; // FATAL: client table is full -OR- replay attack - } - - MESH_DEBUG_PRINTLN("Login success!"); - client->last_timestamp = timestamp; - client->last_activity = getRTCClock()->getCurrentTime(); - client->is_admin = is_admin; - memcpy(client->secret, secret, PUB_KEY_SIZE); - - uint32_t now = getRTCClock()->getCurrentTimeUnique(); - memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp -#if 0 - memcpy(&reply_data[4], "OK", 2); // legacy response -#else - reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) - reply_data[6] = is_admin ? 1 : 0; - reply_data[7] = 0; // FUTURE: reserved - getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness -#endif + if (reply_len == 0) return; // invalid request if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet *path = createPathReturn(sender, client->secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 12); + mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 12); - if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT - sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); - } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); - } - } + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); } } } int MyMesh::searchPeersByHash(const uint8_t *hash) { int n = 0; - for (int i = 0; i < MAX_CLIENTS; i++) { - if (known_clients[i].id.isHashMatch(hash)) { + for (int i = 0; i < acl.getNumClients(); i++) { + if (acl.getClientByIdx(i)->id.isHashMatch(hash)) { matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) } } @@ -328,9 +333,9 @@ int MyMesh::searchPeersByHash(const uint8_t *hash) { void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; - if (i >= 0 && i < MAX_CLIENTS) { + if (i >= 0 && i < acl.getNumClients()) { // lookup pre-calculated shared_secret - memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE); + memcpy(dest_secret, acl.getClientByIdx(i)->shared_secret, PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); } @@ -352,12 +357,12 @@ void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32 void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, uint8_t *data, size_t len) { int i = matching_peer_indexes[sender_idx]; - if (i < 0 || - i >= MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) + if (i < 0 || i >= acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); return; } - auto client = &known_clients[i]; + ClientInfo* client = acl.getClientByIdx(i); + if (type == PAYLOAD_TYPE_REQ) { // request (from a Known admin client!) uint32_t timestamp; memcpy(×tamp, data, 4); @@ -388,7 +393,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } else { MESH_DEBUG_PRINTLN("onPeerDataRecv: possible replay attack detected"); } - } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->is_admin) { // a CLI command + } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) uint flags = (data[4] >> 2); // message attempt number, and other flags @@ -457,11 +462,12 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t // TODO: prevent replay attacks int i = matching_peer_indexes[sender_idx]; - if (i >= 0 && - i < MAX_CLIENTS) { // get from our known_clients table (sender SHOULD already be known in this context) + if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); - auto client = &known_clients[i]; + auto client = acl.getClientByIdx(i); + memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); } @@ -480,8 +486,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc , bridge(_mgr, &rtc) #endif { - memset(known_clients, 0, sizeof(known_clients)); next_local_advert = next_flood_advert = 0; + dirty_contacts_expiry = 0; set_radio_at = revert_radio_at = 0; _logging = false; @@ -515,6 +521,8 @@ void MyMesh::begin(FILESYSTEM *fs) { // load persisted prefs _cli.loadPrefs(_fs); + acl.load(_fs); + #ifdef WITH_BRIDGE bridge.begin(); #endif @@ -664,7 +672,43 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply command += 3; } - _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands + // handle sensor-specific CLI commands + if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8} + char* hex = &command[8]; + char* sp = strchr(hex, ' '); // look for separator char + if (sp == NULL) { + strcpy(reply, "Err - bad params"); + } else { + *sp++ = 0; // replace space with null terminator + + uint8_t pubkey[PUB_KEY_SIZE]; + int hex_len = min(sp - hex, PUB_KEY_SIZE*2); + if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) { + uint8_t perms = atoi(sp); + if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) { + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger acl.save() + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - invalid params"); + } + } else { + strcpy(reply, "Err - bad pubkey"); + } + } + } else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) { + Serial.println("ACL:"); + for (int i = 0; i < acl.getNumClients(); i++) { + auto c = acl.getClientByIdx(i); + if (c->permissions == 0) continue; // skip deleted (or guest) entries + + Serial.printf("%02X ", c->permissions); + mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE); + Serial.printf("\n"); + } + reply[0] = 0; + } else{ + _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands + } } void MyMesh::loop() { @@ -698,4 +742,10 @@ void MyMesh::loop() { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); MESH_DEBUG_PRINTLN("Radio params restored"); } + + // is there are pending dirty contacts write needed? + if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) { + acl.save(_fs); + dirty_contacts_expiry = 0; + } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 06e7ebe4..798d31f2 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -18,6 +18,7 @@ #include <helpers/IdentityStore.h> #include <helpers/AdvertDataHelpers.h> #include <helpers/TxtDataHelpers.h> +#include <helpers/ClientACL.h> #include <RTClib.h> #include <target.h> @@ -52,15 +53,6 @@ struct RepeaterStats { uint32_t total_rx_air_time_secs; }; -struct ClientInfo { - mesh::Identity id; - uint32_t last_timestamp, last_activity; - uint8_t secret[PUB_KEY_SIZE]; - bool is_admin; - int8_t out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; -}; - #ifndef MAX_CLIENTS #define MAX_CLIENTS 32 #endif @@ -91,7 +83,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { NodePrefs _prefs; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; - ClientInfo known_clients[MAX_CLIENTS]; + ClientACL acl; + unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS NeighbourInfo neighbours[MAX_NEIGHBOURS]; #endif @@ -108,8 +101,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { ESPNowBridge bridge; #endif - ClientInfo* putClient(const mesh::Identity& id); void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From a6a0183d440152467675374161b0ec7f343c9740 Mon Sep 17 00:00:00 2001 From: Patrick <4002194+askpatrickw@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:04:31 -0700 Subject: [PATCH 056/546] Update FAQ with new server administration screenshot --- docs/faq.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 09e83c29..4757896f 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -229,8 +229,7 @@ Repeater or room server can be administered with one of the options below: - After a repeater or room server firmware is flashed on to a LoRa device, go to <https://config.meshcore.dev> and use the web user interface to connect to the LoRa device via USB serial. From there you can set the name of the server, its frequency and other related settings, location, passwords etc. -![image](https://github.com/user-attachments/assets/bec28ff3-a7d6-4a1e-8602-cb6b290dd150) - +<img width="803" height="680" alt="image" src="https://github.com/user-attachments/assets/2a9d9894-e34d-4dbe-b57c-fc3c250a2d34" /> - Connect the server device using a USB cable to a computer running Chrome on https://flasher.meshcore.co.uk/, then use the `console` feature to connect to the device From 9b2dbf51cb28ffee875d1c63da9b2d80c6f1c636 Mon Sep 17 00:00:00 2001 From: Patrick <4002194+askpatrickw@users.noreply.github.com> Date: Sat, 13 Sep 2025 14:05:57 -0700 Subject: [PATCH 057/546] fix markdown --- docs/faq.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/faq.md b/docs/faq.md index 4757896f..78e31aa6 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -229,7 +229,7 @@ Repeater or room server can be administered with one of the options below: - After a repeater or room server firmware is flashed on to a LoRa device, go to <https://config.meshcore.dev> and use the web user interface to connect to the LoRa device via USB serial. From there you can set the name of the server, its frequency and other related settings, location, passwords etc. -<img width="803" height="680" alt="image" src="https://github.com/user-attachments/assets/2a9d9894-e34d-4dbe-b57c-fc3c250a2d34" /> +![image](https://github.com/user-attachments/assets/2a9d9894-e34d-4dbe-b57c-fc3c250a2d34) - Connect the server device using a USB cable to a computer running Chrome on https://flasher.meshcore.co.uk/, then use the `console` feature to connect to the device From ce08db6238ae781b533305c85e2ff416edb0dbd1 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 14 Sep 2025 21:22:12 +1000 Subject: [PATCH 058/546] * room server: ClientACL added --- examples/simple_repeater/MyMesh.cpp | 6 +- examples/simple_room_server/MyMesh.cpp | 270 +++++++++++++++---------- examples/simple_room_server/MyMesh.h | 34 +--- src/helpers/ClientACL.cpp | 18 +- src/helpers/ClientACL.h | 13 +- 5 files changed, 187 insertions(+), 154 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index dad2dd83..4265e1cd 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -168,7 +168,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t memcpy(&reply_data[4], telemetry.getBuffer(), tlen); return 4 + tlen; // reply_len } - if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && (sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN) { + if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && sender->isAdmin()) { uint8_t res1 = payload[1]; // reserved for future (extra query params) uint8_t res2 = payload[2]; if (res1 == 0 && res2 == 0) { @@ -672,7 +672,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply command += 3; } - // handle sensor-specific CLI commands + // handle ACL related commands if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8} char* hex = &command[8]; char* sp = strchr(hex, ' '); // look for separator char @@ -743,7 +743,7 @@ void MyMesh::loop() { MESH_DEBUG_PRINTLN("Radio params restored"); } - // is there are pending dirty contacts write needed? + // is pending dirty contacts write needed? if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) { acl.save(_fs); dirty_contacts_expiry = 0; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 83d18137..9e89a7d2 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -15,9 +15,12 @@ #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 +#define REQ_TYPE_GET_ACCESS_LIST 0x05 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ +#define LAZY_CONTACTS_WRITE_DELAY 5000 + struct ServerStats { uint16_t batt_milli_volts; uint16_t curr_tx_queue_len; @@ -35,38 +38,6 @@ struct ServerStats { uint16_t n_posted, n_post_push; }; -ClientInfo *MyMesh::putClient(const mesh::Identity &id) { - for (int i = 0; i < num_clients; i++) { - if (id.matches(known_clients[i].id)) return &known_clients[i]; // already known - } - ClientInfo *newClient; - if (num_clients < MAX_CLIENTS) { - newClient = &known_clients[num_clients++]; - } else { // table is currently full - // evict least active client - uint32_t oldest_timestamp = 0xFFFFFFFF; - newClient = &known_clients[0]; - for (int i = 0; i < num_clients; i++) { - auto c = &known_clients[i]; - if (c->last_activity < oldest_timestamp) { - oldest_timestamp = c->last_activity; - newClient = c; - } - } - } - newClient->id = id; - newClient->out_path_len = -1; // initially out_path is unknown - newClient->last_timestamp = 0; - return newClient; -} - -void MyMesh::evict(ClientInfo *client) { - client->last_activity = 0; // this slot will now be re-used (will be oldest) - memset(client->id.pub_key, 0, sizeof(client->id.pub_key)); - memset(client->secret, 0, sizeof(client->secret)); - client->pending_ack = 0; -} - void MyMesh::addPost(ClientInfo *client, const char *postData) { // TODO: suggested postData format: <title>/<descrption> posts[next_post_idx].author = client->id; // add to cyclic queue @@ -97,22 +68,22 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { len += text_len; // calc expected ACK reply - mesh::Utils::sha256((uint8_t *)&client->pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); - client->push_post_timestamp = post.post_timestamp; + mesh::Utils::sha256((uint8_t *)&client->extra.room.pending_ack, 4, reply_data, len, client->id.pub_key, PUB_KEY_SIZE); + client->extra.room.push_post_timestamp = post.post_timestamp; - auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->secret, reply_data, len); + auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); if (reply) { if (client->out_path_len < 0) { sendFlood(reply); - client->ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); + client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); - client->ack_timeout = + client->extra.room.ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); } _num_post_pushes++; // stats } else { - client->pending_ack = 0; + client->extra.room.pending_ack = 0; MESH_DEBUG_PRINTLN("Unable to push post to client"); } } @@ -120,7 +91,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { uint8_t MyMesh::getUnsyncedCount(ClientInfo *client) { uint8_t count = 0; for (int k = 0; k < MAX_UNSYNCED_POSTS; k++) { - if (posts[k].post_timestamp > client->sync_since // is new post for this Client? + if (posts[k].post_timestamp > client->extra.room.sync_since // is new post for this Client? && !posts[k].author.matches(client->id)) { // don't push posts to the author count++; } @@ -129,12 +100,12 @@ uint8_t MyMesh::getUnsyncedCount(ClientInfo *client) { } bool MyMesh::processAck(const uint8_t *data) { - for (int i = 0; i < num_clients; i++) { - auto client = &known_clients[i]; - if (client->pending_ack && memcmp(data, &client->pending_ack, 4) == 0) { // got an ACK from Client! - client->pending_ack = 0; // clear this, so next push can happen - client->push_failures = 0; - client->sync_since = client->push_post_timestamp; // advance Client's SINCE timestamp, to sync next post + for (int i = 0; i < acl.getNumClients(); i++) { + auto client = acl.getClientByIdx(i); + if (client->extra.room.pending_ack && memcmp(data, &client->extra.room.pending_ack, 4) == 0) { // got an ACK from Client! + client->extra.room.pending_ack = 0; // clear this, so next push can happen + client->extra.room.push_failures = 0; + client->extra.room.sync_since = client->extra.room.push_post_timestamp; // advance Client's SINCE timestamp, to sync next post return true; } } @@ -168,8 +139,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp memcpy(reply_data, &sender_timestamp, 4); // reflect sender_timestamp back in response packet (kind of like a 'tag') - switch (payload[0]) { - case REQ_TYPE_GET_STATUS: { + if (payload[0] == REQ_TYPE_GET_STATUS) { ServerStats stats; stats.batt_milli_volts = board.getBattMilliVolts(); stats.curr_tx_queue_len = _mgr->getOutboundCount(0xFFFFFFFF); @@ -193,19 +163,31 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t memcpy(&reply_data[4], &stats, sizeof(stats)); return 4 + sizeof(stats); } - - case REQ_TYPE_GET_TELEMETRY_DATA: { + if (payload[0] == REQ_TYPE_GET_TELEMETRY_DATA) { uint8_t perm_mask = ~(payload[1]); // NEW: first reserved byte (of 4), is now inverse mask to apply to permissions telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors((sender->permission == RoomPermission::ADMIN ? 0xFF : 0x00) & perm_mask, telemetry); + sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); return 4 + tlen; // reply_len } + if (payload[0] == REQ_TYPE_GET_ACCESS_LIST && sender->isAdmin()) { + uint8_t res1 = payload[1]; // reserved for future (extra query params) + uint8_t res2 = payload[2]; + if (res1 == 0 && res2 == 0) { + uint8_t ofs = 4; + for (int i = 0; i < acl.getNumClients() && ofs + 7 <= sizeof(reply_data) - 4; i++) { + auto c = acl.getClientByIdx(i); + if (!c->isAdmin()) continue; // skip non-Admin entries + memcpy(&reply_data[ofs], c->id.pub_key, 6); ofs += 6; // just 6-byte pub_key prefix + reply_data[ofs++] = c->permissions; + } + return ofs; + } } return 0; // unknown command } @@ -305,56 +287,71 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m memcpy(&sender_timestamp, data, 4); memcpy(&sender_sync_since, &data[4], 4); // sender's "sync messags SINCE x" timestamp - RoomPermission perm; data[len] = 0; // ensure null terminator - if (strcmp((char *)&data[8], _prefs.password) == 0) { // check for valid admin password - perm = RoomPermission::ADMIN; - } else { - if (strcmp((char *)&data[8], _prefs.guest_password) == 0) { // check the room/public password - perm = RoomPermission::GUEST; - } else if (_prefs.allow_read_only) { - perm = RoomPermission::READ_ONLY; - } else { - MESH_DEBUG_PRINTLN("Incorrect room password"); - return; // no response. Client will timeout + + ClientInfo* client; + if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL + client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); + if (client == NULL) { + #if MESH_DEBUG + MESH_DEBUG_PRINTLN("Login, sender not in ACL"); + #endif + return; } + } else { + uint8_t perm; + if (strcmp((char *)&data[8], _prefs.password) == 0) { // check for valid admin password + perm = PERM_ACL_ADMIN; + } else { + if (strcmp((char *)&data[8], _prefs.guest_password) == 0) { // check the room/public password + perm = PERM_ACL_READ_WRITE; + } else if (_prefs.allow_read_only) { + perm = PERM_ACL_GUEST; + } else { + MESH_DEBUG_PRINTLN("Incorrect room password"); + return; // no response. Client will timeout + } + } + + auto client = acl.putClient(sender, 0); // add to known clients (if not already known) + if (sender_timestamp <= client->last_timestamp) { + MESH_DEBUG_PRINTLN("possible replay attack!"); + return; + } + + MESH_DEBUG_PRINTLN("Login success!"); + client->last_timestamp = sender_timestamp; + client->extra.room.sync_since = sender_sync_since; + client->extra.room.pending_ack = 0; + client->extra.room.push_failures = 0; + + client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions |= perm; + memcpy(client->shared_secret, secret, PUB_KEY_SIZE); + + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } - auto client = putClient(sender); // add to known clients (if not already known) - if (sender_timestamp <= client->last_timestamp) { - MESH_DEBUG_PRINTLN("possible replay attack!"); - return; - } - - MESH_DEBUG_PRINTLN("Login success!"); - client->permission = perm; - client->last_timestamp = sender_timestamp; - client->sync_since = sender_sync_since; - client->pending_ack = 0; - client->push_failures = 0; - memcpy(client->secret, secret, PUB_KEY_SIZE); - - uint32_t now = getRTCClock()->getCurrentTime(); - client->last_activity = now; - - now = getRTCClock()->getCurrentTimeUnique(); + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? reply_data[4] = RESP_SERVER_LOGIN_OK; reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16) - reply_data[6] = (perm == RoomPermission::ADMIN ? 1 : (perm == RoomPermission::GUEST ? 0 : 2)); - reply_data[7] = getUnsyncedCount(client); // NEW - memcpy(&reply_data[8], "OK", 2); // REVISIT: not really needed + reply_data[6] = (client->isAdmin() ? 1 : (client->permissions == 0 ? 2 : 0)); + // LEGACY: reply_data[7] = getUnsyncedCount(client); + reply_data[7] = client->permissions; // NEW + getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness + // LEGACY: memcpy(&reply_data[8], "OK", 2); next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response - mesh::Packet *path = createPathReturn(sender, client->secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 8 + 2); + mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, + PAYLOAD_TYPE_RESPONSE, reply_data, 12); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->secret, reply_data, 8 + 2); + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 12); if (reply) { if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); @@ -368,8 +365,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m int MyMesh::searchPeersByHash(const uint8_t *hash) { int n = 0; - for (int i = 0; i < num_clients; i++) { - if (known_clients[i].id.isHashMatch(hash)) { + for (int i = 0; i < acl.getNumClients(); i++) { + if (acl.getClientByIdx(i)->id.isHashMatch(hash)) { matching_peer_indexes[n++] = i; // store the INDEXES of matching contacts (for subsequent 'peer' methods) } } @@ -378,9 +375,9 @@ int MyMesh::searchPeersByHash(const uint8_t *hash) { void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; - if (i >= 0 && i < num_clients) { + if (i >= 0 && i < acl.getNumClients()) { // lookup pre-calculated shared_secret - memcpy(dest_secret, known_clients[i].secret, PUB_KEY_SIZE); + memcpy(dest_secret, acl.getClientByIdx(i)->shared_secret, PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); } @@ -389,11 +386,11 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, const uint8_t *secret, uint8_t *data, size_t len) { int i = matching_peer_indexes[sender_idx]; - if (i < 0 || i >= num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) + if (i < 0 || i >= acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("onPeerDataRecv: invalid peer idx: %d", i); return; } - auto client = &known_clients[i]; + auto client = acl.getClientByIdx(i); if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) @@ -407,7 +404,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, uint32_t now = getRTCClock()->getCurrentTimeUnique(); client->last_activity = now; - client->push_failures = 0; // reset so push can resume (if prev failed) + client->extra.room.push_failures = 0; // reset so push can resume (if prev failed) // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator @@ -420,7 +417,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, uint8_t temp[166]; bool send_ack; if (flags == TXT_TYPE_CLI_DATA) { - if (client->permission == RoomPermission::ADMIN) { + if (client->isAdmin()) { if (is_retry) { temp[5] = 0; // no reply } else { @@ -433,7 +430,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, send_ack = false; // and no ACK... user shoudn't be sending these } } else { // TXT_TYPE_PLAIN - if (client->permission == RoomPermission::READ_ONLY) { + if ((client->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { temp[5] = 0; // no reply send_ack = false; // no ACK } else { @@ -501,7 +498,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, uint32_t now = getRTCClock()->getCurrentTime(); client->last_activity = now; // <-- THIS will keep client connection alive - client->push_failures = 0; // reset so push can resume (if prev failed) + client->extra.room.push_failures = 0; // reset so push can resume (if prev failed) if (data[4] == REQ_TYPE_KEEP_ALIVE && packet->isRouteDirect()) { // request type uint32_t forceSince = 0; @@ -511,10 +508,10 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, memcpy(&data[5], &forceSince, 4); // make sure there are zeroes in payload (for ack_hash calc below) } if (forceSince > 0) { - client->sync_since = forceSince; // force-update the 'sync since' + client->extra.room.sync_since = forceSince; // force-update the 'sync since' } - client->pending_ack = 0; + client->extra.room.pending_ack = 0; // TODO: Throttle KEEP_ALIVE requests! // if client sends too quickly, evict() @@ -559,10 +556,11 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t // TODO: prevent replay attacks int i = matching_peer_indexes[sender_idx]; - if (i >= 0 && i < num_clients) { // get from our known_clients table (sender SHOULD already be known in this context) + if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); - auto client = &known_clients[i]; + auto client = acl.getClientByIdx(i); memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); } @@ -587,6 +585,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; + dirty_contacts_expiry = 0; _logging = false; set_radio_at = revert_radio_at = 0; @@ -613,7 +612,6 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif - num_clients = 0; next_post_idx = 0; next_client_idx = 0; next_push = 0; @@ -627,6 +625,8 @@ void MyMesh::begin(FILESYSTEM *fs) { // load persisted prefs _cli.loadPrefs(_fs); + acl.load(_fs); + radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); @@ -731,33 +731,73 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply command += 3; } - _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands + // handle ACL related commands + if (memcmp(command, "setperm ", 8) == 0) { // format: setperm {pubkey-hex} {permissions-int8} + char* hex = &command[8]; + char* sp = strchr(hex, ' '); // look for separator char + if (sp == NULL) { + strcpy(reply, "Err - bad params"); + } else { + *sp++ = 0; // replace space with null terminator + + uint8_t pubkey[PUB_KEY_SIZE]; + int hex_len = min(sp - hex, PUB_KEY_SIZE*2); + if (mesh::Utils::fromHex(pubkey, hex_len / 2, hex)) { + uint8_t perms = atoi(sp); + if (acl.applyPermissions(self_id, pubkey, hex_len / 2, perms)) { + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // trigger acl.save() + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - invalid params"); + } + } else { + strcpy(reply, "Err - bad pubkey"); + } + } + } else if (sender_timestamp == 0 && strcmp(command, "get acl") == 0) { + Serial.println("ACL:"); + for (int i = 0; i < acl.getNumClients(); i++) { + auto c = acl.getClientByIdx(i); + if (c->permissions == 0) continue; // skip deleted (or guest) entries + + Serial.printf("%02X ", c->permissions); + mesh::Utils::printHex(Serial, c->id.pub_key, PUB_KEY_SIZE); + Serial.printf("\n"); + } + reply[0] = 0; + } else{ + _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands + } +} + +bool MyMesh::saveFilter(ClientInfo* client) { + return client->isAdmin(); // only save Admins } void MyMesh::loop() { mesh::Mesh::loop(); - if (millisHasNowPassed(next_push) && num_clients > 0) { + if (millisHasNowPassed(next_push) && acl.getNumClients() > 0) { // check for ACK timeouts - for (int i = 0; i < num_clients; i++) { - auto c = &known_clients[i]; - if (c->pending_ack && millisHasNowPassed(c->ack_timeout)) { - c->push_failures++; - c->pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) + for (int i = 0; i < acl.getNumClients(); i++) { + auto c = acl.getClientByIdx(i); + if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) { + c->extra.room.push_failures++; + c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); } } // check next Round-Robin client, and sync next new post - auto client = &known_clients[next_client_idx]; + auto client = acl.getClientByIdx(next_client_idx); bool did_push = false; - if (client->pending_ack == 0 && client->last_activity != 0 && - client->push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max + if (client->extra.room.pending_ack == 0 && client->last_activity != 0 && + client->extra.room.push_failures < 3) { // not already waiting for ACK, AND not evicted, AND retries not max MESH_DEBUG_PRINTLN("loop - checking for client %02X", (uint32_t)client->id.pub_key[0]); uint32_t now = getRTCClock()->getCurrentTime(); for (int k = 0, idx = next_post_idx; k < MAX_UNSYNCED_POSTS; k++) { auto p = &posts[idx]; if (now >= p->post_timestamp + POST_SYNC_DELAY_SECS && - p->post_timestamp > client->sync_since // is new post for this Client? + p->post_timestamp > client->extra.room.sync_since // is new post for this Client? && !p->author.matches(client->id)) { // don't push posts to the author // push this post to Client, then wait for ACK pushPostToClient(client, *p); @@ -770,7 +810,7 @@ void MyMesh::loop() { } else { MESH_DEBUG_PRINTLN("loop - skipping busy (or evicted) client %02X", (uint32_t)client->id.pub_key[0]); } - next_client_idx = (next_client_idx + 1) % num_clients; // round robin polling for each client + next_client_idx = (next_client_idx + 1) % acl.getNumClients(); // round robin polling for each client if (did_push) { next_push = futureMillis(SYNC_PUSH_INTERVAL); @@ -805,5 +845,11 @@ void MyMesh::loop() { MESH_DEBUG_PRINTLN("Radio params restored"); } + // is pending dirty contacts write needed? + if (dirty_contacts_expiry && millisHasNowPassed(dirty_contacts_expiry)) { + acl.save(_fs, MyMesh::saveFilter); + dirty_contacts_expiry = 0; + } + // TODO: periodically check for OLD/inactive entries in known_clients[], and evict } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 945cca7d..dc7a6b5a 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -18,6 +18,7 @@ #include <helpers/AdvertDataHelpers.h> #include <helpers/TxtDataHelpers.h> #include <helpers/CommonCLI.h> +#include <helpers/ClientACL.h> #include <RTClib.h> #include <target.h> @@ -61,10 +62,6 @@ #define ADMIN_PASSWORD "password" #endif -#ifndef MAX_CLIENTS - #define MAX_CLIENTS 32 -#endif - #ifndef MAX_UNSYNCED_POSTS #define MAX_UNSYNCED_POSTS 32 #endif @@ -81,27 +78,6 @@ #define PACKET_LOG_FILE "/packet_log" -enum RoomPermission { - ADMIN, - GUEST, - READ_ONLY -}; - -struct ClientInfo { - mesh::Identity id; - uint32_t last_timestamp; // by THEIR clock - uint32_t last_activity; // by OUR clock - uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock) - uint32_t pending_ack; - uint32_t push_post_timestamp; - unsigned long ack_timeout; - RoomPermission permission; - uint8_t push_failures; - uint8_t secret[PUB_KEY_SIZE]; - int out_path_len; - uint8_t out_path[MAX_PATH_SIZE]; -}; - #define MAX_POST_TEXT_LEN (160-9) struct PostInfo { @@ -116,9 +92,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { bool _logging; NodePrefs _prefs; CommonCLI _cli; + ClientACL acl; + unsigned long dirty_contacts_expiry; uint8_t reply_data[MAX_PACKET_PAYLOAD]; - int num_clients; - ClientInfo known_clients[MAX_CLIENTS]; unsigned long next_push; uint16_t _num_posted, _num_post_pushes; int next_client_idx; // for round-robin polling @@ -132,8 +108,6 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t pending_cr; int matching_peer_indexes[MAX_CLIENTS]; - ClientInfo* putClient(const mesh::Identity& id); - void evict(ClientInfo* client); void addPost(ClientInfo* client, const char* postData); void pushPostToClient(ClientInfo* client, PostInfo& post); uint8_t getUnsyncedCount(ClientInfo* client); @@ -213,6 +187,8 @@ public: mesh::LocalIdentity& getSelfId() override { return self_id; } + static bool saveFilter(ClientInfo* client); + void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override; void handleCommand(uint32_t sender_timestamp, char* command, char* reply); diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 23f7b337..4ea19fd2 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -24,16 +24,17 @@ void ClientACL::load(FILESYSTEM* _fs) { while (!full) { ClientInfo c; uint8_t pub_key[32]; - uint8_t unused[6]; + uint8_t unused[2]; + + memset(&c, 0, sizeof(c)); bool success = (file.read(pub_key, 32) == 32); success = success && (file.read((uint8_t *) &c.permissions, 1) == 1); - success = success && (file.read(unused, 6) == 6); + success = success && (file.read((uint8_t *) &c.extra.room.sync_since, 4) == 4); + success = success && (file.read(unused, 2) == 2); success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); success = success && (file.read(c.out_path, 64) == 64); success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); - c.last_timestamp = 0; // transient - c.last_activity = 0; if (!success) break; // EOF @@ -49,19 +50,20 @@ void ClientACL::load(FILESYSTEM* _fs) { } } -void ClientACL::save(FILESYSTEM* _fs) { +void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) { File file = openWrite(_fs, "/s_contacts"); if (file) { - uint8_t unused[6]; + uint8_t unused[2]; memset(unused, 0, sizeof(unused)); for (int i = 0; i < num_clients; i++) { auto c = &clients[i]; - if (c->permissions == 0) continue; // skip deleted entries + if (c->permissions == 0 || (filter && !filter(c))) continue; // skip deleted entries, or by filter function bool success = (file.write(c->id.pub_key, 32) == 32); success = success && (file.write((uint8_t *) &c->permissions, 1) == 1); - success = success && (file.write(unused, 6) == 6); + success = success && (file.write((uint8_t *) &c->extra.room.sync_since, 4) == 4); + success = success && (file.write(unused, 2) == 2); success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1); success = success && (file.write(c->out_path, 64) == 64); success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index f8cc1233..1b650edd 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -18,7 +18,16 @@ struct ClientInfo { uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t last_timestamp; // by THEIR clock (transient) uint32_t last_activity; // by OUR clock (transient) - + union { + struct { + uint32_t sync_since; // sync messages SINCE this timestamp (by OUR clock) + uint32_t pending_ack; + uint32_t push_post_timestamp; + unsigned long ack_timeout; + uint8_t push_failures; + } room; + } extra; + bool isAdmin() const { return (permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_ADMIN; } }; @@ -36,7 +45,7 @@ public: num_clients = 0; } void load(FILESYSTEM* _fs); - void save(FILESYSTEM* _fs); + void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL); ClientInfo* getClient(const uint8_t* pubkey, int key_len); ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms); From 5ccacb2a91251b88c556d84fa786f4029fe348c0 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 14 Sep 2025 21:51:32 +1000 Subject: [PATCH 059/546] * bug fix --- examples/simple_room_server/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 9e89a7d2..24a3a32f 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -313,7 +313,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m } } - auto client = acl.putClient(sender, 0); // add to known clients (if not already known) + client = acl.putClient(sender, 0); // add to known clients (if not already known) if (sender_timestamp <= client->last_timestamp) { MESH_DEBUG_PRINTLN("possible replay attack!"); return; From 21ea63bcd97c43820662a6693c6016149209dca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Sch=C3=BCller?= <sschueller@techdroid.com> Date: Fri, 5 Sep 2025 15:05:03 +0200 Subject: [PATCH 060/546] feat: Added EByte EoRa Pi --- boards/ebyte_eora-s3.json | 45 +++++++++ variants/ebyte_eora_s3/platformio.ini | 135 ++++++++++++++++++++++++++ variants/ebyte_eora_s3/target.cpp | 48 +++++++++ variants/ebyte_eora_s3/target.h | 29 ++++++ 4 files changed, 257 insertions(+) create mode 100644 boards/ebyte_eora-s3.json create mode 100644 variants/ebyte_eora_s3/platformio.ini create mode 100644 variants/ebyte_eora_s3/target.cpp create mode 100644 variants/ebyte_eora_s3/target.h diff --git a/boards/ebyte_eora-s3.json b/boards/ebyte_eora-s3.json new file mode 100644 index 00000000..96945c1d --- /dev/null +++ b/boards/ebyte_eora-s3.json @@ -0,0 +1,45 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default.csv", + "memory_type": "qio_qspi" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_LILYGO_T3_S3_V1_X", + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1", + "-DARDUINO_USB_MODE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": [ + "wifi" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino", + "espidf" + ], + "name": "Ebyte EoRa-S3-XXXTB Radio", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 460800 + }, + "url": "https://www.cdebyte.com/products/EoRa-S3-900TB", + "vendor": "Chengdu Ebyte Electronic Technology Co., Ltd" +} \ No newline at end of file diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini new file mode 100644 index 00000000..6bf3766d --- /dev/null +++ b/variants/ebyte_eora_s3/platformio.ini @@ -0,0 +1,135 @@ +[Ebyte_EoRa-S3] +extends = esp32_base +board = ebyte_eora-s3 +build_flags = + ${esp32_base.build_flags} + -I variants/ebyte_eora_s3 + -D EBYTE_EORA_S3 + -D P_LORA_DIO_1=33 + -D P_LORA_NSS=7 + -D P_LORA_RESET=8 ; RADIOLIB_NC + -D P_LORA_BUSY=34 + -D P_LORA_SCLK=5 + -D P_LORA_MISO=3 + -D P_LORA_MOSI=6 + -D P_LORA_TX_LED=37 + -D PIN_VBAT_READ=1 + -D PIN_USER_BTN=0 + -D PIN_BOARD_SDA=18 + -D PIN_BOARD_SCL=17 + -D PIN_OLED_RESET=21 + +; SD_DAT0/MISO - GPIO2 +; SD_DAT1 - GPIO4 +; SD_CMD/MOSI - GPIO11 +; SD_DAT2 - GPIO112 +; SD_DAT3/CS - GPIO113 +; SD_CLK - GPIO114 + + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/ebyte_eora_s3> +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + +; === EByte EORA_S3 with SX1262 environments === +[env:Ebyte_EoRa-S3_Repeater] +extends = Ebyte_EoRa-S3 +build_flags = + ${Ebyte_EoRa-S3.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"EORA_S3-1262 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${Ebyte_EoRa-S3.lib_deps} + ${esp32_ota.lib_deps} + +[env:Ebyte_EoRa-S3_terminal_chat] +extends = Ebyte_EoRa-S3 +build_flags = + ${Ebyte_EoRa-S3.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${Ebyte_EoRa-S3.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Ebyte_EoRa-S3_room_server] +extends = Ebyte_EoRa-S3 +build_flags = + ${Ebyte_EoRa-S3.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"EORA_S3-1262 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_room_server> +lib_deps = + ${Ebyte_EoRa-S3.lib_deps} + ${esp32_ota.lib_deps} + +[env:Ebyte_EoRa-S3_companion_radio_usb] +extends = Ebyte_EoRa-S3 +build_flags = + ${Ebyte_EoRa-S3.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Ebyte_EoRa-S3.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Ebyte_EoRa-S3_companion_radio_ble] +extends = Ebyte_EoRa-S3 +build_flags = + ${Ebyte_EoRa-S3.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Ebyte_EoRa-S3.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/ebyte_eora_s3/target.cpp b/variants/ebyte_eora_s3/target.cpp new file mode 100644 index 00000000..1c7b3b09 --- /dev/null +++ b/variants/ebyte_eora_s3/target.cpp @@ -0,0 +1,48 @@ +#include <Arduino.h> +#include "target.h" + +ESP32Board board; + +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + return radio.std_init(&spi); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ebyte_eora_s3/target.h b/variants/ebyte_eora_s3/target.h new file mode 100644 index 00000000..f184c757 --- /dev/null +++ b/variants/ebyte_eora_s3/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/ESP32Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern ESP32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From ded81780a40fa5750c650c6e4492e2b38e811f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Sch=C3=BCller?= <sschueller@techdroid.com> Date: Fri, 5 Sep 2025 15:56:18 +0200 Subject: [PATCH 061/546] fix: removed display reset (NC), set SDA and SCL for display --- variants/ebyte_eora_s3/platformio.ini | 3 +- variants/ebyte_eora_s3/target.cpp | 43 +++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini index 6bf3766d..a7e6fe22 100644 --- a/variants/ebyte_eora_s3/platformio.ini +++ b/variants/ebyte_eora_s3/platformio.ini @@ -17,7 +17,6 @@ build_flags = -D PIN_USER_BTN=0 -D PIN_BOARD_SDA=18 -D PIN_BOARD_SCL=17 - -D PIN_OLED_RESET=21 ; SD_DAT0/MISO - GPIO2 ; SD_DAT1 - GPIO4 @@ -25,6 +24,8 @@ build_flags = ; SD_DAT2 - GPIO112 ; SD_DAT3/CS - GPIO113 ; SD_CLK - GPIO114 + -D PIN_BOARD_SDA=18 + -D PIN_BOARD_SCL=17 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 diff --git a/variants/ebyte_eora_s3/target.cpp b/variants/ebyte_eora_s3/target.cpp index 1c7b3b09..647f5997 100644 --- a/variants/ebyte_eora_s3/target.cpp +++ b/variants/ebyte_eora_s3/target.cpp @@ -3,8 +3,13 @@ ESP32Board board; -static SPIClass spi; -RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; @@ -24,7 +29,39 @@ bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); - return radio.std_init(&spi); +#ifdef SX126X_DIO3_TCXO_VOLTAGE + float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + +#if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); +#endif + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(1); + +#if defined(SX126X_RXEN) && defined(SX126X_TXEN) + radio.setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); +#endif + +#ifdef SX126X_CURRENT_LIMIT + radio.setCurrentLimit(SX126X_CURRENT_LIMIT); +#endif +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif + + return true; // success } uint32_t radio_get_rng_seed() { From 561dbea30fdb435f9c3625b842d4df9eca15b174 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Mon, 15 Sep 2025 12:28:26 +1200 Subject: [PATCH 062/546] update lastmod when a new message is received --- src/helpers/BaseChatMesh.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 60366c65..97d021bd 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -158,6 +158,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender data[len] = 0; // need to make a C string again, with null terminator if (flags == TXT_TYPE_PLAIN) { + from.lastmod = getRTCClock()->getCurrentTime(); // update last heard time onMessageRecv(from, packet, timestamp, (const char *) &data[5]); // let UI know uint32_t ack_hash; // calc truncated hash of the message timestamp + text + sender pub_key, to prove to sender that we got it @@ -184,6 +185,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date from.sync_since = timestamp; } + from.lastmod = getRTCClock()->getCurrentTime(); // update last heard time onSignedMessageRecv(from, packet, timestamp, &data[5], (const char *) &data[9]); // let UI know uint32_t ack_hash; // calc truncated hash of the message timestamp + text + OUR pub_key, to prove to sender that we got it From 400e09f318e875b63c9ddbcc180a218fa0c49acf Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Mon, 15 Sep 2025 13:06:35 +1200 Subject: [PATCH 063/546] revert unexpected change to ble advertising interval on nrf52 --- src/helpers/nrf52/SerialBLEInterface.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index ad5823f1..dbe6f393 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -86,7 +86,7 @@ void SerialBLEInterface::startAdv() { * https://developer.apple.com/library/content/qa/qa1931/_index.html */ Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect - Bluefruit.Advertising.setInterval(32, 1600); + Bluefruit.Advertising.setInterval(32, 244); Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds From 81180bbf8c34e4094e1fff6da2ac774ccf63dbd9 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Mon, 15 Sep 2025 14:46:10 +0200 Subject: [PATCH 064/546] xiao nrf52: add all available sensors, remove *_alt envs, cleanup --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 24 ++--- variants/xiao_nrf52/XiaoNrf52Board.h | 18 +--- variants/xiao_nrf52/platformio.ini | 54 ++-------- variants/xiao_nrf52/target.cpp | 9 +- variants/xiao_nrf52/variant.cpp | 135 ++++++++++++------------- variants/xiao_nrf52/variant.h | 4 +- 6 files changed, 97 insertions(+), 147 deletions(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index c603b2af..03bb674e 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -1,21 +1,19 @@ #ifdef XIAO_NRF52 #include <Arduino.h> -#include "XiaoNrf52Board.h" - -#include <bluefruit.h> #include <Wire.h> +#include <bluefruit.h> + +#include "XiaoNrf52Board.h" static BLEDfu bledfu; -static void connect_callback(uint16_t conn_handle) -{ +static void connect_callback(uint16_t conn_handle) { (void)conn_handle; MESH_DEBUG_PRINTLN("BLE client connected"); } -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) -{ +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { (void)conn_handle; (void)reason; @@ -41,12 +39,12 @@ void XiaoNrf52Board::begin() { digitalWrite(P_LORA_TX_LED, HIGH); #endif -// pinMode(SX126X_POWER_EN, OUTPUT); -// digitalWrite(SX126X_POWER_EN, HIGH); - delay(10); // give sx1262 some time to power up + // pinMode(SX126X_POWER_EN, OUTPUT); + // digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up } -bool XiaoNrf52Board::startOTAUpdate(const char* id, char reply[]) { +bool XiaoNrf52Board::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice // Note: All config***() function must be called before begin() @@ -86,10 +84,8 @@ bool XiaoNrf52Board::startOTAUpdate(const char* id, char reply[]) { Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds strcpy(reply, "OK - started"); + return true; - - - return false; } #endif \ No newline at end of file diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 60b9f5bb..b229507a 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -5,20 +5,6 @@ #ifdef XIAO_NRF52 -// redefine lora pins if using the S3 variant of SX1262 board -#ifdef SX1262_XIAO_S3_VARIANT - #undef P_LORA_DIO_1 - #undef P_LORA_BUSY - #undef P_LORA_RESET - #undef P_LORA_NSS - #undef SX126X_RXEN - #define P_LORA_DIO_1 D0 - #define P_LORA_BUSY D1 - #define P_LORA_RESET D2 - #define P_LORA_NSS D3 - #define SX126X_RXEN D4 -#endif - class XiaoNrf52Board : public mesh::MainBoard { protected: uint8_t startup_reason; @@ -40,13 +26,13 @@ public: // Please read befor going further ;) // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging - // We can't drive VBAT_ENABLE to HIGH as long + // We can't drive VBAT_ENABLE to HIGH as long // as we don't know wether we are charging or not ... // this is a 3mA loss (4/1500) digitalWrite(VBAT_ENABLE, LOW); int adcvalue = 0; analogReadResolution(12); - analogReference(AR_INTERNAL_3_0); + analogReference(AR_INTERNAL_3_0); delay(10); adcvalue = analogRead(PIN_VBAT); return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 212a55ea..8054c72d 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -1,37 +1,19 @@ -[nrf52840_xiao] +[Xiao_nrf52] extends = nrf52_base -platform_packages = - toolchain-gccarmnoneeabi@~1.100301.0 - framework-arduinoadafruitnrf52 board = seeed-xiao-afruitnrf52-nrf52840 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} - -D NRF52_PLATFORM -D XIAO_NRF52 + ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -lib_ignore = - BluetoothOTA - lvgl - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - - -[Xiao_nrf52] -extends = nrf52840_xiao -;board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_xiao.build_flags} - -D P_LORA_TX_LED=11 -I variants/xiao_nrf52 - -I src/helpers/nrf52 + -UENV_INCLUDE_GPS + -D NRF52_PLATFORM + -D XIAO_NRF52 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=11 -D P_LORA_DIO_1=D1 -D P_LORA_RESET=D2 -D P_LORA_BUSY=D3 @@ -42,18 +24,16 @@ build_flags = ${nrf52840_xiao.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D PIN_WIRE_SCL=6 - -D PIN_WIRE_SDA=7 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 -build_src_filter = ${nrf52840_xiao.build_src_filter} + -D PIN_WIRE_SCL=D6 + -D PIN_WIRE_SDA=D7 +build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<../variants/xiao_nrf52> debug_tool = jlink upload_protocol = nrfutil +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} [env:Xiao_nrf52_companion_radio_ble] extends = Xiao_nrf52 @@ -94,12 +74,6 @@ lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Xiao_nrf52_alt_pinout_companion_radio_ble] -extends = env:Xiao_nrf52_companion_radio_ble -build_flags = - ${env:Xiao_nrf52_companion_radio_ble.build_flags} - -D SX1262_XIAO_S3_VARIANT - [env:Xiao_nrf52_repeater] extends = Xiao_nrf52 build_flags = @@ -114,12 +88,6 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} +<../examples/simple_repeater/*.cpp> -[env:Xiao_nrf52_alt_pinout_repeater] -extends = env:Xiao_nrf52_repeater -build_flags = - ${env:Xiao_nrf52_repeater.build_flags} - -D SX1262_XIAO_S3_VARIANT - [env:Xiao_nrf52_room_server] extends = Xiao_nrf52 build_flags = diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index 07af2502..41142eb6 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -10,12 +10,13 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); + EnvironmentSensorManager sensors; bool radio_init() { - rtc_clock.begin(Wire); - - return radio.std_init(&SPI); + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); } uint32_t radio_get_rng_seed() { @@ -35,5 +36,5 @@ void radio_set_tx_power(uint8_t dbm) { mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); - return mesh::LocalIdentity(&rng); // create new random identity + return mesh::LocalIdentity(&rng); // create new random identity } diff --git a/variants/xiao_nrf52/variant.cpp b/variants/xiao_nrf52/variant.cpp index 16542e27..04ef3a92 100644 --- a/variants/xiao_nrf52/variant.cpp +++ b/variants/xiao_nrf52/variant.cpp @@ -1,86 +1,85 @@ #include "variant.h" + +#include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" -#include "nrf.h" -const uint32_t g_ADigitalPinMap[] = -{ - // D0 .. D10 - 2, // D0 is P0.02 (A0) - 3, // D1 is P0.03 (A1) - 28, // D2 is P0.28 (A2) - 29, // D3 is P0.29 (A3) - 4, // D4 is P0.04 (A4,SDA) - 5, // D5 is P0.05 (A5,SCL) - 43, // D6 is P1.11 (TX) - 44, // D7 is P1.12 (RX) - 45, // D8 is P1.13 (SCK) - 46, // D9 is P1.14 (MISO) - 47, // D10 is P1.15 (MOSI) +const uint32_t g_ADigitalPinMap[] = { + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) - // LEDs - 26, // D11 is P0.26 (LED RED) - 6, // D12 is P0.06 (LED BLUE) - 30, // D13 is P0.30 (LED GREEN) - 14, // D14 is P0.14 (READ_BAT) + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) - // LSM6DS3TR - 40, // D15 is P1.08 (6D_PWR) - 27, // D16 is P0.27 (6D_I2C_SCL) - 7, // D17 is P0.07 (6D_I2C_SDA) - 11, // D18 is P0.11 (6D_INT1) + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) - // MIC - 42, // D19 is P1.10 (MIC_PWR) - 32, // D20 is P1.00 (PDM_CLK) - 16, // D21 is P0.16 (PDM_DATA) + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) - // BQ25100 - 13, // D22 is P0.13 (HICHG) - 17, // D23 is P0.17 (~CHG) + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) - // - 21, // D24 is P0.21 (QSPI_SCK) - 25, // D25 is P0.25 (QSPI_CSN) - 20, // D26 is P0.20 (QSPI_SIO_0 DI) - 24, // D27 is P0.24 (QSPI_SIO_1 DO) - 22, // D28 is P0.22 (QSPI_SIO_2 WP) - 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) - // NFC - 9, // D30 is P0.09 (NFC1) - 10, // D31 is P0.10 (NFC2) + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) - // VBAT - 31, // D32 is P0.31 (VBAT) + // VBAT + 31, // D32 is P0.31 (VBAT) }; -void initVariant() -{ - // Disable reading of the BAT voltage. - // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging - pinMode(VBAT_ENABLE, OUTPUT); - //digitalWrite(VBAT_ENABLE, HIGH); - // This was taken from Seeed github butis not coherent with the doc, - // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V - // This induces a 3mA current in the resistors :( but it's better than burning the nrf - digitalWrite(VBAT_ENABLE, LOW); +void initVariant() { + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + // digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); - // Low charging current (50mA) - // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current - //pinMode(PIN_CHARGING_CURRENT, INPUT); + // Low charging current (50mA) + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + // pinMode(PIN_CHARGING_CURRENT, INPUT); - // High charging current (100mA) - pinMode(PIN_CHARGING_CURRENT, OUTPUT); - digitalWrite(PIN_CHARGING_CURRENT, LOW); + // High charging current (100mA) + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, LOW); - pinMode(PIN_QSPI_CS, OUTPUT); - digitalWrite(PIN_QSPI_CS, HIGH); + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); - pinMode(LED_RED, OUTPUT); - digitalWrite(LED_RED, HIGH); - pinMode(LED_GREEN, OUTPUT); - digitalWrite(LED_GREEN, HIGH); - pinMode(LED_BLUE, OUTPUT); - digitalWrite(LED_BLUE, HIGH); + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); } diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index d941463e..c54f3c2f 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -113,8 +113,8 @@ static const uint8_t A5 = PIN_A5; // #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! // #define PIN_WIRE_SCL (16) // use WIRE1_SDA -static const uint8_t SDA = PIN_WIRE_SDA; -static const uint8_t SCL = PIN_WIRE_SCL; +// static const uint8_t SDA = PIN_WIRE_SDA; +// static const uint8_t SCL = PIN_WIRE_SCL; //#define PIN_WIRE1_SDA (17) //#define PIN_WIRE1_SCL (16) From 845a497604122722d46bcbbf413dd5ae1388554d Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Mon, 15 Sep 2025 14:56:04 +0200 Subject: [PATCH 065/546] fix compilation errors for wismesh tag --- variants/rak_wismesh_tag/platformio.ini | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 59d55175..0ee5bfed 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -28,14 +28,12 @@ build_flags = ${nrf52_base.build_flags} -D PIN_BOARD_SCL=PIN_WIRE_SCL build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak_wismesh_tag> - +<helpers/ui/buzzer.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/ui/NullDisplayDriver.cpp> +<helpers/sensors> lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} - end2endzone/NonBlockingRTTTL@^1.3.0 [env:RAK_WisMesh_Tag_Repeater] extends = rak_wismesh_tag @@ -77,11 +75,13 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak_wismesh_tag.build_src_filter} + +<helpers/ui/buzzer.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak_wismesh_tag.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 [env:RAK_WisMesh_Tag_companion_radio_ble] extends = rak_wismesh_tag @@ -98,12 +98,14 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak_wismesh_tag.build_src_filter} + +<helpers/ui/buzzer.cpp> +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak4631.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 [env:RAK_WisMesh_Tag_sensor] extends = rak4631 From 19fb7aae63130e848a0eee5a08d5777e9c901657 Mon Sep 17 00:00:00 2001 From: Wesley Ellis <tahnok@gmail.com> Date: Tue, 16 Sep 2025 18:15:14 -0400 Subject: [PATCH 066/546] Use python3 not python in build.sh Since the bin/uf2conv/uf2conf.py script uses python3, use python3 as the command instead of python. On my ubuntu 24.04 machine, I don't have a python command in my path by default --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 47fec4a3..5b091b5a 100755 --- a/build.sh +++ b/build.sh @@ -60,7 +60,7 @@ build_firmware() { # build .uf2 for nrf52 boards if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then - python bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 + python3 bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 fi # copy .bin, .uf2, and .zip to out folder From fca16f1b71d13387b5e8e35eccd628ac6a1d03c3 Mon Sep 17 00:00:00 2001 From: 446564 <robjdev@mailbox.org> Date: Sat, 13 Sep 2025 21:35:27 -0700 Subject: [PATCH 067/546] make offline queue channel messages mutable older channel messages can be overwritten, keeping other mssagage types this allows a user to be away for a long time and still get the most recent channel messages without losing any direct messages for exampe --- examples/companion_radio/MyMesh.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 04d5577e..4d891144 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -177,13 +177,29 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co void MyMesh::addToOfflineQueue(const uint8_t frame[], int len) { if (offline_queue_len >= OFFLINE_QUEUE_SIZE) { - MESH_DEBUG_PRINTLN("ERROR: offline_queue is full!"); + MESH_DEBUG_PRINTLN("WARN: offline_queue is full!"); + int pos = 0; + while (pos < offline_queue_len) { + if ((offline_queue[pos].buf[0] == RESP_CODE_CHANNEL_MSG_RECV) || + offline_queue[pos].buf[0] == RESP_CODE_CHANNEL_MSG_RECV_V3) { + for (int i = pos; i < offline_queue_len; i++) { // delete oldest channel msg from queue + offline_queue[i] = offline_queue[i + 1]; + } + MESH_DEBUG_PRINTLN("INFO: removed oldest channel message from queue."); + offline_queue[offline_queue_len - 1].len = len; + memcpy(offline_queue[offline_queue_len - 1].buf, frame, len); + return; + } + pos++; + } + MESH_DEBUG_PRINTLN("INFO: no channel messages to remove from queue."); } else { offline_queue[offline_queue_len].len = len; memcpy(offline_queue[offline_queue_len].buf, frame, len); offline_queue_len++; } } + int MyMesh::getFromOfflineQueue(uint8_t frame[]) { if (offline_queue_len > 0) { // check offline queue size_t len = offline_queue[0].len; // take from top of queue From bd6aa983a3728af089d46fb04e23b876bb003c9f Mon Sep 17 00:00:00 2001 From: Michael Hart <michaelhart@michaelhart.me> Date: Tue, 16 Sep 2025 17:17:15 -0700 Subject: [PATCH 068/546] feat: add DisplayDriver methods for UTF-8 filtering and text ellipsis - Add translateUTF8ToBlocks() method to convert UTF-8 characters to displayable blocks - Add drawTextEllipsized() method for text truncation with ellipsis - Apply UTF-8 filtering to node names, recent contacts, and message content - Use ellipsized text rendering for recent contacts to prevent overflow - Addresses PR feedback by moving text processing to DisplayDriver level --- examples/companion_radio/ui-new/UITask.cpp | 26 +++++++--- src/helpers/ui/DisplayDriver.h | 56 ++++++++++++++++++++++ 2 files changed, 75 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 1e03f086..7f167f9a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -90,6 +90,7 @@ class HomeScreen : public UIScreen { bool _shutdown_init; AdvertPath recent[UI_RECENT_LIST_SIZE]; + void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { // Convert millivolts to percentage const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) @@ -157,10 +158,12 @@ public: int render(DisplayDriver& display) override { char tmp[80]; // node name - display.setCursor(0, 0); display.setTextSize(1); display.setColor(DisplayDriver::GREEN); - display.print(_node_prefs->node_name); + char filtered_name[sizeof(_node_prefs->node_name)]; + display.translateUTF8ToBlocks(filtered_name, _node_prefs->node_name, sizeof(filtered_name)); + display.setCursor(0, 0); + display.print(filtered_name); // battery voltage renderBatteryIndicator(display, _task->getBattMilliVolts()); @@ -199,8 +202,6 @@ public: for (int i = 0; i < UI_RECENT_LIST_SIZE; i++, y += 11) { auto a = &recent[i]; if (a->name[0] == 0) continue; // empty slot - display.setCursor(0, y); - display.print(a->name); int secs = _rtc->getCurrentTime() - a->recv_timestamp; if (secs < 60) { sprintf(tmp, "%ds", secs); @@ -209,7 +210,14 @@ public: } else { sprintf(tmp, "%dh", secs / (60*60)); } - display.setCursor(display.width() - display.getTextWidth(tmp) - 1, y); + + int timestamp_width = display.getTextWidth(tmp); + int max_name_width = display.width() - timestamp_width - 1; + + char filtered_recent_name[sizeof(a->name)]; + display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name)); + display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name); + display.setCursor(display.width() - timestamp_width - 1, y); display.print(tmp); } } else if (_page == HomePage::RADIO) { @@ -427,11 +435,15 @@ public: display.setCursor(0, 14); display.setColor(DisplayDriver::YELLOW); - display.print(p->origin); + char filtered_origin[sizeof(p->origin)]; + display.translateUTF8ToBlocks(filtered_origin, p->origin, sizeof(filtered_origin)); + display.print(filtered_origin); display.setCursor(0, 25); display.setColor(DisplayDriver::LIGHT); - display.printWordWrap(p->msg, display.width()); + char filtered_msg[sizeof(p->msg)]; + display.translateUTF8ToBlocks(filtered_msg, p->msg, sizeof(filtered_msg)); + display.printWordWrap(filtered_msg, display.width()); #if AUTO_OFF_MILLIS==0 // probably e-ink return 10000; // 10 s diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index d81d99fb..32839edc 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -1,6 +1,7 @@ #pragma once #include <stdint.h> +#include <string.h> class DisplayDriver { int _w, _h; @@ -31,5 +32,60 @@ public: setCursor(mid_x - w/2, y); print(str); } + + // convert UTF-8 characters to displayable block characters for compatibility + virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) { + size_t j = 0; + for (size_t i = 0; src[i] != 0 && j < dest_size - 1; i++) { + unsigned char c = (unsigned char)src[i]; + if (c >= 32 && c <= 126) { + dest[j++] = c; // ASCII printable + } else if (c >= 0x80) { + dest[j++] = '\xDB'; // CP437 full block █ + while (src[i+1] && (src[i+1] & 0xC0) == 0x80) + i++; // skip UTF-8 continuation bytes + } + } + dest[j] = 0; + } + + // draw text with ellipsis if it exceeds max_width + virtual void drawTextEllipsized(int x, int y, int max_width, const char* str) { + char temp_str[256]; // reasonable buffer size + size_t len = strlen(str); + if (len >= sizeof(temp_str)) len = sizeof(temp_str) - 1; + memcpy(temp_str, str, len); + temp_str[len] = 0; + + if (getTextWidth(temp_str) <= max_width) { + setCursor(x, y); + print(temp_str); + return; + } + + // for variable-width fonts (GxEPD), add space after ellipsis + // for fixed-width fonts (OLED), keep tight spacing to save precious characters + const char* ellipsis; + // use a simple heuristic: if 'i' and 'l' have different widths, it's variable-width + int i_width = getTextWidth("i"); + int l_width = getTextWidth("l"); + if (i_width != l_width) { + ellipsis = "... "; // variable-width fonts: add space + } else { + ellipsis = "..."; // fixed-width fonts: no space + } + + int ellipsis_width = getTextWidth(ellipsis); + int str_len = strlen(temp_str); + + while (str_len > 0 && getTextWidth(temp_str) > max_width - ellipsis_width) { + temp_str[--str_len] = 0; + } + strcat(temp_str, ellipsis); + + setCursor(x, y); + print(temp_str); + } + virtual void endFrame() = 0; }; From 2da50882c006930678b93cfee6025225ef2971da Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sun, 7 Sep 2025 15:16:15 +0800 Subject: [PATCH 069/546] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20vibration=20f?= =?UTF-8?q?eedback=20support=20for=20UI=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add genericVibration class with 5-second cooldown and 1-second pulse - Integrate vibration triggers for new messages and contact discoveries - Add conditional compilation support with PIN_VIBRATION guard - Implement abstract interface for vibration in UITask system --- examples/companion_radio/AbstractUITask.h | 3 ++ examples/companion_radio/MyMesh.cpp | 5 +++ examples/companion_radio/ui-new/UITask.cpp | 18 +++++++++ examples/companion_radio/ui-new/UITask.h | 9 +++++ src/helpers/ui/vibration.cpp | 43 ++++++++++++++++++++++ src/helpers/ui/vibration.h | 34 +++++++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 src/helpers/ui/vibration.cpp create mode 100644 src/helpers/ui/vibration.h diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index 1277bba9..fa0146a1 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -42,5 +42,8 @@ public: virtual void msgRead(int msgcount) = 0; virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0; virtual void soundBuzzer(UIEventType bet = UIEventType::none) = 0; +#ifdef PIN_VIBRATION + virtual void triggerVibration() = 0; +#endif virtual void loop() = 0; }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 04d5577e..ea54e051 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -244,6 +244,11 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } else { #ifdef DISPLAY_CLASS if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage); + if (_ui) { +#ifdef PIN_VIBRATION + if (is_new) _ui->triggerVibration(); +#endif + } #endif } diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 1e03f086..0e906e41 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -483,6 +483,10 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no buzzer.begin(); #endif +#ifdef PIN_VIBRATION + vibration.begin(); +#endif + ui_started_at = millis(); _alert_expiry = 0; @@ -519,6 +523,12 @@ switch(bet){ #endif } +#ifdef PIN_VIBRATION +void UITask::triggerVibration() { + vibration.trigger(); +} +#endif + void UITask::msgRead(int msgcount) { _msgcount = msgcount; if (msgcount == 0) { @@ -532,6 +542,10 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text); setCurrScreen(msg_preview); +#ifdef PIN_VIBRATION + triggerVibration(); +#endif + if (_display != NULL) { if (!_display->isOn()) _display->turnOn(); _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer @@ -687,6 +701,10 @@ void UITask::loop() { #endif } +#ifdef PIN_VIBRATION + vibration.loop(); +#endif + #ifdef AUTO_SHUTDOWN_MILLIVOLTS if (millis() > next_batt_chck) { uint16_t milliVolts = getBattMilliVolts(); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 769b2c64..7aaeabc7 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -11,6 +11,9 @@ #ifdef PIN_BUZZER #include <helpers/ui/buzzer.h> #endif +#ifdef PIN_VIBRATION + #include <helpers/ui/vibration.h> +#endif #include "../AbstractUITask.h" #include "../NodePrefs.h" @@ -20,6 +23,9 @@ class UITask : public AbstractUITask { SensorManager* _sensors; #ifdef PIN_BUZZER genericBuzzer buzzer; +#endif +#ifdef PIN_VIBRATION + genericVibration vibration; #endif unsigned long _next_refresh, _auto_off; NodePrefs* _node_prefs; @@ -72,6 +78,9 @@ public: void msgRead(int msgcount) override; void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; void soundBuzzer(UIEventType bet = UIEventType::none) override; +#ifdef PIN_VIBRATION + void triggerVibration() override; +#endif void loop() override; void shutdown(bool restart = false); diff --git a/src/helpers/ui/vibration.cpp b/src/helpers/ui/vibration.cpp new file mode 100644 index 00000000..37db54ed --- /dev/null +++ b/src/helpers/ui/vibration.cpp @@ -0,0 +1,43 @@ +#ifdef PIN_VIBRATION +#include "vibration.h" + +void genericVibration::begin() +{ + pinMode(PIN_VIBRATION, OUTPUT); + digitalWrite(PIN_VIBRATION, LOW); + duration = 0; +} + +void genericVibration::trigger() +{ + duration = millis(); + digitalWrite(PIN_VIBRATION, HIGH); +} + +void genericVibration::loop() +{ + if (isVibrating()) { + if ((millis() / 1000) % 2 == 0) { + digitalWrite(PIN_VIBRATION, LOW); + } else { + digitalWrite(PIN_VIBRATION, HIGH); + } + + if (millis() - duration > VIBRATION_TIMEOUT) { + stop(); + } + } +} + +bool genericVibration::isVibrating() +{ + return duration > 0; +} + +void genericVibration::stop() +{ + duration = 0; + digitalWrite(PIN_VIBRATION, LOW); +} + +#endif // ifdef PIN_VIBRATION diff --git a/src/helpers/ui/vibration.h b/src/helpers/ui/vibration.h new file mode 100644 index 00000000..42cd0f67 --- /dev/null +++ b/src/helpers/ui/vibration.h @@ -0,0 +1,34 @@ +#pragma once + +#ifdef PIN_VIBRATION + +#include <Arduino.h> + +/* + * Vibration motor control class + * + * Provides vibration feedback for events like new messages and new contacts + * Features: + * - 1-second vibration pulse + * - 5-second nag timeout (cooldown between vibrations) + * - Non-blocking operation + */ + +#ifndef VIBRATION_TIMEOUT +#define VIBRATION_TIMEOUT 5000 // 5 seconds default +#endif + +class genericVibration +{ + public: + void begin(); // set up vibration pin + void trigger(); // trigger vibration if cooldown has passed + void loop(); // non-blocking timer handling + bool isVibrating(); // returns true if currently vibrating + void stop(); // stop vibration immediately + + private: + unsigned long duration; +}; + +#endif // ifdef PIN_VIBRATION From 043f37a08ec0b2defc51b2ce05efe259e4e78eb4 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Wed, 17 Sep 2025 08:53:50 +0800 Subject: [PATCH 070/546] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor:=20unify?= =?UTF-8?q?=20UI=20notification=20methods=20into=20single=20notify()=20fun?= =?UTF-8?q?ction?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidates soundBuzzer() and triggerVibration() into a unified notify() method that handles both audio and haptic feedback based on UIEventType. --- examples/companion_radio/AbstractUITask.h | 5 +--- examples/companion_radio/MyMesh.cpp | 11 +++------ examples/companion_radio/ui-new/UITask.cpp | 27 +++++++++------------ examples/companion_radio/ui-new/UITask.h | 5 +--- examples/companion_radio/ui-orig/UITask.cpp | 16 ++++++------ examples/companion_radio/ui-orig/UITask.h | 2 +- 6 files changed, 26 insertions(+), 40 deletions(-) diff --git a/examples/companion_radio/AbstractUITask.h b/examples/companion_radio/AbstractUITask.h index fa0146a1..0eee45ae 100644 --- a/examples/companion_radio/AbstractUITask.h +++ b/examples/companion_radio/AbstractUITask.h @@ -41,9 +41,6 @@ public: void disableSerial() { _serial->disable(); } virtual void msgRead(int msgcount) = 0; virtual void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) = 0; - virtual void soundBuzzer(UIEventType bet = UIEventType::none) = 0; -#ifdef PIN_VIBRATION - virtual void triggerVibration() = 0; -#endif + virtual void notify(UIEventType t = UIEventType::none) = 0; virtual void loop() = 0; }; diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index ea54e051..a2c2f8f8 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -243,12 +243,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } } else { #ifdef DISPLAY_CLASS - if (_ui) _ui->soundBuzzer(UIEventType::newContactMessage); - if (_ui) { -#ifdef PIN_VIBRATION - if (is_new) _ui->triggerVibration(); -#endif - } + if (_ui) _ui->notify(UIEventType::newContactMessage); #endif } @@ -358,7 +353,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe if (should_display && _ui) { _ui->newMsg(path_len, from.name, text, offline_queue_len); if (!_serial->isConnected()) { - _ui->soundBuzzer(UIEventType::contactMessage); + _ui->notify(UIEventType::contactMessage); } } #endif @@ -417,7 +412,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe _serial->writeFrame(frame, 1); } else { #ifdef DISPLAY_CLASS - if (_ui) _ui->soundBuzzer(UIEventType::channelMessage); + if (_ui) _ui->notify(UIEventType::channelMessage); #endif } #ifdef DISPLAY_CLASS diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 0e906e41..d117a7fb 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -348,9 +348,7 @@ public: return true; } if (c == KEY_ENTER && _page == HomePage::ADVERT) { - #ifdef PIN_BUZZER - _task->soundBuzzer(UIEventType::ack); - #endif + _task->notify(UIEventType::ack); if (the_mesh.advert()) { _task->showAlert("Advert sent!", 1000); } else { @@ -501,9 +499,9 @@ void UITask::showAlert(const char* text, int duration_millis) { _alert_expiry = millis() + duration_millis; } -void UITask::soundBuzzer(UIEventType bet) { +void UITask::notify(UIEventType t) { #if defined(PIN_BUZZER) -switch(bet){ +switch(t){ case UIEventType::contactMessage: // gemini's pick buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); @@ -521,13 +519,15 @@ switch(bet){ break; } #endif -} #ifdef PIN_VIBRATION -void UITask::triggerVibration() { - vibration.trigger(); -} + // Trigger vibration for all UI events except none + if (t != UIEventType::none) { + vibration.trigger(); + } #endif +} + void UITask::msgRead(int msgcount) { _msgcount = msgcount; @@ -542,9 +542,6 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text); setCurrScreen(msg_preview); -#ifdef PIN_VIBRATION - triggerVibration(); -#endif if (_display != NULL) { if (!_display->isOn()) _display->turnOn(); @@ -773,11 +770,11 @@ void UITask::toggleGPS() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); showAlert("GPS: Disabled", 800); } else { _sensors->setSettingValue("gps", "1"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); showAlert("GPS: Enabled", 800); } _next_refresh = 0; @@ -792,7 +789,7 @@ void UITask::toggleBuzzer() { #ifdef PIN_BUZZER if (buzzer.isQuiet()) { buzzer.quiet(false); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); showAlert("Buzzer: ON", 800); } else { buzzer.quiet(true); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 7aaeabc7..bc12c679 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -77,10 +77,7 @@ public: // from AbstractUITask void msgRead(int msgcount) override; void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; - void soundBuzzer(UIEventType bet = UIEventType::none) override; -#ifdef PIN_VIBRATION - void triggerVibration() override; -#endif + void notify(UIEventType t = UIEventType::none) override; void loop() override; void shutdown(bool restart = false); diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 29d995a7..045c955d 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -88,9 +88,9 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no ui_started_at = millis(); } -void UITask::soundBuzzer(UIEventType bet) { +void UITask::notify(UIEventType t) { #if defined(PIN_BUZZER) -switch(bet){ +switch(t){ case UIEventType::contactMessage: // gemini's pick buzzer.play("MsgRcv3:d=4,o=6,b=200:32e,32g,32b,16c7"); @@ -108,8 +108,8 @@ switch(bet){ break; } #endif -// Serial.print("DBG: Buzzzzzz -> "); -// Serial.println((int) bet); +// Serial.print("DBG: Alert user -> "); +// Serial.println((int) t); } void UITask::msgRead(int msgcount) { @@ -370,7 +370,7 @@ void UITask::handleButtonDoublePress() { MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert"); // ADVERT #ifdef PIN_BUZZER - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); #endif if (the_mesh.advert()) { MESH_DEBUG_PRINTLN("Advert sent!"); @@ -388,7 +388,7 @@ void UITask::handleButtonTriplePress() { #ifdef PIN_BUZZER if (buzzer.isQuiet()) { buzzer.quiet(false); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); sprintf(_alert, "Buzzer: ON"); } else { buzzer.quiet(true); @@ -407,11 +407,11 @@ void UITask::handleButtonQuadruplePress() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); sprintf(_alert, "GPS: Disabled"); } else { _sensors->setSettingValue("gps", "1"); - soundBuzzer(UIEventType::ack); + notify(UIEventType::ack); sprintf(_alert, "GPS: Enabled"); } break; diff --git a/examples/companion_radio/ui-orig/UITask.h b/examples/companion_radio/ui-orig/UITask.h index a59ddc41..60cd0d04 100644 --- a/examples/companion_radio/ui-orig/UITask.h +++ b/examples/companion_radio/ui-orig/UITask.h @@ -66,7 +66,7 @@ public: // from AbstractUITask void msgRead(int msgcount) override; void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) override; - void soundBuzzer(UIEventType bet = UIEventType::none) override; + void notify(UIEventType t = UIEventType::none) override; void loop() override; void shutdown(bool restart = false); From 6f8ce425d8f63d73e326db85a283ca34e8c5a184 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Wed, 17 Sep 2025 09:19:18 +0800 Subject: [PATCH 071/546] remove the unnecessary blank line --- examples/companion_radio/ui-new/UITask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d117a7fb..e15a1d67 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -542,7 +542,6 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text); setCurrScreen(msg_preview); - if (_display != NULL) { if (!_display->isOn()) _display->turnOn(); _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer From 384b02bec46a2553e7bbe2051269556f413168bf Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 18 Sep 2025 13:19:54 +1000 Subject: [PATCH 072/546] * GenericVibration: code style refactor --- examples/companion_radio/ui-new/UITask.h | 4 +-- src/helpers/ui/GenericVibration.cpp | 38 +++++++++++++++++++++ src/helpers/ui/GenericVibration.h | 33 ++++++++++++++++++ src/helpers/ui/vibration.cpp | 43 ------------------------ src/helpers/ui/vibration.h | 34 ------------------- 5 files changed, 73 insertions(+), 79 deletions(-) create mode 100644 src/helpers/ui/GenericVibration.cpp create mode 100644 src/helpers/ui/GenericVibration.h delete mode 100644 src/helpers/ui/vibration.cpp delete mode 100644 src/helpers/ui/vibration.h diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index bc12c679..5a087eeb 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -12,7 +12,7 @@ #include <helpers/ui/buzzer.h> #endif #ifdef PIN_VIBRATION - #include <helpers/ui/vibration.h> + #include <helpers/ui/GenericVibration.h> #endif #include "../AbstractUITask.h" @@ -25,7 +25,7 @@ class UITask : public AbstractUITask { genericBuzzer buzzer; #endif #ifdef PIN_VIBRATION - genericVibration vibration; + GenericVibration vibration; #endif unsigned long _next_refresh, _auto_off; NodePrefs* _node_prefs; diff --git a/src/helpers/ui/GenericVibration.cpp b/src/helpers/ui/GenericVibration.cpp new file mode 100644 index 00000000..9226b812 --- /dev/null +++ b/src/helpers/ui/GenericVibration.cpp @@ -0,0 +1,38 @@ +#ifdef PIN_VIBRATION +#include "GenericVibration.h" + +void GenericVibration::begin() { + pinMode(PIN_VIBRATION, OUTPUT); + digitalWrite(PIN_VIBRATION, LOW); + duration = 0; +} + +void GenericVibration::trigger() { + duration = millis(); + digitalWrite(PIN_VIBRATION, HIGH); +} + +void GenericVibration::loop() { + if (isVibrating()) { + if ((millis() / 1000) % 2 == 0) { + digitalWrite(PIN_VIBRATION, LOW); + } else { + digitalWrite(PIN_VIBRATION, HIGH); + } + + if (millis() - duration > VIBRATION_TIMEOUT) { + stop(); + } + } +} + +bool GenericVibration::isVibrating() { + return duration > 0; +} + +void GenericVibration::stop() { + duration = 0; + digitalWrite(PIN_VIBRATION, LOW); +} + +#endif // ifdef PIN_VIBRATION diff --git a/src/helpers/ui/GenericVibration.h b/src/helpers/ui/GenericVibration.h new file mode 100644 index 00000000..38755bd8 --- /dev/null +++ b/src/helpers/ui/GenericVibration.h @@ -0,0 +1,33 @@ +#pragma once + +#ifdef PIN_VIBRATION + +#include <Arduino.h> + +/* + * Vibration motor control class + * + * Provides vibration feedback for events like new messages and new contacts + * Features: + * - 1-second vibration pulse + * - 5-second nag timeout (cooldown between vibrations) + * - Non-blocking operation + */ + +#ifndef VIBRATION_TIMEOUT +#define VIBRATION_TIMEOUT 5000 // 5 seconds default +#endif + +class GenericVibration { +public: + void begin(); // set up vibration pin + void trigger(); // trigger vibration if cooldown has passed + void loop(); // non-blocking timer handling + bool isVibrating(); // returns true if currently vibrating + void stop(); // stop vibration immediately + +private: + unsigned long duration; +}; + +#endif // ifdef PIN_VIBRATION diff --git a/src/helpers/ui/vibration.cpp b/src/helpers/ui/vibration.cpp deleted file mode 100644 index 37db54ed..00000000 --- a/src/helpers/ui/vibration.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#ifdef PIN_VIBRATION -#include "vibration.h" - -void genericVibration::begin() -{ - pinMode(PIN_VIBRATION, OUTPUT); - digitalWrite(PIN_VIBRATION, LOW); - duration = 0; -} - -void genericVibration::trigger() -{ - duration = millis(); - digitalWrite(PIN_VIBRATION, HIGH); -} - -void genericVibration::loop() -{ - if (isVibrating()) { - if ((millis() / 1000) % 2 == 0) { - digitalWrite(PIN_VIBRATION, LOW); - } else { - digitalWrite(PIN_VIBRATION, HIGH); - } - - if (millis() - duration > VIBRATION_TIMEOUT) { - stop(); - } - } -} - -bool genericVibration::isVibrating() -{ - return duration > 0; -} - -void genericVibration::stop() -{ - duration = 0; - digitalWrite(PIN_VIBRATION, LOW); -} - -#endif // ifdef PIN_VIBRATION diff --git a/src/helpers/ui/vibration.h b/src/helpers/ui/vibration.h deleted file mode 100644 index 42cd0f67..00000000 --- a/src/helpers/ui/vibration.h +++ /dev/null @@ -1,34 +0,0 @@ -#pragma once - -#ifdef PIN_VIBRATION - -#include <Arduino.h> - -/* - * Vibration motor control class - * - * Provides vibration feedback for events like new messages and new contacts - * Features: - * - 1-second vibration pulse - * - 5-second nag timeout (cooldown between vibrations) - * - Non-blocking operation - */ - -#ifndef VIBRATION_TIMEOUT -#define VIBRATION_TIMEOUT 5000 // 5 seconds default -#endif - -class genericVibration -{ - public: - void begin(); // set up vibration pin - void trigger(); // trigger vibration if cooldown has passed - void loop(); // non-blocking timer handling - bool isVibrating(); // returns true if currently vibrating - void stop(); // stop vibration immediately - - private: - unsigned long duration; -}; - -#endif // ifdef PIN_VIBRATION From 985b290d028a418765d673ecb2eee368c7f35852 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Thu, 18 Sep 2025 09:15:01 +0200 Subject: [PATCH 073/546] use sensor_base for seeed sensecap solar --- variants/sensecap_solar/platformio.ini | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 31364ffe..8655021c 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -3,10 +3,12 @@ extends = nrf52_base board = seeed_sensecap_solar board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -I variants/sensecap_solar -I src/helpers/nrf52 + -UENV_INCLUDE_GPS -D NRF52_PLATFORM=1 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper @@ -22,13 +24,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_SHTC3=1 - -D ENV_INCLUDE_LPS22HB=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> @@ -37,13 +32,7 @@ debug_tool = jlink upload_protocol = nrfutil lib_deps = ${nrf52_base.lib_deps} - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit BMP280 Library @ ^2.6.8 - adafruit/Adafruit SHTC3 Library @ ^1.0.1 - arduino-libraries/Arduino_LPS22HB @ ^1.0.2 + ${sensor_base.lib_deps} [env:SenseCap_Solar_repeater] extends = SenseCap_Solar From 736118fe6b55e91608b403914c61626b5210e6f7 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 20 Sep 2025 10:58:02 +0800 Subject: [PATCH 074/546] Add tiny_relay variant files - platformio.ini: Build configuration for tiny relay variant - target.cpp/h: Hardware-specific implementation - variant.h: Variant identification header --- variants/tiny_relay/platformio.ini | 44 ++++++++++++++++ variants/tiny_relay/target.cpp | 82 ++++++++++++++++++++++++++++++ variants/tiny_relay/target.h | 59 +++++++++++++++++++++ variants/tiny_relay/variant.h | 5 ++ 4 files changed, 190 insertions(+) create mode 100644 variants/tiny_relay/platformio.ini create mode 100644 variants/tiny_relay/target.cpp create mode 100644 variants/tiny_relay/target.h create mode 100644 variants/tiny_relay/variant.h diff --git a/variants/tiny_relay/platformio.ini b/variants/tiny_relay/platformio.ini new file mode 100644 index 00000000..6816ed20 --- /dev/null +++ b/variants/tiny_relay/platformio.ini @@ -0,0 +1,44 @@ +[Tiny_Relay] +extends = stm32_base +board = tiny_relay +board_upload.maximum_size = 229376 ; 32kb for FS +build_flags = ${stm32_base.build_flags} + -D RADIO_CLASS=CustomSTM32WLx + -D WRAPPER_CLASS=CustomSTM32WLxWrapper + -D SPI_INTERFACES_COUNT=0 + -D RX_BOOSTED_GAIN=true +; -D STM32WL_TCXO_VOLTAGE=1.6 ; defaults to 0 if undef +; -D LORA_TX_POWER=14 ; Defaults to 22 for HP, 14 is for LP version + -D LORA_TX_POWER=22 ; Enable 22dBm transmission + -D MAX_LORA_TX_POWER=22 ; Allow setting up to 22dBm in companion radio + -I variants/tiny_relay +build_src_filter = ${stm32_base.build_src_filter} + +<../variants/tiny_relay> + +[env:Tiny_Relay-repeater] +extends = Tiny_Relay +build_flags = ${Tiny_Relay.build_flags} + -D ADVERT_NAME='"tiny_relay Repeater"' + -D ADMIN_PASSWORD='"password"' +build_src_filter = ${Tiny_Relay.build_src_filter} + +<../examples/simple_repeater/main.cpp> + +[env:Tiny_Relay-sensor] +extends = Tiny_Relay +build_flags = ${Tiny_Relay.build_flags} + -D ADVERT_NAME='"tiny_relay Sensor"' + -D ADMIN_PASSWORD='"password"' +build_src_filter = ${Tiny_Relay.build_src_filter} + +<../examples/simple_sensor> + +[env:Tiny_Relay_companion_radio_usb] +extends = Tiny_Relay +build_flags = ${Tiny_Relay.build_flags} +; -D FORMAT_FS=true + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 + -D MAX_LORA_TX_POWER=22 +build_src_filter = ${Tiny_Relay.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = ${Tiny_Relay.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/tiny_relay/target.cpp b/variants/tiny_relay/target.cpp new file mode 100644 index 00000000..f738ac17 --- /dev/null +++ b/variants/tiny_relay/target.cpp @@ -0,0 +1,82 @@ +#include "target.h" +#include <Arduino.h> +#include <helpers/ArduinoHelpers.h> + +TinyRelayBoard board; + +RADIO_CLASS radio = new STM32WLx_Module(); + +WRAPPER_CLASS radio_driver(radio, board); + +static const uint32_t rfswitch_pins[] = {LORAWAN_RFSWITCH_PINS, RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC}; +static const Module::RfSwitchMode_t rfswitch_table[] = { + {STM32WLx::MODE_IDLE, {LOW, LOW}}, + {STM32WLx::MODE_RX, {HIGH, LOW}}, + {STM32WLx::MODE_TX_LP, {LOW, HIGH}}, + {STM32WLx::MODE_TX_HP, {LOW, HIGH}}, + END_OF_MODE_TABLE, +}; + +VolatileRTCClock rtc_clock; +SensorManager sensors; + +#ifndef LORA_CR +#define LORA_CR 5 +#endif + +#ifndef STM32WL_TCXO_VOLTAGE +// TCXO set to 0 for RAK3172 +#define STM32WL_TCXO_VOLTAGE 0 +#endif + +#ifndef LORA_TX_POWER +#define LORA_TX_POWER 22 +#endif + +bool radio_init() +{ + // rtc_clock.begin(Wire); + + radio.setRfSwitchTable(rfswitch_pins, rfswitch_table); + + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, + STM32WL_TCXO_VOLTAGE, 0); + + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + radio.setCRC(1); + + return true; // success +} + +uint32_t radio_get_rng_seed() +{ + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) +{ + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) +{ + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() +{ + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/tiny_relay/target.h b/variants/tiny_relay/target.h new file mode 100644 index 00000000..82747cdc --- /dev/null +++ b/variants/tiny_relay/target.h @@ -0,0 +1,59 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/SensorManager.h> +#include <helpers/radiolib/CustomSTM32WLxWrapper.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/stm32/STM32Board.h> + +#define PIN_VBAT_READ A0 +#define ADC_MULTIPLIER (5 * 1.73 * 1000) + +class TinyRelayBoard : public STM32Board +{ + public: + void begin() override + { + STM32Board::begin(); + pinMode(PA0, OUTPUT); + pinMode(PA1, OUTPUT); + } + + const char *getManufacturerName() const override { return "Tiny Relay"; } + + uint16_t getBattMilliVolts() override + { + analogReadResolution(12); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + return ((double)raw) * ADC_MULTIPLIER / 8 / 4096; + } + + void setGpio(uint32_t values) override + { + // set led values + digitalWrite(PA0, values & 1); + digitalWrite(PA1, (values & 2) >> 1); + } + + uint32_t getGpio() override + { + // get led value + return (digitalRead(PA1) << 1) | digitalRead(PA0); + } +}; + +extern TinyRelayBoard board; +extern WRAPPER_CLASS radio_driver; +extern VolatileRTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/tiny_relay/variant.h b/variants/tiny_relay/variant.h new file mode 100644 index 00000000..4405be0b --- /dev/null +++ b/variants/tiny_relay/variant.h @@ -0,0 +1,5 @@ +#pragma once + +#include <variant_RAK3172_MODULE.h> + +#undef RNG From b3af4d9c723e6c8ef8fe9706152b7328c97e61a0 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 20 Sep 2025 10:59:36 +0800 Subject: [PATCH 075/546] =?UTF-8?q?=E2=9C=A8=20feat:=20add=20tiny=5Frelay?= =?UTF-8?q?=20board=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add board configuration for BB-STM32WL tiny relay variant with STM32WLE5CC MCU support including debug and upload protocols. --- boards/tiny_relay.json | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 boards/tiny_relay.json diff --git a/boards/tiny_relay.json b/boards/tiny_relay.json new file mode 100644 index 00000000..c2bfae14 --- /dev/null +++ b/boards/tiny_relay.json @@ -0,0 +1,33 @@ +{ + "build": { + "arduino": { + "variant_h": "variant_RAK3172_MODULE.h" + }, + "core": "stm32", + "cpu": "cortex-m4", + "extra_flags": "-DSTM32WL -DSTM32WLxx -DSTM32WLE5xx", + "framework_extra_flags": { + "arduino": "-DUSE_CM4_STARTUP_FILE -DARDUINO_RAK3172_MODULE" + }, + "f_cpu": "48000000L", + "mcu": "stm32wle5ccu", + "product_line": "STM32WLE5xx", + "variant": "STM32WLxx/WL54CCU_WL55CCU_WLE4C(8-B-C)U_WLE5C(8-B-C)U" + }, + "debug": { + "default_tools": ["stlink"], + "jlink_device": "STM32WLE5CC", + "openocd_target": "stm32wlx", + "svd_path": "STM32WLE5_CM4.svd" + }, + "frameworks": ["arduino"], + "name": "BB-STM32WL", + "upload": { + "maximum_ram_size": 65536, + "maximum_size": 262144, + "protocol": "stlink", + "protocols": ["stlink", "jlink"] + }, + "url": "YAOAO", + "vendor": "YAOYAO" +} From a1622bad752bcbc947acdb5fae962b52e37a0df7 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 20 Sep 2025 14:07:22 +0800 Subject: [PATCH 076/546] =?UTF-8?q?=F0=9F=94=97=20fix:=20update=20tiny=5Fr?= =?UTF-8?q?elay=20board=20URL=20to=20proper=20STM32WLE5CC=20documentation?= =?UTF-8?q?=20link?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- boards/tiny_relay.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/boards/tiny_relay.json b/boards/tiny_relay.json index c2bfae14..517d11f0 100644 --- a/boards/tiny_relay.json +++ b/boards/tiny_relay.json @@ -28,6 +28,6 @@ "protocol": "stlink", "protocols": ["stlink", "jlink"] }, - "url": "YAOAO", + "url": "https://www.st.com/en/microcontrollers-microprocessors/stm32wle5cc.html", "vendor": "YAOYAO" } From 757ff9fb55656bf8e78794722b9925d6fd106042 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 20 Sep 2025 08:54:30 +0200 Subject: [PATCH 077/546] stm32: force the use of Adafruit BusIO v1.17.2 as 1.17.3 won't compile on this platform --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 11814a22..4fe17af9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -107,6 +107,7 @@ build_src_filter = ${arduino_base.build_src_filter} +<helpers/stm32> lib_deps = ${arduino_base.lib_deps} file://arch/stm32/Adafruit_LittleFS_stm32 + adafruit/Adafruit BusIO @ 1.17.2 [sensor_base] build_flags = From 2922b62888aafd32093f75d61430d6ae548599ff Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sat, 20 Sep 2025 17:36:52 +1000 Subject: [PATCH 078/546] add bounds check to _countLfsBlock / _getLfsUsedBlockCount --- examples/companion_radio/DataStore.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 7631b905..f1adb05e 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -42,12 +42,17 @@ static File openWrite(FILESYSTEM* fs, const char* filename) { #endif } +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + static uint32_t _ContactsChannelsTotalBlocks = 0; +#endif + void DataStore::begin() { #if defined(RP2040_PLATFORM) identity_store.begin(); #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _ContactsChannelsTotalBlocks = _getContactsChannelsFS()->_getFS()->cfg->block_count; checkAdvBlobFile(); #if defined(EXTRAFS) || defined(QSPIFLASH) migrateToSecondaryFS(); @@ -74,14 +79,22 @@ void DataStore::begin() { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) int _countLfsBlock(void *p, lfs_block_t block){ + if (block > _ContactsChannelsTotalBlocks) { + MESH_DEBUG_PRINTLN("ERROR: Block %d exceeds filesystem bounds - CORRUPTION DETECTED!", block); + return LFS_ERR_CORRUPT; // return error to abort lfs_traverse() gracefully + } lfs_size_t *size = (lfs_size_t*) p; *size += 1; - return 0; + return 0; } lfs_ssize_t _getLfsUsedBlockCount(FILESYSTEM* fs) { lfs_size_t size = 0; - lfs_traverse(fs->_getFS(), _countLfsBlock, &size); + int err = lfs_traverse(fs->_getFS(), _countLfsBlock, &size); + if (err) { + MESH_DEBUG_PRINTLN("ERROR: lfs_traverse() error: %d", err); + return 0; + } return size; } #endif From 59ea6cdb89d536fe24c96884d33f65db2bbe514b Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 20 Sep 2025 21:45:13 +0200 Subject: [PATCH 079/546] wio-l1-eink initial support --- boards/seeed-wio-tracker-l1.json | 1 + src/helpers/ui/GxEPDDisplay.h | 4 +- variants/wio-tracker-l1-eink/platformio.ini | 66 +++++++++++++++++++++ variants/wio-tracker-l1/target.h | 6 +- variants/wio-tracker-l1/variant.cpp | 9 +++ variants/wio-tracker-l1/variant.h | 21 ++++++- 6 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 variants/wio-tracker-l1-eink/platformio.ini diff --git a/boards/seeed-wio-tracker-l1.json b/boards/seeed-wio-tracker-l1.json index 6235b8bf..56e3b424 100644 --- a/boards/seeed-wio-tracker-l1.json +++ b/boards/seeed-wio-tracker-l1.json @@ -46,6 +46,7 @@ "speed": 115200, "protocols": [ "jlink", + "stlink", "nrfjprog", "nrfutil", "cmsis-dap", diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index d53446d5..4fe6c0f2 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -24,7 +24,7 @@ class GxEPDDisplay : public DisplayDriver { -#if defined(HELTEC_MESH_POCKET) +#if defined(EINK_DISPLAY_MODEL) GxEPD2_BW<EINK_DISPLAY_MODEL, EINK_DISPLAY_MODEL::HEIGHT> display; const float scale_x = EINK_SCALE_X; const float scale_y = EINK_SCALE_Y; @@ -45,7 +45,7 @@ class GxEPDDisplay : public DisplayDriver { public: // there is a margin in y... -#if defined(HELTEC_MESH_POCKET) +#if defined(EINK_DISPLAY_MODEL) GxEPDDisplay() : DisplayDriver(128, 128), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} #else GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {} diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini new file mode 100644 index 00000000..fa672f99 --- /dev/null +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -0,0 +1,66 @@ +[WioTrackerL1Eink] +extends = nrf52_base +board = seeed-wio-tracker-l1 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 + -I variants/wio-tracker-l1 + -D WIO_TRACKER_L1 + -D WIO_TRACKER_L1_EINK + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_OLED_RESET=-1 + -D EINK_DISPLAY_MODEL=GxEPD2_213_B74 + -D EINK_SCALE_X=1.953125f + -D EINK_SCALE_Y=1.28f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D DISPLAY_ROTATION=1 + -D DISABLE_DIAGNOSTIC_OUTPUT + -D AUTO_OFF_MILLIS=0 +build_src_filter = ${nrf52_base.build_src_filter} + +<WioTrackerL1Board.cpp> + +<../variants/wio-tracker-l1> + +<helpers/ui/GxEPDDisplay.cpp> + +<helpers/sensors> +lib_deps= ${nrf52_base.lib_deps} + adafruit/Adafruit SH110X @ ^2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + stevemarple/MicroNMEA @ ^2.0.6 + zinggjm/GxEPD2 @ 1.6.2 + bakercp/CRC32 @ ^2.0.0 + adafruit/Adafruit BME280 Library @ ^2.3.0 + +[env:WioTrackerL1Eink_companion_radio_ble] +extends = WioTrackerL1Eink +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${WioTrackerL1Eink.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=GxEPDDisplay + -D PIN_BUZZER=12 + -D QSPIFLASH=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 +build_src_filter = ${WioTrackerL1Eink.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/ui/buzzer.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${WioTrackerL1Eink.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 +debug_tool=stlink diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index 6f5da7c6..14e4591f 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -8,7 +8,11 @@ #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/ArduinoHelpers.h> #ifdef DISPLAY_CLASS - #include <helpers/ui/SH1106Display.h> + #if defined(WIO_TRACKER_L1_EINK) + #include <helpers/ui/GxEPDDisplay.h> + #else + #include <helpers/ui/SH1106Display.h> + #endif #include <helpers/ui/MomentaryButton.h> #endif #include <helpers/sensors/EnvironmentSensorManager.h> diff --git a/variants/wio-tracker-l1/variant.cpp b/variants/wio-tracker-l1/variant.cpp index 3db5ec9a..4fc2606a 100644 --- a/variants/wio-tracker-l1/variant.cpp +++ b/variants/wio-tracker-l1/variant.cpp @@ -52,6 +52,15 @@ const uint32_t g_ADigitalPinMap[] = { // VBAT ENABLE 4, // D30 BAT_CTL + + // EINK + 13, // 31 SCK + 14, // 32 RST + 15, // 33 MOSI + 16, // 34 DC + 17, // 35 BUSY + 19, // 36 CS + 0xFF, // 37 MISO }; void initVariant() { diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index af01177e..5cb0d97a 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -12,8 +12,8 @@ #include "WVariant.h" -#define PINS_COUNT (33) -#define NUM_DIGITAL_PINS (33) +#define PINS_COUNT (38) +#define NUM_DIGITAL_PINS (38) #define NUM_ANALOG_INPUTS (8) #define NUM_ANALOG_OUTPUTS (0) @@ -54,7 +54,7 @@ #define PIN_SERIAL1_TX (6) // SPI Interfaces -#define SPI_INTERFACES_COUNT (1) +#define SPI_INTERFACES_COUNT (2) #define PIN_SPI_MISO (9) #define PIN_SPI_MOSI (10) @@ -101,4 +101,19 @@ #define EXTERNAL_FLASH_DEVICES P25Q16H #define EXTERNAL_FLASH_USE_QSPI +// EInk on SPI1 +#define PIN_DISPLAY_CS (36) +#define PIN_DISPLAY_BUSY (35) +#define PIN_DISPLAY_DC (34) +#define PIN_DISPLAY_RST (32) + +#define PIN_SPI1_MISO (37) +#define PIN_SPI1_MOSI (33) +#define PIN_SPI1_SCK (31) + +// GxEPD2 needs that for a panel that is not even used ! +extern const int MISO; +extern const int MOSI; +extern const int SCK; + #endif \ No newline at end of file From f9543bb7bb5eeec05d046ec6848220ecbc64c74b Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 21 Sep 2025 22:14:22 +0200 Subject: [PATCH 080/546] tracker_l1: support for EnvironmentSensorManager --- .../sensors/EnvironmentSensorManager.cpp | 6 + variants/wio-tracker-l1-eink/platformio.ini | 8 +- variants/wio-tracker-l1/platformio.ini | 7 +- variants/wio-tracker-l1/target.cpp | 110 ++---------------- variants/wio-tracker-l1/target.h | 23 +--- variants/wio-tracker-l1/variant.h | 4 +- 6 files changed, 30 insertions(+), 128 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 6b1b9e47..6d681bb5 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -108,7 +108,13 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_PIN_SDA && ENV_PIN_SCL + #ifdef NRF52_PLATFORM + Wire1.setPins(ENV_PIN_SDA, ENV_PIN_SCL); + Wire1.setClock(100000); + Wire1.begin(); + #else Wire1.begin(ENV_PIN_SDA, ENV_PIN_SCL, 100000); + #endif MESH_DEBUG_PRINTLN("Second I2C initialized on pins SDA: %d SCL: %d", ENV_PIN_SDA, ENV_PIN_SCL); #endif diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index fa672f99..742408e9 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -22,6 +22,12 @@ build_flags = ${nrf52_base.build_flags} -D DISPLAY_ROTATION=1 -D DISABLE_DIAGNOSTIC_OUTPUT -D AUTO_OFF_MILLIS=0 + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_BME280=1 + -D GPS_BAUD_RATE=9600 + -D PIN_GPS_EN=PIN_GPS_STANDBY + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} +<WioTrackerL1Board.cpp> +<../variants/wio-tracker-l1> @@ -51,7 +57,7 @@ build_flags = ${WioTrackerL1Eink.build_flags} -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 - -D UI_RECENT_LIST_SIZE=9 + -D UI_RECENT_LIST_SIZE=6 -D UI_SENSORS_PAGE=1 build_src_filter = ${WioTrackerL1Eink.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 585e0ba7..396f6fa8 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -13,7 +13,12 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_OLED_RESET=-1 - ; -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_BME280=1 + -D GPS_BAUD_RATE=9600 + -D PIN_GPS_EN=PIN_GPS_STANDBY + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} +<WioTrackerL1Board.cpp> +<../variants/wio-tracker-l1> diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 349d73b4..374e53af 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -11,8 +11,13 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); -WioTrackerL1SensorManager sensors = WioTrackerL1SensorManager(nmea); + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -42,107 +47,6 @@ void radio_set_tx_power(uint8_t dbm) { radio.setOutputPower(dbm); } -void WioTrackerL1SensorManager::start_gps() -{ - if (!gps_active) - { - MESH_DEBUG_PRINTLN("starting GPS"); - digitalWrite(PIN_GPS_STANDBY, HIGH); - gps_active = true; - } -} - -void WioTrackerL1SensorManager::stop_gps() -{ - if (gps_active) - { - MESH_DEBUG_PRINTLN("stopping GPS"); - digitalWrite(PIN_GPS_STANDBY, LOW); - gps_active = false; - } -} - -bool WioTrackerL1SensorManager::begin() -{ - Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); // be sure to tx into rx and rx into tx - Serial1.begin(GPS_BAUDRATE); - - pinMode(PIN_GPS_STANDBY, OUTPUT); - digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby - delay(500); - - // We'll consider GPS detected if we see any data on Serial1 - if (Serial1.available() > 0) - { - MESH_DEBUG_PRINTLN("GPS detected"); - } - else - { - MESH_DEBUG_PRINTLN("No GPS detected"); - } - digitalWrite(PIN_GPS_STANDBY, LOW); // Put GPS back into standby mode - return true; -} - -bool WioTrackerL1SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) -{ - if (requester_permissions & TELEM_PERM_LOCATION) - { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); - } - return true; -} - -void WioTrackerL1SensorManager::loop() -{ - static long next_gps_update = 0; - _location->loop(); - if (millis() > next_gps_update && gps_active) // don't bother if gps position is not enabled - { - if (_location->isValid()) - { - node_lat = ((double)_location->getLatitude()) / 1000000.; - node_lon = ((double)_location->getLongitude()) / 1000000.; - node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); - } - next_gps_update = millis() + (1000 * 60); // after initial update, only check every minute TODO: should be configurable - } -} - -int WioTrackerL1SensorManager::getNumSettings() const { return 1; } // just one supported: "gps" (power switch) - -const char *WioTrackerL1SensorManager::getSettingName(int i) const -{ - return i == 0 ? "gps" : NULL; -} - -const char *WioTrackerL1SensorManager::getSettingValue(int i) const -{ - if (i == 0) - { - return gps_active ? "1" : "0"; - } - return NULL; -} - -bool WioTrackerL1SensorManager::setSettingValue(const char *name, const char *value) -{ - if (strcmp(name, "gps") == 0) - { - if (strcmp(value, "0") == 0) - { - stop_gps(); - } - else - { - start_gps(); - } - return true; - } - return false; // not supported -} - mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index 14e4591f..d3021d6d 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -7,6 +7,7 @@ #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS #if defined(WIO_TRACKER_L1_EINK) #include <helpers/ui/GxEPDDisplay.h> @@ -17,30 +18,10 @@ #endif #include <helpers/sensors/EnvironmentSensorManager.h> -class WioTrackerL1SensorManager : public SensorManager -{ - bool gps_active = false; - LocationProvider *_location; - - void start_gps(); - void stop_gps(); - -public: - WioTrackerL1SensorManager(LocationProvider &location) : _location(&location) {} - bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) override; - void loop() override; - int getNumSettings() const override; - const char *getSettingName(int i) const override; - const char *getSettingValue(int i) const override; - bool setSettingValue(const char *name, const char *value) override; -}; - - extern WioTrackerL1Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern WioTrackerL1SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; extern MomentaryButton user_btn; diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index 5cb0d97a..b6e611cd 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -78,8 +78,8 @@ #define PIN_WIRE_SDA (14) #define PIN_WIRE_SCL (15) -#define PIN_WIRE1_SDA (17) -#define PIN_WIRE1_SCL (18) +#define PIN_WIRE1_SDA (18) +#define PIN_WIRE1_SCL (17) #define I2C_NO_RESCAN #define DISPLAY_ADDRESS 0x3D // SH1106 OLED I2C address From 9d009074daf9fec303f743180a173a4e45052a7a Mon Sep 17 00:00:00 2001 From: kelsey hudson <khudson@tsss.org> Date: Sun, 7 Sep 2025 21:29:10 -0700 Subject: [PATCH 081/546] Ikoka Stick: Move to unified code naming conventions --- ...stick_nrf_board.cpp => IkokaStickNRFBoard.cpp} | 15 +++++---------- ...oka_stick_nrf_board.h => IkokaStickNRFBoard.h} | 6 +++--- variants/ikoka_stick_nrf/target.cpp | 2 +- variants/ikoka_stick_nrf/target.h | 4 ++-- 4 files changed, 11 insertions(+), 16 deletions(-) rename variants/ikoka_stick_nrf/{ikoka_stick_nrf_board.cpp => IkokaStickNRFBoard.cpp} (91%) rename variants/ikoka_stick_nrf/{ikoka_stick_nrf_board.h => IkokaStickNRFBoard.h} (89%) diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp similarity index 91% rename from variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp rename to variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 8634cda1..6b660383 100644 --- a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -1,28 +1,26 @@ #ifdef XIAO_NRF52 #include <Arduino.h> -#include "ikoka_stick_nrf_board.h" +#include "IkokaStickNRFBoard.h" #include <bluefruit.h> #include <Wire.h> static BLEDfu bledfu; -static void connect_callback(uint16_t conn_handle) -{ +static void connect_callback(uint16_t conn_handle) { (void)conn_handle; MESH_DEBUG_PRINTLN("BLE client connected"); } -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) -{ +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { (void)conn_handle; (void)reason; MESH_DEBUG_PRINTLN("BLE client disconnected"); } -void ikoka_stick_nrf_board::begin() { +void IkokaStickNRFBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; @@ -50,7 +48,7 @@ void ikoka_stick_nrf_board::begin() { delay(10); // give sx1262 some time to power up } -bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) { +bool IkokaStickNRFBoard::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice // Note: All config***() function must be called before begin() @@ -91,9 +89,6 @@ bool ikoka_stick_nrf_board::startOTAUpdate(const char* id, char reply[]) { strcpy(reply, "OK - started"); return true; - - - return false; } #endif diff --git a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h similarity index 89% rename from variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h rename to variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 08061c23..4a061d42 100644 --- a/variants/ikoka_stick_nrf/ikoka_stick_nrf_board.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -5,7 +5,7 @@ #ifdef XIAO_NRF52 -class ikoka_stick_nrf_board : public mesh::MainBoard { +class IkokaStickNRFBoard : public mesh::MainBoard { protected: uint8_t startup_reason; @@ -46,7 +46,7 @@ public: return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; } - const char* getManufacturerName() const override { + const char *getManufacturerName() const override { return MANUFACTURER_STRING; } @@ -54,7 +54,7 @@ public: NVIC_SystemReset(); } - bool startOTAUpdate(const char* id, char reply[]) override; + bool startOTAUpdate(const char *id, char reply[]) override; }; #endif diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index c2712761..bd803399 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -2,7 +2,7 @@ #include "target.h" #include <helpers/ArduinoHelpers.h> -ikoka_stick_nrf_board board; +IkokaStickNRFBoard board; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h index 8311503a..c276e89f 100644 --- a/variants/ikoka_stick_nrf/target.h +++ b/variants/ikoka_stick_nrf/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <ikoka_stick_nrf_board.h> +#include <IkokaStickNRFBoard.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/ArduinoHelpers.h> @@ -16,7 +16,7 @@ extern MomentaryButton user_btn; #endif -extern ikoka_stick_nrf_board board; +extern IkokaStickNRFBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern EnvironmentSensorManager sensors; From 52d5cc6068cb19a3d319f798714ee685fa786673 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 22 Sep 2025 15:01:28 +1000 Subject: [PATCH 082/546] * tidy and minor fix for offline queue deletion --- examples/companion_radio/MyMesh.cpp | 9 ++++++--- examples/companion_radio/MyMesh.h | 2 ++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index e53ad378..509b0c63 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -175,14 +175,17 @@ void MyMesh::updateContactFromFrame(ContactInfo &contact, uint32_t& last_mod, co } } +bool MyMesh::Frame::isChannelMsg() const { + return buf[0] == RESP_CODE_CHANNEL_MSG_RECV || buf[0] == RESP_CODE_CHANNEL_MSG_RECV_V3; +} + void MyMesh::addToOfflineQueue(const uint8_t frame[], int len) { if (offline_queue_len >= OFFLINE_QUEUE_SIZE) { MESH_DEBUG_PRINTLN("WARN: offline_queue is full!"); int pos = 0; while (pos < offline_queue_len) { - if ((offline_queue[pos].buf[0] == RESP_CODE_CHANNEL_MSG_RECV) || - offline_queue[pos].buf[0] == RESP_CODE_CHANNEL_MSG_RECV_V3) { - for (int i = pos; i < offline_queue_len; i++) { // delete oldest channel msg from queue + if (offline_queue[pos].isChannelMsg()) { + for (int i = pos; i < offline_queue_len - 1; i++) { // delete oldest channel msg from queue offline_queue[i] = offline_queue[i + 1]; } MESH_DEBUG_PRINTLN("INFO: removed oldest channel message from queue."); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 0dc9ad61..20efb46b 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -198,6 +198,8 @@ private: struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; + + bool isChannelMsg() const; }; int offline_queue_len; Frame offline_queue[OFFLINE_QUEUE_SIZE]; From 0cb34740d2513fd940841cfd5fbfb33218566dc3 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Mon, 22 Sep 2025 12:06:05 +0200 Subject: [PATCH 083/546] tracker-l1: correct bad definition for PIN_GPS_EN --- variants/wio-tracker-l1-eink/platformio.ini | 1 - variants/wio-tracker-l1/platformio.ini | 1 - variants/wio-tracker-l1/variant.h | 2 +- 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index 742408e9..62301354 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -25,7 +25,6 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_GPS=1 -D ENV_INCLUDE_BME280=1 -D GPS_BAUD_RATE=9600 - -D PIN_GPS_EN=PIN_GPS_STANDBY -D ENV_PIN_SDA=PIN_WIRE1_SDA -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 396f6fa8..63228995 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -16,7 +16,6 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_GPS=1 -D ENV_INCLUDE_BME280=1 -D GPS_BAUD_RATE=9600 - -D PIN_GPS_EN=PIN_GPS_STANDBY -D ENV_PIN_SDA=PIN_WIRE1_SDA -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index b6e611cd..7e1b1b86 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -88,7 +88,7 @@ #define PIN_GPS_TX PIN_SERIAL1_RX #define PIN_GPS_RX PIN_SERIAL1_TX #define PIN_GPS_STANDBY (0) -#define PIN_GPS_EN (18) +#define PIN_GPS_EN (PIN_GPS_STANDBY) // QSPI Pins #define PIN_QSPI_SCK (19) From 669bea04a0ff9e2fd9312809f3d0c205f2806212 Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Mon, 22 Sep 2025 19:58:27 +0800 Subject: [PATCH 084/546] add heltec_v4 board. --- boards/heltec_v4.json | 43 ++++ .../sensors/EnvironmentSensorManager.cpp | 35 ++- variants/heltec_v4/HeltecV4Board.cpp | 90 ++++++++ variants/heltec_v4/HeltecV4Board.h | 23 ++ variants/heltec_v4/pins_arduino.h | 67 ++++++ variants/heltec_v4/platformio.ini | 208 ++++++++++++++++++ variants/heltec_v4/target.cpp | 60 +++++ variants/heltec_v4/target.h | 30 +++ 8 files changed, 555 insertions(+), 1 deletion(-) create mode 100644 boards/heltec_v4.json create mode 100644 variants/heltec_v4/HeltecV4Board.cpp create mode 100644 variants/heltec_v4/HeltecV4Board.h create mode 100644 variants/heltec_v4/pins_arduino.h create mode 100644 variants/heltec_v4/platformio.ini create mode 100644 variants/heltec_v4/target.cpp create mode 100644 variants/heltec_v4/target.h diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json new file mode 100644 index 00000000..36cdfc04 --- /dev/null +++ b/boards/heltec_v4.json @@ -0,0 +1,43 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_qspi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "qspi", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_v4" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 2097152, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/", + "vendor": "heltec" +} \ No newline at end of file diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 6b1b9e47..06a4a2ab 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -433,8 +433,19 @@ void EnvironmentSensorManager::initBasicGPS() { // Try to detect if GPS is physically connected to determine if we should expose the setting #ifdef PIN_GPS_EN pinMode(PIN_GPS_EN, OUTPUT); + #ifdef PIN_GPS_EN_ACTIVE + digitalWrite(PIN_GPS_EN, PIN_GPS_EN_ACTIVE); // Power on GPS + #else digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS #endif + #endif + +#ifdef PIN_GPS_RESET + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, PIN_GPS_RESET_ACTIVE); // assert for 10ms + delay(10); + digitalWrite(PIN_GPS_RESET, !PIN_GPS_RESET_ACTIVE); +#endif #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("No GPS wake/reset pin found for this board. Continuing on..."); @@ -456,8 +467,12 @@ void EnvironmentSensorManager::initBasicGPS() { MESH_DEBUG_PRINTLN("No GPS detected"); } #ifdef PIN_GPS_EN + #ifdef PIN_GPS_EN_ACTIVE + digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); + #else digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed #endif + #endif gps_active = false; //Set GPS visibility off until setting is changed } @@ -542,9 +557,23 @@ void EnvironmentSensorManager::start_gps() { #endif #ifdef PIN_GPS_EN pinMode(PIN_GPS_EN, OUTPUT); + #ifdef PIN_GPS_EN_ACTIVE + digitalWrite(PIN_GPS_EN, PIN_GPS_EN_ACTIVE); + #else digitalWrite(PIN_GPS_EN, HIGH); + #endif + #ifndef PIN_GPS_RESET return; #endif + #endif + +#ifdef PIN_GPS_RESET + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, PIN_GPS_RESET_ACTIVE); // assert for 10ms + delay(10); + digitalWrite(PIN_GPS_RESET, !PIN_GPS_RESET_ACTIVE); + return; +#endif MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged"); } @@ -558,8 +587,12 @@ void EnvironmentSensorManager::stop_gps() { #endif #ifdef PIN_GPS_EN pinMode(PIN_GPS_EN, OUTPUT); + #ifdef PIN_GPS_EN_ACTIVE + digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); + #else digitalWrite(PIN_GPS_EN, LOW); - return; + #endif + return; #endif MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged"); diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp new file mode 100644 index 00000000..f143db36 --- /dev/null +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -0,0 +1,90 @@ +#include "HeltecV4Board.h" + +void HeltecV4Board::begin() { + ESP32Board::begin(); + + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER,HIGH); + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN,HIGH); + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN,LOW); + + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void HeltecV4Board::onBeforeTransmit(void) { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + digitalWrite(P_LORA_PA_TX_EN,HIGH); + } + + void HeltecV4Board::onAfterTransmit(void) { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + digitalWrite(P_LORA_PA_TX_EN,LOW); + } + + void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecV4Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t HeltecV4Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + delay(10); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecV4Board::getManufacturerName() const { + return "Heltec V4"; + } diff --git a/variants/heltec_v4/HeltecV4Board.h b/variants/heltec_v4/HeltecV4Board.h new file mode 100644 index 00000000..745e8d8f --- /dev/null +++ b/variants/heltec_v4/HeltecV4Board.h @@ -0,0 +1,23 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/RefCountedDigitalPin.h> +#include <helpers/ESP32Board.h> +#include <driver/rtc_io.h> + +class HeltecV4Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecV4Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void onBeforeTransmit(void) override; + void onAfterTransmit(void) override; + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_v4/pins_arduino.h b/variants/heltec_v4/pins_arduino.h new file mode 100644 index 00000000..a8b9f291 --- /dev/null +++ b/variants/heltec_v4/pins_arduino.h @@ -0,0 +1,67 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include <stdint.h> + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 3; +static const uint8_t SCL = 4; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; +static const uint8_t LED = 35; +static const uint8_t RST_OLED = 21; +static const uint8_t SCL_OLED = 18; +static const uint8_t SDA_OLED = 17; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini new file mode 100644 index 00000000..d50c27a5 --- /dev/null +++ b/variants/heltec_v4/platformio.ini @@ -0,0 +1,208 @@ +[Heltec_lora32_v4] +extends = esp32_base +board = heltec_v4 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/heltec_v4 + -D HELTEC_LORA_V4 + -D ESP32_CPU_FREQ=80 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=35 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=12 + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 + -D P_LORA_PA_POWER=7 ;power en + -D P_LORA_PA_EN=2 + -D P_LORA_PA_TX_EN=46 ;enable tx + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=36 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_GPS_RX=38 + -D PIN_GPS_TX=39 + -D PIN_GPS_RESET=42 + -D PIN_GPS_RESET_ACTIVE=LOW + -D PIN_GPS_EN=34 + -D PIN_GPS_EN_ACTIVE=LOW + -D ENV_INCLUDE_GPS=1 + -D PIN_ADC_CTRL=37 + -D PIN_VBAT_READ=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_v4> + +<helpers/sensors> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[env:heltec_v4_repeater] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_v4_repeater_bridge_espnow] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_room_server] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_room_server> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_terminal_chat] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_companion_radio_usb] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_companion_radio_ble] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_companion_radio_wifi] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=SSD1306Display + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_sensor] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D ADVERT_NAME='"Heltec v3 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_sensor> +lib_deps = + ${Heltec_lora32_v4.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp new file mode 100644 index 00000000..015c3a8e --- /dev/null +++ b/variants/heltec_v4/target.cpp @@ -0,0 +1,60 @@ +#include <Arduino.h> +#include "target.h" + +HeltecV4Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h new file mode 100644 index 00000000..a153b2af --- /dev/null +++ b/variants/heltec_v4/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <HeltecV4Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern HeltecV4Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From f100894882ebf60ff4c5d40d4052294867ca85a8 Mon Sep 17 00:00:00 2001 From: WattleFoxxo <wattle@wattlefoxxo.au> Date: Mon, 22 Sep 2025 23:48:46 +1000 Subject: [PATCH 085/546] LillyGo TDeck support --- boards/t-deck.json | 38 ++++++++ src/helpers/ui/ST7789LCDDisplay.cpp | 141 +++++++++++++++++++++++++++ src/helpers/ui/ST7789LCDDisplay.h | 60 ++++++++++++ variants/lilygo_tdeck/TDeckBoard.cpp | 35 +++++++ variants/lilygo_tdeck/TDeckBoard.h | 68 +++++++++++++ variants/lilygo_tdeck/platformio.ini | 96 ++++++++++++++++++ variants/lilygo_tdeck/target.cpp | 53 ++++++++++ variants/lilygo_tdeck/target.h | 29 ++++++ 8 files changed, 520 insertions(+) create mode 100644 boards/t-deck.json create mode 100644 src/helpers/ui/ST7789LCDDisplay.cpp create mode 100644 src/helpers/ui/ST7789LCDDisplay.h create mode 100644 variants/lilygo_tdeck/TDeckBoard.cpp create mode 100644 variants/lilygo_tdeck/TDeckBoard.h create mode 100644 variants/lilygo_tdeck/platformio.ini create mode 100644 variants/lilygo_tdeck/target.cpp create mode 100644 variants/lilygo_tdeck/target.h diff --git a/boards/t-deck.json b/boards/t-deck.json new file mode 100644 index 00000000..6942ab01 --- /dev/null +++ b/boards/t-deck.json @@ -0,0 +1,38 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "LilyGo T-Deck (16M Flash 8M PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.lilygo.cc", + "vendor": "LilyGo" +} \ No newline at end of file diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp new file mode 100644 index 00000000..38c28893 --- /dev/null +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -0,0 +1,141 @@ +#include "ST7789LCDDisplay.h" + +#ifndef DISPLAY_ROTATION + #define DISPLAY_ROTATION 3 +#endif + +#ifndef DISPLAY_SCALE_X + #define DISPLAY_SCALE_X 2.5f // 320 / 128 +#endif + +#ifndef DISPLAY_SCALE_Y + #define DISPLAY_SCALE_Y 3.75f // 240 / 64 +#endif + +#define DISPLAY_WIDTH 240 +#define DISPLAY_HEIGHT 320 + +bool ST7789LCDDisplay::i2c_probe(TwoWire& wire, uint8_t addr) { + return true; +} + +bool ST7789LCDDisplay::begin() { + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + digitalWrite(PIN_TFT_RST, HIGH); + + // Im not sure if this is just a t-deck problem or not, if your display is slow try this. + #ifdef LILYGO_TDECK + displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); + #endif + + display.init(DISPLAY_WIDTH, DISPLAY_HEIGHT); + display.setRotation(DISPLAY_ROTATION); + + display.setSPISpeed(40e6); + + display.fillScreen(ST77XX_BLACK); + display.setTextColor(ST77XX_WHITE); + display.setTextSize(2); + display.cp437(true); // Use full 256 char 'Code Page 437' font + + _isOn = true; + } + + return true; +} + +void ST7789LCDDisplay::turnOn() { + ST7789LCDDisplay::begin(); +} + +void ST7789LCDDisplay::turnOff() { + if (_isOn) { + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + digitalWrite(PIN_TFT_RST, LOW); + digitalWrite(PIN_TFT_LEDA_CTL, LOW); + _isOn = false; + + if (_peripher_power) _peripher_power->release(); + } +} + +void ST7789LCDDisplay::clear() { + display.fillScreen(ST77XX_BLACK); +} + +void ST7789LCDDisplay::startFrame(Color bkg) { + display.fillScreen(ST77XX_BLACK); + display.setTextColor(ST77XX_WHITE); + display.setTextSize(1); // This one affects size of Please wait... message + display.cp437(true); // Use full 256 char 'Code Page 437' font +} + +void ST7789LCDDisplay::setTextSize(int sz) { + display.setTextSize(sz); +} + +void ST7789LCDDisplay::setColor(Color c) { + switch (c) { + case DisplayDriver::DARK : + _color = ST77XX_BLACK; + break; + case DisplayDriver::LIGHT : + _color = ST77XX_WHITE; + break; + case DisplayDriver::RED : + _color = ST77XX_RED; + break; + case DisplayDriver::GREEN : + _color = ST77XX_GREEN; + break; + case DisplayDriver::BLUE : + _color = ST77XX_BLUE; + break; + case DisplayDriver::YELLOW : + _color = ST77XX_YELLOW; + break; + case DisplayDriver::ORANGE : + _color = ST77XX_ORANGE; + break; + default: + _color = ST77XX_WHITE; + break; + } + display.setTextColor(_color); +} + +void ST7789LCDDisplay::setCursor(int x, int y) { + display.setCursor(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y); +} + +void ST7789LCDDisplay::print(const char* str) { + display.print(str); +} + +void ST7789LCDDisplay::fillRect(int x, int y, int w, int h) { + display.fillRect(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, w * DISPLAY_SCALE_X, h * DISPLAY_SCALE_Y, _color); +} + +void ST7789LCDDisplay::drawRect(int x, int y, int w, int h) { + display.drawRect(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, w * DISPLAY_SCALE_X, h * DISPLAY_SCALE_Y, _color); +} + +void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { + display.drawBitmap(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, bits, w, h, _color); +} + +uint16_t ST7789LCDDisplay::getTextWidth(const char* str) { + int16_t x1, y1; + uint16_t w, h; + display.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); + + return w / DISPLAY_SCALE_X; +} + +void ST7789LCDDisplay::endFrame() { + // display.display(); +} diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h new file mode 100644 index 00000000..a8077148 --- /dev/null +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -0,0 +1,60 @@ +#pragma once + +#include "DisplayDriver.h" +#include <Wire.h> +#include <SPI.h> +#include <Adafruit_GFX.h> +#include <Adafruit_ST7789.h> +#include <helpers/RefCountedDigitalPin.h> + +class ST7789LCDDisplay : public DisplayDriver { + #ifdef LILYGO_TDECK + SPIClass displaySPI; + #endif + Adafruit_ST7789 display; + bool _isOn; + uint16_t _color; + RefCountedDigitalPin* _peripher_power; + + bool i2c_probe(TwoWire& wire, uint8_t addr); +public: +#ifdef USE_PIN_TFT + ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_SDA, PIN_TFT_SCL, PIN_TFT_RST), + _peripher_power(peripher_power) + { + _isOn = false; + } +#elif LILYGO_TDECK + ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + displaySPI(HSPI), + display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), + _peripher_power(peripher_power) + { + _isOn = false; + } +#else + ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(&SPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), + _peripher_power(peripher_power) + { + _isOn = false; + } +#endif + bool begin(); + + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char* str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; + void endFrame() override; +}; diff --git a/variants/lilygo_tdeck/TDeckBoard.cpp b/variants/lilygo_tdeck/TDeckBoard.cpp new file mode 100644 index 00000000..ad1e435b --- /dev/null +++ b/variants/lilygo_tdeck/TDeckBoard.cpp @@ -0,0 +1,35 @@ +#include <Arduino.h> +#include "TDeckBoard.h" + +uint32_t deviceOnline = 0x00; + +void TDeckBoard::begin() { + + ESP32Board::begin(); + + // Enable peripheral power + pinMode(PIN_PERF_POWERON, OUTPUT); + digitalWrite(PIN_PERF_POWERON, HIGH); + + // Configure user button + pinMode(PIN_USER_BTN, INPUT); + + // Configure LoRa Pins + pinMode(P_LORA_MISO, INPUT_PULLUP); + // pinMode(P_LORA_DIO_1, INPUT_PULLUP); + + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, HIGH); // inverted pin for SX1276 - HIGH for off + #endif + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { + startup_reason = BD_STARTUP_RX_PACKET; // received a LoRa packet (while in deep sleep) + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } +} \ No newline at end of file diff --git a/variants/lilygo_tdeck/TDeckBoard.h b/variants/lilygo_tdeck/TDeckBoard.h new file mode 100644 index 00000000..7ed007af --- /dev/null +++ b/variants/lilygo_tdeck/TDeckBoard.h @@ -0,0 +1,68 @@ +#pragma once + +#include <Wire.h> +#include <Arduino.h> +#include "helpers/ESP32Board.h" +#include <driver/rtc_io.h> + +#define PIN_VBAT_READ 4 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (2.0f * 3.3f * 1000) + +class TDeckBoard : public ESP32Board { +public: + void begin(); + + #ifdef P_LORA_TX_LED + void onBeforeTransmit() override{ + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on - invert pin for SX1276 + } + + void onAfterTransmit() override{ + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off - invert pin for SX1276 + } + #endif + + void enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + uint16_t getBattMilliVolts() { + #if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + + raw = raw / BATTERY_SAMPLES; + return (ADC_MULTIPLIER * raw) / 4096; + #else + return 0; + #endif + } + + const char* getManufacturerName() const{ + return "LilyGo T-Deck"; + } +}; \ No newline at end of file diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini new file mode 100644 index 00000000..ade370f7 --- /dev/null +++ b/variants/lilygo_tdeck/platformio.ini @@ -0,0 +1,96 @@ +[LilyGo_TDeck] +extends = esp32_base +board = t-deck +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/lilygo_tdeck + -D LILYGO_TDECK + -D BOARD_HAS_PSRAM=1 + -D CORE_DEBUG_LEVEL=1 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D PIN_USER_BTN=0 ; Trackball button + -D PIN_PERF_POWERON=10 ; Peripheral power pin + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_DIO2_AS_RF_SWITCH=false + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8f + -D P_LORA_DIO_1=45 ; LORA IRQ pin + -D P_LORA_NSS=9 ; LORA SS pin + -D P_LORA_RESET=17 ; LORA RST pin + -D P_LORA_BUSY=13 ; LORA Busy pin + -D P_LORA_SCLK=40 ; LORA SCLK pin + -D P_LORA_MISO=38 ; LORA MISO pin + -D P_LORA_MOSI=41 ; LORA MOSI pin + -D DISPLAY_CLASS=ST7789LCDDisplay + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=-1 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=42 + -D PIN_TFT_CS=12 + -D PIN_TFT_DC=11 + -D PIN_TFT_SCL=40 + -D PIN_TFT_SDA=41 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/lilygo_tdeck> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:LilyGo_TDeck_companion_radio_usb] +extends = LilyGo_TDeck +build_flags = + ${LilyGo_TDeck.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${LilyGo_TDeck.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/ST7789LCDDisplay.cpp> +lib_deps = + ${LilyGo_TDeck.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_TDeck_companion_radio_ble] +extends = LilyGo_TDeck +build_flags = + ${LilyGo_TDeck.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${LilyGo_TDeck.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/ST7789LCDDisplay.cpp> +lib_deps = + ${LilyGo_TDeck.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_TDeck_repeater] +extends = LilyGo_TDeck +build_flags = + ${LilyGo_TDeck.build_flags} + -D ADVERT_NAME='"TDeck Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +build_src_filter = ${LilyGo_TDeck.build_src_filter} + +<../examples/simple_repeater> + +<helpers/ui/ST7789LCDDisplay.cpp> +lib_deps = + ${LilyGo_TDeck.lib_deps} + ${esp32_ota.lib_deps} \ No newline at end of file diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp new file mode 100644 index 00000000..1120b3ad --- /dev/null +++ b/variants/lilygo_tdeck/target.cpp @@ -0,0 +1,53 @@ +#include <Arduino.h> +#include "target.h" + +TDeckBoard board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} \ No newline at end of file diff --git a/variants/lilygo_tdeck/target.h b/variants/lilygo_tdeck/target.h new file mode 100644 index 00000000..c803bf2c --- /dev/null +++ b/variants/lilygo_tdeck/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <TDeckBoard.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/ST7789LCDDisplay.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern TDeckBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); \ No newline at end of file From 611d61b6c6dc0b5a810202f0b1992a14705e0e75 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Mon, 22 Sep 2025 19:10:01 +0200 Subject: [PATCH 086/546] tracker_l1: fix bme226 init in ESM to include all sensors --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- variants/wio-tracker-l1-eink/platformio.ini | 7 ++----- variants/wio-tracker-l1/platformio.ini | 5 ++--- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 6d681bb5..0b55abd6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -70,7 +70,7 @@ static Adafruit_INA260 INA260; #define TELEM_INA226_SHUNT_VALUE 0.100 #define TELEM_INA226_MAX_AMP 0.8 #include <INA226.h> -static INA226 INA226(TELEM_INA226_ADDRESS); +static INA226 INA226(TELEM_INA226_ADDRESS, TELEM_WIRE); #endif #if ENV_INCLUDE_MLX90614 diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index 62301354..a41e9e23 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -3,6 +3,7 @@ extends = nrf52_base board = seeed-wio-tracker-l1 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -I variants/wio-tracker-l1 @@ -22,8 +23,6 @@ build_flags = ${nrf52_base.build_flags} -D DISPLAY_ROTATION=1 -D DISABLE_DIAGNOSTIC_OUTPUT -D AUTO_OFF_MILLIS=0 - -D ENV_INCLUDE_GPS=1 - -D ENV_INCLUDE_BME280=1 -D GPS_BAUD_RATE=9600 -D ENV_PIN_SDA=PIN_WIRE1_SDA -D ENV_PIN_SCL=PIN_WIRE1_SCL @@ -33,12 +32,10 @@ build_src_filter = ${nrf52_base.build_src_filter} +<helpers/ui/GxEPDDisplay.cpp> +<helpers/sensors> lib_deps= ${nrf52_base.lib_deps} - adafruit/Adafruit SH110X @ ^2.1.13 + ${sensor_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 - stevemarple/MicroNMEA @ ^2.0.6 zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 - adafruit/Adafruit BME280 Library @ ^2.3.0 [env:WioTrackerL1Eink_companion_radio_ble] extends = WioTrackerL1Eink diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 63228995..35ca2135 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -3,6 +3,7 @@ extends = nrf52_base board = seeed-wio-tracker-l1 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -I variants/wio-tracker-l1 @@ -13,8 +14,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_OLED_RESET=-1 - -D ENV_INCLUDE_GPS=1 - -D ENV_INCLUDE_BME280=1 -D GPS_BAUD_RATE=9600 -D ENV_PIN_SDA=PIN_WIRE1_SDA -D ENV_PIN_SCL=PIN_WIRE1_SCL @@ -24,9 +23,9 @@ build_src_filter = ${nrf52_base.build_src_filter} +<helpers/ui/SH1106Display.cpp> +<helpers/sensors> lib_deps= ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} adafruit/Adafruit SH110X @ ^2.1.13 adafruit/Adafruit GFX Library @ ^1.12.1 - stevemarple/MicroNMEA @ ^2.0.6 [env:WioTrackerL1_Repeater] extends = WioTrackerL1 From adecd1e58d6fe13e3c01f908a088478bea830f7f Mon Sep 17 00:00:00 2001 From: silverphish-io <github@silverphish.io> Date: Mon, 22 Sep 2025 21:49:56 +0100 Subject: [PATCH 087/546] Updated some typos and spelling mistakes in FAQ --- docs/faq.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/faq.md b/docs/faq.md index 78e31aa6..5ed1d2a5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -75,7 +75,7 @@ author: https://github.com/LitBomb<!-- omit from toc --> - [7.3. Q: Is there a way to lower the chance of a failed OTA device firmware update (DFU)?](#73-q-is-there-a-way-to-lower-the-chance-of-a-failed-ota-device-firmware-update-dfu) - [7.4. Q: are the MeshCore logo and font available?](#74-q-are-the-meshcore-logo-and-font-available) - [7.5. Q: What is the format of a contact or channel QR code?](#75-q-what-is-the-format-of-a-contact-or-channel-qr-code) - - [7.6. Q: How do I connect to the comnpanion via WIFI, e.g. using a heltec v3?](#76-q-how-do-i-connect-to-the-comnpanion-via-wifi-eg-using-a-heltec-v3) + - [7.6. Q: How do I connect to the companion via WIFI, e.g. using a heltec v3?](#76-q-how-do-i-connect-to-the-comnpanion-via-wifi-eg-using-a-heltec-v3) ## 1. Introduction @@ -101,7 +101,7 @@ Anyone is able to build anything they like on top of MeshCore without paying any Main web site: [https://meshcore.co.uk/](https://meshcore.co.uk/) Firmware Flasher: https://flasher.meshcore.co.uk/ Phone Client Applications: https://meshcore.co.uk/apps.html - MeshCore Fimrware GitHub: https://github.com/ripplebiz/MeshCore + MeshCore Firmware GitHub: https://github.com/ripplebiz/MeshCore NOTE: Andy Kirby has a very useful [intro video](https://www.youtube.com/watch?v=t1qne8uJBAc) for beginners. @@ -159,7 +159,7 @@ The recommendation is to run repeater and room server on separate devices for th If you have two supported devices, and there are not many MeshCore users near you, flash both to BLE Companion firmware so you can use your devices to communicate with your near-by friends and family. -If you have two supported devices, and there are other MeshcCore users nearby, you can flash one of your devices with BLE Companion firmware and flash another supported device to repeater firmware. Place the repeater high above ground to extend your MeshCore network's reach. +If you have two supported devices, and there are other MeshCore users nearby, you can flash one of your devices with BLE Companion firmware and flash another supported device to repeater firmware. Place the repeater high above ground to extend your MeshCore network's reach. After you flashed the latest firmware onto your repeater device, keep the device connected to your computer via USB serial, use the console feature on the web flasher and set the frequency for your region or country, so your client can remote administer the repeater or room server over RF: @@ -391,7 +391,7 @@ In MeshCore, only repeaters and room server with `set repeat on` repeat. **A:** If you used to reach a node through a repeater and the repeater is no longer reachable, the client will send the message using the existing (but now broken) known path, the message will fail after 3 retries, and the app will reset the path and send the message as flood on the last retry by default. This can be turned off in settings. If the destination is reachable directly or through another repeater, the new path will be used going forward. Or you can set the path manually if you know a specific repeater to use to reach that destination. -In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to reestablish a path. +In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to re-establish a path. ### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic? @@ -463,7 +463,7 @@ Andy also has a video on how to build using VS Code: ### 5.10. Q: Are there other MeshCore related open source projects? -**A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript libary are open source under MIT license. +**A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript library are open source under MIT license. Web client: https://github.com/liamcottle/meshcore-web Javascript: https://github.com/liamcottle/meshcore.js @@ -471,7 +471,7 @@ Javascript: https://github.com/liamcottle/meshcore.js ### 5.11. Q: Does MeshCore support ATAK **A:** ATAK is not currently on MeshCore's roadmap. -Meshcore would not be best suited to ATAK because MeshCore: +Meshcore would not be best suited to ATAK because MeshCore: clients do not repeat and therefore you would need a network of repeaters in place will not have a stable path where all clients are constantly moving between repeaters @@ -569,7 +569,7 @@ Bindings to access your MeshCore companion radio nodes in python. https://github.com/fdlamotte/meshcore_py #### 5.14.4. meshcore-cli -CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Pyton MeshCore above. +CLI interface to MeshCore companion radio over BLE, TCP, or serial. Uses Python MeshCore above. https://github.com/fdlamotte/meshcore-cli #### 5.14.5. meshcore.js @@ -637,13 +637,13 @@ Allow the browser user on it: 1. Download nRF's DFU app from iOS App Store or Android's Play Store, you can find the app by searching for `nrf dfu`, the app's full name is `nRF Device Firmware Update` 2. On flasher.meshcore.co.uk, download the **ZIP** version of the firmware for your nRF device (e.g. RAK or Heltec T114 or Seeed Studio's Xiao) -3. From the MeshCore app, login remotely to the repeater you want to update with admin priviledge +3. From the MeshCore app, login remotely to the repeater you want to update with admin privilege 4. Go to the Command Line tab, type `start ota` and hit enter. 5. you should see `OK` to confirm the repeater device is now in OTA mode 6. Run the DFU app,tab `Settings` on the top right corner 7. Enable `Packets receipt notifications`, and change `Number of Packets` to 10 for RAK, 8 for T114. 8 also works for RAK. 9. Select the firmware zip file you downloaded -10. Select the device you want to update. If the device you want to updat is not on the list, try enabling`OTA` on the device again +10. Select the device you want to update. If the device you want to update is not on the list, try enabling`OTA` on the device again 11. If the device is not found, enable `Force Scanning` in the DFU app 12. Tab the `Upload` to begin OTA update 13. If it fails, try turning off and on Bluetooth on your phone. If that doesn't work, try rebooting your phone. @@ -654,7 +654,7 @@ Allow the browser user on it: **A:** For ESP32-based devices (e.g. Heltec V3): 1. On flasher.meshcore.co.uk, download the **non-merged** version of the firmware for your ESP32 device (e.g. `Heltec_v3_repeater-v1.6.2-4449fd3.bin`, no `"merged"` in the file name) -2. From the MeshCore app, login remotely to the repeater you want to update with admin priviledge +2. From the MeshCore app, login remotely to the repeater you want to update with admin privilege 4. Go to the Command Line tab, type `start ota` and hit enter. 5. you should see `OK` to confirm the repeater device is now in OTA mode 6. The command `start ota` on an ESP32-based device starts a wifi hotspot named `MeshCore OTA` @@ -694,7 +694,7 @@ where `&type` is: `room = 3` `sensor = 4` -### 7.6. Q: How do I connect to the comnpanion via WIFI, e.g. using a heltec v3? +### 7.6. Q: How do I connect to the companion via WIFI, e.g. using a heltec v3? **A:** WiFi firmware requires you to compile it yourself, as you need to set the wifi ssid and password. Edit WIFI_SSID and WIFI_PWD in `./variants/heltec_v3/platformio.ini` and then flash it to your device. From c1915a11338032e84641b632cddb891236156908 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Tue, 23 Sep 2025 11:12:07 +0200 Subject: [PATCH 088/546] ESM: delegate gps management to LocationProvider --- .../sensors/EnvironmentSensorManager.cpp | 60 ++++--------------- .../sensors/MicroNMEALocationProvider.h | 30 +++++++--- 2 files changed, 34 insertions(+), 56 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 8ab46726..faa04275 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -437,21 +437,7 @@ void EnvironmentSensorManager::initBasicGPS() { #endif // Try to detect if GPS is physically connected to determine if we should expose the setting - #ifdef PIN_GPS_EN - pinMode(PIN_GPS_EN, OUTPUT); - #ifdef PIN_GPS_EN_ACTIVE - digitalWrite(PIN_GPS_EN, PIN_GPS_EN_ACTIVE); // Power on GPS - #else - digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS - #endif - #endif - -#ifdef PIN_GPS_RESET - pinMode(PIN_GPS_RESET, OUTPUT); - digitalWrite(PIN_GPS_RESET, PIN_GPS_RESET_ACTIVE); // assert for 10ms - delay(10); - digitalWrite(PIN_GPS_RESET, !PIN_GPS_RESET_ACTIVE); -#endif + _location->begin(); #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("No GPS wake/reset pin found for this board. Continuing on..."); @@ -472,16 +458,12 @@ void EnvironmentSensorManager::initBasicGPS() { } else { MESH_DEBUG_PRINTLN("No GPS detected"); } - #ifdef PIN_GPS_EN - #ifdef PIN_GPS_EN_ACTIVE - digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); - #else - digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed - #endif - #endif + _location->stop(); gps_active = false; //Set GPS visibility off until setting is changed } +// gps code for rak might be moved to MicroNMEALoactionProvider +// or make a new location provider ... #ifdef RAK_WISBLOCK_GPS void EnvironmentSensorManager::rakGPSInit(){ @@ -561,27 +543,12 @@ void EnvironmentSensorManager::start_gps() { digitalWrite(gpsResetPin, HIGH); return; #endif - #ifdef PIN_GPS_EN - pinMode(PIN_GPS_EN, OUTPUT); - #ifdef PIN_GPS_EN_ACTIVE - digitalWrite(PIN_GPS_EN, PIN_GPS_EN_ACTIVE); - #else - digitalWrite(PIN_GPS_EN, HIGH); - #endif - #ifndef PIN_GPS_RESET - return; - #endif - #endif -#ifdef PIN_GPS_RESET - pinMode(PIN_GPS_RESET, OUTPUT); - digitalWrite(PIN_GPS_RESET, PIN_GPS_RESET_ACTIVE); // assert for 10ms - delay(10); - digitalWrite(PIN_GPS_RESET, !PIN_GPS_RESET_ACTIVE); - return; -#endif + _location->begin(); +#ifndef PIN_GPS_RESET MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged"); +#endif } void EnvironmentSensorManager::stop_gps() { @@ -591,17 +558,12 @@ void EnvironmentSensorManager::stop_gps() { digitalWrite(gpsResetPin, LOW); return; #endif - #ifdef PIN_GPS_EN - pinMode(PIN_GPS_EN, OUTPUT); - #ifdef PIN_GPS_EN_ACTIVE - digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE); - #else - digitalWrite(PIN_GPS_EN, LOW); - #endif - return; - #endif + _location->stop(); + + #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged"); + #endif } void EnvironmentSensorManager::loop() { diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 5a2c59d3..678baf4d 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -5,15 +5,31 @@ #include <RTClib.h> #ifndef GPS_EN -#define GPS_EN (-1) + #ifdef PIN_GPS_EN + #define GPS_EN PIN_GPS_EN + #else + #define GPS_EN (-1) + #endif +#endif + +#ifndef PIN_GPS_EN_ACTIVE + #define PIN_GPS_EN_ACTIVE HIGH #endif #ifndef GPS_RESET -#define GPS_RESET (-1) + #ifdef PIN_GPS_RESET + #define GPS_RESET PIN_GPS_RESET + #else + #define GPS_RESET (-1) + #endif #endif #ifndef GPS_RESET_FORCE -#define GPS_RESET_FORCE LOW + #ifdef PIN_GPS_RESET_ACTIVE + #define GPS_RESET_FORCE PIN_GPS_RESET_ACTIVE + #else + #define GPS_RESET_FORCE LOW + #endif #endif class MicroNMEALocationProvider : public LocationProvider { @@ -40,12 +56,12 @@ public : } void begin() override { + if (_pin_en != -1) { + digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE); + } if (_pin_reset != -1) { digitalWrite(_pin_reset, !GPS_RESET_FORCE); } - if (_pin_en != -1) { - digitalWrite(_pin_en, HIGH); - } } void reset() override { @@ -58,7 +74,7 @@ public : void stop() override { if (_pin_en != -1) { - digitalWrite(_pin_en, LOW); + digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); } } From 76aa7cf488e8d4195d1ec88b4497da0e3fdc79d0 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Tue, 23 Sep 2025 10:39:43 +0200 Subject: [PATCH 089/546] ui_task: initial gps page --- examples/companion_radio/ui-new/UITask.cpp | 62 +++++++++++++++++++ examples/companion_radio/ui-new/UITask.h | 1 + src/helpers/SensorManager.h | 2 + .../sensors/EnvironmentSensorManager.h | 1 + src/helpers/sensors/LocationProvider.h | 8 +-- variants/lilygo_techo/platformio.ini | 2 + variants/t1000-e/target.h | 1 + variants/wio-tracker-l1-eink/platformio.ini | 1 + 8 files changed, 74 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 248b9bd5..c48c6972 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -75,6 +75,9 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, +#if UI_GPS_PAGE == 1 + GPS, +#endif #if UI_SENSORS_PAGE == 1 SENSORS, #endif @@ -250,6 +253,47 @@ public: 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); +#if UI_GPS_PAGE == 1 + } else if (_page == HomePage::GPS) { + LocationProvider* nmea = sensors.getLocationProvider(); + int y = 18; + display.setCursor(0, y); + display.print(_task->getGPSState() ? "gps on" : "gps off"); + if (nmea == NULL) { + y = y + 12; + display.setCursor(0, y); + display.print("Can't access GPS"); + } else { + char buf[50]; + strcpy(buf, nmea->isValid()?"fix":"no fix"); + display.setCursor( + display.width()-display.getTextWidth(buf)-1, y); + display.print(buf); + y = y + 12; + display.setCursor(0,y); + display.print("sat"); + sprintf(buf, "%d", nmea->satellitesCount()); + display.setCursor( + display.width()-display.getTextWidth(buf)-1, y); + display.print(buf); + y = y + 12; + display.setCursor(0,y); + display.print("pos"); + sprintf(buf, "%.4f %.4f", + nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); + display.setCursor( + display.width()-display.getTextWidth(buf)-1, y); + display.print(buf); + y = y + 12; + display.setCursor(0,y); + display.print("alt"); + sprintf(buf, "%.2f", nmea->getAltitude()/1000.); + display.setCursor( + display.width()-display.getTextWidth(buf)-1, y); + display.print(buf); + y = y + 12; + } +#endif #if UI_SENSORS_PAGE == 1 } else if (_page == HomePage::SENSORS) { int y = 18; @@ -364,6 +408,12 @@ public: } return true; } +#if UI_GPS_PAGE == 1 + if (c == KEY_ENTER && _page == HomePage::GPS) { + _task->toggleGPS(); + return true; + } +#endif #if UI_SENSORS_PAGE == 1 if (c == KEY_ENTER && _page == HomePage::SENSORS) { _task->toggleGPS(); @@ -773,6 +823,18 @@ char UITask::handleTripleClick(char c) { return c; } +bool UITask::getGPSState() { + if (_sensors != NULL) { + int num = _sensors->getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(_sensors->getSettingName(i), "gps") == 0) { + return !strcmp(_sensors->getSettingValue(i), "1"); + } + } + } + return false; +} + void UITask::toggleGPS() { if (_sensors != NULL) { // toggle GPS on/off diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 5a087eeb..c24d33a4 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -71,6 +71,7 @@ public: bool isButtonPressed() const; void toggleBuzzer(); + bool getGPSState(); void toggleGPS(); diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 0e4bc27d..1ace6220 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -1,6 +1,7 @@ #pragma once #include <CayenneLPP.h> +#include "sensors/LocationProvider.h" #define TELEM_PERM_BASE 0x01 // 'base' permission includes battery #define TELEM_PERM_LOCATION 0x02 @@ -21,4 +22,5 @@ public: virtual const char* getSettingName(int i) const { return NULL; } virtual const char* getSettingValue(int i) const { return NULL; } virtual bool setSettingValue(const char* name, const char* value) { return false; } + virtual LocationProvider* getLocationProvider() { return NULL; } }; diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 3302d6f6..09c6cae4 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -39,6 +39,7 @@ protected: public: #if ENV_INCLUDE_GPS EnvironmentSensorManager(LocationProvider &location): _location(&location){}; + LocationProvider* getLocationProvider() { return _location; } #else EnvironmentSensorManager(){}; #endif diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index f51eea28..f93dec48 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -17,8 +17,8 @@ public: virtual bool isValid() = 0; virtual long getTimestamp() = 0; virtual void sendSentence(const char * sentence); - virtual void reset(); - virtual void begin(); - virtual void stop(); - virtual void loop(); + virtual void reset() = 0; + virtual void begin() = 0; + virtual void stop() = 0; + virtual void loop() = 0; }; diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index e814ea54..b5e94b2d 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -29,6 +29,7 @@ build_flags = ${nrf52_base.build_flags} -D ENV_INCLUDE_BME280=1 -D GPS_BAUD_RATE=9600 -D PIN_GPS_EN=GPS_EN + -D PIN_GPS_RESET_ACTIVE=LOW -D TELEM_BME280_ADDRESS=0x77 -D DISPLAY_CLASS=GxEPDDisplay -D BACKLIGHT_BTN=PIN_BUTTON2 @@ -92,6 +93,7 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 -D UI_RECENT_LIST_SIZE=9 -D UI_SENSORS_PAGE=1 + -D UI_GPS_PAGE=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 6ac0d3a6..27351b94 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -28,6 +28,7 @@ public: const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; bool setSettingValue(const char* name, const char* value) override; + LocationProvider* getLocationProvider() { return _nmea; } }; #ifdef DISPLAY_CLASS diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index a41e9e23..db22c01b 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -55,6 +55,7 @@ build_flags = ${WioTrackerL1Eink.build_flags} ; -D MESH_DEBUG=1 -D UI_RECENT_LIST_SIZE=6 -D UI_SENSORS_PAGE=1 + -D UI_GPS_PAGE=1 build_src_filter = ${WioTrackerL1Eink.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/MomentaryButton.cpp> From db40a9cea60b66f341fbd9f0b9b027d13f589d91 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Tue, 23 Sep 2025 19:38:45 +0200 Subject: [PATCH 090/546] import missing eInk display --- src/helpers/ui/GxEPDDisplay.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index 4fe6c0f2..200abd91 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -13,6 +13,7 @@ #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeSans18pt7b.h> +#include <epd/GxEPD2_122_T61.h> // 1.22" b/w #include <epd/GxEPD2_150_BN.h> // 1.54" b/w #include <epd/GxEPD2_213_B74.h> // 2.13" b/w #include <CRC32.h> @@ -44,9 +45,8 @@ class GxEPDDisplay : public DisplayDriver { int last_display_crc_value = 0; public: - // there is a margin in y... #if defined(EINK_DISPLAY_MODEL) - GxEPDDisplay() : DisplayDriver(128, 128), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} + GxEPDDisplay() : DisplayDriver(EINK_DISPLAY_MODEL::WIDTH, EINK_DISPLAY_MODEL::HEIGHT), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} #else GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {} #endif From b11f08422ef6177d662c13b8ebe3bc6b57edb080 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Tue, 23 Sep 2025 19:39:11 +0200 Subject: [PATCH 091/546] add `T-Echo-Lite` Device Variant --- variants/lilygo_techo_lite/TechoBoard.cpp | 90 +++++++++++++ variants/lilygo_techo_lite/TechoBoard.h | 55 ++++++++ variants/lilygo_techo_lite/platformio.ini | 107 +++++++++++++++ variants/lilygo_techo_lite/target.cpp | 57 ++++++++ variants/lilygo_techo_lite/target.h | 31 +++++ variants/lilygo_techo_lite/variant.cpp | 39 ++++++ variants/lilygo_techo_lite/variant.h | 150 ++++++++++++++++++++++ 7 files changed, 529 insertions(+) create mode 100644 variants/lilygo_techo_lite/TechoBoard.cpp create mode 100644 variants/lilygo_techo_lite/TechoBoard.h create mode 100644 variants/lilygo_techo_lite/platformio.ini create mode 100644 variants/lilygo_techo_lite/target.cpp create mode 100644 variants/lilygo_techo_lite/target.h create mode 100644 variants/lilygo_techo_lite/variant.cpp create mode 100644 variants/lilygo_techo_lite/variant.h diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp new file mode 100644 index 00000000..dee14688 --- /dev/null +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -0,0 +1,90 @@ +#include <Arduino.h> +#include "TechoBoard.h" + +#ifdef LILYGO_TECHO + +#include <bluefruit.h> +#include <Wire.h> + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void TechoBoard::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + Wire.begin(); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +uint16_t TechoBoard::getBattMilliVolts() { + int adcvalue = 0; + + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + delay(10); + + // ADC range is 0..3000mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); +} + +bool TechoBoard::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("TECHO_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h new file mode 100644 index 00000000..4792153a --- /dev/null +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -0,0 +1,55 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +// built-ins +#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 + +#define VBAT_DIVIDER (0.5F) // 150K + 150K voltage divider on VBAT +#define VBAT_DIVIDER_COMP (2.0F) // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ (4) +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class TechoBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + + void begin(); + uint16_t getBattMilliVolts() override; + bool startOTAUpdate(const char* id, char reply[]) override; + + uint8_t getStartupReason() const override { + return startup_reason; + } + + const char* getManufacturerName() const override { + return "LilyGo T-Echo"; + } + + void powerOff() override { + #ifdef LED_RED + digitalWrite(LED_RED, LOW); + #endif + #ifdef LED_GREEN + digitalWrite(LED_GREEN, LOW); + #endif + #ifdef LED_BLUE + digitalWrite(LED_BLUE, LOW); + #endif + #ifdef DISP_BACKLIGHT + digitalWrite(DISP_BACKLIGHT, LOW); + #endif + #ifdef PIN_PWR_EN + digitalWrite(PIN_PWR_EN, LOW); + #endif + sd_power_system_off(); + } + + void reboot() override { + NVIC_SystemReset(); + } +}; diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini new file mode 100644 index 00000000..1ac14585 --- /dev/null +++ b/variants/lilygo_techo_lite/platformio.ini @@ -0,0 +1,107 @@ +[LilyGo_T-Echo-Lite] +extends = nrf52_base +board = t-echo +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I variants/lilygo_techo_lite + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -D LILYGO_TECHO + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_DIO_1=40 + -D P_LORA_NSS=11 + -D P_LORA_RESET=7 + -D P_LORA_BUSY=14 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=17 + -D P_LORA_MOSI=15 + -D SX126X_POWER_EN=30 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D P_LORA_TX_LED=LED_GREEN + -D DISABLE_DIAGNOSTIC_OUTPUT + -D ENV_INCLUDE_GPS=1 + ; -D ENV_INCLUDE_BME280=1 + -D GPS_BAUD_RATE=9600 + -D PIN_GPS_EN=GPS_EN + ; -D TELEM_BME280_ADDRESS=0x77 + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_122_T61 + -D EINK_SCALE_X=1.0f + -D EINK_SCALE_Y=2.0f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D DISPLAY_ROTATION=4 + -D AUTO_OFF_MILLIS=0 +build_src_filter = ${nrf52_base.build_src_filter} + +<helpers/*.cpp> + +<TechoBoard.cpp> + +<helpers/sensors/EnvironmentSensorManager.cpp> + +<helpers/ui/GxEPDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../variants/lilygo_techo_lite> +lib_deps = + ${nrf52_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit BME280 Library @ ^2.3.0 + zinggjm/GxEPD2 @ 1.6.2 + bakercp/CRC32 @ ^2.0.0 +debug_tool = jlink +upload_protocol = nrfutil + +[env:LilyGo_T-Echo-Lite_repeater] +extends = LilyGo_T-Echo-Lite +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/simple_repeater> +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -D ADVERT_NAME='"T-Echo-Lite Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +[env:LilyGo_T-Echo-Lite_room_server] +extends = LilyGo_T-Echo-Lite +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -D ADVERT_NAME='"T-Echo-Lite Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +[env:LilyGo_T-Echo-Lite_companion_radio_ble] +extends = LilyGo_T-Echo-Lite +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + ; -D QSPIFLASH=1 + -D BLE_PIN_CODE=123456 + ; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T-Echo-Lite.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp new file mode 100644 index 00000000..62b1f6c1 --- /dev/null +++ b/variants/lilygo_techo_lite/target.cpp @@ -0,0 +1,57 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/MicroNMEALocationProvider.h> + +TechoBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + + // pinMode(SX126X_RF_VC1, OUTPUT); + // pinMode(SX126X_RF_VC2, OUTPUT); + // set_SX126X_RF_Transmitter_Switch(false); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); + // set_SX126X_RF_Transmitter_Switch(true); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/lilygo_techo_lite/target.h b/variants/lilygo_techo_lite/target.h new file mode 100644 index 00000000..2b6ed45f --- /dev/null +++ b/variants/lilygo_techo_lite/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <TechoBoard.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#include <helpers/sensors/LocationProvider.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/GxEPDDisplay.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern TechoBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_techo_lite/variant.cpp b/variants/lilygo_techo_lite/variant.cpp new file mode 100644 index 00000000..3cd82d70 --- /dev/null +++ b/variants/lilygo_techo_lite/variant.cpp @@ -0,0 +1,39 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const int MISO = PIN_SPI1_MISO; +const int MOSI = PIN_SPI1_MOSI; +const int SCK = PIN_SPI1_SCK; + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(PIN_BUTTON1, INPUT_PULLUP); + pinMode(PIN_BUTTON2, INPUT_PULLUP); + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, HIGH); + + // pinMode(PIN_TXCO, OUTPUT); + // digitalWrite(PIN_TXCO, HIGH); + + pinMode(DISP_POWER, OUTPUT); + digitalWrite(DISP_POWER, LOW); + + // shutdown gps + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, LOW); +} diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h new file mode 100644 index 00000000..c35ce0ca --- /dev/null +++ b/variants/lilygo_techo_lite/variant.h @@ -0,0 +1,150 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#define _PINNUM(port, pin) ((port) * 32 + (pin)) + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +#define WIRE_INTERFACES_COUNT (1) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_PWR_EN (30) // RT9080_EN + +#define BATTERY_PIN (0 + 2) +#define ADC_MULTIPLIER (4.90F) + +#define ADC_RESOLUTION (14) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define PIN_WIRE_SDA (4) // (SDA) +#define PIN_WIRE_SCL (2) // (SCL) + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (17) // (MISO) +#define PIN_SPI_MOSI (15) // (MOSI) +#define PIN_SPI_SCK (13) // (SCK) +#define PIN_SPI_NSS (-1) + +//////////////////////////////////////////////////////////////////////////////// +// QSPI FLASH + +#define PIN_QSPI_SCK (4) +#define PIN_QSPI_CS (12) +#define PIN_QSPI_IO0 (6) +#define PIN_QSPI_IO1 (8) +#define PIN_QSPI_IO2 (32 + 9) +#define PIN_QSPI_IO3 (26) + +#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_RED (32 + 14) // LED_3 +#define LED_BLUE (32 + 5) // LED_2 +#define LED_GREEN (32 + 7) // LED_1 + +//#define PIN_STATUS_LED LED_BLUE +#define LED_BUILTIN (-1) +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON LOW + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (24) // BOOT +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +#define PIN_BUTTON2 (18) +#define BUTTON_PIN2 PIN_BUTTON2 + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// Lora + +#define USE_SX1262 +#define LORA_CS (11) +#define SX126X_POWER_EN (30) +#define SX126X_DIO1 (32 + 8) +#define SX126X_BUSY (14) +#define SX126X_RESET (7) +#define SX126X_RF_VC1 (27) +#define SX126X_RF_VC2 (33) + +//////////////////////////////////////////////////////////////////////////////// +// SPI1 + +#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MOSI (20) +#define PIN_SPI1_SCK (19) + +// GxEPD2 needs that for a panel that is not even used ! +extern const int MISO; +extern const int MOSI; +extern const int SCK; + +//////////////////////////////////////////////////////////////////////////////// +// Display + +// #define DISP_MISO (-1) +#define DISP_MOSI (20) +#define DISP_SCLK (19) +#define DISP_CS (22) +#define DISP_DC (21) +#define DISP_RST (28) +#define DISP_BUSY (3) +#define DISP_POWER (32 + 12) +// #define DISP_BACKLIGHT (-1) + +#define PIN_DISPLAY_CS DISP_CS +#define PIN_DISPLAY_DC DISP_DC +#define PIN_DISPLAY_RST DISP_RST +#define PIN_DISPLAY_BUSY DISP_BUSY + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define PIN_GPS_RX (32 + 13) // RXD +#define PIN_GPS_TX (32 + 15) // TXD +#define GPS_EN (32 + 11) // POWER_RT9080_EN +#define PIN_GPS_STANDBY (32 + 10) +#define PIN_GPS_PPS (29) // 1PPS From e2fa70d6f3a9af49f068aeeea1db17e717f89d46 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Tue, 23 Sep 2025 21:57:35 +0200 Subject: [PATCH 092/546] chore: refactor to use GxEPD2 fork --- src/helpers/ui/GxEPDDisplay.h | 5 ----- variants/lilygo_techo_lite/platformio.ini | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index 200abd91..bdfbef9f 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -13,16 +13,11 @@ #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeSans18pt7b.h> -#include <epd/GxEPD2_122_T61.h> // 1.22" b/w #include <epd/GxEPD2_150_BN.h> // 1.54" b/w -#include <epd/GxEPD2_213_B74.h> // 2.13" b/w #include <CRC32.h> #include "DisplayDriver.h" -//GxEPD2_BW<GxEPD2_150_BN, 200> display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)); // DEPG0150BN 200x200, SSD1681, TTGO T5 V2.4.1 - - class GxEPDDisplay : public DisplayDriver { #if defined(EINK_DISPLAY_MODEL) diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 1ac14585..1bbbcde8 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -47,7 +47,7 @@ lib_deps = ${nrf52_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit BME280 Library @ ^2.3.0 - zinggjm/GxEPD2 @ 1.6.2 + https://github.com/SoulOfNoob/GxEPD2.git bakercp/CRC32 @ ^2.0.0 debug_tool = jlink upload_protocol = nrfutil From 955b7321e81851787744ad388cf24ab121db9c37 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Tue, 23 Sep 2025 22:10:27 +0200 Subject: [PATCH 093/546] chore: cleanup --- variants/lilygo_techo_lite/target.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp index 62b1f6c1..6979e347 100644 --- a/variants/lilygo_techo_lite/target.cpp +++ b/variants/lilygo_techo_lite/target.cpp @@ -26,10 +26,6 @@ EnvironmentSensorManager sensors = EnvironmentSensorManager(); bool radio_init() { rtc_clock.begin(Wire); - - // pinMode(SX126X_RF_VC1, OUTPUT); - // pinMode(SX126X_RF_VC2, OUTPUT); - // set_SX126X_RF_Transmitter_Switch(false); return radio.std_init(&SPI); } @@ -47,7 +43,6 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { void radio_set_tx_power(uint8_t dbm) { radio.setOutputPower(dbm); - // set_SX126X_RF_Transmitter_Switch(true); } mesh::LocalIdentity radio_new_identity() { From f85db18242f74f110ccb7efa0eaa6349119abf86 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Tue, 23 Sep 2025 22:18:04 +0200 Subject: [PATCH 094/546] refactor: use macro from ttgo repo for readability --- variants/lilygo_techo_lite/variant.h | 80 ++++++++++++++-------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index c35ce0ca..aa4568ef 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -21,9 +21,9 @@ //////////////////////////////////////////////////////////////////////////////// // Power -#define PIN_PWR_EN (30) // RT9080_EN +#define PIN_PWR_EN _PINNUM(0, 30) // RT9080_EN -#define BATTERY_PIN (0 + 2) +#define BATTERY_PIN _PINNUM(0, 2) #define ADC_MULTIPLIER (4.90F) #define ADC_RESOLUTION (14) @@ -47,28 +47,28 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (4) // (SDA) -#define PIN_WIRE_SCL (2) // (SCL) +#define PIN_WIRE_SDA _PINNUM(0, 4) // (SDA) +#define PIN_WIRE_SCL _PINNUM(0, 2) // (SCL) //////////////////////////////////////////////////////////////////////////////// // SPI pin definition -#define SPI_INTERFACES_COUNT (2) +#define SPI_INTERFACES_COUNT _PINNUM(0, 2) -#define PIN_SPI_MISO (17) // (MISO) -#define PIN_SPI_MOSI (15) // (MOSI) -#define PIN_SPI_SCK (13) // (SCK) +#define PIN_SPI_MISO _PINNUM(0, 17) // (MISO) +#define PIN_SPI_MOSI _PINNUM(0, 15) // (MOSI) +#define PIN_SPI_SCK _PINNUM(0, 13) // (SCK) #define PIN_SPI_NSS (-1) //////////////////////////////////////////////////////////////////////////////// // QSPI FLASH -#define PIN_QSPI_SCK (4) -#define PIN_QSPI_CS (12) -#define PIN_QSPI_IO0 (6) -#define PIN_QSPI_IO1 (8) -#define PIN_QSPI_IO2 (32 + 9) -#define PIN_QSPI_IO3 (26) +#define PIN_QSPI_SCK _PINNUM(0, 4) +#define PIN_QSPI_CS _PINNUM(0, 12) +#define PIN_QSPI_IO0 _PINNUM(0, 6) +#define PIN_QSPI_IO1 _PINNUM(0, 8) +#define PIN_QSPI_IO2 _PINNUM(1, 9) +#define PIN_QSPI_IO3 _PINNUM(0, 26) #define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR #define EXTERNAL_FLASH_USE_QSPI @@ -76,9 +76,9 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin LEDs -#define LED_RED (32 + 14) // LED_3 -#define LED_BLUE (32 + 5) // LED_2 -#define LED_GREEN (32 + 7) // LED_1 +#define LED_RED _PINNUM(1, 14) // LED_3 +#define LED_BLUE _PINNUM(1, 5) // LED_2 +#define LED_GREEN _PINNUM(1, 7) // LED_1 //#define PIN_STATUS_LED LED_BLUE #define LED_BUILTIN (-1) @@ -88,11 +88,11 @@ //////////////////////////////////////////////////////////////////////////////// // Builtin buttons -#define PIN_BUTTON1 (24) // BOOT +#define PIN_BUTTON1 _PINNUM(0, 24) // BOOT #define BUTTON_PIN PIN_BUTTON1 #define PIN_USER_BTN BUTTON_PIN -#define PIN_BUTTON2 (18) +#define PIN_BUTTON2 _PINNUM(0, 18) #define BUTTON_PIN2 PIN_BUTTON2 #define EXTERNAL_FLASH_DEVICES MX25R1635F @@ -102,20 +102,20 @@ // Lora #define USE_SX1262 -#define LORA_CS (11) -#define SX126X_POWER_EN (30) -#define SX126X_DIO1 (32 + 8) -#define SX126X_BUSY (14) -#define SX126X_RESET (7) -#define SX126X_RF_VC1 (27) -#define SX126X_RF_VC2 (33) +#define LORA_CS _PINNUM(0, 11) +#define SX126X_POWER_EN _PINNUM(0, 30) +#define SX126X_DIO1 _PINNUM(1, 8) +#define SX126X_BUSY _PINNUM(0, 14) +#define SX126X_RESET _PINNUM(0, 7) +#define SX126X_RF_VC1 _PINNUM(0, 27) +#define SX126X_RF_VC2 _PINNUM(0, 33) //////////////////////////////////////////////////////////////////////////////// // SPI1 #define PIN_SPI1_MISO (-1) -#define PIN_SPI1_MOSI (20) -#define PIN_SPI1_SCK (19) +#define PIN_SPI1_MOSI _PINNUM(0, 20) +#define PIN_SPI1_SCK _PINNUM(0, 19) // GxEPD2 needs that for a panel that is not even used ! extern const int MISO; @@ -126,13 +126,13 @@ extern const int SCK; // Display // #define DISP_MISO (-1) -#define DISP_MOSI (20) -#define DISP_SCLK (19) -#define DISP_CS (22) -#define DISP_DC (21) -#define DISP_RST (28) -#define DISP_BUSY (3) -#define DISP_POWER (32 + 12) +#define DISP_MOSI _PINNUM(0, 20) +#define DISP_SCLK _PINNUM(0, 19) +#define DISP_CS _PINNUM(0, 22) +#define DISP_DC _PINNUM(0, 21) +#define DISP_RST _PINNUM(0, 28) +#define DISP_BUSY _PINNUM(0, 3) +#define DISP_POWER _PINNUM(1, 12) // #define DISP_BACKLIGHT (-1) #define PIN_DISPLAY_CS DISP_CS @@ -143,8 +143,8 @@ extern const int SCK; //////////////////////////////////////////////////////////////////////////////// // GPS -#define PIN_GPS_RX (32 + 13) // RXD -#define PIN_GPS_TX (32 + 15) // TXD -#define GPS_EN (32 + 11) // POWER_RT9080_EN -#define PIN_GPS_STANDBY (32 + 10) -#define PIN_GPS_PPS (29) // 1PPS +#define PIN_GPS_RX _PINNUM(1, 13) // RXD +#define PIN_GPS_TX _PINNUM(1, 15) // TXD +#define GPS_EN _PINNUM(1, 11) // POWER_RT9080_EN +#define PIN_GPS_STANDBY _PINNUM(1, 10) +#define PIN_GPS_PPS _PINNUM(0, 29) // 1PPS From 20b0fd1dc907209b9be7ecb6d6789dd390b7a3b0 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Tue, 23 Sep 2025 22:28:54 +0200 Subject: [PATCH 095/546] refactor: readability --- variants/lilygo_techo_lite/platformio.ini | 9 --------- variants/lilygo_techo_lite/variant.h | 14 +++++++++++--- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 1bbbcde8..52358b80 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -11,23 +11,14 @@ build_flags = ${nrf52_base.build_flags} -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 - -D P_LORA_DIO_1=40 - -D P_LORA_NSS=11 - -D P_LORA_RESET=7 - -D P_LORA_BUSY=14 - -D P_LORA_SCLK=13 - -D P_LORA_MISO=17 - -D P_LORA_MOSI=15 -D SX126X_POWER_EN=30 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D P_LORA_TX_LED=LED_GREEN -D DISABLE_DIAGNOSTIC_OUTPUT -D ENV_INCLUDE_GPS=1 - ; -D ENV_INCLUDE_BME280=1 -D GPS_BAUD_RATE=9600 -D PIN_GPS_EN=GPS_EN - ; -D TELEM_BME280_ADDRESS=0x77 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_122_T61 -D EINK_SCALE_X=1.0f diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index aa4568ef..16e0b5cb 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -110,10 +110,18 @@ #define SX126X_RF_VC1 _PINNUM(0, 27) #define SX126X_RF_VC2 _PINNUM(0, 33) +#define P_LORA_DIO_1 SX126X_DIO1 +#define P_LORA_NSS LORA_CS +#define P_LORA_RESET SX126X_RESET +#define P_LORA_BUSY SX126X_BUSY +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + //////////////////////////////////////////////////////////////////////////////// // SPI1 -#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MISO (-1) // Not used for Display #define PIN_SPI1_MOSI _PINNUM(0, 20) #define PIN_SPI1_SCK _PINNUM(0, 19) @@ -125,7 +133,7 @@ extern const int SCK; //////////////////////////////////////////////////////////////////////////////// // Display -// #define DISP_MISO (-1) +// #define DISP_MISO (-1) // Not used for Display #define DISP_MOSI _PINNUM(0, 20) #define DISP_SCLK _PINNUM(0, 19) #define DISP_CS _PINNUM(0, 22) @@ -133,7 +141,7 @@ extern const int SCK; #define DISP_RST _PINNUM(0, 28) #define DISP_BUSY _PINNUM(0, 3) #define DISP_POWER _PINNUM(1, 12) -// #define DISP_BACKLIGHT (-1) +// #define DISP_BACKLIGHT (-1) // Display has no backlight #define PIN_DISPLAY_CS DISP_CS #define PIN_DISPLAY_DC DISP_DC From f2cff53b0eb57a267e5f519f0fb08e03bea86a3c Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Wed, 24 Sep 2025 09:04:16 +0200 Subject: [PATCH 096/546] fixed meshadventurer failing build --- variants/meshadventurer/platformio.ini | 3 ++- variants/meshadventurer/target.cpp | 1 + variants/meshadventurer/target.h | 3 +++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index be3b4943..270e0314 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -8,7 +8,7 @@ build_flags = -D MESHADVENTURER -D P_LORA_TX_LED=2 -D PIN_VBAT_READ=35 - -D PIN_USER_BTN_ANA=39 + -D PIN_USER_BTN=39 -D P_LORA_DIO_1=33 -D P_LORA_NSS=18 -D P_LORA_RESET=23 @@ -28,6 +28,7 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display build_src_filter = ${esp32_base.build_src_filter} +<../variants/meshadventurer> + +<helpers/ui/MomentaryButton.cpp> lib_deps = ${esp32_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index cabcee58..0e3b03f2 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -16,6 +16,7 @@ MASensorManager sensors = MASensorManager(nmea); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { diff --git a/variants/meshadventurer/target.h b/variants/meshadventurer/target.h index 6aeaf079..31bc4066 100644 --- a/variants/meshadventurer/target.h +++ b/variants/meshadventurer/target.h @@ -11,6 +11,7 @@ #include <helpers/sensors/LocationProvider.h> #ifdef DISPLAY_CLASS #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> #endif class MASensorManager : public SensorManager { @@ -37,8 +38,10 @@ extern MASensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; #endif + bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); From 128119fe4038caf0bcdf47a911bb15de4d1cca20 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Wed, 24 Sep 2025 14:45:40 +0200 Subject: [PATCH 097/546] refactor: remove redundant import statement --- src/helpers/ui/GxEPDDisplay.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index bdfbef9f..ff35b60d 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -12,8 +12,6 @@ #include <Fonts/FreeSans9pt7b.h> #include <Fonts/FreeSansBold12pt7b.h> #include <Fonts/FreeSans18pt7b.h> - -#include <epd/GxEPD2_150_BN.h> // 1.54" b/w #include <CRC32.h> #include "DisplayDriver.h" From 088b8fd98cf0387f373fed9972fcee5c1f5e19b1 Mon Sep 17 00:00:00 2001 From: Jan Ryklikas <jan.ryklikas@tqgg.de> Date: Wed, 24 Sep 2025 15:10:51 +0200 Subject: [PATCH 098/546] fix: revert to orignal default scaling and fix it in variant config --- src/helpers/ui/GxEPDDisplay.h | 2 +- variants/lilygo_techo_lite/platformio.ini | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/ui/GxEPDDisplay.h b/src/helpers/ui/GxEPDDisplay.h index ff35b60d..1a04cc24 100644 --- a/src/helpers/ui/GxEPDDisplay.h +++ b/src/helpers/ui/GxEPDDisplay.h @@ -39,7 +39,7 @@ class GxEPDDisplay : public DisplayDriver { public: #if defined(EINK_DISPLAY_MODEL) - GxEPDDisplay() : DisplayDriver(EINK_DISPLAY_MODEL::WIDTH, EINK_DISPLAY_MODEL::HEIGHT), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} + GxEPDDisplay() : DisplayDriver(128, 128), display(EINK_DISPLAY_MODEL(PIN_DISPLAY_CS, PIN_DISPLAY_DC, PIN_DISPLAY_RST, PIN_DISPLAY_BUSY)) {} #else GxEPDDisplay() : DisplayDriver(128, 128), display(GxEPD2_150_BN(DISP_CS, DISP_DC, DISP_RST, DISP_BUSY)) {} #endif diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 52358b80..d3748fd1 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -21,7 +21,7 @@ build_flags = ${nrf52_base.build_flags} -D PIN_GPS_EN=GPS_EN -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_122_T61 - -D EINK_SCALE_X=1.0f + -D EINK_SCALE_X=1.5f -D EINK_SCALE_Y=2.0f -D EINK_X_OFFSET=0 -D EINK_Y_OFFSET=10 From 1d45c7ec66b5f52d9b4037b530f7c99ffd687d22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Wed, 24 Sep 2025 16:30:00 +0100 Subject: [PATCH 099/546] Add bridge management CLI --- examples/simple_repeater/MyMesh.cpp | 11 ++- examples/simple_repeater/MyMesh.h | 37 ++++--- src/helpers/AbstractBridge.h | 16 ++- src/helpers/CommonCLI.cpp | 143 +++++++++++++++++---------- src/helpers/CommonCLI.h | 57 ++++++----- src/helpers/bridges/BridgeBase.cpp | 11 +++ src/helpers/bridges/BridgeBase.h | 10 ++ src/helpers/bridges/ESPNowBridge.cpp | 53 ++++++++-- src/helpers/bridges/ESPNowBridge.h | 30 ++++++ src/helpers/bridges/RS232Bridge.cpp | 103 +++++++++++-------- src/helpers/bridges/RS232Bridge.h | 6 ++ 11 files changed, 340 insertions(+), 137 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4265e1cd..4806c28a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -513,6 +513,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled + _prefs.bridge_enabled = 1; // enabled + _prefs.bridge_channel = 0; // auto } void MyMesh::begin(FILESYSTEM *fs) { @@ -523,8 +525,13 @@ void MyMesh::begin(FILESYSTEM *fs) { acl.load(_fs); -#ifdef WITH_BRIDGE - bridge.begin(); +#if defined(WITH_ESPNOW_BRIDGE) + bridge.setChannel(_prefs.bridge_channel); +#endif +#if defined(WITH_BRIDGE) + if (_prefs.bridge_enabled) { + bridge.begin(); + } #endif radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 798d31f2..7ae10812 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -2,7 +2,8 @@ #include <Arduino.h> #include <Mesh.h> -#include <helpers/CommonCLI.h> +#include <RTClib.h> +#include <target.h> #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #include <InternalFileSystem.h> @@ -12,16 +13,6 @@ #include <SPIFFS.h> #endif -#include <helpers/ArduinoHelpers.h> -#include <helpers/StaticPoolPacketManager.h> -#include <helpers/SimpleMeshTables.h> -#include <helpers/IdentityStore.h> -#include <helpers/AdvertDataHelpers.h> -#include <helpers/TxtDataHelpers.h> -#include <helpers/ClientACL.h> -#include <RTClib.h> -#include <target.h> - #ifdef WITH_RS232_BRIDGE #include "helpers/bridges/RS232Bridge.h" #define WITH_BRIDGE @@ -32,6 +23,15 @@ #define WITH_BRIDGE #endif +#include <helpers/AdvertDataHelpers.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/ClientACL.h> +#include <helpers/CommonCLI.h> +#include <helpers/IdentityStore.h> +#include <helpers/SimpleMeshTables.h> +#include <helpers/StaticPoolPacketManager.h> +#include <helpers/TxtDataHelpers.h> + #ifdef WITH_BRIDGE extern AbstractBridge* bridge; #endif @@ -165,6 +165,21 @@ public: void updateAdvertTimer() override; void updateFloodAdvertTimer() override; +#if defined(WITH_ESPNOW_BRIDGE) + void setBridgeState(bool enable) { + if (enable == bridge.getState()) return; + enable ? bridge.begin() : bridge.end(); + } + + void updateBridgeChannel(int ch) override { + bridge.setChannel(ch); + if (bridge.getState()) { + bridge.end(); + bridge.begin(); + } + } +#endif + void setLoggingOn(bool enable) override { _logging = enable; } void eraseLogFile() override { diff --git a/src/helpers/AbstractBridge.h b/src/helpers/AbstractBridge.h index a348e933..73a967d8 100644 --- a/src/helpers/AbstractBridge.h +++ b/src/helpers/AbstractBridge.h @@ -11,6 +11,18 @@ public: */ virtual void begin() = 0; + /** + * @brief Stops the bridge. + */ + virtual void end() = 0; + + /** + * @brief Gets the current state of the bridge. + * + * @return true if the bridge is initialized and running, false otherwise. + */ + virtual bool getState() const = 0; + /** * @brief A method to be called on every main loop iteration. * Used for tasks like checking for incoming data. @@ -20,14 +32,14 @@ public: /** * @brief A callback that is triggered when the mesh transmits a packet. * The bridge can use this to forward the packet. - * + * * @param packet The packet that was transmitted. */ virtual void onPacketTransmitted(mesh::Packet* packet) = 0; /** * @brief Processes a received packet from the bridge's medium. - * + * * @param packet The packet that was received. */ virtual void onPacketReceived(mesh::Packet* packet) = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 7125e5b0..97c7eada 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -32,32 +32,34 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { if (file) { uint8_t pad[8]; - file.read((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 - file.read((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4 - file.read(pad, 4); // 36 - file.read((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 - file.read((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 - file.read((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56 - file.read((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72 - file.read((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 - file.read((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 - file.read((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 - file.read((uint8_t *) pad, 1); // 79 was 'unused' - file.read((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 - file.read((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 - file.read((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 - file.read((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 - file.read(pad, 4); // 108 - file.read((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 - file.read((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 - file.read((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 - file.read((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 - file.read((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 - file.read((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.read(pad, 3); // 121 - file.read((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 - file.read((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 - file.read((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 + file.read((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 + file.read((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4 + file.read(pad, 4); // 36 + file.read((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 + file.read((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 + file.read((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56 + file.read((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72 + file.read((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 + file.read((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 + file.read((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 + file.read((uint8_t *)pad, 1); // 79 was 'unused' + file.read((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 + file.read((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 + file.read((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 + file.read((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 + file.read(pad, 4); // 108 + file.read((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112 + file.read((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113 + file.read((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 + file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 + file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 + file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 + file.read(pad, 3); // 121 + file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 + file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 + file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 + file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127 + file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 128 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -70,6 +72,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); + _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); + _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); file.close(); } @@ -88,32 +92,34 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { uint8_t pad[8]; memset(pad, 0, sizeof(pad)); - file.write((uint8_t *) &_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 - file.write((uint8_t *) &_prefs->node_name, sizeof(_prefs->node_name)); // 4 - file.write(pad, 4); // 36 - file.write((uint8_t *) &_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 - file.write((uint8_t *) &_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 - file.write((uint8_t *) &_prefs->password[0], sizeof(_prefs->password)); // 56 - file.write((uint8_t *) &_prefs->freq, sizeof(_prefs->freq)); // 72 - file.write((uint8_t *) &_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 - file.write((uint8_t *) &_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 - file.write((uint8_t *) &_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 - file.write((uint8_t *) pad, 1); // 79 was 'unused' - file.write((uint8_t *) &_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 - file.write((uint8_t *) &_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 - file.write((uint8_t *) &_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 - file.write((uint8_t *) &_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 - file.write(pad, 4); // 108 - file.write((uint8_t *) &_prefs->sf, sizeof(_prefs->sf)); // 112 - file.write((uint8_t *) &_prefs->cr, sizeof(_prefs->cr)); // 113 - file.write((uint8_t *) &_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 - file.write((uint8_t *) &_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 - file.write((uint8_t *) &_prefs->bw, sizeof(_prefs->bw)); // 116 - file.write((uint8_t *) &_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.write(pad, 3); // 121 - file.write((uint8_t *) &_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 - file.write((uint8_t *) &_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 - file.write((uint8_t *) &_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 + file.write((uint8_t *)&_prefs->airtime_factor, sizeof(_prefs->airtime_factor)); // 0 + file.write((uint8_t *)&_prefs->node_name, sizeof(_prefs->node_name)); // 4 + file.write(pad, 4); // 36 + file.write((uint8_t *)&_prefs->node_lat, sizeof(_prefs->node_lat)); // 40 + file.write((uint8_t *)&_prefs->node_lon, sizeof(_prefs->node_lon)); // 48 + file.write((uint8_t *)&_prefs->password[0], sizeof(_prefs->password)); // 56 + file.write((uint8_t *)&_prefs->freq, sizeof(_prefs->freq)); // 72 + file.write((uint8_t *)&_prefs->tx_power_dbm, sizeof(_prefs->tx_power_dbm)); // 76 + file.write((uint8_t *)&_prefs->disable_fwd, sizeof(_prefs->disable_fwd)); // 77 + file.write((uint8_t *)&_prefs->advert_interval, sizeof(_prefs->advert_interval)); // 78 + file.write((uint8_t *)pad, 1); // 79 was 'unused' + file.write((uint8_t *)&_prefs->rx_delay_base, sizeof(_prefs->rx_delay_base)); // 80 + file.write((uint8_t *)&_prefs->tx_delay_factor, sizeof(_prefs->tx_delay_factor)); // 84 + file.write((uint8_t *)&_prefs->guest_password[0], sizeof(_prefs->guest_password)); // 88 + file.write((uint8_t *)&_prefs->direct_tx_delay_factor, sizeof(_prefs->direct_tx_delay_factor)); // 104 + file.write(pad, 4); // 108 + file.write((uint8_t *)&_prefs->sf, sizeof(_prefs->sf)); // 112 + file.write((uint8_t *)&_prefs->cr, sizeof(_prefs->cr)); // 113 + file.write((uint8_t *)&_prefs->allow_read_only, sizeof(_prefs->allow_read_only)); // 114 + file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 + file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 + file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 + file.write(pad, 3); // 121 + file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 + file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 + file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 + file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127 + file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 128 file.close(); } @@ -252,11 +258,41 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { sprintf(reply, "> %s", _callbacks->getRole()); +#ifdef WITH_BRIDGE + } else if (memcmp(config, "bridge.enabled", 14) == 0) { + sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off"); +#ifdef WITH_ESPNOW_BRIDGE + } else if (memcmp(config, "bridge.channel", 14) == 0) { + sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel); +#endif +#endif } else { sprintf(reply, "??: %s", config); } } else if (memcmp(command, "set ", 4) == 0) { const char* config = &command[4]; +#ifdef WITH_BRIDGE + if (memcmp(config, "bridge.enabled ", 15) == 0) { + _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0; + _callbacks->setBridgeState(_prefs->bridge_enabled); + savePrefs(); + strcpy(reply, "OK"); + } + else +#ifdef WITH_ESPNOW_BRIDGE + if (memcmp(config, "bridge.channel ", 15) == 0) { + int ch = atoi(&config[15]); + if (ch > 0 && ch < 15) { + _prefs->bridge_channel = (uint8_t)ch; + _callbacks->updateBridgeChannel(ch); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error: channel must be 0 (AUTO) or 1-14"); + } + } else +#endif +#endif if (memcmp(config, "af ", 3) == 0) { _prefs->airtime_factor = atof(&config[3]); savePrefs(); @@ -301,7 +337,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); savePrefs(); strcpy(reply, "OK"); - } else if (sender_timestamp == 0 && memcmp(config, "prv.key ", 8) == 0) { // from serial command line only + } else if (sender_timestamp == 0 && + memcmp(config, "prv.key ", 8) == 0) { // from serial command line only uint8_t prv_key[PRV_KEY_SIZE]; bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); if (success) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ff8ff50e..55751d45 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -3,29 +3,35 @@ #include "Mesh.h" #include <helpers/IdentityStore.h> -struct NodePrefs { // persisted to file - float airtime_factor; - char node_name[32]; - double node_lat, node_lon; - char password[16]; - float freq; - uint8_t tx_power_dbm; - uint8_t disable_fwd; - uint8_t advert_interval; // minutes / 2 - uint8_t flood_advert_interval; // hours - float rx_delay_base; - float tx_delay_factor; - char guest_password[16]; - float direct_tx_delay_factor; - uint32_t guard; - uint8_t sf; - uint8_t cr; - uint8_t allow_read_only; - uint8_t multi_acks; - float bw; - uint8_t flood_max; - uint8_t interference_threshold; - uint8_t agc_reset_interval; // secs / 4 +#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) +#define WITH_BRIDGE +#endif + +struct NodePrefs { // persisted to file + float airtime_factor; + char node_name[32]; + double node_lat, node_lon; + char password[16]; + float freq; + uint8_t tx_power_dbm; + uint8_t disable_fwd; + uint8_t advert_interval; // minutes / 2 + uint8_t flood_advert_interval; // hours + float rx_delay_base; + float tx_delay_factor; + char guest_password[16]; + float direct_tx_delay_factor; + uint32_t guard; + uint8_t sf; + uint8_t cr; + uint8_t allow_read_only; + uint8_t multi_acks; + float bw; + uint8_t flood_max; + uint8_t interference_threshold; + uint8_t agc_reset_interval; // secs / 4 + uint8_t bridge_enabled; // boolean + uint8_t bridge_channel; // 0 = AUTO, 1-14 valid }; class CommonCLICallbacks { @@ -50,6 +56,11 @@ public: virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; + +#ifdef WITH_ESPNOW_BRIDGE + virtual void setBridgeState(bool enable) = 0; + virtual void updateBridgeChannel(int ch) = 0; +#endif }; class CommonCLI { diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp index 03871418..9434b23b 100644 --- a/src/helpers/bridges/BridgeBase.cpp +++ b/src/helpers/bridges/BridgeBase.cpp @@ -2,6 +2,10 @@ #include <Arduino.h> +bool BridgeBase::getState() const { + return _initialized; +} + const char *BridgeBase::getLogDateTime() { static char tmp[32]; uint32_t now = _rtc->getCurrentTime(); @@ -28,6 +32,13 @@ bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t rece } void BridgeBase::handleReceivedPacket(mesh::Packet *packet) { + // Guard against uninitialized state + if (_initialized == false) { + Serial.printf("%s: BRIDGE: RX packet received before initialization\n", getLogDateTime()); + _mgr->free(packet); + return; + } + if (!_seen_packets.hasSeen(packet)) { _mgr->queueInbound(packet, millis() + BRIDGE_DELAY); } else { diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index ab62619f..a3b1c85e 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/src/helpers/bridges/BridgeBase.h @@ -21,6 +21,13 @@ class BridgeBase : public AbstractBridge { public: virtual ~BridgeBase() = default; + /** + * @brief Gets the current state of the bridge. + * + * @return true if the bridge is initialized and running, false otherwise. + */ + bool getState() const override; + /** * @brief Common magic number used by all bridge implementations for packet identification * @@ -50,6 +57,9 @@ public: static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ? protected: + /** Tracks bridge state */ + bool _initialized = false; + /** Packet manager for allocating and queuing mesh packets */ mesh::PacketManager *_mgr; diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index a02f804e..c508c5a0 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -27,8 +27,16 @@ ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc) } void ESPNowBridge::begin() { + Serial.printf("%s: ESPNOW BRIDGE: Initializing...\n", getLogDateTime()); + // Initialize WiFi in station mode WiFi.mode(WIFI_STA); + + // Set wifi channel + if (esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: Error setting WIFI channel to %d\n", getLogDateTime(), _channel); + return; + } // Initialize ESP-NOW if (esp_now_init() != ESP_OK) { @@ -44,13 +52,41 @@ void ESPNowBridge::begin() { esp_now_peer_info_t peerInfo = {}; memset(&peerInfo, 0, sizeof(peerInfo)); memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address - peerInfo.channel = 0; + peerInfo.channel = _channel; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime()); return; } + + // Update bridge state + _initialized = true; +} + +void ESPNowBridge::end() { + Serial.printf("%s: ESPNOW BRIDGE: Stopping...\n", getLogDateTime()); + + // Remove broadcast peer + uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + if (esp_now_del_peer(broadcastAddress) != ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: Error removing broadcast peer\n", getLogDateTime()); + } + + // Unregister callbacks + esp_now_register_recv_cb(nullptr); + esp_now_register_send_cb(nullptr); + + // Deinitialize ESP-NOW + if (esp_now_deinit() != ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: Error deinitializing ESP-NOW\n", getLogDateTime()); + } + + // Turn off WiFi + WiFi.mode(WIFI_OFF); + + // Update bridge state + _initialized = false; } void ESPNowBridge::loop() { @@ -130,11 +166,13 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta // Could add transmission error handling here if needed } -void ESPNowBridge::onPacketReceived(mesh::Packet *packet) { - handleReceivedPacket(packet); -} - void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { + // Guard against uninitialized state + if (_initialized == false) { + Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime()); + return; + } + // First validate the packet pointer if (!packet) { #if MESH_PACKET_LOGGING @@ -144,7 +182,6 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { } if (!_seen_packets.hasSeen(packet)) { - // Create a temporary buffer just for size calculation and reuse for actual writing uint8_t sizingBuffer[MAX_PAYLOAD_SIZE]; uint16_t meshPacketLen = packet->writeTo(sizingBuffer); @@ -193,4 +230,8 @@ void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { } } +void ESPNowBridge::onPacketReceived(mesh::Packet *packet) { + handleReceivedPacket(packet); +} + #endif diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index b43f1744..401c9eee 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/src/helpers/bridges/ESPNowBridge.h @@ -80,6 +80,12 @@ private: */ const char *_secret = WITH_ESPNOW_BRIDGE_SECRET; + /** + * Channel for ESP-NOW communication + * Valid 2.4GHz channels: 1-14 + */ + int _channel = 0; + /** * Performs XOR encryption/decryption of data * @@ -130,6 +136,16 @@ public: */ void begin() override; + /** + * Stops the ESP-NOW bridge + * + * - Removes broadcast peer + * - Unregisters callbacks + * - Deinitializes ESP-NOW protocol + * - Turns off WiFi to release radio resources + */ + void end() override; + /** * Main loop handler * ESP-NOW is callback-based, so this is currently empty @@ -151,6 +167,20 @@ public: * @param packet The mesh packet to transmit */ void onPacketTransmitted(mesh::Packet *packet) override; + + /** + * Gets the current channel + * + * @return The current channel (0 = AUTO, 1-14 = valid channel) + */ + int getChannel() const { return _channel; } + + /** + * Sets the channel for ESP-NOW communication + * + * @param ch The channel to set (0 = AUTO, 1-14 = valid channel) + */ + void setChannel(int ch) { _channel = ch; } }; #endif diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index d182aea6..02e36397 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -8,6 +8,7 @@ RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCCloc : BridgeBase(mgr, rtc), _serial(&serial) {} void RS232Bridge::begin() { + Serial.printf("%s: RS232 BRIDGE: Initializing...\n", getLogDateTime()); #if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX) #error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined" #endif @@ -26,52 +27,25 @@ void RS232Bridge::begin() { #error RS232Bridge was not tested on the current platform #endif ((HardwareSerial *)_serial)->begin(115200); + + // Update bridge state + _initialized = true; } -void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) { - // First validate the packet pointer - if (!packet) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime()); -#endif - return; - } +void RS232Bridge::end() { + Serial.printf("%s: RS232 BRIDGE: Stopping...\n", getLogDateTime()); + ((HardwareSerial *)_serial)->end(); - if (!_seen_packets.hasSeen(packet)) { - - uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; - uint16_t len = packet->writeTo(buffer + 4); - - // Check if packet fits within our maximum payload size - if (len > (MAX_TRANS_UNIT + 1)) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len, - MAX_TRANS_UNIT + 1); -#endif - return; - } - - // Build packet header - buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte - buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte - buffer[2] = (len >> 8) & 0xFF; // Length high byte - buffer[3] = len & 0xFF; // Length low byte - - // Calculate checksum over the payload - uint16_t checksum = fletcher16(buffer + 4, len); - buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte - buffer[5 + len] = checksum & 0xFF; // Checksum low byte - - // Send complete packet - _serial->write(buffer, len + SERIAL_OVERHEAD); - -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); -#endif - } + // Update bridge state + _initialized = false; } void RS232Bridge::loop() { + // Guard against uninitialized state + if (_initialized == false) { + return; + } + while (_serial->available()) { uint8_t b = _serial->read(); @@ -140,6 +114,55 @@ void RS232Bridge::loop() { } } +void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) { + // Guard against uninitialized state + if (_initialized == false) { + Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime()); + return; + } + + // First validate the packet pointer + if (!packet) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime()); +#endif + return; + } + + if (!_seen_packets.hasSeen(packet)) { + + uint8_t buffer[MAX_SERIAL_PACKET_SIZE]; + uint16_t len = packet->writeTo(buffer + 4); + + // Check if packet fits within our maximum payload size + if (len > (MAX_TRANS_UNIT + 1)) { +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len, + MAX_TRANS_UNIT + 1); +#endif + return; + } + + // Build packet header + buffer[0] = (BRIDGE_PACKET_MAGIC >> 8) & 0xFF; // Magic high byte + buffer[1] = BRIDGE_PACKET_MAGIC & 0xFF; // Magic low byte + buffer[2] = (len >> 8) & 0xFF; // Length high byte + buffer[3] = len & 0xFF; // Length low byte + + // Calculate checksum over the payload + uint16_t checksum = fletcher16(buffer + 4, len); + buffer[4 + len] = (checksum >> 8) & 0xFF; // Checksum high byte + buffer[5 + len] = checksum & 0xFF; // Checksum low byte + + // Send complete packet + _serial->write(buffer, len + SERIAL_OVERHEAD); + +#if MESH_PACKET_LOGGING + Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); +#endif + } +} + void RS232Bridge::onPacketReceived(mesh::Packet *packet) { handleReceivedPacket(packet); } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 3b09de75..e9cc22d0 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -65,6 +65,12 @@ public: */ void begin() override; + /** + * Stops the RS232 bridge + * + */ + void end() override; + /** * @brief Main loop handler for processing incoming serial data * From 2297d2401356a9f388c24231fce3b72df9e0e759 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Wed, 24 Sep 2025 16:46:03 +0100 Subject: [PATCH 100/546] Minor fixes --- examples/simple_repeater/MyMesh.h | 4 +++- src/helpers/CommonCLI.h | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7ae10812..c1d1f264 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -165,12 +165,13 @@ public: void updateAdvertTimer() override; void updateFloodAdvertTimer() override; -#if defined(WITH_ESPNOW_BRIDGE) +#if defined(WITH_BRIDGE) void setBridgeState(bool enable) { if (enable == bridge.getState()) return; enable ? bridge.begin() : bridge.end(); } +#if defined(WITH_ESPNOW_BRIDGE) void updateBridgeChannel(int ch) override { bridge.setChannel(ch); if (bridge.getState()) { @@ -178,6 +179,7 @@ public: bridge.begin(); } } +#endif #endif void setLoggingOn(bool enable) override { _logging = enable; } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 55751d45..4344ff1d 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -31,7 +31,7 @@ struct NodePrefs { // persisted to file uint8_t interference_threshold; uint8_t agc_reset_interval; // secs / 4 uint8_t bridge_enabled; // boolean - uint8_t bridge_channel; // 0 = AUTO, 1-14 valid + uint8_t bridge_channel; // 1-14 }; class CommonCLICallbacks { @@ -56,7 +56,7 @@ public: virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; - + #ifdef WITH_ESPNOW_BRIDGE virtual void setBridgeState(bool enable) = 0; virtual void updateBridgeChannel(int ch) = 0; From c21596341a7d2b22aa4e31b0cb908d2106e0b417 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 25 Sep 2025 09:07:59 +1000 Subject: [PATCH 101/546] * Login response payload: now includes FIRMWARE_VER_LEVEL --- examples/simple_repeater/MyMesh.cpp | 4 +++- examples/simple_room_server/MyMesh.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 4 +++- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4265e1cd..c193f329 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -40,6 +40,8 @@ #define TXT_ACK_DELAY 200 #endif +#define FIRMWARE_VER_LEVEL 1 + #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 @@ -119,7 +121,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) + reply_data[5] = FIRMWARE_VER_LEVEL; // Legacy: was recommended keep-alive interval (secs / 16) reply_data[6] = client->isAdmin() ? 1 : 0; reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 24a3a32f..dc3a66a9 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -10,7 +10,7 @@ #define POST_SYNC_DELAY_SECS 6 -#define CLIENT_KEEP_ALIVE_SECS 0 // Now Disabled (was 128) +#define FIRMWARE_VER_LEVEL 1 #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 @@ -336,7 +336,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = (CLIENT_KEEP_ALIVE_SECS >> 4); // NEW: recommended keep-alive interval (secs / 16) + reply_data[5] = FIRMWARE_VER_LEVEL; // Legacy: was recommended keep-alive interval (secs / 16) reply_data[6] = (client->isAdmin() ? 1 : (client->permissions == 0 ? 2 : 0)); // LEGACY: reply_data[7] = getUnsyncedCount(client); reply_data[7] = client->permissions; // NEW diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 0508713c..157b2fb3 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -46,6 +46,8 @@ /* ------------------------------ Code -------------------------------- */ +#define FIRMWARE_VER_LEVEL 1 + #define REQ_TYPE_LOGIN 0x00 #define REQ_TYPE_GET_STATUS 0x01 #define REQ_TYPE_KEEP_ALIVE 0x02 @@ -364,7 +366,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = 0; // NEW: recommended keep-alive interval (secs / 16) + reply_data[5] = FIRMWARE_VER_LEVEL; reply_data[6] = client->isAdmin() ? 1 : 0; reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness From 76be66313efbd2df02d7f644cf613667889d0155 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 25 Sep 2025 09:11:48 +1000 Subject: [PATCH 102/546] * repeater: reduce FS writes on login --- examples/simple_repeater/MyMesh.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c193f329..55d87580 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -115,7 +115,9 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr client->permissions |= perms; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + if (perms != PERM_ACL_GUEST) { // keep number of FS writes to a minimum + dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + } } uint32_t now = getRTCClock()->getCurrentTimeUnique(); From a5af1b5bcd12e25339c8d80133a2be725f1667b0 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 25 Sep 2025 09:39:11 +1000 Subject: [PATCH 103/546] * companion: disabled processing/sending of keep_alive packets (deprecated) * FIRMWARE_VER_LEVEL now moved to end of response payloads --- examples/companion_radio/MyMesh.cpp | 4 ++-- examples/simple_repeater/MyMesh.cpp | 5 +++-- examples/simple_room_server/MyMesh.cpp | 8 ++++---- examples/simple_sensor/SensorMesh.cpp | 5 +++-- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 509b0c63..a7f4e965 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1690,8 +1690,8 @@ void MyMesh::checkSerialInterface() { _serial->writeFrame(out_frame, 5); _iter_started = false; } - } else if (!_serial->isWriteBusy()) { - checkConnections(); + //} else if (!_serial->isWriteBusy()) { + // checkConnections(); // TODO - deprecate the 'Connections' stuff } } diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 55d87580..5ae05ffa 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -123,12 +123,13 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = FIRMWARE_VER_LEVEL; // Legacy: was recommended keep-alive interval (secs / 16) + reply_data[5] = 0; // Legacy: was recommended keep-alive interval (secs / 16) reply_data[6] = client->isAdmin() ? 1 : 0; reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness + reply_data[12] = FIRMWARE_VER_LEVEL; // New field - return 12; // reply length + return 13; // reply length } int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index dc3a66a9..398938e6 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -336,22 +336,22 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = FIRMWARE_VER_LEVEL; // Legacy: was recommended keep-alive interval (secs / 16) + reply_data[5] = 0; // Legacy: was recommended keep-alive interval (secs / 16) reply_data[6] = (client->isAdmin() ? 1 : (client->permissions == 0 ? 2 : 0)); // LEGACY: reply_data[7] = getUnsyncedCount(client); reply_data[7] = client->permissions; // NEW getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness - // LEGACY: memcpy(&reply_data[8], "OK", 2); + reply_data[12] = FIRMWARE_VER_LEVEL; // New field next_push = futureMillis(PUSH_NOTIFY_DELAY_MILLIS); // delay next push, give RESPONSE packet time to arrive first if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, - PAYLOAD_TYPE_RESPONSE, reply_data, 12); + PAYLOAD_TYPE_RESPONSE, reply_data, 13); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); } else { - mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 12); + mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13); if (reply) { if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 157b2fb3..ba41ca45 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -366,12 +366,13 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; - reply_data[5] = FIRMWARE_VER_LEVEL; + reply_data[5] = 0; reply_data[6] = client->isAdmin() ? 1 : 0; reply_data[7] = client->permissions; getRNG()->random(&reply_data[8], 4); // random blob to help packet-hash uniqueness + reply_data[12] = FIRMWARE_VER_LEVEL; - return 12; // reply length + return 13; // reply length } void SensorMesh::handleCommand(uint32_t sender_timestamp, char* command, char* reply) { From 02c178dae73951295cac9ef7cf518ed76b8d672d Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Tue, 23 Sep 2025 17:32:58 +1200 Subject: [PATCH 104/546] implement new binary request/response for paginated neighbours --- examples/simple_repeater/MyMesh.cpp | 94 +++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5ae05ffa..23280272 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1,4 +1,5 @@ #include "MyMesh.h" +#include <algorithm> /* ------------------------------ Config -------------------------------- */ @@ -46,6 +47,7 @@ #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 +#define REQ_TYPE_GET_NEIGHBOURS 0x06 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -187,6 +189,98 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return ofs; } } + if (payload[0] == REQ_TYPE_GET_NEIGHBOURS) { + uint8_t request_version = payload[1]; + if (request_version == 0) { + + // reply data offset (after response sender_timestamp/tag) + int reply_offset = 4; + + // get request params + uint8_t count = payload[2]; // how many neighbours to fetch + uint8_t offset = payload[3]; // offset from start of neighbours list + uint8_t order_by = payload[4]; // how to order neighbours. 0=newest_to_oldest, 1=oldest_to_newest, 2=strongest_to_weakest, 3=weakest_to_strongest + uint8_t pubkey_prefix_length = payload[5]; // how many bytes of neighbour pub key we want + // we also send a 4 byte random blob in payload[6...9] to help packet uniqueness + + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS count=%d, offset=%d, order_by=%d, pubkey_prefix_length=%d", count, offset, order_by, pubkey_prefix_length); + + // clamp pub key prefix length to max pub key length + if(pubkey_prefix_length > PUB_KEY_SIZE){ + pubkey_prefix_length = PUB_KEY_SIZE; + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS invalid pubkey_prefix_length=%d clamping to %d", pubkey_prefix_length, PUB_KEY_SIZE); + } + + // create copy of neighbours list, skipping empty entries so we can sort it separately from main list + int16_t neighbours_count = 0; + NeighbourInfo sorted_neighbours[MAX_NEIGHBOURS]; + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + auto neighbour = &neighbours[i]; + if (neighbour->heard_timestamp > 0) { + sorted_neighbours[neighbours_count] = *neighbour; + neighbours_count++; + } + } + + // sort neighbours based on order + if (order_by == 0) { + // sort by newest to oldest + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting newest to oldest"); + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { + return a.heard_timestamp > b.heard_timestamp; // desc + }); + } else if (order_by == 1) { + // sort by oldest to newest + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting oldest to newest"); + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { + return a.heard_timestamp < b.heard_timestamp; // asc + }); + } else if (order_by == 2) { + // sort by strongest to weakest + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting strongest to weakest"); + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { + return a.snr > b.snr; // desc + }); + } else if (order_by == 3) { + // sort by weakest to strongest + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting weakest to strongest"); + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { + return a.snr < b.snr; // asc + }); + } + + // build results buffer + int results_count = 0; + int results_offset = 0; + uint8_t results_buffer[130]; + for(int index = 0; index < count && index + offset < neighbours_count; index++){ + + // stop if we can't fit another entry in results + int entry_size = pubkey_prefix_length + 4 + 1; + if(results_offset + entry_size > sizeof(results_buffer)){ + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS no more entries can fit in results buffer"); + break; + } + + // add next neighbour to results + auto neighbour = &sorted_neighbours[index + offset]; + uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; + memcpy(&results_buffer[results_offset], neighbour->id.pub_key, pubkey_prefix_length); results_offset += pubkey_prefix_length; + memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4; + memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1; + results_count++; + + } + + // build reply + MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS neighbours_count=%d results_count=%d", neighbours_count, results_count); + memcpy(&reply_data[reply_offset], &neighbours_count, 2); reply_offset += 2; + memcpy(&reply_data[reply_offset], &results_count, 2); reply_offset += 2; + memcpy(&reply_data[reply_offset], &results_buffer, results_offset); reply_offset += results_offset; + + return reply_offset; + } + } return 0; // unknown command } From 1c7a0ce2bd3cfc88de8dd0c2f2d4dff90ce7c20e Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Tue, 23 Sep 2025 18:00:52 +1200 Subject: [PATCH 105/546] use uint16_t to allow fetching up to 65535 neighbours --- examples/simple_repeater/MyMesh.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 23280272..f868d5a7 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -197,11 +197,12 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t int reply_offset = 4; // get request params - uint8_t count = payload[2]; // how many neighbours to fetch - uint8_t offset = payload[3]; // offset from start of neighbours list - uint8_t order_by = payload[4]; // how to order neighbours. 0=newest_to_oldest, 1=oldest_to_newest, 2=strongest_to_weakest, 3=weakest_to_strongest - uint8_t pubkey_prefix_length = payload[5]; // how many bytes of neighbour pub key we want - // we also send a 4 byte random blob in payload[6...9] to help packet uniqueness + uint8_t count = payload[2]; // how many neighbours to fetch (0-255) + uint16_t offset; + memcpy(&offset, &payload[3], 2); // offset from start of neighbours list (0-65535) + uint8_t order_by = payload[5]; // how to order neighbours. 0=newest_to_oldest, 1=oldest_to_newest, 2=strongest_to_weakest, 3=weakest_to_strongest + uint8_t pubkey_prefix_length = payload[6]; // how many bytes of neighbour pub key we want + // we also send a 4 byte random blob in payload[7...10] to help packet uniqueness MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS count=%d, offset=%d, order_by=%d, pubkey_prefix_length=%d", count, offset, order_by, pubkey_prefix_length); From b8394a4e624ff140bb4ffa0cc28b81a7637ed18a Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Tue, 23 Sep 2025 22:52:02 +1200 Subject: [PATCH 106/546] use pointer array --- examples/simple_repeater/MyMesh.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f868d5a7..2010f8cf 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -214,11 +214,11 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t // create copy of neighbours list, skipping empty entries so we can sort it separately from main list int16_t neighbours_count = 0; - NeighbourInfo sorted_neighbours[MAX_NEIGHBOURS]; + NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS]; for (int i = 0; i < MAX_NEIGHBOURS; i++) { auto neighbour = &neighbours[i]; if (neighbour->heard_timestamp > 0) { - sorted_neighbours[neighbours_count] = *neighbour; + sorted_neighbours[neighbours_count] = neighbour; neighbours_count++; } } @@ -227,26 +227,26 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t if (order_by == 0) { // sort by newest to oldest MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting newest to oldest"); - std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { - return a.heard_timestamp > b.heard_timestamp; // desc + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo* a, const NeighbourInfo* b) { + return a->heard_timestamp > b->heard_timestamp; // desc }); } else if (order_by == 1) { // sort by oldest to newest MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting oldest to newest"); - std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { - return a.heard_timestamp < b.heard_timestamp; // asc + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo* a, const NeighbourInfo* b) { + return a->heard_timestamp < b->heard_timestamp; // asc }); } else if (order_by == 2) { // sort by strongest to weakest MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting strongest to weakest"); - std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { - return a.snr > b.snr; // desc + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo* a, const NeighbourInfo* b) { + return a->snr > b->snr; // desc }); } else if (order_by == 3) { // sort by weakest to strongest MESH_DEBUG_PRINTLN("REQ_TYPE_GET_NEIGHBOURS sorting weakest to strongest"); - std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo &a, const NeighbourInfo &b) { - return a.snr < b.snr; // asc + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo* a, const NeighbourInfo* b) { + return a->snr < b->snr; // asc }); } @@ -264,7 +264,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t } // add next neighbour to results - auto neighbour = &sorted_neighbours[index + offset]; + auto neighbour = sorted_neighbours[index + offset]; uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; memcpy(&results_buffer[results_offset], neighbour->id.pub_key, pubkey_prefix_length); results_offset += pubkey_prefix_length; memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4; From 418ae08b4dfb52877b6a8d1a22ebe49158b3b8ae Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Thu, 25 Sep 2025 15:21:58 +1200 Subject: [PATCH 107/546] add FIRMWARE_VER_LEVEL to companion PUSH_CODE_LOGIN_SUCCESS --- examples/companion_radio/MyMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a7f4e965..3772fc1b 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -515,6 +515,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, memcpy(&out_frame[i], &tag, 4); i += 4; // NEW: include server timestamp out_frame[i++] = data[7]; // NEW (v7): ACL permissions + out_frame[i++] = data[12]; // FIRMWARE_VER_LEVEL } else { out_frame[i++] = PUSH_CODE_LOGIN_FAIL; out_frame[i++] = 0; // reserved From a3e6b79c2f00ec90d5708d21f80d003426c74d75 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Thu, 25 Sep 2025 20:08:18 +1000 Subject: [PATCH 108/546] Revert addition of ENV_PIN_SDA --- variants/wio-tracker-l1/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 35ca2135..b6b73d93 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -15,8 +15,6 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_OLED_RESET=-1 -D GPS_BAUD_RATE=9600 - -D ENV_PIN_SDA=PIN_WIRE1_SDA - -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} +<WioTrackerL1Board.cpp> +<../variants/wio-tracker-l1> From 3fbdaf7ce671e775719582f3b44c26226edb0fa8 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Fri, 26 Sep 2025 22:46:38 +1200 Subject: [PATCH 109/546] don't overwrite existing platformio build flags in build script --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 47fec4a3..f48205ed 100755 --- a/build.sh +++ b/build.sh @@ -47,8 +47,8 @@ build_firmware() { # e.g: RAK_4631_Repeater-v1.0.0-SHA FIRMWARE_FILENAME="$1-${FIRMWARE_VERSION_STRING}" - # export build flags for pio so we can inject firmware version info - export PLATFORMIO_BUILD_FLAGS="-DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" + # add firmware version info to end of existing platformio build flags in environment vars + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" # build firmware target pio run -e $1 From e49eef558849247d9784fe9a46640d3e63f12b73 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Fri, 26 Sep 2025 22:59:20 +1200 Subject: [PATCH 110/546] allow building multiple specific targets at same time --- build.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index f48205ed..79690b54 100755 --- a/build.sh +++ b/build.sh @@ -143,8 +143,11 @@ mkdir -p out # handle script args if [[ $1 == "build-firmware" ]]; then - if [ "$2" ]; then - build_firmware $2 + TARGETS=${@:2} + if [ "$TARGETS" ]; then + for env in $TARGETS; do + build_firmware $env + done else echo "usage: $0 build-firmware <target>" exit 1 From 95e533d60b0973297a5e3f2d1141ec12ea3201d6 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 27 Sep 2025 01:56:27 +1000 Subject: [PATCH 111/546] * repeater & room server fix for blank guest password --- examples/simple_repeater/MyMesh.cpp | 6 +++--- examples/simple_room_server/MyMesh.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2010f8cf..e16ae56b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -83,16 +83,16 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn } uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { - ClientInfo* client; + ClientInfo* client = NULL; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { #if MESH_DEBUG MESH_DEBUG_PRINTLN("Login, sender not in ACL"); #endif - return 0; } - } else { + } + if (client == NULL) { uint8_t perms; if (strcmp((char *)data, _prefs.password) == 0) { // check for valid admin password perms = PERM_ACL_ADMIN; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 398938e6..89f2afb3 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -289,16 +289,16 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator - ClientInfo* client; + ClientInfo* client = NULL; if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { #if MESH_DEBUG MESH_DEBUG_PRINTLN("Login, sender not in ACL"); #endif - return; } - } else { + } + if (client == NULL) { uint8_t perm; if (strcmp((char *)&data[8], _prefs.password) == 0) { // check for valid admin password perm = PERM_ACL_ADMIN; From cc822c029b8176c3793726ce74b10ab7e25c52f7 Mon Sep 17 00:00:00 2001 From: David Hall <david@datajack.org> Date: Sat, 27 Sep 2025 11:14:28 +0100 Subject: [PATCH 112/546] Create Xiao_S3_WIO_companion_radio_usb profile Create Xiao_S3_WIO_companion_radio_usb profile from Xiao_S3_WIO_companion_radio_ble profile --- variants/xiao_s3_wio/platformio.ini | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index b4f25e53..8f163105 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -75,6 +75,28 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:Xiao_S3_WIO_companion_radio_usb] +extends = Xiao_S3_WIO +build_flags = + ${Xiao_S3_WIO.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=SSD1306Display + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Xiao_S3_WIO.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + [env:Xiao_S3_WIO_companion_radio_ble] extends = Xiao_S3_WIO build_flags = From f3b9c0664655e3390a1c8252ca740b0ad0a19b97 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sat, 27 Sep 2025 23:45:33 +1200 Subject: [PATCH 113/546] refactor variants to use standard firmware type suffixes --- variants/rak3x72/platformio.ini | 4 ++-- variants/tenstar_c3/platformio.ini | 12 ++++++------ variants/tiny_relay/platformio.ini | 4 ++-- variants/wio-e5-dev/platformio.ini | 2 +- variants/wio-e5-mini/platformio.ini | 4 ++-- variants/xiao_c3/platformio.ini | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index da9b2f22..67db107a 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -13,7 +13,7 @@ build_flags = ${stm32_base.build_flags} build_src_filter = ${stm32_base.build_src_filter} +<../variants/rak3x72> -[env:rak3x72-repeater] +[env:rak3x72_repeater] extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Repeater"' @@ -21,7 +21,7 @@ build_flags = ${rak3x72.build_flags} build_src_filter = ${rak3x72.build_src_filter} +<../examples/simple_repeater/*.cpp> -[env:rak3x72-sensor] +[env:rak3x72_sensor] extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Sensor"' diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 04a1f20b..904f5488 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -23,7 +23,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/tenstar_c3> -[env:Tenstar_C3_repeater_sx1262] +[env:Tenstar_C3_sx1262_repeater] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -44,7 +44,7 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} -; [env:Tenstar_C3_repeater_sx1262_bridge_rs232] +; [env:Tenstar_C3_sx1262_repeater_bridge_rs232] ; extends = Tenstar_esp32_C3 ; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> @@ -69,7 +69,7 @@ lib_deps = ; ${Tenstar_esp32_C3.lib_deps} ; ${esp32_ota.lib_deps} -[env:Tenstar_C3_repeater_sx1262_bridge_espnow] +[env:Tenstar_C3_sx1262_repeater_bridge_espnow] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> @@ -93,7 +93,7 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} -[env:Tenstar_C3_repeater_sx1268] +[env:Tenstar_C3_sx1268_repeater] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -113,7 +113,7 @@ lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} -; [env:Tenstar_C3_repeater_sx1268_bridge_rs232] +; [env:Tenstar_C3_sx1268_repeater_bridge_rs232] ; extends = Tenstar_esp32_C3 ; build_src_filter = ${Tenstar_esp32_C3.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> @@ -137,7 +137,7 @@ lib_deps = ; ${Tenstar_esp32_C3.lib_deps} ; ${esp32_ota.lib_deps} -[env:Tenstar_C3_repeater_sx1268_bridge_espnow] +[env:Tenstar_C3_sx1268_repeater_bridge_espnow] extends = Tenstar_esp32_C3 build_src_filter = ${Tenstar_esp32_C3.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> diff --git a/variants/tiny_relay/platformio.ini b/variants/tiny_relay/platformio.ini index 6816ed20..787d99e4 100644 --- a/variants/tiny_relay/platformio.ini +++ b/variants/tiny_relay/platformio.ini @@ -15,7 +15,7 @@ build_flags = ${stm32_base.build_flags} build_src_filter = ${stm32_base.build_src_filter} +<../variants/tiny_relay> -[env:Tiny_Relay-repeater] +[env:Tiny_Relay_repeater] extends = Tiny_Relay build_flags = ${Tiny_Relay.build_flags} -D ADVERT_NAME='"tiny_relay Repeater"' @@ -23,7 +23,7 @@ build_flags = ${Tiny_Relay.build_flags} build_src_filter = ${Tiny_Relay.build_src_filter} +<../examples/simple_repeater/main.cpp> -[env:Tiny_Relay-sensor] +[env:Tiny_Relay_sensor] extends = Tiny_Relay build_flags = ${Tiny_Relay.build_flags} -D ADVERT_NAME='"tiny_relay Sensor"' diff --git a/variants/wio-e5-dev/platformio.ini b/variants/wio-e5-dev/platformio.ini index 350d79fb..c50bc247 100644 --- a/variants/wio-e5-dev/platformio.ini +++ b/variants/wio-e5-dev/platformio.ini @@ -13,7 +13,7 @@ build_flags = ${stm32_base.build_flags} build_src_filter = ${stm32_base.build_src_filter} +<../variants/wio-e5-dev> -[env:wio-e5-repeater] +[env:wio-e5_repeater] extends = lora_e5 build_flags = ${lora_e5.build_flags} -D LORA_TX_POWER=22 diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 30c8ef87..919ee90c 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -16,7 +16,7 @@ build_src_filter = ${stm32_base.build_src_filter} lib_deps = ${stm32_base.lib_deps} finitespace/BME280 @ ^3.0.0 -[env:wio-e5-mini-repeater] +[env:wio-e5-mini_repeater] extends = lora_e5_mini build_flags = ${lora_e5_mini.build_flags} -D LORA_TX_POWER=22 @@ -25,7 +25,7 @@ build_flags = ${lora_e5_mini.build_flags} build_src_filter = ${lora_e5_mini.build_src_filter} +<../examples/simple_repeater/*.cpp> -[env:wio-e5-mini-sensor] +[env:wio-e5-mini_sensor] extends = lora_e5_mini build_flags = ${lora_e5_mini.build_flags} -D LORA_TX_POWER=22 diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 84c6d0f7..fd55abab 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -19,7 +19,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_c3> -[env:Xiao_C3_repeater_sx1262] +[env:Xiao_C3_sx1262_repeater] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> From f8f5f0054903505171d7860cc512e7e415712bf9 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sun, 28 Sep 2025 14:38:13 +1300 Subject: [PATCH 114/546] admin cli neighbors command should sort newest to oldest --- examples/simple_repeater/MyMesh.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e16ae56b..8dbb4565 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -706,9 +706,24 @@ void MyMesh::formatNeighborsReply(char *reply) { char *dp = reply; #if MAX_NEIGHBOURS - for (int i = 0; i < MAX_NEIGHBOURS && dp - reply < 134; i++) { - NeighbourInfo *neighbour = &neighbours[i]; - if (neighbour->heard_timestamp == 0) continue; // skip empty slots + // create copy of neighbours list, skipping empty entries so we can sort it separately from main list + int16_t neighbours_count = 0; + NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS]; + for (int i = 0; i < MAX_NEIGHBOURS; i++) { + auto neighbour = &neighbours[i]; + if (neighbour->heard_timestamp > 0) { + sorted_neighbours[neighbours_count] = neighbour; + neighbours_count++; + } + } + + // sort neighbours newest to oldest + std::sort(sorted_neighbours, sorted_neighbours + neighbours_count, [](const NeighbourInfo* a, const NeighbourInfo* b) { + return a->heard_timestamp > b->heard_timestamp; // desc + }); + + for (int i = 0; i < neighbours_count && dp - reply < 134; i++) { + NeighbourInfo *neighbour = sorted_neighbours[i]; // add new line if not first item if (i > 0) *dp++ = '\n'; From 58ed14d97142945f71a035d3a21a4ffce65746a7 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sun, 28 Sep 2025 15:00:45 +1300 Subject: [PATCH 115/546] build script should check for firmware type suffix --- build.sh | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 79690b54..11a41eb4 100755 --- a/build.sh +++ b/build.sh @@ -24,6 +24,17 @@ get_pio_envs_containing_string() { done } +# $1 should be the string to find (case insensitive) +get_pio_envs_ending_with_string() { + shopt -s nocasematch + envs=($(get_pio_envs)) + for env in "${envs[@]}"; do + if [[ "$env" == *${1} ]]; then + echo $env + fi + done +} + # build firmware for the provided pio env in $1 build_firmware() { @@ -85,6 +96,14 @@ build_all_firmwares_matching() { done } +# firmwares ending with $1 will be built +build_all_firmwares_by_suffix() { + envs=($(get_pio_envs_ending_with_string "$1")) + for env in "${envs[@]}"; do + build_firmware $env + done +} + build_repeater_firmwares() { # # build specific repeater firmwares @@ -96,7 +115,7 @@ build_repeater_firmwares() { # build_firmware "RAK_4631_Repeater" # build all repeater firmwares - build_all_firmwares_matching "repeater" + build_all_firmwares_by_suffix "_repeater" } @@ -115,8 +134,8 @@ build_companion_firmwares() { # build_firmware "t1000e_companion_radio_ble" # build all companion firmwares - build_all_firmwares_matching "companion_radio_usb" - build_all_firmwares_matching "companion_radio_ble" + build_all_firmwares_by_suffix "_companion_radio_usb" + build_all_firmwares_by_suffix "_companion_radio_ble" } @@ -127,7 +146,7 @@ build_room_server_firmwares() { # build_firmware "RAK_4631_room_server" # build all room server firmwares - build_all_firmwares_matching "room_server" + build_all_firmwares_by_suffix "_room_server" } From 0307b6119e362591aa61dc6c95c71a6c619786ac Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sun, 28 Sep 2025 16:11:58 +1300 Subject: [PATCH 116/546] increase MAX_NEIGHBOURS from 8 to 50 --- variants/ebyte_eora_s3/platformio.ini | 2 +- variants/generic-e22/platformio.ini | 12 ++++++------ variants/generic_espnow/platformio.ini | 2 +- variants/heltec_ct62/platformio.ini | 6 +++--- variants/heltec_e213/platformio.ini | 4 ++-- variants/heltec_e290/platformio.ini | 4 ++-- variants/heltec_mesh_solar/platformio.ini | 2 +- variants/heltec_t114/platformio.ini | 4 ++-- variants/heltec_t190/platformio.ini | 4 ++-- variants/heltec_tracker/platformio.ini | 6 +++--- variants/heltec_v2/platformio.ini | 6 +++--- variants/heltec_v3/platformio.ini | 12 ++++++------ variants/heltec_v4/platformio.ini | 4 ++-- variants/heltec_wireless_paper/platformio.ini | 4 ++-- variants/ikoka_stick_nrf/platformio.ini | 2 +- variants/lilygo_t3s3/platformio.ini | 6 +++--- variants/lilygo_t3s3_sx1276/platformio.ini | 6 +++--- variants/lilygo_tbeam_SX1262/platformio.ini | 6 +++--- variants/lilygo_tbeam_SX1276/platformio.ini | 6 +++--- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 6 +++--- variants/lilygo_tdeck/platformio.ini | 2 +- variants/lilygo_techo/platformio.ini | 2 +- variants/lilygo_techo_lite/platformio.ini | 2 +- variants/lilygo_tlora_c6/platformio.ini | 2 +- variants/lilygo_tlora_v2_1/platformio.ini | 6 +++--- variants/mesh_pocket/platformio.ini | 2 +- variants/meshadventurer/platformio.ini | 12 ++++++------ variants/minewsemi_me25ls01/platformio.ini | 6 +++--- variants/promicro/platformio.ini | 2 +- variants/rak4631/platformio.ini | 2 +- variants/rak_wismesh_tag/platformio.ini | 2 +- variants/rpi_picow/platformio.ini | 2 +- variants/sensecap_solar/platformio.ini | 2 +- variants/station_g2/platformio.ini | 12 ++++++------ variants/t1000-e/platformio.ini | 2 +- variants/tenstar_c3/platformio.ini | 12 ++++++------ variants/thinknode_m1/platformio.ini | 2 +- variants/waveshare_rp2040_lora/platformio.ini | 4 ++-- variants/wio-tracker-l1/platformio.ini | 2 +- variants/xiao_c3/platformio.ini | 2 +- variants/xiao_c6/platformio.ini | 6 +++--- variants/xiao_nrf52/platformio.ini | 2 +- variants/xiao_rp2040/platformio.ini | 2 +- variants/xiao_s3_wio/platformio.ini | 6 +++--- 44 files changed, 100 insertions(+), 100 deletions(-) diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini index a7e6fe22..df622a34 100644 --- a/variants/ebyte_eora_s3/platformio.ini +++ b/variants/ebyte_eora_s3/platformio.ini @@ -50,7 +50,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 2f61f412..3ee90841 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -40,7 +40,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -61,7 +61,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -85,7 +85,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 @@ -107,7 +107,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -128,7 +128,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -152,7 +152,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index db87a81b..c40833dc 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -42,7 +42,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 build_src_filter = ${Generic_ESPNOW.build_src_filter} +<../examples/simple_repeater/*.cpp> lib_deps = diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 1b83adbf..cd57edda 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -40,7 +40,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} @@ -57,7 +57,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -78,7 +78,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index a6fe2560..857307e1 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -102,7 +102,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -125,7 +125,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 0223b30c..1f31e558 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -98,7 +98,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -121,7 +121,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index c5d8c3e8..7bfbac85 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -37,7 +37,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 4bbc05b1..53fd5e02 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -50,7 +50,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -142,7 +142,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 52bb79e0..3c83bff0 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -102,7 +102,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -123,7 +123,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 5c0df007..c95dfdd8 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -70,7 +70,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_base.build_src_filter} @@ -90,7 +90,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -114,7 +114,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index d2afe4db..9c62c0a8 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -29,7 +29,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} @@ -49,7 +49,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -73,7 +73,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 417f6edb..d31648bc 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -38,7 +38,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -58,7 +58,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=5 -D WITH_RS232_BRIDGE_TX=6 @@ -81,7 +81,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 @@ -221,7 +221,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -239,7 +239,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=5 -D WITH_RS232_BRIDGE_TX=6 @@ -261,7 +261,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index d50c27a5..5fc9350e 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -55,7 +55,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} @@ -75,7 +75,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 43ac2a82..585fbbc9 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -77,7 +77,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -100,7 +100,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 071d2d4f..13d82aca 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -148,7 +148,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 9ca7f5ef..c5d4175e 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -42,7 +42,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} @@ -61,7 +61,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -84,7 +84,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 9df1fe0c..a98c17d9 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -40,7 +40,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} @@ -59,7 +59,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -82,7 +82,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index f7d1a764..fc466fb1 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -65,7 +65,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} @@ -82,7 +82,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -103,7 +103,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index d7e119ef..3f88792a 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -62,7 +62,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D PERSISTANT_GPS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -80,7 +80,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D PERSISTANT_GPS=1 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 @@ -102,7 +102,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D PERSISTANT_GPS=1 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 328ebf07..b7b0ed71 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -43,7 +43,7 @@ build_flags = -D ADVERT_LAT=0 -D ADVERT_LON=0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} @@ -60,7 +60,7 @@ lib_deps = ; -D ADVERT_LAT=0 ; -D ADVERT_LON=0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -81,7 +81,7 @@ build_flags = -D ADVERT_LAT=0 -D ADVERT_LON=0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index ade370f7..adfe9d1e 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -87,7 +87,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 build_src_filter = ${LilyGo_TDeck.build_src_filter} +<../examples/simple_repeater> +<helpers/ui/ST7789LCDDisplay.cpp> diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index e814ea54..75a101e9 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -59,7 +59,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index d3748fd1..0ba6a197 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -53,7 +53,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index f8a935cd..308c85eb 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -40,7 +40,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 0f62243c..f2e0caee 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -56,7 +56,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 @@ -172,7 +172,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=34 -D WITH_RS232_BRIDGE_TX=25 @@ -195,7 +195,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/mesh_pocket/platformio.ini b/variants/mesh_pocket/platformio.ini index 1ed0d1ec..015c2ca4 100644 --- a/variants/mesh_pocket/platformio.ini +++ b/variants/mesh_pocket/platformio.ini @@ -47,7 +47,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index 270e0314..8b8a9395 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -48,7 +48,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -70,7 +70,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -95,7 +95,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 @@ -118,7 +118,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -140,7 +140,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -165,7 +165,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index da234dd2..fd9c3819 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -90,7 +90,7 @@ build_flags = ${me25ls01.build_flags} -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_repeater> @@ -116,7 +116,7 @@ build_flags = ${me25ls01.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_room_server> @@ -140,7 +140,7 @@ build_flags = ${me25ls01.build_flags} -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D ROOM_PASSWORD='"hello"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_secure_chat/main.cpp> diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index afd3bbcf..b1c0c4ea 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -46,7 +46,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index a1ccdc29..a8807e36 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -39,7 +39,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 28499888..a55cec19 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -43,7 +43,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak_wismesh_tag.build_src_filter} diff --git a/variants/rpi_picow/platformio.ini b/variants/rpi_picow/platformio.ini index a0a80f76..ec5cdb83 100644 --- a/variants/rpi_picow/platformio.ini +++ b/variants/rpi_picow/platformio.ini @@ -34,7 +34,7 @@ build_flags = ${rpi_picow.build_flags} -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rpi_picow.build_src_filter} diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index 8655021c..d4fb7b44 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -42,7 +42,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${SenseCap_Solar.build_src_filter} diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 908d6443..aacb2dfb 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -36,7 +36,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -53,7 +53,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -74,7 +74,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 @@ -94,7 +94,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D MESH_PACKET_LOGGING=1 -D SX126X_RX_BOOSTED_GAIN=1 ; https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance @@ -113,7 +113,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D SX126X_RX_BOOSTED_GAIN=1 ; -D WITH_RS232_BRIDGE=Serial2 @@ -135,7 +135,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D MESH_PACKET_LOGGING=1 -D SX126X_RX_BOOSTED_GAIN=1 -D WITH_ESPNOW_BRIDGE=1 diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 69d9dccf..6bb3bdb5 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -41,7 +41,7 @@ build_flags = ${t1000-e.build_flags} -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${t1000-e.build_src_filter} diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 904f5488..b560bbf5 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -37,7 +37,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -59,7 +59,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -84,7 +84,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 @@ -106,7 +106,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -127,7 +127,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -151,7 +151,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index a0273ce2..ade487e9 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -42,7 +42,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M1.build_src_filter} diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index cf113189..a6c6b484 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -40,7 +40,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} @@ -53,7 +53,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=9 -D WITH_RS232_BRIDGE_TX=8 diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index e316408a..971a32cb 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -33,7 +33,7 @@ build_flags = ${WioTrackerL1.build_flags} -D ADVERT_NAME='"WioTrackerL1 Repeater"' -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D DISPLAY_CLASS=SH1106Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index fd55abab..1f27dfc8 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -33,7 +33,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index ab032224..6e963432 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -40,7 +40,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -100,7 +100,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 lib_deps = ${Meshimi.lib_deps} @@ -157,7 +157,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 8054c72d..888e9493 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -82,7 +82,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} diff --git a/variants/xiao_rp2040/platformio.ini b/variants/xiao_rp2040/platformio.ini index 4a1ee4ef..2b3e7442 100644 --- a/variants/xiao_rp2040/platformio.ini +++ b/variants/xiao_rp2040/platformio.ini @@ -36,7 +36,7 @@ build_flags = ${Xiao_rp2040.build_flags} -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${Xiao_rp2040.build_src_filter} diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 69cb5c1f..6acde324 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -37,7 +37,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -55,7 +55,7 @@ lib_deps = ; -D ADVERT_LAT=0.0 ; -D ADVERT_LON=0.0 ; -D ADMIN_PASSWORD='"password"' -; -D MAX_NEIGHBOURS=8 +; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 @@ -76,7 +76,7 @@ build_flags = -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=8 + -D MAX_NEIGHBOURS=50 -D WITH_ESPNOW_BRIDGE=1 -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 From 030f0d5d82f2fc2edee2ec23908e88ab2010249e Mon Sep 17 00:00:00 2001 From: fdlamotte <florent@frizoncorrea.fr> Date: Sun, 28 Sep 2025 09:16:45 +0200 Subject: [PATCH 117/546] location provider: reduce reset delay --- src/helpers/sensors/MicroNMEALocationProvider.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 678baf4d..ec82f25e 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -67,7 +67,7 @@ public : void reset() override { if (_pin_reset != -1) { digitalWrite(_pin_reset, GPS_RESET_FORCE); - delay(100); + delay(10); digitalWrite(_pin_reset, !GPS_RESET_FORCE); } } @@ -123,4 +123,4 @@ public : } } } -}; \ No newline at end of file +}; From c83abbeff67a71f9cace583dc99d1d177e39a811 Mon Sep 17 00:00:00 2001 From: fdlamotte <florent@frizoncorrea.fr> Date: Sun, 28 Sep 2025 09:20:59 +0200 Subject: [PATCH 118/546] ESM: add gps reset after begin --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index faa04275..99605ff3 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -438,6 +438,7 @@ void EnvironmentSensorManager::initBasicGPS() { // Try to detect if GPS is physically connected to determine if we should expose the setting _location->begin(); + _location->reset(); #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("No GPS wake/reset pin found for this board. Continuing on..."); @@ -545,6 +546,7 @@ void EnvironmentSensorManager::start_gps() { #endif _location->begin(); + _location->reset(); #ifndef PIN_GPS_RESET MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged"); From db7635102d77ffb8609fd4206c86b12458fc3450 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 28 Sep 2025 09:43:28 +0200 Subject: [PATCH 119/546] gps_page: enable if gps enabled --- examples/companion_radio/ui-new/UITask.cpp | 8 ++++---- variants/wio-tracker-l1-eink/platformio.ini | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index c48c6972..d124a494 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -75,7 +75,7 @@ class HomeScreen : public UIScreen { RADIO, BLUETOOTH, ADVERT, -#if UI_GPS_PAGE == 1 +#if ENV_INCLUDE_GPS == 1 GPS, #endif #if UI_SENSORS_PAGE == 1 @@ -173,7 +173,7 @@ public: // curr page indicator int y = 14; - int x = display.width() / 2 - 25; + int x = display.width() / 2 - 5 * (HomePage::Count-1); for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) { if (i == _page) { display.fillRect(x-1, y-1, 3, 3); @@ -253,7 +253,7 @@ public: 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); -#if UI_GPS_PAGE == 1 +#if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); int y = 18; @@ -408,7 +408,7 @@ public: } return true; } -#if UI_GPS_PAGE == 1 +#if ENV_INCLUDE_GPS == 1 if (c == KEY_ENTER && _page == HomePage::GPS) { _task->toggleGPS(); return true; diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index db22c01b..a41e9e23 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -55,7 +55,6 @@ build_flags = ${WioTrackerL1Eink.build_flags} ; -D MESH_DEBUG=1 -D UI_RECENT_LIST_SIZE=6 -D UI_SENSORS_PAGE=1 - -D UI_GPS_PAGE=1 build_src_filter = ${WioTrackerL1Eink.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/MomentaryButton.cpp> From 3f4f9eff17d6156399f3000d9cd60a7a85234e96 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sun, 28 Sep 2025 21:01:41 +1300 Subject: [PATCH 120/546] fix multiple candidates warning --- src/Identity.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Identity.h b/src/Identity.h index 24a7c0ff..42fb9d9a 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -64,7 +64,7 @@ public: * \param secret OUT - the 'shared secret' (must be PUB_KEY_SIZE bytes) * \param other IN - the second party in the exchange. */ - void calcSharedSecret(uint8_t* secret, const Identity& other) { calcSharedSecret(secret, other.pub_key); } + void calcSharedSecret(uint8_t* secret, const Identity& other) const { calcSharedSecret(secret, other.pub_key); } /** * \brief the ECDH key exhange, with Ed25519 public key transposed to Ex25519. From b92d9bd97281d285c8fcf15f23b6c24b3d64fd58 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 28 Sep 2025 19:24:00 +1000 Subject: [PATCH 121/546] * ver 1.9.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 20efb46b..d2a66499 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 7 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "1 Sep 2025" +#define FIRMWARE_BUILD_DATE "28 Sep 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.8.1" +#define FIRMWARE_VERSION "v1.9.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 798d31f2..d5e6840b 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -65,11 +65,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "1 Sep 2025" + #define FIRMWARE_BUILD_DATE "28 Sep 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.8.1" + #define FIRMWARE_VERSION "v1.9.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index dc7a6b5a..468692b5 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -25,11 +25,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "1 Sep 2025" + #define FIRMWARE_BUILD_DATE "28 Sep 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.8.1" + #define FIRMWARE_VERSION "v1.9.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 7f7ee794..0b7365f4 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -32,11 +32,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "1 Sep 2025" + #define FIRMWARE_BUILD_DATE "28 Sep 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.8.1" + #define FIRMWARE_VERSION "v1.9.0" #endif #define FIRMWARE_ROLE "sensor" From 914001344f1e30a3698e8ee06a4f3c8f39890706 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sun, 28 Sep 2025 23:32:04 +1300 Subject: [PATCH 122/546] add missing build flags for failed builds --- variants/heltec_e213/platformio.ini | 2 ++ variants/heltec_e290/platformio.ini | 2 ++ variants/heltec_t190/platformio.ini | 2 ++ variants/heltec_wireless_paper/platformio.ini | 2 ++ 4 files changed, 8 insertions(+) diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 857307e1..c703539f 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -86,6 +86,8 @@ build_flags = -D ADVERT_NAME='"Heltec E213 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${Heltec_E213_base.build_src_filter} +<helpers/ui/E213Display.cpp> +<../examples/simple_repeater> diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 1f31e558..c912998b 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -82,6 +82,8 @@ build_flags = -D ADVERT_NAME='"Heltec E290 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${Heltec_E290_base.build_src_filter} +<helpers/ui/E290Display.cpp> +<../examples/simple_repeater> diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 3c83bff0..bda689f4 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -88,6 +88,8 @@ build_flags = -D ADVERT_NAME='"Heltec T190 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${Heltec_T190_base.build_src_filter} +<../examples/simple_repeater> lib_deps = diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 585fbbc9..8aa075f0 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -61,6 +61,8 @@ build_flags = -D ADVERT_NAME='"Heltec WP Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} +<helpers/ui/E213Display.cpp> +<../examples/simple_repeater> From fc0cf5f3708f1283017fe9aa430f85e00647e0e8 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Mon, 29 Sep 2025 07:48:26 +0800 Subject: [PATCH 123/546] =?UTF-8?q?=F0=9F=94=A7=20chore:=20update=20tiny?= =?UTF-8?q?=5Frelay=20platformio.ini=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ADVERT_LAT and ADVERT_LON definitions for both repeater and sensor variants - Set MAX_NEIGHBOURS to 50 for improved network capacity - Fix repeater build source filter path to include entire directory --- variants/tiny_relay/platformio.ini | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/variants/tiny_relay/platformio.ini b/variants/tiny_relay/platformio.ini index 787d99e4..ed178727 100644 --- a/variants/tiny_relay/platformio.ini +++ b/variants/tiny_relay/platformio.ini @@ -19,15 +19,21 @@ build_src_filter = ${stm32_base.build_src_filter} extends = Tiny_Relay build_flags = ${Tiny_Relay.build_flags} -D ADVERT_NAME='"tiny_relay Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${Tiny_Relay.build_src_filter} - +<../examples/simple_repeater/main.cpp> + +<../examples/simple_repeater> [env:Tiny_Relay_sensor] extends = Tiny_Relay build_flags = ${Tiny_Relay.build_flags} -D ADVERT_NAME='"tiny_relay Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${Tiny_Relay.build_src_filter} +<../examples/simple_sensor> From ec48e6acfcb2167482cf959321cdf8675aef0ccb Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Mon, 29 Sep 2025 15:24:25 +1300 Subject: [PATCH 124/546] added 'board' cli command to get board name --- src/helpers/CommonCLI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 7125e5b0..68acdf2b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -399,6 +399,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "File system erase: %s", s ? "OK" : "Err"); } else if (memcmp(command, "ver", 3) == 0) { sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); + } else if (memcmp(command, "board", 5) == 0) { + sprintf(reply, "%s", _board->getManufacturerName()); } else if (memcmp(command, "log start", 9) == 0) { _callbacks->setLoggingOn(true); strcpy(reply, " logging on"); From 6a1f8d65c992012d42141d354c03124e0efcabf6 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Tue, 30 Sep 2025 00:31:10 +1300 Subject: [PATCH 125/546] add missing null terminator for login payload --- examples/simple_repeater/MyMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 8dbb4565..df945d45 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -405,6 +405,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m uint32_t timestamp; memcpy(×tamp, data, 4); + data[len] = 0; // ensure null terminator uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); if (reply_len == 0) return; // invalid request From 18bfc2d81a45589f810ab7afc20a25810b8fa7e3 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Tue, 30 Sep 2025 09:21:12 +0200 Subject: [PATCH 126/546] DisplayDriver: introduce drawTextRightAlign and drawTextLeftAlign --- examples/companion_radio/ui-new/UITask.cpp | 31 +++++++--------------- src/helpers/ui/DisplayDriver.h | 9 +++++++ 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d124a494..c3da0643 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -257,40 +257,27 @@ public: } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); int y = 18; - display.setCursor(0, y); - display.print(_task->getGPSState() ? "gps on" : "gps off"); + display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off"); if (nmea == NULL) { y = y + 12; - display.setCursor(0, y); - display.print("Can't access GPS"); + display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { char buf[50]; strcpy(buf, nmea->isValid()?"fix":"no fix"); - display.setCursor( - display.width()-display.getTextWidth(buf)-1, y); - display.print(buf); + display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; - display.setCursor(0,y); - display.print("sat"); + display.drawTextLeftAlign(0, y, "sat"); sprintf(buf, "%d", nmea->satellitesCount()); - display.setCursor( - display.width()-display.getTextWidth(buf)-1, y); - display.print(buf); + display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; - display.setCursor(0,y); - display.print("pos"); + display.drawTextLeftAlign(0, y, "pos"); sprintf(buf, "%.4f %.4f", nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); - display.setCursor( - display.width()-display.getTextWidth(buf)-1, y); - display.print(buf); + display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; - display.setCursor(0,y); - display.print("alt"); + display.drawTextLeftAlign(0, y, "alt"); sprintf(buf, "%.2f", nmea->getAltitude()/1000.); - display.setCursor( - display.width()-display.getTextWidth(buf)-1, y); - display.print(buf); + display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; } #endif diff --git a/src/helpers/ui/DisplayDriver.h b/src/helpers/ui/DisplayDriver.h index 32839edc..ec63c191 100644 --- a/src/helpers/ui/DisplayDriver.h +++ b/src/helpers/ui/DisplayDriver.h @@ -32,6 +32,15 @@ public: setCursor(mid_x - w/2, y); print(str); } + virtual void drawTextRightAlign(int x_anch, int y, const char* str) { + int w = getTextWidth(str); + setCursor(x_anch - w, y); + print(str); + } + virtual void drawTextLeftAlign(int x_anch, int y, const char* str) { + setCursor(x_anch, y); + print(str); + } // convert UTF-8 characters to displayable block characters for compatibility virtual void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) { From 8d8b9a614107cb4372c3d9c255c88943779008e4 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 2 Oct 2025 12:52:19 +1000 Subject: [PATCH 127/546] * ver 1.9.1 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index d2a66499..e6400871 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 7 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "28 Sep 2025" +#define FIRMWARE_BUILD_DATE "2 Oct 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.9.0" +#define FIRMWARE_VERSION "v1.9.1" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d5e6840b..05a8d13b 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -65,11 +65,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "28 Sep 2025" + #define FIRMWARE_BUILD_DATE "2 Oct 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.0" + #define FIRMWARE_VERSION "v1.9.1" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 468692b5..b2df60c3 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -25,11 +25,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "28 Sep 2025" + #define FIRMWARE_BUILD_DATE "2 Oct 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.0" + #define FIRMWARE_VERSION "v1.9.1" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 0b7365f4..d26bcb14 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -32,11 +32,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "28 Sep 2025" + #define FIRMWARE_BUILD_DATE "2 Oct 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.0" + #define FIRMWARE_VERSION "v1.9.1" #endif #define FIRMWARE_ROLE "sensor" From aa946bbe3671ea025196de249f4f6232e616fe92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Thu, 2 Oct 2025 09:47:00 +0100 Subject: [PATCH 128/546] WITH_BRIDGE was not implementing setBridgeState() --- src/helpers/CommonCLI.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 4344ff1d..364cf518 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -57,10 +57,12 @@ public: virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; -#ifdef WITH_ESPNOW_BRIDGE +#ifdef WITH_BRIDGE virtual void setBridgeState(bool enable) = 0; +#ifdef WITH_ESPNOW_BRIDGE virtual void updateBridgeChannel(int ch) = 0; #endif +#endif }; class CommonCLI { From 262e9864e75c346d625b6c99d5ee6033908a24f5 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Thu, 2 Oct 2025 12:18:47 +0200 Subject: [PATCH 129/546] stm32: upd repeater targets --- variants/rak3x72/platformio.ini | 1 + variants/wio-e5-dev/platformio.ini | 16 ++++++++++++++++ variants/wio-e5-mini/platformio.ini | 1 + 3 files changed, 18 insertions(+) diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index 67db107a..a6260089 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -18,6 +18,7 @@ extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Repeater"' -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${rak3x72.build_src_filter} +<../examples/simple_repeater/*.cpp> diff --git a/variants/wio-e5-dev/platformio.ini b/variants/wio-e5-dev/platformio.ini index c50bc247..d7e63c83 100644 --- a/variants/wio-e5-dev/platformio.ini +++ b/variants/wio-e5-dev/platformio.ini @@ -19,9 +19,25 @@ build_flags = ${lora_e5.build_flags} -D LORA_TX_POWER=22 -D ADVERT_NAME='"WIO-E5 Repeater"' -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${lora_e5.build_src_filter} +<../examples/simple_repeater/*.cpp> +[env:wio-e5-repeater_bridge_rs232] +extends = lora_e5 +build_flags = ${lora_e5.build_flags} + -D LORA_TX_POWER=22 + -D ADVERT_NAME='"WIO-E5 Repeater"' + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D ENABLE_HWSERIAL2 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=PA3 + -D WITH_RS232_BRIDGE_TX=PA2 +build_src_filter = ${lora_e5.build_src_filter} + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater/*.cpp> + [env:wio-e5_companion_radio_usb] extends = lora_e5 build_flags = ${lora_e5.build_flags} diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index 919ee90c..83784443 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -22,6 +22,7 @@ build_flags = ${lora_e5_mini.build_flags} -D LORA_TX_POWER=22 -D ADVERT_NAME='"wio-e5-mini Repeater"' -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 build_src_filter = ${lora_e5_mini.build_src_filter} +<../examples/simple_repeater/*.cpp> From 8edcb46a28271be84d1f5e422fa7e4ee5f6ec963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Fri, 3 Oct 2025 00:20:09 +0100 Subject: [PATCH 130/546] Bridge: enhance CLI configuration options --- examples/simple_repeater/MyMesh.cpp | 31 +++++--- examples/simple_repeater/MyMesh.h | 30 ++++---- src/helpers/AbstractBridge.h | 2 +- src/helpers/CommonCLI.cpp | 106 ++++++++++++++++++++------- src/helpers/CommonCLI.h | 22 ++++-- src/helpers/bridges/BridgeBase.cpp | 3 +- src/helpers/bridges/BridgeBase.h | 18 ++--- src/helpers/bridges/ESPNowBridge.cpp | 17 ++--- src/helpers/bridges/ESPNowBridge.h | 35 ++------- src/helpers/bridges/RS232Bridge.cpp | 11 ++- src/helpers/bridges/RS232Bridge.h | 5 +- 11 files changed, 162 insertions(+), 118 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4806c28a..3124354e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -231,6 +231,12 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) { } void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { +#ifdef WITH_BRIDGE + if (_prefs.bridge_pkt_src == 1) { + bridge.sendPacket(pkt); + } +#endif + if (_logging) { File f = openAppend(PACKET_LOG_FILE); if (f) { @@ -252,8 +258,11 @@ void MyMesh::logRx(mesh::Packet *pkt, int len, float score) { void MyMesh::logTx(mesh::Packet *pkt, int len) { #ifdef WITH_BRIDGE - bridge.onPacketTransmitted(pkt); + if (_prefs.bridge_pkt_src == 0) { + bridge.sendPacket(pkt); + } #endif + if (_logging) { File f = openAppend(PACKET_LOG_FILE); if (f) { @@ -481,9 +490,10 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) #if defined(WITH_RS232_BRIDGE) - , bridge(WITH_RS232_BRIDGE, _mgr, &rtc) -#elif defined(WITH_ESPNOW_BRIDGE) - , bridge(_mgr, &rtc) + , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) +#endif +#if defined(WITH_ESPNOW_BRIDGE) + , bridge(&_prefs, _mgr, &rtc) #endif { next_local_advert = next_flood_advert = 0; @@ -513,8 +523,14 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.flood_advert_interval = 12; // 12 hours _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled - _prefs.bridge_enabled = 1; // enabled - _prefs.bridge_channel = 0; // auto + + // bridge defaults + _prefs.bridge_enabled = 1; // enabled + _prefs.bridge_delay = 500; // milliseconds + _prefs.bridge_pkt_src = 0; // logTx + _prefs.bridge_baud = 115200; // baud rate + _prefs.bridge_channel = 1; // channel 1 + StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret)); } void MyMesh::begin(FILESYSTEM *fs) { @@ -525,9 +541,6 @@ void MyMesh::begin(FILESYSTEM *fs) { acl.load(_fs); -#if defined(WITH_ESPNOW_BRIDGE) - bridge.setChannel(_prefs.bridge_channel); -#endif #if defined(WITH_BRIDGE) if (_prefs.bridge_enabled) { bridge.begin(); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index c1d1f264..2e84843a 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -165,23 +165,6 @@ public: void updateAdvertTimer() override; void updateFloodAdvertTimer() override; -#if defined(WITH_BRIDGE) - void setBridgeState(bool enable) { - if (enable == bridge.getState()) return; - enable ? bridge.begin() : bridge.end(); - } - -#if defined(WITH_ESPNOW_BRIDGE) - void updateBridgeChannel(int ch) override { - bridge.setChannel(ch); - if (bridge.getState()) { - bridge.end(); - bridge.begin(); - } - } -#endif -#endif - void setLoggingOn(bool enable) override { _logging = enable; } void eraseLogFile() override { @@ -199,4 +182,17 @@ public: void clearStats() override; void handleCommand(uint32_t sender_timestamp, char* command, char* reply); void loop(); + +#if defined(WITH_BRIDGE) + void setBridgeState(bool enable) override { + if (enable == bridge.getState()) return; + enable ? bridge.begin() : bridge.end(); + } + + void restartBridge() override { + if (!bridge.getState()) return; + bridge.end(); + bridge.begin(); + } +#endif }; diff --git a/src/helpers/AbstractBridge.h b/src/helpers/AbstractBridge.h index 73a967d8..89e2dcdd 100644 --- a/src/helpers/AbstractBridge.h +++ b/src/helpers/AbstractBridge.h @@ -35,7 +35,7 @@ public: * * @param packet The packet that was transmitted. */ - virtual void onPacketTransmitted(mesh::Packet* packet) = 0; + virtual void sendPacket(mesh::Packet* packet) = 0; /** * @brief Processes a received packet from the bridge's medium. diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 97c7eada..d7eba363 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -59,7 +59,11 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 file.read((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127 - file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 128 + file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128 + file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130 + file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 + file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132 + file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -72,7 +76,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); + + // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); + _prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000); + _prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1); + _prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200); _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); file.close(); @@ -119,7 +128,11 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 file.write((uint8_t *)&_prefs->bridge_enabled, sizeof(_prefs->bridge_enabled)); // 127 - file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 128 + file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128 + file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130 + file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 + file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132 + file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133 file.close(); } @@ -205,6 +218,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(command, "clear stats", 11) == 0) { _callbacks->clearStats(); strcpy(reply, "(OK - stats reset)"); + /* + * GET commands + */ } else if (memcmp(command, "get ", 4) == 0) { const char* config = &command[4]; if (memcmp(config, "af", 2) == 0) { @@ -261,38 +277,29 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch #ifdef WITH_BRIDGE } else if (memcmp(config, "bridge.enabled", 14) == 0) { sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off"); + } else if (memcmp(config, "bridge.delay", 12) == 0) { + sprintf(reply, "> %d", (uint32_t)_prefs->bridge_delay); + } else if (memcmp(config, "bridge.source", 13) == 0) { + sprintf(reply, "> %s", _prefs->bridge_pkt_src ? "logRx" : "logTx"); +#endif +#ifdef WITH_RS232_BRIDGE + } else if (memcmp(config, "bridge.baud", 11) == 0) { + sprintf(reply, "> %d", (uint32_t)_prefs->bridge_baud); +#endif #ifdef WITH_ESPNOW_BRIDGE } else if (memcmp(config, "bridge.channel", 14) == 0) { sprintf(reply, "> %d", (uint32_t)_prefs->bridge_channel); -#endif + } else if (memcmp(config, "bridge.secret", 13) == 0) { + sprintf(reply, "> %s", _prefs->bridge_secret); #endif } else { sprintf(reply, "??: %s", config); } + /* + * SET commands + */ } else if (memcmp(command, "set ", 4) == 0) { const char* config = &command[4]; -#ifdef WITH_BRIDGE - if (memcmp(config, "bridge.enabled ", 15) == 0) { - _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0; - _callbacks->setBridgeState(_prefs->bridge_enabled); - savePrefs(); - strcpy(reply, "OK"); - } - else -#ifdef WITH_ESPNOW_BRIDGE - if (memcmp(config, "bridge.channel ", 15) == 0) { - int ch = atoi(&config[15]); - if (ch > 0 && ch < 15) { - _prefs->bridge_channel = (uint8_t)ch; - _callbacks->updateBridgeChannel(ch); - savePrefs(); - strcpy(reply, "OK"); - } else { - strcpy(reply, "Error: channel must be 0 (AUTO) or 1-14"); - } - } else -#endif -#endif if (memcmp(config, "af ", 3) == 0) { _prefs->airtime_factor = atof(&config[3]); savePrefs(); @@ -428,6 +435,55 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->freq = atof(&config[5]); savePrefs(); strcpy(reply, "OK - reboot to apply"); +#ifdef WITH_BRIDGE + } else if (memcmp(config, "bridge.enabled ", 15) == 0) { + _prefs->bridge_enabled = memcmp(&config[15], "on", 2) == 0; + _callbacks->setBridgeState(_prefs->bridge_enabled); + savePrefs(); + strcpy(reply, "OK"); + } else if (memcmp(config, "bridge.delay ", 13) == 0) { + int delay = _atoi(&config[13]); + if (delay >= 0 && delay <= 10000) { + _prefs->bridge_delay = (uint16_t)delay; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error: delay must be between 0-10000 ms"); + } + } else if (memcmp(config, "bridge.source ", 14) == 0) { + _prefs->bridge_pkt_src = memcmp(&config[14], "rx", 2) == 0; + savePrefs(); + strcpy(reply, "OK"); +#endif +#ifdef WITH_RS232_BRIDGE + } else if (memcmp(config, "bridge.baud ", 12) == 0) { + uint32_t baud = atoi(&config[12]); + if (baud >= 9600 && baud <= 115200) { + _prefs->bridge_baud = (uint32_t)baud; + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error: baud rate must be between 9600-115200"); + } +#endif +#ifdef WITH_ESPNOW_BRIDGE + } else if (memcmp(config, "bridge.channel ", 15) == 0) { + int ch = atoi(&config[15]); + if (ch > 0 && ch < 15) { + _prefs->bridge_channel = (uint8_t)ch; + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error: channel must be between 1-14"); + } + } else if (memcmp(config, "bridge.secret ", 14) == 0) { + StrHelper::strncpy(_prefs->bridge_secret, &config[14], sizeof(_prefs->bridge_secret)); + _callbacks->restartBridge(); + savePrefs(); + strcpy(reply, "OK"); +#endif } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 364cf518..227a9efb 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -30,8 +30,13 @@ struct NodePrefs { // persisted to file uint8_t flood_max; uint8_t interference_threshold; uint8_t agc_reset_interval; // secs / 4 - uint8_t bridge_enabled; // boolean - uint8_t bridge_channel; // 1-14 + // Bridge settings + uint8_t bridge_enabled; // boolean + uint16_t bridge_delay; // milliseconds (default 500 ms) + uint8_t bridge_pkt_src; // 0 = logTx, 1 = logRx (default logTx) + uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) + uint8_t bridge_channel; // 1-14 (ESP-NOW only) + char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) }; class CommonCLICallbacks { @@ -57,12 +62,13 @@ public: virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; -#ifdef WITH_BRIDGE - virtual void setBridgeState(bool enable) = 0; -#ifdef WITH_ESPNOW_BRIDGE - virtual void updateBridgeChannel(int ch) = 0; -#endif -#endif + virtual void setBridgeState(bool enable) { + // no op by default + }; + + virtual void restartBridge() { + // no op by default + }; }; class CommonCLI { diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp index 9434b23b..527ec358 100644 --- a/src/helpers/bridges/BridgeBase.cpp +++ b/src/helpers/bridges/BridgeBase.cpp @@ -40,7 +40,8 @@ void BridgeBase::handleReceivedPacket(mesh::Packet *packet) { } if (!_seen_packets.hasSeen(packet)) { - _mgr->queueInbound(packet, millis() + BRIDGE_DELAY); + // bridge_delay provides a buffer to prevent immediate processing conflicts in the mesh network. + _mgr->queueInbound(packet, millis() + _prefs->bridge_delay); } else { _mgr->free(packet); } diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index a3b1c85e..20b136e2 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/src/helpers/bridges/BridgeBase.h @@ -1,6 +1,7 @@ #pragma once #include "helpers/AbstractBridge.h" +#include "helpers/CommonCLI.h" #include "helpers/SimpleMeshTables.h" #include <RTClib.h> @@ -48,34 +49,31 @@ public: static constexpr uint16_t BRIDGE_LENGTH_SIZE = sizeof(uint16_t); static constexpr uint16_t BRIDGE_CHECKSUM_SIZE = sizeof(uint16_t); - /** - * @brief Default delay in milliseconds for scheduling inbound packet processing - * - * It provides a buffer to prevent immediate processing conflicts in the mesh network. - * Used in handleReceivedPacket() as: millis() + BRIDGE_DELAY - */ - static constexpr uint16_t BRIDGE_DELAY = 500; // TODO: maybe too high ? - protected: /** Tracks bridge state */ bool _initialized = false; - + /** Packet manager for allocating and queuing mesh packets */ mesh::PacketManager *_mgr; /** RTC clock for timestamping debug messages */ mesh::RTCClock *_rtc; + /** Node preferences for configuration settings */ + NodePrefs *_prefs; + /** Tracks seen packets to prevent loops in broadcast communications */ SimpleMeshTables _seen_packets; /** * @brief Constructs a BridgeBase instance * + * @param prefs Node preferences for configuration settings * @param mgr PacketManager for allocating and queuing packets * @param rtc RTCClock for timestamping debug messages */ - BridgeBase(mesh::PacketManager *mgr, mesh::RTCClock *rtc) : _mgr(mgr), _rtc(rtc) {} + BridgeBase(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc) + : _prefs(prefs), _mgr(mgr), _rtc(rtc) {} /** * @brief Gets formatted date/time string for logging diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index c508c5a0..a8a6fb53 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -21,8 +21,8 @@ void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) { } } -ESPNowBridge::ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc) - : BridgeBase(mgr, rtc), _rx_buffer_pos(0) { +ESPNowBridge::ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc) + : BridgeBase(prefs, mgr, rtc), _rx_buffer_pos(0) { _instance = this; } @@ -33,8 +33,8 @@ void ESPNowBridge::begin() { WiFi.mode(WIFI_STA); // Set wifi channel - if (esp_wifi_set_channel(_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: Error setting WIFI channel to %d\n", getLogDateTime(), _channel); + if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) { + Serial.printf("%s: ESPNOW BRIDGE: Error setting WIFI channel to %d\n", getLogDateTime(), _prefs->bridge_channel); return; } @@ -52,7 +52,7 @@ void ESPNowBridge::begin() { esp_now_peer_info_t peerInfo = {}; memset(&peerInfo, 0, sizeof(peerInfo)); memset(peerInfo.peer_addr, 0xFF, ESP_NOW_ETH_ALEN); // Broadcast address - peerInfo.channel = _channel; + peerInfo.channel = _prefs->bridge_channel; peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { @@ -94,9 +94,9 @@ void ESPNowBridge::loop() { } void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) { - size_t keyLen = strlen(_secret); + size_t keyLen = strlen(_prefs->bridge_secret); for (size_t i = 0; i < len; i++) { - data[i] ^= _secret[i % keyLen]; + data[i] ^= _prefs->bridge_secret[i % keyLen]; } } @@ -166,10 +166,9 @@ void ESPNowBridge::onDataSent(const uint8_t *mac_addr, esp_now_send_status_t sta // Could add transmission error handling here if needed } -void ESPNowBridge::onPacketTransmitted(mesh::Packet *packet) { +void ESPNowBridge::sendPacket(mesh::Packet *packet) { // Guard against uninitialized state if (_initialized == false) { - Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime()); return; } diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index 401c9eee..e5450dc4 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/src/helpers/bridges/ESPNowBridge.h @@ -73,22 +73,10 @@ private: /** Current position in receive buffer */ size_t _rx_buffer_pos; - /** - * Network encryption key from build define - * Must be defined with WITH_ESPNOW_BRIDGE_SECRET - * Used for XOR encryption to isolate different mesh networks - */ - const char *_secret = WITH_ESPNOW_BRIDGE_SECRET; - - /** - * Channel for ESP-NOW communication - * Valid 2.4GHz channels: 1-14 - */ - int _channel = 0; - /** * Performs XOR encryption/decryption of data - * + * Used to isolate different mesh networks + * * Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation. * The same operation is used for both encryption and decryption. * While not cryptographically secure, it provides basic network isolation. @@ -121,10 +109,11 @@ public: /** * Constructs an ESPNowBridge instance * + * @param prefs Node preferences for configuration settings * @param mgr PacketManager for allocating and queuing packets * @param rtc RTCClock for timestamping debug messages */ - ESPNowBridge(mesh::PacketManager *mgr, mesh::RTCClock *rtc); + ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTCClock *rtc); /** * Initializes the ESP-NOW bridge @@ -166,21 +155,7 @@ public: * * @param packet The mesh packet to transmit */ - void onPacketTransmitted(mesh::Packet *packet) override; - - /** - * Gets the current channel - * - * @return The current channel (0 = AUTO, 1-14 = valid channel) - */ - int getChannel() const { return _channel; } - - /** - * Sets the channel for ESP-NOW communication - * - * @param ch The channel to set (0 = AUTO, 1-14 = valid channel) - */ - void setChannel(int ch) { _channel = ch; } + void sendPacket(mesh::Packet *packet) override; }; #endif diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 02e36397..70f4b7d8 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -4,11 +4,11 @@ #ifdef WITH_RS232_BRIDGE -RS232Bridge::RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc) - : BridgeBase(mgr, rtc), _serial(&serial) {} +RS232Bridge::RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc) + : BridgeBase(prefs, mgr, rtc), _serial(&serial) {} void RS232Bridge::begin() { - Serial.printf("%s: RS232 BRIDGE: Initializing...\n", getLogDateTime()); + Serial.printf("%s: RS232 BRIDGE: Initializing at %d baud...\n", getLogDateTime(), _prefs->bridge_baud); #if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX) #error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined" #endif @@ -26,7 +26,7 @@ void RS232Bridge::begin() { #else #error RS232Bridge was not tested on the current platform #endif - ((HardwareSerial *)_serial)->begin(115200); + ((HardwareSerial *)_serial)->begin(_prefs->bridge_baud); // Update bridge state _initialized = true; @@ -114,10 +114,9 @@ void RS232Bridge::loop() { } } -void RS232Bridge::onPacketTransmitted(mesh::Packet *packet) { +void RS232Bridge::sendPacket(mesh::Packet *packet) { // Guard against uninitialized state if (_initialized == false) { - Serial.printf("%s: ESPNOW BRIDGE: TX packet attempted before initialization\n", getLogDateTime()); return; } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index e9cc22d0..839c0ba0 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -49,11 +49,12 @@ public: /** * @brief Constructs an RS232Bridge instance * + * @param prefs Node preferences for configuration settings * @param serial The hardware serial port to use * @param mgr PacketManager for allocating and queuing packets * @param rtc RTCClock for timestamping debug messages */ - RS232Bridge(Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc); + RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager *mgr, mesh::RTCClock *rtc); /** * Initializes the RS232 bridge @@ -96,7 +97,7 @@ public: * * @param packet The mesh packet to transmit */ - void onPacketTransmitted(mesh::Packet *packet) override; + void sendPacket(mesh::Packet *packet) override; /** * @brief Called when a complete valid packet has been received from serial From e48f3a58ae71d74772886c418b0000865d96c504 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Fri, 3 Oct 2025 00:23:09 +0100 Subject: [PATCH 131/546] Remove WITH_ESPNOW_BRIDGE_SECRET definition from platformio.ini files and update documentation to use _prefs->bridge_secret --- src/helpers/bridges/ESPNowBridge.h | 12 ++++-------- variants/generic-e22/platformio.ini | 2 -- variants/heltec_ct62/platformio.ini | 1 - variants/heltec_e213/platformio.ini | 1 - variants/heltec_e290/platformio.ini | 1 - variants/heltec_t190/platformio.ini | 1 - variants/heltec_tracker/platformio.ini | 1 - variants/heltec_v2/platformio.ini | 1 - variants/heltec_v3/platformio.ini | 2 -- variants/heltec_v4/platformio.ini | 1 - variants/heltec_wireless_paper/platformio.ini | 1 - variants/lilygo_t3s3/platformio.ini | 1 - variants/lilygo_t3s3_sx1276/platformio.ini | 1 - variants/lilygo_tbeam_SX1262/platformio.ini | 1 - variants/lilygo_tbeam_SX1276/platformio.ini | 1 - variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 1 - variants/lilygo_tlora_v2_1/platformio.ini | 1 - variants/meshadventurer/platformio.ini | 2 -- variants/station_g2/platformio.ini | 2 -- variants/tenstar_c3/platformio.ini | 2 -- variants/xiao_s3_wio/platformio.ini | 1 - 21 files changed, 4 insertions(+), 33 deletions(-) diff --git a/src/helpers/bridges/ESPNowBridge.h b/src/helpers/bridges/ESPNowBridge.h index e5450dc4..431a036b 100644 --- a/src/helpers/bridges/ESPNowBridge.h +++ b/src/helpers/bridges/ESPNowBridge.h @@ -6,10 +6,6 @@ #ifdef WITH_ESPNOW_BRIDGE -#ifndef WITH_ESPNOW_BRIDGE_SECRET -#error WITH_ESPNOW_BRIDGE_SECRET must be defined to use ESPNowBridge -#endif - /** * @brief Bridge implementation using ESP-NOW protocol for packet transport * @@ -36,11 +32,11 @@ * * Configuration: * - Define WITH_ESPNOW_BRIDGE to enable this bridge - * - Define WITH_ESPNOW_BRIDGE_SECRET with a string to set the network encryption key + * - Define _prefs->bridge_secret with a string to set the network encryption key * * Network Isolation: * Multiple independent mesh networks can coexist by using different - * WITH_ESPNOW_BRIDGE_SECRET values. Packets encrypted with a different key will + * _prefs->bridge_secret values. Packets encrypted with a different key will * fail the checksum validation and be discarded. */ class ESPNowBridge : public BridgeBase { @@ -76,8 +72,8 @@ private: /** * Performs XOR encryption/decryption of data * Used to isolate different mesh networks - * - * Uses WITH_ESPNOW_BRIDGE_SECRET as the key in a simple XOR operation. + * + * Uses _prefs->bridge_secret as the key in a simple XOR operation. * The same operation is used for both encryption and decryption. * While not cryptographically secure, it provides basic network isolation. * diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 2f61f412..9e4bcf72 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -87,7 +87,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -154,7 +153,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 1b83adbf..9dc67334 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -80,7 +80,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index a6fe2560..0d63b7e4 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -127,7 +127,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_E213_base.build_src_filter} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 0223b30c..63703d7c 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -123,7 +123,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_E290_base.build_src_filter} diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 52bb79e0..7ab4da55 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -125,7 +125,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_T190_base.build_src_filter} diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 5c0df007..d2f7f6b1 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -116,7 +116,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_base.build_src_filter} diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index d2afe4db..049b83bb 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -75,7 +75,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 417f6edb..26b7754e 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -83,7 +83,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -263,7 +262,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index d50c27a5..72fbfea9 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -77,7 +77,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 43ac2a82..9f125d75 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -102,7 +102,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index ca221108..3686ba2b 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -86,7 +86,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 1c0d5cf1..e7d22602 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -84,7 +84,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index f7d1a764..70aed341 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -105,7 +105,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index d7e119ef..512083ff 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -105,7 +105,6 @@ build_flags = -D MAX_NEIGHBOURS=8 -D PERSISTANT_GPS=1 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 328ebf07..7a2b8daa 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -83,7 +83,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index aa957fba..a9ad946b 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -197,7 +197,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index be3b4943..fe3c7b77 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -96,7 +96,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -166,7 +165,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 908d6443..83813dc6 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -76,7 +76,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -139,7 +138,6 @@ build_flags = -D MESH_PACKET_LOGGING=1 -D SX126X_RX_BOOSTED_GAIN=1 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 25bf6713..60014fc9 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -86,7 +86,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -153,7 +152,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index e6d2357d..f5713e42 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -78,7 +78,6 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 - -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = From 69e6d69798f5bdc7fd90deb765c48fcb94f601f7 Mon Sep 17 00:00:00 2001 From: WattleFoxxo <wattle@wattlefoxxo.com> Date: Fri, 3 Oct 2025 22:55:32 +1000 Subject: [PATCH 132/546] Fix font and icon scaling issues for TDeck --- src/helpers/ui/ST7789LCDDisplay.cpp | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 38c28893..87f9b8ad 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -39,7 +39,7 @@ bool ST7789LCDDisplay::begin() { display.fillScreen(ST77XX_BLACK); display.setTextColor(ST77XX_WHITE); - display.setTextSize(2); + display.setTextSize(2 * DISPLAY_SCALE_X); display.cp437(true); // Use full 256 char 'Code Page 437' font _isOn = true; @@ -70,12 +70,12 @@ void ST7789LCDDisplay::clear() { void ST7789LCDDisplay::startFrame(Color bkg) { display.fillScreen(ST77XX_BLACK); display.setTextColor(ST77XX_WHITE); - display.setTextSize(1); // This one affects size of Please wait... message + display.setTextSize(1 * DISPLAY_SCALE_X); // This one affects size of Please wait... message display.cp437(true); // Use full 256 char 'Code Page 437' font } void ST7789LCDDisplay::setTextSize(int sz) { - display.setTextSize(sz); + display.setTextSize(sz * DISPLAY_SCALE_X); } void ST7789LCDDisplay::setColor(Color c) { @@ -125,7 +125,22 @@ void ST7789LCDDisplay::drawRect(int x, int y, int w, int h) { } void ST7789LCDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { - display.drawBitmap(x * DISPLAY_SCALE_X, y * DISPLAY_SCALE_Y, bits, w, h, _color); + uint8_t byteWidth = (w + 7) / 8; + + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + uint8_t byte = bits[j * byteWidth + i / 8]; + bool pixelOn = byte & (0x80 >> (i & 7)); + + if (pixelOn) { + for (int dy = 0; dy < DISPLAY_SCALE_X; dy++) { + for (int dx = 0; dx < DISPLAY_SCALE_X; dx++) { + display.drawPixel(x * DISPLAY_SCALE_X + i * DISPLAY_SCALE_X + dx, y * DISPLAY_SCALE_Y + j * DISPLAY_SCALE_X + dy, _color); + } + } + } + } + } } uint16_t ST7789LCDDisplay::getTextWidth(const char* str) { @@ -138,4 +153,4 @@ uint16_t ST7789LCDDisplay::getTextWidth(const char* str) { void ST7789LCDDisplay::endFrame() { // display.display(); -} +} \ No newline at end of file From c568edc8d0e1da9999a1498e923fbd8e4eb09d8e Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 4 Oct 2025 23:04:47 +0200 Subject: [PATCH 133/546] Add MAX_LORA_TX_POWER build flag for Heltec V4 configuration --- variants/heltec_v4/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 5fc9350e..c9ba9e61 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -26,6 +26,7 @@ build_flags = -D PIN_VEXT_EN=36 -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 From 9b4d93d112cc293ba9d6ec788fbce5d61afee5d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Sun, 5 Oct 2025 11:48:05 +0100 Subject: [PATCH 134/546] Add bridge type command to CLI for reporting bridge configuration --- src/helpers/CommonCLI.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index d7eba363..cb425b6c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -274,6 +274,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch mesh::Utils::toHex(&reply[2], _callbacks->getSelfId().pub_key, PUB_KEY_SIZE); } else if (memcmp(config, "role", 4) == 0) { sprintf(reply, "> %s", _callbacks->getRole()); + } else if (memcmp(config, "bridge.type", 11) == 0) { + sprintf(reply, "> %s", +#ifdef WITH_RS232_BRIDGE + "rs232" +#elif WITH_ESPNOW_BRIDGE + "espnow" +#else + "none" +#endif + ); #ifdef WITH_BRIDGE } else if (memcmp(config, "bridge.enabled", 14) == 0) { sprintf(reply, "> %s", _prefs->bridge_enabled ? "on" : "off"); From 45ab0e8cf7656ceff4e30041cece1a656a8f00d5 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 5 Oct 2025 13:58:25 +0200 Subject: [PATCH 135/546] sensecap_indicator: initial espnow support --- src/helpers/ui/LGFXDisplay.cpp | 125 +++++++++++++++++ src/helpers/ui/LGFXDisplay.h | 44 ++++++ .../SCIndicatorDisplay.h | 129 ++++++++++++++++++ .../sensecap_indicator-espnow/platformio.ini | 50 +++++++ variants/sensecap_indicator-espnow/target.cpp | 56 ++++++++ variants/sensecap_indicator-espnow/target.h | 29 ++++ 6 files changed, 433 insertions(+) create mode 100644 src/helpers/ui/LGFXDisplay.cpp create mode 100644 src/helpers/ui/LGFXDisplay.h create mode 100644 variants/sensecap_indicator-espnow/SCIndicatorDisplay.h create mode 100644 variants/sensecap_indicator-espnow/platformio.ini create mode 100644 variants/sensecap_indicator-espnow/target.cpp create mode 100644 variants/sensecap_indicator-espnow/target.h diff --git a/src/helpers/ui/LGFXDisplay.cpp b/src/helpers/ui/LGFXDisplay.cpp new file mode 100644 index 00000000..a53cbc62 --- /dev/null +++ b/src/helpers/ui/LGFXDisplay.cpp @@ -0,0 +1,125 @@ +#include "LGFXDisplay.h" + +bool LGFXDisplay::begin() { + turnOn(); + display->init(); + display->setRotation(1); + display->setBrightness(64); + display->setColorDepth(8); + display->setTextColor(TFT_WHITE); + + buffer.setColorDepth(8); + buffer.setPsram(true); + buffer.createSprite(width(), height()); + + return true; +} + +void LGFXDisplay::turnOn() { +// display->wakeup(); + if (!_isOn) { + display->wakeup(); + } + _isOn = true; +} + +void LGFXDisplay::turnOff() { + if (_isOn) { + display->sleep(); + } + _isOn = false; +} + +void LGFXDisplay::clear() { +// display->clearDisplay(); + buffer.clearDisplay(); +} + +void LGFXDisplay::startFrame(Color bkg) { +// display->startWrite(); +// display->getScanLine(); + buffer.clearDisplay(); + buffer.setTextColor(TFT_WHITE); +} + +void LGFXDisplay::setTextSize(int sz) { + buffer.setTextSize(sz); +} + +void LGFXDisplay::setColor(Color c) { + // _color = (c != 0) ? ILI9342_WHITE : ILI9342_BLACK; + switch (c) { + case DARK: + _color = TFT_BLACK; + break; + case LIGHT: + _color = TFT_WHITE; + break; + case RED: + _color = TFT_RED; + break; + case GREEN: + _color = TFT_GREEN; + break; + case BLUE: + _color = TFT_BLUE; + break; + case YELLOW: + _color = TFT_YELLOW; + break; + case ORANGE: + _color = TFT_ORANGE; + break; + default: + _color = TFT_WHITE; + } + buffer.setTextColor(_color); +} + +void LGFXDisplay::setCursor(int x, int y) { + buffer.setCursor(x, y); +} + +void LGFXDisplay::print(const char* str) { + buffer.println(str); +// Serial.println(str); +} + +void LGFXDisplay::fillRect(int x, int y, int w, int h) { + buffer.fillRect(x, y, w, h, _color); +} + +void LGFXDisplay::drawRect(int x, int y, int w, int h) { + buffer.drawRect(x, y, w, h, _color); +} + +void LGFXDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { + buffer.drawBitmap(x, y, bits, w, h, _color); +} + +uint16_t LGFXDisplay::getTextWidth(const char* str) { + return buffer.textWidth(str); +} + +void LGFXDisplay::endFrame() { + display->startWrite(); + if (UI_ZOOM != 1) { + buffer.pushRotateZoom(display, display->width()/2, display->height()/2 , 0, UI_ZOOM, UI_ZOOM); + } else { + buffer.pushSprite(display, 0, 0); + } + display->endWrite(); +} + +bool LGFXDisplay::getTouch(int *x, int *y) { + lgfx::v1::touch_point_t point; + display->getTouch(&point); + if (UI_ZOOM != 1) { + *x = point.x / UI_ZOOM; + *y = point.y / UI_ZOOM; + } else { + *x = point.x; + *y = point.y; + } + return (*x >= 0) && (*y >= 0); +} \ No newline at end of file diff --git a/src/helpers/ui/LGFXDisplay.h b/src/helpers/ui/LGFXDisplay.h new file mode 100644 index 00000000..81d0239f --- /dev/null +++ b/src/helpers/ui/LGFXDisplay.h @@ -0,0 +1,44 @@ + +/* + * Base class for LovyanGFX supported display (works on ESP32 mainly) + * You can extend this class to support your display, providing your own LGFX + */ + +#pragma once + +#include <helpers/ui/DisplayDriver.h> + +#define LGFX_USE_V1 +#include <LovyanGFX.hpp> + +#ifndef UI_ZOOM + #define UI_ZOOM 1 +#endif + +class LGFXDisplay : public DisplayDriver { +protected: + LGFX_Device* display; + LGFX_Sprite buffer; + + bool _isOn; + int _color = TFT_WHITE; + +public: + LGFXDisplay(int w, int h):DisplayDriver(w/UI_ZOOM, h/UI_ZOOM) {_isOn = false;} + bool begin(); + bool isOn() override { return _isOn; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char* str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; + void endFrame() override; + virtual bool getTouch(int *x, int *y); +}; diff --git a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h new file mode 100644 index 00000000..6a7e3177 --- /dev/null +++ b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h @@ -0,0 +1,129 @@ +#pragma once + +#include <helpers/ui/LGFXDisplay.h> + +#define LGFX_USE_V1 +#include <LovyanGFX.hpp> + +#include <lgfx/v1/platforms/esp32s3/Panel_RGB.hpp> +#include <lgfx/v1/platforms/esp32s3/Bus_RGB.hpp> + +class LGFX : public lgfx::LGFX_Device +{ + lgfx::Panel_ST7701 _panel_instance; + lgfx::Bus_RGB _bus_instance; + lgfx::Light_PWM _light_instance; + lgfx::Touch_FT5x06 _touch_instance; + +public: + const uint16_t screenWidth = 480; + const uint16_t screenHeight = 480; + + bool hasButton(void) { return true; } + + LGFX(void) + { + { + auto cfg = _panel_instance.config(); + cfg.memory_width = 480; + cfg.memory_height = 480; + cfg.panel_width = screenWidth; + cfg.panel_height = screenHeight; + cfg.offset_x = 0; + cfg.offset_y = 0; + cfg.offset_rotation = 1; + _panel_instance.config(cfg); + } + + { + auto cfg = _panel_instance.config_detail(); + cfg.pin_cs = 4 | IO_EXPANDER; + cfg.pin_sclk = 41; + cfg.pin_mosi = 48; + cfg.use_psram = 1; + _panel_instance.config_detail(cfg); + } + + { + auto cfg = _bus_instance.config(); + cfg.panel = &_panel_instance; + + cfg.freq_write = 8000000; + cfg.pin_henable = 18; + + cfg.pin_pclk = 21; + cfg.pclk_active_neg = 0; + cfg.pclk_idle_high = 0; + cfg.de_idle_high = 1; + + cfg.pin_hsync = 16; + cfg.hsync_polarity = 0; + cfg.hsync_front_porch = 10; + cfg.hsync_pulse_width = 8; + cfg.hsync_back_porch = 50; + + cfg.pin_vsync = 17; + cfg.vsync_polarity = 0; + cfg.vsync_front_porch = 10; + cfg.vsync_pulse_width = 8; + cfg.vsync_back_porch = 20; + + cfg.pin_d0 = 15; + cfg.pin_d1 = 14; + cfg.pin_d2 = 13; + cfg.pin_d3 = 12; + cfg.pin_d4 = 11; + cfg.pin_d5 = 10; + cfg.pin_d6 = 9; + cfg.pin_d7 = 8; + cfg.pin_d8 = 7; + cfg.pin_d9 = 6; + cfg.pin_d10 = 5; + cfg.pin_d11 = 4; + cfg.pin_d12 = 3; + cfg.pin_d13 = 2; + cfg.pin_d14 = 1; + cfg.pin_d15 = 0; + + _bus_instance.config(cfg); + } + _panel_instance.setBus(&_bus_instance); + + { + auto cfg = _light_instance.config(); + cfg.pin_bl = 45; + _light_instance.config(cfg); + } + _panel_instance.light(&_light_instance); + + { + auto cfg = _touch_instance.config(); + cfg.pin_cs = GPIO_NUM_NC; + cfg.x_min = 0; + cfg.x_max = 479; + cfg.y_min = 0; + cfg.y_max = 479; + cfg.pin_int = GPIO_NUM_NC; + cfg.pin_rst = GPIO_NUM_NC; + cfg.bus_shared = true; + cfg.offset_rotation = 0; + + cfg.i2c_port = 0; + cfg.i2c_addr = 0x48; + cfg.pin_sda = 39; + cfg.pin_scl = 40; + cfg.freq = 400000; + _touch_instance.config(cfg); + _panel_instance.setTouch(&_touch_instance); + } + + setPanel(&_panel_instance); + } +}; + +class SCIndicatorDisplay : public LGFXDisplay { + LGFX disp; +public: + SCIndicatorDisplay() : LGFXDisplay(480, 480) + { display=&disp; } +}; diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini new file mode 100644 index 00000000..064f3ae8 --- /dev/null +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -0,0 +1,50 @@ +[SenseCapIndicator-ESPNow] +extends = esp32_base +board = esp32-s3-devkitc-1 +board_build.arduino.memory_type = qio_opi +board_build.flash_mode = qio +board_build.psram_type = opi +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 +board_build.partitions = default.csv +build_flags = + ${esp32_base.build_flags} + -D PIN_BOARD_SDA=39 + -D PIN_BOARD_SCL=40 + -D DISPLAY_CLASS=SCIndicatorDisplay + -D DISPLAY_LINES=21 + -D LINE_LENGTH=53 + -D DISABLE_WIFI_OTA=1 + -D IO_EXPANDER=0x40 + -D IO_EXPANDER_IRQ=42 + -D UI_ZOOM=3.5 + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + -D PIN_USER_BTN=38 + -D HAS_TOUCH + -I variants/sensecap_indicator-espnow +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/sensecap_indicator-espnow/*.cpp> + +<helpers/esp32/ESPNOWRadio.cpp> + +<helpers/ui/LGFXDisplay.cpp> + +<helpers/sensors/*> +lib_deps=${esp32_base.lib_deps} + adafruit/Adafruit BusIO @ ^1.17.2 + lovyan03/LovyanGFX @ ^1.2.7 + +[env:SenseCapIndicator-ESPNow_comp_radio_usb] +extends =SenseCapIndicator-ESPNow +build_flags = + ${SenseCapIndicator-ESPNow.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 +build_src_filter = ${SenseCapIndicator-ESPNow.build_src_filter} + +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${SenseCapIndicator-ESPNow.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp new file mode 100644 index 00000000..efdaac61 --- /dev/null +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -0,0 +1,56 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> + +ESP32Board board; + +ESPNOWRadio radio_driver; + +ESP32RTCClock rtc_clock; +#if defined(ENV_INCLUDE_GPS) +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, (mesh::RTCClock*)&rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + #ifdef PIN_USER_BTN + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); + #endif +#endif + +bool radio_init() { + rtc_clock.begin(); + + radio_driver.init(); + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return millis() + radio_driver.intID(); // TODO: where to get some entropy? +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + // no-op +} + +void radio_set_tx_power(uint8_t dbm) { + radio_driver.setTxPower(dbm); +} + +// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG: +// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html +class ESP_RNG : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + esp_fill_random(dest, sz); + } +}; + +mesh::LocalIdentity radio_new_identity() { + ESP_RNG rng; + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/sensecap_indicator-espnow/target.h b/variants/sensecap_indicator-espnow/target.h new file mode 100644 index 00000000..bb78e923 --- /dev/null +++ b/variants/sensecap_indicator-espnow/target.h @@ -0,0 +1,29 @@ +#pragma once + +#include <helpers/ESP32Board.h> +#include <helpers/esp32/ESPNOWRadio.h> +#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#ifdef ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> +#endif +#ifdef DISPLAY_CLASS + #include "SCIndicatorDisplay.h" + #include <helpers/ui/MomentaryButton.h> +#endif + +extern ESP32Board board; +extern ESPNOWRadio radio_driver; +extern ESP32RTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From 0502bc370dcdce6c106632511e32f3ca7713ae2f Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 5 Oct 2025 19:23:52 +0200 Subject: [PATCH 136/546] CommonCLI: gps management commands --- examples/simple_repeater/MyMesh.cpp | 38 +++++++++++++++++++ examples/simple_repeater/MyMesh.h | 6 +++ src/helpers/CommonCLI.cpp | 13 +++++++ src/helpers/CommonCLI.h | 4 ++ src/helpers/sensors/LocationProvider.h | 1 + .../sensors/MicroNMEALocationProvider.h | 10 +++++ 6 files changed, 72 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index df945d45..93175eb6 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -758,6 +758,44 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } +void MyMesh::gpsGetStatus(char * reply) { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + bool status = l->isActive(); + bool sync = l->isValid(); + int sats = l->satellitesCount(); + if (status) { + sprintf(reply, "on, %s, %d sats", sync?"fix":"no fix", sats); + } else { + strcpy(reply, "off"); + } + } else { + strcpy(reply, "Can't find GPS"); + } +} + +void MyMesh::gpsStart() { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->begin(); + l->reset(); + } +} + +void MyMesh::gpsStop() { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->stop(); + } +} + +void MyMesh::gpsSyncTime() { + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->syncTime(); + } +} + void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 05a8d13b..19bef70e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -176,6 +176,12 @@ public: void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; + // Gps mgmt cli callbacks + void gpsGetStatus(char * reply) override; + void gpsStart() override; + void gpsStop() override; + void gpsSyncTime() override; + mesh::LocalIdentity& getSelfId() override { return self_id; } void saveIdentity(const mesh::LocalIdentity& new_id) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 68acdf2b..e674cbc2 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -401,6 +401,19 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); +#if ENV_INCLUDE_GPS == 1 + } else if (memcmp(command, "gps on", 6) == 0) { + _callbacks->gpsStart(); + strcpy(reply, "ok"); + } else if (memcmp(command, "gps off", 7) == 0) { + _callbacks->gpsStop(); + strcpy(reply, "ok"); + } else if (memcmp(command, "gps sync", 8) == 0) { + _callbacks->gpsSyncTime(); + strcpy(reply, "Waiting fix ..."); + } else if (memcmp(command, "gps", 3) == 0) { + _callbacks->gpsGetStatus(reply); +#endif } else if (memcmp(command, "log start", 9) == 0) { _callbacks->setLoggingOn(true); strcpy(reply, " logging on"); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ff8ff50e..08e5f988 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -50,6 +50,10 @@ public: virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; + virtual void gpsGetStatus(char * reply) {} + virtual void gpsStart() {} + virtual void gpsStop() {} + virtual void gpsSyncTime() {} }; class CommonCLI { diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index f93dec48..f1c934e5 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -21,4 +21,5 @@ public: virtual void begin() = 0; virtual void stop() = 0; virtual void loop() = 0; + virtual bool isActive() = 0; }; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index ec82f25e..16344108 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -78,6 +78,16 @@ public : } } + bool isActive() override { + // directly read the enable pin if present as gps can be + // activated/deactivated outside of here ... + if (_pin_en != -1) { + return digitalRead(_pin_en) == PIN_GPS_EN_ACTIVE; + } else { + return true; // no enable so must be active + } + } + void syncTime() override { nmea.clear(); LocationProvider::syncTime(); } long getLatitude() override { return nmea.getLatitude(); } long getLongitude() override { return nmea.getLongitude(); } From e4f2d63b0abd850fe602e0ab045c6147ff2f4037 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 5 Oct 2025 20:31:25 +0200 Subject: [PATCH 137/546] cli_gps: use sensormanger to toggle gps on/off to keep state coherent --- examples/simple_repeater/MyMesh.cpp | 44 +++++++++++++------ examples/simple_repeater/MyMesh.h | 3 ++ src/helpers/sensors/LocationProvider.h | 2 +- .../sensors/MicroNMEALocationProvider.h | 2 +- 4 files changed, 36 insertions(+), 15 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 93175eb6..e5a8b6b3 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -761,11 +761,15 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { void MyMesh::gpsGetStatus(char * reply) { LocationProvider * l = sensors.getLocationProvider(); if (l != NULL) { - bool status = l->isActive(); - bool sync = l->isValid(); + bool enabled = l->isEnabled(); // is EN pin on ? + bool active = gpsGetState(); // is enabled at SensorManager level ? + bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - if (status) { - sprintf(reply, "on, %s, %d sats", sync?"fix":"no fix", sats); + if (enabled) { + sprintf(reply, "on, %s, %s, %d sats", + active?"active":"deactivated", + fix?"fix":"no fix", + sats); } else { strcpy(reply, "off"); } @@ -774,19 +778,33 @@ void MyMesh::gpsGetStatus(char * reply) { } } -void MyMesh::gpsStart() { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - l->begin(); - l->reset(); +bool MyMesh::gpsGetState() { + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), "gps") == 0) { + return !strcmp(sensors.getSettingValue(i), "1"); + } } + return false; +} + +void MyMesh::gpsSetState(bool value) { + // toggle GPS on/off + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), "gps") == 0) { + sensors.setSettingValue("gps", value?"1":"0"); + break; + } + } +} + +void MyMesh::gpsStart() { + gpsSetState(true); } void MyMesh::gpsStop() { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - l->stop(); - } + gpsSetState(false); } void MyMesh::gpsSyncTime() { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 19bef70e..880d1043 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -142,6 +142,9 @@ protected: void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + bool gpsGetState(); + void gpsSetState(bool value); + public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index f1c934e5..81d08652 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -21,5 +21,5 @@ public: virtual void begin() = 0; virtual void stop() = 0; virtual void loop() = 0; - virtual bool isActive() = 0; + virtual bool isEnabled() = 0; }; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 16344108..fb29fd79 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -78,7 +78,7 @@ public : } } - bool isActive() override { + bool isEnabled() override { // directly read the enable pin if present as gps can be // activated/deactivated outside of here ... if (_pin_en != -1) { From 7be65c148eac7d2f5b95b68b47c9db994a5ecd5a Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Mon, 6 Oct 2025 10:25:10 +0200 Subject: [PATCH 138/546] cli_gps: remove callbacks and add generic sensor set/get. --- examples/simple_repeater/MyMesh.cpp | 56 --------------------- examples/simple_repeater/MyMesh.h | 9 ---- src/helpers/CommonCLI.cpp | 77 ++++++++++++++++++++++++++--- src/helpers/CommonCLI.h | 8 +-- 4 files changed, 74 insertions(+), 76 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e5a8b6b3..df945d45 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -758,62 +758,6 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } -void MyMesh::gpsGetStatus(char * reply) { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - bool enabled = l->isEnabled(); // is EN pin on ? - bool active = gpsGetState(); // is enabled at SensorManager level ? - bool fix = l->isValid(); // has fix ? - int sats = l->satellitesCount(); - if (enabled) { - sprintf(reply, "on, %s, %s, %d sats", - active?"active":"deactivated", - fix?"fix":"no fix", - sats); - } else { - strcpy(reply, "off"); - } - } else { - strcpy(reply, "Can't find GPS"); - } -} - -bool MyMesh::gpsGetState() { - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), "gps") == 0) { - return !strcmp(sensors.getSettingValue(i), "1"); - } - } - return false; -} - -void MyMesh::gpsSetState(bool value) { - // toggle GPS on/off - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), "gps") == 0) { - sensors.setSettingValue("gps", value?"1":"0"); - break; - } - } -} - -void MyMesh::gpsStart() { - gpsSetState(true); -} - -void MyMesh::gpsStop() { - gpsSetState(false); -} - -void MyMesh::gpsSyncTime() { - LocationProvider * l = sensors.getLocationProvider(); - if (l != NULL) { - l->syncTime(); - } -} - void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 880d1043..05a8d13b 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -142,9 +142,6 @@ protected: void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; - bool gpsGetState(); - void gpsSetState(bool value); - public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); @@ -179,12 +176,6 @@ public: void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; - // Gps mgmt cli callbacks - void gpsGetStatus(char * reply) override; - void gpsStart() override; - void gpsStop() override; - void gpsSyncTime() override; - mesh::LocalIdentity& getSelfId() override { return self_id; } void saveIdentity(const mesh::LocalIdentity& new_id) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e674cbc2..ee029ec3 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -128,6 +128,27 @@ void CommonCLI::savePrefs() { _callbacks->savePrefs(); } +const char* CommonCLI::sensorGetCustomVar(const char* key) { + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), key) == 0) { + return sensors.getSettingValue(i); + } + } + return NULL; +} + +bool CommonCLI::sensorSetCustomVar(const char* key, const char* value) { + int num = sensors.getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(sensors.getSettingName(i), key) == 0) { + sensors.setSettingValue(key, value); + return true; + } + } + return false; +} + void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return @@ -401,18 +422,60 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s (Build: %s)", _callbacks->getFirmwareVer(), _callbacks->getBuildDate()); } else if (memcmp(command, "board", 5) == 0) { sprintf(reply, "%s", _board->getManufacturerName()); + } else if (memcmp(command, "sensor get ", 11) == 0) { + const char* key = command + 11; + const char* val = sensorGetCustomVar(key); + if (val != NULL) { + strcpy(reply, val); + } else { + strcpy(reply, "can't find custom var"); + } + } else if (memcmp(command, "sensor set ", 11) == 0) { + const char* args = &command[11]; + const char* value = strchr(args,' ') + 1; + char key [value-args+1]; + strncpy(key, args, value-args-1); + if (sensorSetCustomVar(key, value)) { + strcpy(reply, "ok"); + } else { + strcpy(reply, "can't find custom var"); + } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - _callbacks->gpsStart(); - strcpy(reply, "ok"); + if (sensorSetCustomVar("gps", "1")) { + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps toggle not found"); + } } else if (memcmp(command, "gps off", 7) == 0) { - _callbacks->gpsStop(); - strcpy(reply, "ok"); + if (sensorSetCustomVar("gps", "0")) { + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps toggle not found"); + } } else if (memcmp(command, "gps sync", 8) == 0) { - _callbacks->gpsSyncTime(); - strcpy(reply, "Waiting fix ..."); + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + l->syncTime(); + } } else if (memcmp(command, "gps", 3) == 0) { - _callbacks->gpsGetStatus(reply); + LocationProvider * l = sensors.getLocationProvider(); + if (l != NULL) { + bool enabled = l->isEnabled(); // is EN pin on ? + bool fix = l->isValid(); // has fix ? + int sats = l->satellitesCount(); + bool active = !strcmp(sensorGetCustomVar("gps"), "1"); + if (enabled) { + sprintf(reply, "on, %s, %s, %d sats", + active?"active":"deactivated", + fix?"fix":"no fix", + sats); + } else { + strcpy(reply, "off"); + } + } else { + strcpy(reply, "Can't find GPS"); + } #endif } else if (memcmp(command, "log start", 9) == 0) { _callbacks->setLoggingOn(true); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 08e5f988..d3e3a19d 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -2,6 +2,7 @@ #include "Mesh.h" #include <helpers/IdentityStore.h> +#include <target.h> struct NodePrefs { // persisted to file float airtime_factor; @@ -50,10 +51,6 @@ public: virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0; - virtual void gpsGetStatus(char * reply) {} - virtual void gpsStart() {} - virtual void gpsStop() {} - virtual void gpsSyncTime() {} }; class CommonCLI { @@ -67,6 +64,9 @@ class CommonCLI { void savePrefs(); void loadPrefsInt(FILESYSTEM* _fs, const char* filename); + const char* sensorGetCustomVar(const char* key); + bool sensorSetCustomVar(const char* key, const char* value); + public: CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } From fb46e5cc8a228e8cea70013de6a0d692a463038f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Mon, 6 Oct 2025 12:57:04 +0100 Subject: [PATCH 139/546] Refactor debug logging across bridge implementations --- examples/simple_repeater/MyMesh.h | 13 +++++-- src/MeshCore.h | 6 ++++ src/helpers/AbstractBridge.h | 2 +- src/helpers/bridges/BridgeBase.cpp | 4 +-- src/helpers/bridges/BridgeBase.h | 2 +- src/helpers/bridges/ESPNowBridge.cpp | 51 ++++++++++------------------ src/helpers/bridges/RS232Bridge.cpp | 40 ++++++---------------- 7 files changed, 48 insertions(+), 70 deletions(-) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 2e84843a..1dc5a76a 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -185,12 +185,19 @@ public: #if defined(WITH_BRIDGE) void setBridgeState(bool enable) override { - if (enable == bridge.getState()) return; - enable ? bridge.begin() : bridge.end(); + if (enable == bridge.isRunning()) return; + if (enable) + { + bridge.begin(); + } + else + { + bridge.end(); + } } void restartBridge() override { - if (!bridge.getState()) return; + if (!bridge.isRunning()) return; bridge.end(); bridge.begin(); } diff --git a/src/MeshCore.h b/src/MeshCore.h index d8886136..5c7e1760 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -28,6 +28,12 @@ #define MESH_DEBUG_PRINTLN(...) {} #endif +#if BRIDGE_DEBUG && ARDUINO +#define BRIDGE_DEBUG_PRINTLN(F, ...) Serial.printf("%s BRIDGE: " F, getLogDateTime(), ##__VA_ARGS__) +#else +#define BRIDGE_DEBUG_PRINTLN(...) {} +#endif + namespace mesh { #define BD_STARTUP_NORMAL 0 // getStartupReason() codes diff --git a/src/helpers/AbstractBridge.h b/src/helpers/AbstractBridge.h index 89e2dcdd..62284bd5 100644 --- a/src/helpers/AbstractBridge.h +++ b/src/helpers/AbstractBridge.h @@ -21,7 +21,7 @@ public: * * @return true if the bridge is initialized and running, false otherwise. */ - virtual bool getState() const = 0; + virtual bool isRunning() const = 0; /** * @brief A method to be called on every main loop iteration. diff --git a/src/helpers/bridges/BridgeBase.cpp b/src/helpers/bridges/BridgeBase.cpp index 527ec358..d2e2e5e0 100644 --- a/src/helpers/bridges/BridgeBase.cpp +++ b/src/helpers/bridges/BridgeBase.cpp @@ -2,7 +2,7 @@ #include <Arduino.h> -bool BridgeBase::getState() const { +bool BridgeBase::isRunning() const { return _initialized; } @@ -34,7 +34,7 @@ bool BridgeBase::validateChecksum(const uint8_t *data, size_t len, uint16_t rece void BridgeBase::handleReceivedPacket(mesh::Packet *packet) { // Guard against uninitialized state if (_initialized == false) { - Serial.printf("%s: BRIDGE: RX packet received before initialization\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("RX packet received before initialization\n"); _mgr->free(packet); return; } diff --git a/src/helpers/bridges/BridgeBase.h b/src/helpers/bridges/BridgeBase.h index 20b136e2..04c1564b 100644 --- a/src/helpers/bridges/BridgeBase.h +++ b/src/helpers/bridges/BridgeBase.h @@ -27,7 +27,7 @@ public: * * @return true if the bridge is initialized and running, false otherwise. */ - bool getState() const override; + bool isRunning() const override; /** * @brief Common magic number used by all bridge implementations for packet identification diff --git a/src/helpers/bridges/ESPNowBridge.cpp b/src/helpers/bridges/ESPNowBridge.cpp index a8a6fb53..b9eb1c10 100644 --- a/src/helpers/bridges/ESPNowBridge.cpp +++ b/src/helpers/bridges/ESPNowBridge.cpp @@ -27,20 +27,20 @@ ESPNowBridge::ESPNowBridge(NodePrefs *prefs, mesh::PacketManager *mgr, mesh::RTC } void ESPNowBridge::begin() { - Serial.printf("%s: ESPNOW BRIDGE: Initializing...\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Initializing...\n"); // Initialize WiFi in station mode WiFi.mode(WIFI_STA); // Set wifi channel if (esp_wifi_set_channel(_prefs->bridge_channel, WIFI_SECOND_CHAN_NONE) != ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: Error setting WIFI channel to %d\n", getLogDateTime(), _prefs->bridge_channel); + BRIDGE_DEBUG_PRINTLN("Error setting WIFI channel to %d\n", _prefs->bridge_channel); return; } // Initialize ESP-NOW if (esp_now_init() != ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: Error initializing ESP-NOW\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Error initializing ESP-NOW\n"); return; } @@ -56,7 +56,7 @@ void ESPNowBridge::begin() { peerInfo.encrypt = false; if (esp_now_add_peer(&peerInfo) != ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: Failed to add broadcast peer\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Failed to add broadcast peer\n"); return; } @@ -65,12 +65,12 @@ void ESPNowBridge::begin() { } void ESPNowBridge::end() { - Serial.printf("%s: ESPNOW BRIDGE: Stopping...\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Stopping...\n"); // Remove broadcast peer uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; if (esp_now_del_peer(broadcastAddress) != ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: Error removing broadcast peer\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Error removing broadcast peer\n"); } // Unregister callbacks @@ -79,7 +79,7 @@ void ESPNowBridge::end() { // Deinitialize ESP-NOW if (esp_now_deinit() != ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: Error deinitializing ESP-NOW\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Error deinitializing ESP-NOW\n"); } // Turn off WiFi @@ -103,26 +103,20 @@ void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) { void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len) { // Ignore packets that are too small to contain header + checksum if (len < (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE)) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX packet too small, len=%d\n", getLogDateTime(), len); -#endif + BRIDGE_DEBUG_PRINTLN("RX packet too small, len=%d\n", len); return; } // Validate total packet size if (len > MAX_ESPNOW_PACKET_SIZE) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX packet too large, len=%d\n", getLogDateTime(), len); -#endif + BRIDGE_DEBUG_PRINTLN("RX packet too large, len=%d\n", len); return; } // Check packet header magic uint16_t received_magic = (data[0] << 8) | data[1]; if (received_magic != BRIDGE_PACKET_MAGIC) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX invalid magic 0x%04X\n", getLogDateTime(), received_magic); -#endif + BRIDGE_DEBUG_PRINTLN("RX invalid magic 0x%04X\n", received_magic); return; } @@ -140,16 +134,11 @@ void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t l if (!validateChecksum(decrypted + BRIDGE_CHECKSUM_SIZE, payloadLen, received_checksum)) { // Failed to decrypt - likely from a different network -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX checksum mismatch, rcv=0x%04X\n", getLogDateTime(), - received_checksum); -#endif + BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04X\n", received_checksum); return; } -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: RX, payload_len=%d\n", getLogDateTime(), payloadLen); -#endif + BRIDGE_DEBUG_PRINTLN("RX, payload_len=%d\n", payloadLen); // Create mesh packet mesh::Packet *pkt = _instance->_mgr->allocNew(); @@ -174,9 +163,7 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) { // First validate the packet pointer if (!packet) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: TX invalid packet pointer\n", getLogDateTime()); -#endif + BRIDGE_DEBUG_PRINTLN("TX invalid packet pointer\n"); return; } @@ -187,10 +174,8 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) { // Check if packet fits within our maximum payload size if (meshPacketLen > MAX_PAYLOAD_SIZE) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: ESPNOW BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), - meshPacketLen, MAX_PAYLOAD_SIZE); -#endif + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", meshPacketLen, + MAX_PAYLOAD_SIZE); return; } @@ -219,13 +204,11 @@ void ESPNowBridge::sendPacket(mesh::Packet *packet) { uint8_t broadcastAddress[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; esp_err_t result = esp_now_send(broadcastAddress, buffer, totalPacketSize); -#if MESH_PACKET_LOGGING if (result == ESP_OK) { - Serial.printf("%s: ESPNOW BRIDGE: TX, len=%d\n", getLogDateTime(), meshPacketLen); + BRIDGE_DEBUG_PRINTLN("TX, len=%d\n", meshPacketLen); } else { - Serial.printf("%s: ESPNOW BRIDGE: TX FAILED!\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("TX FAILED!\n"); } -#endif } } diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 70f4b7d8..554e8fcc 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -8,7 +8,7 @@ RS232Bridge::RS232Bridge(NodePrefs *prefs, Stream &serial, mesh::PacketManager * : BridgeBase(prefs, mgr, rtc), _serial(&serial) {} void RS232Bridge::begin() { - Serial.printf("%s: RS232 BRIDGE: Initializing at %d baud...\n", getLogDateTime(), _prefs->bridge_baud); + BRIDGE_DEBUG_PRINTLN("Initializing at %d baud...\n", _prefs->bridge_baud); #if !defined(WITH_RS232_BRIDGE_RX) || !defined(WITH_RS232_BRIDGE_TX) #error "WITH_RS232_BRIDGE_RX and WITH_RS232_BRIDGE_TX must be defined" #endif @@ -33,7 +33,7 @@ void RS232Bridge::begin() { } void RS232Bridge::end() { - Serial.printf("%s: RS232 BRIDGE: Stopping...\n", getLogDateTime()); + BRIDGE_DEBUG_PRINTLN("Stopping...\n"); ((HardwareSerial *)_serial)->end(); // Update bridge state @@ -71,9 +71,7 @@ void RS232Bridge::loop() { // Validate length field if (len > (MAX_TRANS_UNIT + 1)) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: RX invalid length %d, resetting\n", getLogDateTime(), len); -#endif + BRIDGE_DEBUG_PRINTLN("RX invalid length %d, resetting\n", len); _rx_buffer_pos = 0; // Invalid length, reset continue; } @@ -82,30 +80,20 @@ void RS232Bridge::loop() { uint16_t received_checksum = (_rx_buffer[4 + len] << 8) | _rx_buffer[5 + len]; if (validateChecksum(_rx_buffer + 4, len, received_checksum)) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: RX, len=%d crc=0x%04x\n", getLogDateTime(), len, - received_checksum); -#endif + BRIDGE_DEBUG_PRINTLN("RX, len=%d crc=0x%04x\n", len, received_checksum); mesh::Packet *pkt = _mgr->allocNew(); if (pkt) { if (pkt->readFrom(_rx_buffer + 4, len)) { onPacketReceived(pkt); } else { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: RX failed to parse packet\n", getLogDateTime()); -#endif + BRIDGE_DEBUG_PRINTLN("RX failed to parse packet\n"); _mgr->free(pkt); } } else { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: RX failed to allocate packet\n", getLogDateTime()); -#endif + BRIDGE_DEBUG_PRINTLN("RX failed to allocate packet\n"); } } else { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: RX checksum mismatch, rcv=0x%04x\n", getLogDateTime(), - received_checksum); -#endif + BRIDGE_DEBUG_PRINTLN("RX checksum mismatch, rcv=0x%04x\n", received_checksum); } _rx_buffer_pos = 0; // Reset for next packet } @@ -122,9 +110,7 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { // First validate the packet pointer if (!packet) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: TX invalid packet pointer\n", getLogDateTime()); -#endif + BRIDGE_DEBUG_PRINTLN("TX invalid packet pointer\n"); return; } @@ -135,10 +121,8 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { // Check if packet fits within our maximum payload size if (len > (MAX_TRANS_UNIT + 1)) { -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: TX packet too large (payload=%d, max=%d)\n", getLogDateTime(), len, - MAX_TRANS_UNIT + 1); -#endif + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, + MAX_TRANS_UNIT + 1); return; } @@ -156,9 +140,7 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { // Send complete packet _serial->write(buffer, len + SERIAL_OVERHEAD); -#if MESH_PACKET_LOGGING - Serial.printf("%s: RS232 BRIDGE: TX, len=%d crc=0x%04x\n", getLogDateTime(), len, checksum); -#endif + BRIDGE_DEBUG_PRINTLN("TX, len=%d crc=0x%04x\n", len, checksum); } } From 13a0202062c403731dee000392b97556671f96a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Mon, 6 Oct 2025 12:57:32 +0100 Subject: [PATCH 140/546] Add BRIDGE_DEBUG flag --- variants/generic-e22/platformio.ini | 4 ++++ variants/heltec_ct62/platformio.ini | 2 ++ variants/heltec_e213/platformio.ini | 2 ++ variants/heltec_e290/platformio.ini | 2 ++ variants/heltec_t190/platformio.ini | 2 ++ variants/heltec_tracker/platformio.ini | 2 ++ variants/heltec_v2/platformio.ini | 2 ++ variants/heltec_v3/platformio.ini | 4 ++++ variants/heltec_v4/platformio.ini | 1 + variants/heltec_wireless_paper/platformio.ini | 2 ++ variants/lilygo_t3s3/platformio.ini | 2 ++ variants/lilygo_t3s3_sx1276/platformio.ini | 2 ++ variants/lilygo_tbeam_SX1262/platformio.ini | 2 ++ variants/lilygo_tbeam_SX1276/platformio.ini | 2 ++ variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 2 ++ variants/lilygo_tlora_v2_1/platformio.ini | 2 ++ variants/meshadventurer/platformio.ini | 4 ++++ variants/station_g2/platformio.ini | 4 ++++ variants/tenstar_c3/platformio.ini | 4 ++++ variants/waveshare_rp2040_lora/platformio.ini | 1 + variants/xiao_s3_wio/platformio.ini | 2 ++ 21 files changed, 50 insertions(+) diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 9e4bcf72..5a544747 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -65,6 +65,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -87,6 +88,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -131,6 +133,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -153,6 +156,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 9dc67334..ff8d59db 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -61,6 +61,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_ct62.build_src_filter} @@ -80,6 +81,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_ct62.build_src_filter} diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 0d63b7e4..ca7a601f 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -106,6 +106,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_E213_base.build_src_filter} @@ -127,6 +128,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_E213_base.build_src_filter} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 63703d7c..e5046854 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -102,6 +102,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_E290_base.build_src_filter} @@ -123,6 +124,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_E290_base.build_src_filter} diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 7ab4da55..b4fe2a8a 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -106,6 +106,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_T190_base.build_src_filter} @@ -125,6 +126,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_T190_base.build_src_filter} diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index d2f7f6b1..c3f52739 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -94,6 +94,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_tracker_base.build_src_filter} @@ -116,6 +117,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_base.build_src_filter} diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 049b83bb..025f9b4d 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -53,6 +53,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_lora32_v2.build_src_filter} @@ -75,6 +76,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 26b7754e..8ba0be70 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -62,6 +62,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=5 -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -83,6 +84,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -242,6 +244,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=5 -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -262,6 +265,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 72fbfea9..609ef5e6 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -77,6 +77,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 9f125d75..507b3790 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -81,6 +81,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} @@ -102,6 +103,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 3686ba2b..507c1dfe 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -65,6 +65,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} @@ -86,6 +87,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index e7d22602..54545f52 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -63,6 +63,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} @@ -84,6 +85,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index 70aed341..2a61165b 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -86,6 +86,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} @@ -105,6 +106,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 512083ff..806ca8cb 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -85,6 +85,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} @@ -105,6 +106,7 @@ build_flags = -D MAX_NEIGHBOURS=8 -D PERSISTANT_GPS=1 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 7a2b8daa..21bd2520 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -64,6 +64,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} @@ -83,6 +84,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index a9ad946b..ddcb1b10 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -176,6 +176,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=34 -D WITH_RS232_BRIDGE_TX=25 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 @@ -197,6 +198,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 ; -D CORE_DEBUG_LEVEL=3 diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index fe3c7b77..c051767f 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -73,6 +73,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -96,6 +97,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -142,6 +144,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -165,6 +168,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 83813dc6..6e2d0f2c 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -57,6 +57,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Station_G2.build_src_filter} @@ -76,6 +77,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -118,6 +120,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_DEBUG=1 ; build_src_filter = ${Station_G2.build_src_filter} ; +<helpers/bridges/RS232Bridge.cpp> @@ -138,6 +141,7 @@ build_flags = -D MESH_PACKET_LOGGING=1 -D SX126X_RX_BOOSTED_GAIN=1 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 60014fc9..f4c743b6 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -63,6 +63,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -86,6 +87,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -130,6 +132,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -152,6 +155,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index f2f180aa..76758c81 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -57,6 +57,7 @@ build_flags = ${waveshare_rp2040_lora.build_flags} -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=9 -D WITH_RS232_BRIDGE_TX=8 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${waveshare_rp2040_lora.build_src_filter} diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index f5713e42..86fde33c 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -59,6 +59,7 @@ lib_deps = ; -D WITH_RS232_BRIDGE=Serial2 ; -D WITH_RS232_BRIDGE_RX=5 ; -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 ; lib_deps = @@ -78,6 +79,7 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=8 -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = From 341b69e3c949cf5c52d5f51f4668fe52ccfaf7a6 Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Mon, 6 Oct 2025 14:08:16 +0200 Subject: [PATCH 141/546] sensor list command --- src/helpers/CommonCLI.cpp | 27 ++++++++++++++++++- .../sensors/EnvironmentSensorManager.cpp | 25 ++++++++++------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index ee029ec3..e20de609 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -439,7 +439,32 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); - } + } + } else if (memcmp(command, "sensor list", 11) == 0) { + char* dp = reply; + int start = 0; + int end = sensors.getNumSettings(); + if (strlen(command) > 11) { + start = _atoi(command+12); + } + if (start >= end) { + strcpy(reply, "no custom var"); + } else { + sprintf(dp, "%d vars\n", end); + dp = strchr(dp, 0); + int i; + for (i = start; i < end && (dp-reply < 134); i++) { + sprintf(dp, "%s=%s\n", + sensors.getSettingName(i), + sensors.getSettingValue(i)); + dp = strchr(dp, 0); + } + if (i < end) { + sprintf(dp, "... next:%d", i); + } else { + *(dp-1) = 0; // remove last CR + } + } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { if (sensorSetCustomVar("gps", "1")) { diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 99605ff3..aa51c85a 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -387,27 +387,34 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen int EnvironmentSensorManager::getNumSettings() const { + int settings = 0; #if ENV_INCLUDE_GPS - return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected - #else - return 0; + if (gps_detected) settings++; // only show GPS setting if GPS is detected #endif + return settings; } const char* EnvironmentSensorManager::getSettingName(int i) const { + int settings = 0; #if ENV_INCLUDE_GPS - return (gps_detected && i == 0) ? "gps" : NULL; - #else - return NULL; + if (gps_detected && i == settings++) { + return "gps"; + } #endif + // convenient way to add params (needed for some tests) +// if (i == settings++) return "param.2"; + return NULL; } const char* EnvironmentSensorManager::getSettingValue(int i) const { + int settings = 0; #if ENV_INCLUDE_GPS - if (gps_detected && i == 0) { - return gps_active ? "1" : "0"; - } + if (gps_detected && i == settings++) { + return gps_active ? "1" : "0"; + } #endif + // convenient way to add params ... +// if (i == settings++) return "2"; return NULL; } From 6ed8e9d5142715b61a22fed6dc8627fb3b3476cc Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Mon, 6 Oct 2025 15:12:03 +0200 Subject: [PATCH 142/546] gps_cli: gps state is now saved and restored upon reboot --- examples/simple_repeater/MyMesh.cpp | 15 ++++++++ examples/simple_repeater/MyMesh.h | 4 +++ src/helpers/CommonCLI.cpp | 53 +++++++++++++---------------- src/helpers/CommonCLI.h | 6 ++-- src/helpers/SensorManager.h | 21 ++++++++++++ 5 files changed, 66 insertions(+), 33 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b2b47ac3..1c6c0d77 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -631,7 +631,12 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.bridge_pkt_src = 0; // logTx _prefs.bridge_baud = 115200; // baud rate _prefs.bridge_channel = 1; // channel 1 + StrHelper::strncpy(_prefs.bridge_secret, "LVSITANOS", sizeof(_prefs.bridge_secret)); + + // GPS defaults + _prefs.gps_enabled = 0; + _prefs.gps_interval = 0; } void MyMesh::begin(FILESYSTEM *fs) { @@ -653,8 +658,18 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + +#if ENV_INCLUDE_GPS == 1 + applyGpsPrefs(); +#endif } +#if ENV_INCLUDE_GPS == 1 +void MyMesh::applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); +} +#endif + void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params pending_freq = freq; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d77b74d8..ca85d1a4 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -159,6 +159,10 @@ public: _cli.savePrefs(_fs); } +#if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs(); +#endif + void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; void sendSelfAdvertisement(int delay_millis) override; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e6f8a3a6..2efdd00a 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -62,8 +62,12 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128 file.read((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130 file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 - file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132 - file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133 + file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 + file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 + file.read(pad, 4); // 152 + file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 + file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 + // 161 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -84,6 +88,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200); _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); + _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); + file.close(); } } @@ -131,8 +137,12 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_delay, sizeof(_prefs->bridge_delay)); // 128 file.write((uint8_t *)&_prefs->bridge_pkt_src, sizeof(_prefs->bridge_pkt_src)); // 130 file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 - file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 132 - file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 133 + file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 + file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 + file.write(pad, 4); // 152 + file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 + file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 + // 161 file.close(); } @@ -147,27 +157,6 @@ void CommonCLI::savePrefs() { _callbacks->savePrefs(); } -const char* CommonCLI::sensorGetCustomVar(const char* key) { - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), key) == 0) { - return sensors.getSettingValue(i); - } - } - return NULL; -} - -bool CommonCLI::sensorSetCustomVar(const char* key, const char* value) { - int num = sensors.getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(sensors.getSettingName(i), key) == 0) { - sensors.setSettingValue(key, value); - return true; - } - } - return false; -} - void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return @@ -527,7 +516,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s", _board->getManufacturerName()); } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; - const char* val = sensorGetCustomVar(key); + const char* val = sensors.getSettingByKey(key); if (val != NULL) { strcpy(reply, val); } else { @@ -538,7 +527,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch const char* value = strchr(args,' ') + 1; char key [value-args+1]; strncpy(key, args, value-args-1); - if (sensorSetCustomVar(key, value)) { + if (sensors.setSettingByKey(key, value)) { strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); @@ -570,13 +559,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - if (sensorSetCustomVar("gps", "1")) { + if (sensors.setSettingByKey("gps", "1")) { + _prefs->gps_enabled = 1; + savePrefs(); strcpy(reply, "ok"); } else { strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps off", 7) == 0) { - if (sensorSetCustomVar("gps", "0")) { + if (sensors.setSettingByKey("gps", "0")) { + _prefs->gps_enabled = 0; + savePrefs(); strcpy(reply, "ok"); } else { strcpy(reply, "gps toggle not found"); @@ -592,7 +585,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch bool enabled = l->isEnabled(); // is EN pin on ? bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - bool active = !strcmp(sensorGetCustomVar("gps"), "1"); + bool active = !strcmp(sensors.getSettingByKey("gps"), "1"); if (enabled) { sprintf(reply, "on, %s, %s, %d sats", active?"active":"deactivated", diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 14e249de..07523643 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -38,6 +38,9 @@ struct NodePrefs { // persisted to file uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) uint8_t bridge_channel; // 1-14 (ESP-NOW only) char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) + // Gps settings + uint8_t gps_enabled; + uint32_t gps_interval; // in seconds }; class CommonCLICallbacks { @@ -83,9 +86,6 @@ class CommonCLI { void savePrefs(); void loadPrefsInt(FILESYSTEM* _fs, const char* filename); - const char* sensorGetCustomVar(const char* key); - bool sensorSetCustomVar(const char* key, const char* value); - public: CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 1ace6220..38c1d806 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -23,4 +23,25 @@ public: virtual const char* getSettingValue(int i) const { return NULL; } virtual bool setSettingValue(const char* name, const char* value) { return false; } virtual LocationProvider* getLocationProvider() { return NULL; } + + // Helper functions to manage setting by keys (useful in many places ...) + const char* getSettingByKey(const char* key) { + int num = getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(getSettingName(i), key) == 0) { + return getSettingValue(i); + } + } + return NULL; + } + + bool setSettingByKey(const char* key, const char* value) { + int num = getNumSettings(); + for (int i = 0; i < num; i++) { + if (strcmp(getSettingName(i), key) == 0) { + return setSettingValue(key, value); + } + } + return false; + } }; From 9e3c2fc9d9f75cdcda0106529d5cae51eecfab5c Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Mon, 6 Oct 2025 15:30:18 +0200 Subject: [PATCH 143/546] gps_cli: gps also restored on sensors and rooms --- examples/simple_repeater/MyMesh.cpp | 6 ------ examples/simple_repeater/MyMesh.h | 10 ++++++---- examples/simple_room_server/MyMesh.cpp | 4 ++++ examples/simple_room_server/MyMesh.h | 6 ++++++ examples/simple_sensor/SensorMesh.cpp | 4 ++++ examples/simple_sensor/SensorMesh.h | 5 +++++ 6 files changed, 25 insertions(+), 10 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1c6c0d77..f7153da3 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -664,12 +664,6 @@ void MyMesh::begin(FILESYSTEM *fs) { #endif } -#if ENV_INCLUDE_GPS == 1 -void MyMesh::applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); -} -#endif - void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { set_radio_at = futureMillis(2000); // give CLI reply some time to be sent back, before applying temp radio params pending_freq = freq; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index ca85d1a4..c45c141d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -135,6 +135,12 @@ protected: return _prefs.multi_acks; } +#if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + } +#endif + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; @@ -159,10 +165,6 @@ public: _cli.savePrefs(_fs); } -#if ENV_INCLUDE_GPS == 1 - void applyGpsPrefs(); -#endif - void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; void sendSelfAdvertisement(int delay_millis) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 89f2afb3..d9a36397 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -632,6 +632,10 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + +#if ENV_INCLUDE_GPS == 1 + applyGpsPrefs(); +#endif } void MyMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index b2df60c3..60ef1e73 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -149,6 +149,12 @@ protected: bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; +#if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + } +#endif + public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index ba41ca45..00da006a 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -697,6 +697,10 @@ void SensorMesh::begin(FILESYSTEM* fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + +#if ENV_INCLUDE_GPS == 1 + applyGpsPrefs(); +#endif } bool SensorMesh::formatFileSystem() { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index d26bcb14..cdc3940c 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -149,4 +149,9 @@ private: void sendAlert(const ClientInfo* c, Trigger* t); + #if ENV_INCLUDE_GPS == 1 + void applyGpsPrefs() { + sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + } +#endif }; From 601479e572eba359a985f8941fe84bd864b154c2 Mon Sep 17 00:00:00 2001 From: Sebastian Muszynski <basti@linkt.de> Date: Tue, 7 Oct 2025 11:12:37 +0200 Subject: [PATCH 144/546] Introduce Heltec_WSL3_companion_radio_wifi target --- variants/heltec_v3/platformio.ini | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index aa79e20e..b43c0870 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -325,6 +325,24 @@ lib_deps = ${Heltec_lora32_v3.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:Heltec_WSL3_companion_radio_wifi] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${Heltec_lora32_v3.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Heltec_WSL3_sensor] extends = Heltec_lora32_v3 build_flags = From da7b8ad669395a073ec8da289fac6f77b7ef78b2 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Thu, 9 Oct 2025 20:30:25 +0200 Subject: [PATCH 145/546] Add powerOff support for MeshPocket --- variants/mesh_pocket/MeshPocket.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 82f66dd5..8f5b09c9 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -41,5 +41,9 @@ public: NVIC_SystemReset(); } + void powerOff() override { + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; From b588e3f1e3b8a75b1519c4f640cbe40f5587e9df Mon Sep 17 00:00:00 2001 From: Bill Plein <260078+bplein@users.noreply.github.com> Date: Thu, 9 Oct 2025 17:31:32 -0500 Subject: [PATCH 146/546] Ikoka Nano Variant Created as a fork of the ikoka stick variant. - Updated for Ikoka Nano legacy pinout - Removed display support - Removed user button support - Retains I2C sensor support Tested with the ebytes E22 30W module, companion-ble and repeater firmware versions, with an I2C INA3221 power sensor. --- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 94 ++++++ variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 60 ++++ variants/ikoka_nano_nrf/platformio.ini | 312 ++++++++++++++++++ variants/ikoka_nano_nrf/target.cpp | 44 +++ variants/ikoka_nano_nrf/target.h | 28 ++ variants/ikoka_nano_nrf/variant.cpp | 86 +++++ variants/ikoka_nano_nrf/variant.h | 149 +++++++++ 7 files changed, 773 insertions(+) create mode 100644 variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp create mode 100644 variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h create mode 100644 variants/ikoka_nano_nrf/platformio.ini create mode 100644 variants/ikoka_nano_nrf/target.cpp create mode 100644 variants/ikoka_nano_nrf/target.h create mode 100644 variants/ikoka_nano_nrf/variant.cpp create mode 100644 variants/ikoka_nano_nrf/variant.h diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp new file mode 100644 index 00000000..ee799692 --- /dev/null +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -0,0 +1,94 @@ +#ifdef XIAO_NRF52 + +#include <Arduino.h> +#include "IkokaNanoNRFBoard.h" + +#include <bluefruit.h> +#include <Wire.h> + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void IkokaNanoNRFBoard::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + +// pinMode(SX126X_POWER_EN, OUTPUT); +// digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + +bool IkokaNanoNRFBoard::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("XIAO_NRF52_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} + +#endif diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h new file mode 100644 index 00000000..8484085b --- /dev/null +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -0,0 +1,60 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +#ifdef XIAO_NRF52 + +class IkokaNanoNRFBoard : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + #if defined(LED_BLUE) + // turn off that annoying blue LED before transmitting + digitalWrite(LED_BLUE, HIGH); + #endif + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + #if defined(LED_BLUE) + // do it after transmitting too, just in case + digitalWrite(LED_BLUE, HIGH); + #endif + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char *getManufacturerName() const override { + return MANUFACTURER_STRING; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_nano_nrf/platformio.ini b/variants/ikoka_nano_nrf/platformio.ini new file mode 100644 index 00000000..abfbcf67 --- /dev/null +++ b/variants/ikoka_nano_nrf/platformio.ini @@ -0,0 +1,312 @@ +[nrf52840_xiao] +extends = nrf52_base +platform_packages = + toolchain-gccarmnoneeabi@~1.100301.0 + framework-arduinoadafruitnrf52 +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + -D NRF52_PLATFORM -D XIAO_NRF52 + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 +lib_ignore = + BluetoothOTA + lvgl + lib5b4 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + adafruit/Adafruit INA3221 Library @ ^1.0.1 + adafruit/Adafruit INA219 @ ^1.2.3 + adafruit/Adafruit AHTX0 @ ^2.0.5 + adafruit/Adafruit BME280 Library @ ^2.3.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[ikoka_nano_nrf_baseboard] +extends = nrf52840_xiao +;board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52840_xiao.build_flags} + -D P_LORA_TX_LED=11 + -I variants/ikoka_nano_nrf + -I src/helpers/nrf52 + -D DISPLAY_CLASS=NullDisplayDriver + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=D1 +; -D P_LORA_BUSY=D3 + -D P_LORA_BUSY=D2 ; specific to ikoka nano variant. +; -D P_LORA_RESET=D2 + -D P_LORA_RESET=D3 ; specific to ikoka nano variant. +; -D P_LORA_NSS=D4 + -D P_LORA_NSS=D0 ; specific to ikoka nano variant. +; -D SX126X_RXEN=D5 + -D SX126X_RXEN=D7 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_WIRE_SCL=5 ; specific to ikoka nano variant. + -D PIN_WIRE_SDA=4 ; specific to ikoka nano variant. + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 +debug_tool = jlink +upload_protocol = nrfutil + + +;;; abstracted hardware variants + +[ikoka_nano_nrf_e22_22dbm] +extends = ikoka_nano_nrf_baseboard +; No PA in this model, full 22dBm +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"' + -D LORA_TX_POWER=22 +build_src_filter = ${nrf52840_xiao.build_src_filter} + +<helpers/*.cpp> + +<helpers/sensors> + +<helpers/ui/NullDisplayDriver.cpp> + +<../variants/ikoka_nano_nrf> + +[ikoka_nano_nrf_e22_30dbm] +extends = ikoka_nano_nrf_baseboard +; limit txpower to 20dBm on E22-900M30S. Anything higher will +; cause distortion in the PA output. 20dBm in -> 30dBm out +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"' + -D LORA_TX_POWER=20 +build_src_filter = ${nrf52840_xiao.build_src_filter} + +<helpers/*.cpp> + +<helpers/sensors> + +<helpers/ui/NullDisplayDriver.cpp> + +<../variants/ikoka_nano_nrf> + +[ikoka_nano_nrf_e22_33dbm] +extends = ikoka_nano_nrf_baseboard +; limit txpower to 9dBm on E22-900M33S to avoid hardware damage +; to the rf amplifier frontend. 9dBm in -> 33dBm out +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"' + -D LORA_TX_POWER=9 +build_src_filter = ${nrf52840_xiao.build_src_filter} + +<helpers/*.cpp> + +<helpers/sensors> + +<helpers/ui/NullDisplayDriver.cpp> + +<../variants/ikoka_nano_nrf> + +;;; abstracted firmware roles + +[ikoka_nano_nrf_companion_radio_ble] +extends = ikoka_nano_nrf_baseboard +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -I examples/companion_radio/ui-new +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_nano_nrf_baseboard.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[ikoka_nano_nrf_companion_radio_usb] +extends = ikoka_nano_nrf_baseboard +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -I examples/companion_radio/ui-new +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ikoka_nano_nrf_baseboard.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[ikoka_nano_nrf_repeater] +extends = ikoka_nano_nrf_baseboard +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D ADVERT_NAME='"Ikoka Nano Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[ikoka_nano_nrf_room_server] +extends = ikoka_nano_nrf_baseboard +build_flags = + ${ikoka_nano_nrf_baseboard.build_flags} + -D ADVERT_NAME='"Ikoka Nano Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} + +<../examples/simple_room_server/*.cpp> + +;;; hardware + firmware variants + +;;; 22dBm EBYTE E22-900M22 variants + +[env:ikoka_nano_nrf_22dbm_companion_radio_usb] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_companion_radio_usb +build_flags = + ${ikoka_nano_nrf_companion_radio_usb.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_usb.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_companion_radio_ble] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_companion_radio_ble +build_flags = + ${ikoka_nano_nrf_companion_radio_ble.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_ble.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_repeater] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_repeater +build_flags = + ${ikoka_nano_nrf_repeater.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_repeater.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_room_server] +extends = + ikoka_nano_nrf_e22_22dbm + ikoka_nano_nrf_room_server +build_flags = + ${ikoka_nano_nrf_room_server.build_flags} + ${ikoka_nano_nrf_e22_22dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_room_server.build_src_filter} + ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + + +;;; 30dBm EBYTE E22-900M30 variants + +[env:ikoka_nano_nrf_30dbm_companion_radio_usb] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_companion_radio_usb +build_flags = + ${ikoka_nano_nrf_companion_radio_usb.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_usb.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_nano_nrf_30dbm_companion_radio_ble] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_companion_radio_ble +build_flags = + ${ikoka_nano_nrf_companion_radio_ble.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_ble.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_nano_nrf_30dbm_repeater] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_repeater +build_flags = + ${ikoka_nano_nrf_repeater.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_repeater.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +[env:ikoka_nano_nrf_30dbm_room_server] +extends = + ikoka_nano_nrf_e22_30dbm + ikoka_nano_nrf_room_server +build_flags = + ${ikoka_nano_nrf_room_server.build_flags} + ${ikoka_nano_nrf_e22_30dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_room_server.build_src_filter} + ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + + +;;; 33dBm EBYTE E22-900M33 variants + +[env:ikoka_nano_nrf_33dbm_companion_radio_usb] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_companion_radio_usb +build_flags = + ${ikoka_nano_nrf_companion_radio_usb.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_usb.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_33dbm_companion_radio_ble] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_companion_radio_ble +build_flags = + ${ikoka_nano_nrf_companion_radio_ble.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_companion_radio_ble.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_33dbm_repeater] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_repeater +build_flags = + ${ikoka_nano_nrf_repeater.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_repeater.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_33dbm_room_server] +extends = + ikoka_nano_nrf_e22_33dbm + ikoka_nano_nrf_room_server +build_flags = + ${ikoka_nano_nrf_room_server.build_flags} + ${ikoka_nano_nrf_e22_33dbm.build_flags} +build_src_filter = + ${ikoka_nano_nrf_room_server.build_src_filter} + ${ikoka_nano_nrf_e22_33dbm.build_src_filter} diff --git a/variants/ikoka_nano_nrf/target.cpp b/variants/ikoka_nano_nrf/target.cpp new file mode 100644 index 00000000..aed59182 --- /dev/null +++ b/variants/ikoka_nano_nrf/target.cpp @@ -0,0 +1,44 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> + +IkokaNanoNRFBoard board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + // MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +EnvironmentSensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ikoka_nano_nrf/target.h b/variants/ikoka_nano_nrf/target.h new file mode 100644 index 00000000..9b4e908e --- /dev/null +++ b/variants/ikoka_nano_nrf/target.h @@ -0,0 +1,28 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <IkokaNanoNRFBoard.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/EnvironmentSensorManager.h> + +#ifdef DISPLAY_CLASS + #include <helpers/ui/NullDisplayDriver.h> + #include <helpers/ui/MomentaryButton.h> + extern DISPLAY_CLASS display; + // extern MomentaryButton user_btn; +#endif + +extern IkokaNanoNRFBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_nano_nrf/variant.cpp b/variants/ikoka_nano_nrf/variant.cpp new file mode 100644 index 00000000..16542e27 --- /dev/null +++ b/variants/ikoka_nano_nrf/variant.cpp @@ -0,0 +1,86 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() +{ + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + //digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // Low charging current (50mA) + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + //pinMode(PIN_CHARGING_CURRENT, INPUT); + + // High charging current (100mA) + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, LOW); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_nano_nrf/variant.h b/variants/ikoka_nano_nrf/variant.h new file mode 100644 index 00000000..1496aad0 --- /dev/null +++ b/variants/ikoka_nano_nrf/variant.h @@ -0,0 +1,149 @@ +#ifndef _IKOKA_NANO_NRF_H_ +#define _IKOKA_NANO_NRF_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (0) // State when LED is litted + +// Buttons +// #define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seeedstudio.com/XIAO_BLE#battery-charging-current + +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +// #define PIN_WIRE_SDA (17) // 4 and 5 are used for the sx1262 ! +// #define PIN_WIRE_SCL (16) // use WIRE1_SDA + +static const uint8_t SDA = PIN_WIRE_SDA; +static const uint8_t SCL = PIN_WIRE_SCL; + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 8a2e4721d1dcb67663a0219e8bfd9cfd902b1fb0 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Fri, 10 Oct 2025 16:01:48 +0200 Subject: [PATCH 147/546] heltec wireless tracker: use `-D ARDUINO_USB_CDC_ON_BOOT=1` with all envs repeater and room server envs did not have arduino cdc flag enabled which resulted in broken serial. --- variants/heltec_tracker/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 33a7d9b8..19ab49a8 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -5,6 +5,7 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_tracker -D HELTEC_LORA_V3 + -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -40,7 +41,6 @@ build_flags = ${Heltec_tracker_base.build_flags} -I src/helpers/ui -I examples/companion_radio/ui-new - -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display -D MAX_CONTACTS=300 From 70ac82059437007ee4c44ae246868af8940d2947 Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Sat, 11 Oct 2025 18:01:26 +0800 Subject: [PATCH 148/546] add heltec tracker v2 board. --- boards/heltec_tracker_v2.json | 41 ++++ .../sensors/MicroNMEALocationProvider.h | 10 +- src/helpers/ui/ST7735Display.cpp | 9 +- .../HeltecTrackerV2Board.cpp | 88 +++++++ .../heltec_tracker_v2/HeltecTrackerV2Board.h | 23 ++ variants/heltec_tracker_v2/pins_arduino.h | 60 +++++ variants/heltec_tracker_v2/platformio.ini | 218 ++++++++++++++++++ variants/heltec_tracker_v2/target.cpp | 60 +++++ variants/heltec_tracker_v2/target.h | 30 +++ 9 files changed, 535 insertions(+), 4 deletions(-) create mode 100644 boards/heltec_tracker_v2.json create mode 100644 variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp create mode 100644 variants/heltec_tracker_v2/HeltecTrackerV2Board.h create mode 100644 variants/heltec_tracker_v2/pins_arduino.h create mode 100644 variants/heltec_tracker_v2/platformio.ini create mode 100644 variants/heltec_tracker_v2/target.cpp create mode 100644 variants/heltec_tracker_v2/target.h diff --git a/boards/heltec_tracker_v2.json b/boards/heltec_tracker_v2.json new file mode 100644 index 00000000..277dcabd --- /dev/null +++ b/boards/heltec_tracker_v2.json @@ -0,0 +1,41 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_8MB.csv" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_tracker_v2" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "heltec_tracker v2", + "upload": { + "flash_size": "8MB", + "maximum_ram_size": 327680, + "maximum_size": 8388608, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/", + "vendor": "heltec" +} \ No newline at end of file diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index ec82f25e..1cf97358 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -3,6 +3,7 @@ #include "LocationProvider.h" #include <MicroNMEA.h> #include <RTClib.h> +#include <helpers/RefCountedDigitalPin.h> #ifndef GPS_EN #ifdef PIN_GPS_EN @@ -37,14 +38,15 @@ class MicroNMEALocationProvider : public LocationProvider { MicroNMEA nmea; mesh::RTCClock* _clock; Stream* _gps_serial; + RefCountedDigitalPin* _peripher_power; int _pin_reset; int _pin_en; long next_check = 0; long time_valid = 0; public : - MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN) : - _gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock) { + MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : + _gps_serial(&ser), nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _pin_reset(pin_reset), _pin_en(pin_en), _clock(clock), _peripher_power(peripher_power) { if (_pin_reset != -1) { pinMode(_pin_reset, OUTPUT); digitalWrite(_pin_reset, GPS_RESET_FORCE); @@ -56,6 +58,7 @@ public : } void begin() override { + if (_peripher_power) _peripher_power->claim(); if (_pin_en != -1) { digitalWrite(_pin_en, PIN_GPS_EN_ACTIVE); } @@ -75,7 +78,8 @@ public : void stop() override { if (_pin_en != -1) { digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); - } + } + if (_peripher_power) _peripher_power->release(); } void syncTime() override { nmea.clear(); LocationProvider::syncTime(); } diff --git a/src/helpers/ui/ST7735Display.cpp b/src/helpers/ui/ST7735Display.cpp index e9eea69b..0a28077c 100644 --- a/src/helpers/ui/ST7735Display.cpp +++ b/src/helpers/ui/ST7735Display.cpp @@ -24,14 +24,21 @@ bool ST7735Display::begin() { digitalWrite(PIN_TFT_LEDA_CTL, HIGH); digitalWrite(PIN_TFT_RST, HIGH); +#if defined(HELTEC_TRACKER_V2) + display.initR(INITR_MINI160x80); + display.setRotation(DISPLAY_ROTATION); + uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR + display.sendCommand(ST77XX_MADCTL, &madctl, 1); +#else display.initR(INITR_MINI160x80_PLUGIN); display.setRotation(DISPLAY_ROTATION); +#endif display.setSPISpeed(40000000); display.fillScreen(ST77XX_BLACK); display.setTextColor(ST77XX_WHITE); display.setTextSize(2); display.cp437(true); // Use full 256 char 'Code Page 437' font - + _isOn = true; } return true; diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp new file mode 100644 index 00000000..4975d5cd --- /dev/null +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -0,0 +1,88 @@ +#include "HeltecTrackerV2Board.h" + +void HeltecTrackerV2Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER,HIGH); + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN,HIGH); + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN,LOW); + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void HeltecTrackerV2Board::onBeforeTransmit(void) { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + digitalWrite(P_LORA_PA_TX_EN,HIGH); + } + + void HeltecTrackerV2Board::onAfterTransmit(void) { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + digitalWrite(P_LORA_PA_TX_EN,LOW); + } + + void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void HeltecTrackerV2Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t HeltecTrackerV2Board::getBattMilliVolts() { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, HIGH); + delay(10); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, LOW); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* HeltecTrackerV2Board::getManufacturerName() const { + return "Heltec Tracker V2"; + } diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.h b/variants/heltec_tracker_v2/HeltecTrackerV2Board.h new file mode 100644 index 00000000..d93c86cd --- /dev/null +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.h @@ -0,0 +1,23 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/RefCountedDigitalPin.h> +#include <helpers/ESP32Board.h> +#include <driver/rtc_io.h> + +class HeltecTrackerV2Board : public ESP32Board { + +public: + RefCountedDigitalPin periph_power; + + HeltecTrackerV2Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void onBeforeTransmit(void) override; + void onAfterTransmit(void) override; + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; diff --git a/variants/heltec_tracker_v2/pins_arduino.h b/variants/heltec_tracker_v2/pins_arduino.h new file mode 100644 index 00000000..982cb5e5 --- /dev/null +++ b/variants/heltec_tracker_v2/pins_arduino.h @@ -0,0 +1,60 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include <stdint.h> + +static const uint8_t LED_BUILTIN = 18; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 5; +static const uint8_t SCL = 6; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 3; +static const uint8_t LED = 18; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini new file mode 100644 index 00000000..c4a79d9e --- /dev/null +++ b/variants/heltec_tracker_v2/platformio.ini @@ -0,0 +1,218 @@ +[Heltec_tracker_v2] +extends = esp32_base +board = heltec_tracker_v2 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/heltec_tracker_v2 + -D HELTEC_TRACKER_V2 + -D ESP32_CPU_FREQ=160 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=18 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=12 + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 + -D P_LORA_PA_POWER=7 ;power en + -D P_LORA_PA_EN=4 + -D P_LORA_PA_TX_EN=46 ;enable tx + -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. + -D MAX_LORA_TX_POWER=22 ;Max SX1262 output + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=5 + -D PIN_BOARD_SCL=6 + -D PIN_USER_BTN=0 + -D PIN_TFT_SDA=42 ; SDIN + -D PIN_TFT_SCL=41 ; SCLK + -D PIN_TFT_DC=40 ; RS (register select) + -D PIN_TFT_RST=39 ; RES + -D PIN_TFT_CS=38 + -D USE_PIN_TFT=1 + -D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) + -D DISPLAY_ROTATION=1 + -D PIN_GPS_RX=34 + -D PIN_GPS_TX=33 + -D PIN_GPS_RESET=35 + -D PIN_GPS_RESET_ACTIVE=LOW + -D GPS_BAUD_RATE=115200 + -D ENV_INCLUDE_GPS=1 + -D PIN_ADC_CTRL=2 + -D PIN_VBAT_READ=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_tracker_v2> + +<helpers/sensors> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_tracker_v2_repeater] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/ui/ST7735Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_tracker_v2_repeater_bridge_espnow] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<helpers/ui/ST7735Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_tracker_v2_room_server] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D DISPLAY_CLASS=ST7735Display + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/ui/ST7735Display.cpp> + +<../examples/simple_room_server> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_tracker_v2_terminal_chat] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_companion_radio_usb] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=ST7735Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/ui/ST7735Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_companion_radio_ble] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=ST7735Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/ui/ST7735Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_companion_radio_wifi] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D DISPLAY_CLASS=ST7735Display + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/ui/ST7735Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_tracker_v2_sensor] +extends = Heltec_tracker_v2 +build_flags = + ${Heltec_tracker_v2.build_flags} + -D ADVERT_NAME='"Heltec Tracker V2 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7735Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<helpers/ui/ST7735Display.cpp> + +<../examples/simple_sensor> +lib_deps = + ${Heltec_tracker_v2.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/heltec_tracker_v2/target.cpp b/variants/heltec_tracker_v2/target.cpp new file mode 100644 index 00000000..da397fb7 --- /dev/null +++ b/variants/heltec_tracker_v2/target.cpp @@ -0,0 +1,60 @@ +#include <Arduino.h> +#include "target.h" + +HeltecTrackerV2Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, NULL, GPS_RESET, GPS_EN, &board.periph_power); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display(&board.periph_power); // peripheral power pin is shared + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/heltec_tracker_v2/target.h b/variants/heltec_tracker_v2/target.h new file mode 100644 index 00000000..190404ef --- /dev/null +++ b/variants/heltec_tracker_v2/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <HeltecTrackerV2Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/ST7735Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern HeltecTrackerV2Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From ad2894a0399d3337070ef5d0c8fa451c1322ddcd Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Sat, 11 Oct 2025 18:03:15 +0800 Subject: [PATCH 149/546] delete PSRAM. --- boards/heltec_tracker_v2.json | 1 - 1 file changed, 1 deletion(-) diff --git a/boards/heltec_tracker_v2.json b/boards/heltec_tracker_v2.json index 277dcabd..62b569e0 100644 --- a/boards/heltec_tracker_v2.json +++ b/boards/heltec_tracker_v2.json @@ -6,7 +6,6 @@ }, "core": "esp32", "extra_flags": [ - "-DBOARD_HAS_PSRAM", "-DARDUINO_USB_CDC_ON_BOOT=1", "-DARDUINO_USB_MODE=0", "-DARDUINO_RUNNING_CORE=1", From 76dcfbb23a50cd026a78a9719f54bd0bba81dc39 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 11 Oct 2025 15:29:17 +0200 Subject: [PATCH 150/546] gpsCli: use parseTextParts --- src/helpers/CommonCLI.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2efdd00a..6616a056 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -518,15 +518,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch const char* key = command + 11; const char* val = sensors.getSettingByKey(key); if (val != NULL) { - strcpy(reply, val); + sprintf(reply, "> %s", val); } else { - strcpy(reply, "can't find custom var"); + strcpy(reply, "null"); } } else if (memcmp(command, "sensor set ", 11) == 0) { - const char* args = &command[11]; - const char* value = strchr(args,' ') + 1; - char key [value-args+1]; - strncpy(key, args, value-args-1); + strcpy(tmp, &command[11]); + const char *parts[2]; + int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); + const char *key = (num > 0) ? parts[0] : ""; + const char *value = (num > 1) ? parts[1] : "null"; if (sensors.setSettingByKey(key, value)) { strcpy(reply, "ok"); } else { From f6064b41e9cdd5faac3a080d028640062b1aaa43 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 11 Oct 2025 18:00:57 +0200 Subject: [PATCH 151/546] gps_cli: set node location based on gps --- src/helpers/CommonCLI.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 6616a056..cc63cfeb 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -580,6 +580,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (l != NULL) { l->syncTime(); } + } else if (memcmp(command, "gps setloc", 10) == 0) { + _prefs->node_lat = sensors.node_lat; + _prefs->node_lon = sensors.node_lon; + savePrefs(); + strcpy(reply, "ok"); } else if (memcmp(command, "gps", 3) == 0) { LocationProvider * l = sensors.getLocationProvider(); if (l != NULL) { From 4dc3dda2d8d13f2d76260589fed96e026b63eb5b Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sat, 11 Oct 2025 18:32:02 +0200 Subject: [PATCH 152/546] xiao c3: migrated to esm, added missing roles, cleanup --- .../xiao_c3}/XiaoC3Board.h | 5 -- variants/xiao_c3/platformio.ini | 63 +++++++++++++++---- variants/xiao_c3/target.cpp | 9 ++- variants/xiao_c3/target.h | 7 +-- 4 files changed, 61 insertions(+), 23 deletions(-) rename {src/helpers => variants/xiao_c3}/XiaoC3Board.h (95%) diff --git a/src/helpers/XiaoC3Board.h b/variants/xiao_c3/XiaoC3Board.h similarity index 95% rename from src/helpers/XiaoC3Board.h rename to variants/xiao_c3/XiaoC3Board.h index c97f22b7..6ea1c15f 100644 --- a/src/helpers/XiaoC3Board.h +++ b/variants/xiao_c3/XiaoC3Board.h @@ -3,11 +3,6 @@ #include <helpers/ESP32Board.h> #include <Arduino.h> -// LoRa radio module pins for custom Seeduino XiaoC3 build -// #define P_LORA_SCLK D8 -// #define P_LORA_MISO D9 -// #define P_LORA_MOSI D10 - #include <driver/rtc_io.h> #include <driver/uart.h> diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 1f27dfc8..617f610e 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -3,6 +3,8 @@ extends = esp32_base board = seeed_xiao_esp32c3 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} + -UENV_INCLUDE_GPS -I variants/xiao_c3 -D ESP32_CPU_FREQ=80 -D PIN_VBAT_READ=D0 @@ -12,23 +14,27 @@ build_flags = -D P_LORA_BUSY=D3 -D PIN_BOARD_SDA=D6 -D PIN_BOARD_SCL=D7 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 -D SX126X_RXEN=D5 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_c3> + +<helpers/sensors> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} -[env:Xiao_C3_sx1262_repeater] +[env:Xiao_C3_repeater] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<../examples/simple_repeater/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 -D ADVERT_NAME='"Xiao C3 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -41,6 +47,24 @@ lib_deps = ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 +[env:Xiao_C3_room_server] +extends = Xiao_esp32_C3 +build_src_filter = ${Xiao_esp32_C3.build_src_filter} + +<../examples/simple_room_server/*.cpp> +build_flags = + ${Xiao_esp32_C3.build_flags} + -D ADVERT_NAME='"Xiao C3 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_esp32_C3.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + [env:Xiao_C3_companion_radio_ble] extends = Xiao_esp32_C3 build_src_filter = ${Xiao_esp32_C3.build_src_filter} @@ -48,10 +72,6 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<helpers/esp32/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -71,10 +91,6 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<helpers/esp32/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D RADIO_CLASS=CustomSX1262 - -D WRAPPER_CLASS=CustomSX1262Wrapper - -D SX126X_RX_BOOSTED_GAIN=1 - -D LORA_TX_POWER=22 -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D OFFLINE_QUEUE_SIZE=256 @@ -85,3 +101,24 @@ lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Xiao_C3_companion_radio_wifi] +extends = Xiao_esp32_C3 +build_src_filter = ${Xiao_esp32_C3.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<helpers/esp32/*.cpp> +build_flags = + ${Xiao_esp32_C3.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_esp32_C3.lib_deps} + ${esp32_ota.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index b3701ca7..fe3f7196 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -14,7 +14,14 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif bool radio_init() { fallback_clock.begin(); diff --git a/variants/xiao_c3/target.h b/variants/xiao_c3/target.h index fa29e52b..a7ef4421 100644 --- a/variants/xiao_c3/target.h +++ b/variants/xiao_c3/target.h @@ -3,16 +3,15 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/XiaoC3Board.h> +#include <XiaoC3Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> -#include <helpers/radiolib/CustomSX1268Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> -#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> extern XiaoC3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); From bf1da43d7dd6395badfe81f57e29f0403e5bcb27 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 11 Oct 2025 19:00:02 +0200 Subject: [PATCH 153/546] gps_cli: gps advert to control advert location policy --- examples/simple_repeater/MyMesh.cpp | 12 ++++++-- examples/simple_room_server/MyMesh.cpp | 12 ++++++-- examples/simple_sensor/SensorMesh.cpp | 12 ++++++-- src/helpers/CommonCLI.cpp | 39 ++++++++++++++++++++++++-- src/helpers/CommonCLI.h | 5 ++++ 5 files changed, 72 insertions(+), 8 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f7153da3..de8072f1 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -289,8 +289,16 @@ mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); + if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + app_data_len = builder.encodeTo(app_data); + } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + app_data_len = builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } } return createAdvert(self_id, app_data, app_data_len); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index d9a36397..00736820 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -116,8 +116,16 @@ mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); + if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + app_data_len = builder.encodeTo(app_data); + } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + app_data_len = builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } } return createAdvert(self_id, app_data, app_data_len); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 00da006a..97d95d5f 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -241,8 +241,16 @@ mesh::Packet* SensorMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); + if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + app_data_len = builder.encodeTo(app_data); + } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + app_data_len = builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + app_data_len = builder.encodeTo(app_data); + } } return createAdvert(self_id, app_data, app_data_len); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index cc63cfeb..757180f8 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -67,7 +67,10 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read(pad, 4); // 152 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 - // 161 + if (file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)) == -1) { + _prefs->advert_loc_policy = ADVERT_LOC_PREFS; // default value + } // 161 + // 162 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -89,6 +92,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); + _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); file.close(); } @@ -142,7 +146,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write(pad, 4); // 152 file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 - // 161 + file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 + // 162 file.close(); } @@ -585,6 +590,36 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->node_lon = sensors.node_lon; savePrefs(); strcpy(reply, "ok"); + } else if (memcmp(command, "gps advert", 10) == 0) { + if (strlen(command) == 10) { + switch (_prefs->advert_loc_policy) { + case ADVERT_LOC_NONE: + strcpy(reply, "> none"); + break; + case ADVERT_LOC_PREFS: + strcpy(reply, "> prefs"); + break; + case ADVERT_LOC_SHARE: + strcpy(reply, "> share"); + break; + default: + strcpy(reply, "error"); + } + } else if (memcmp(command+11, "none", 4) == 0) { + _prefs->advert_loc_policy = ADVERT_LOC_NONE; + savePrefs(); + strcpy(reply, "ok"); + } else if (memcmp(command+11, "share", 5) == 0) { + _prefs->advert_loc_policy = ADVERT_LOC_SHARE; + savePrefs(); + strcpy(reply, "ok"); + } else if (memcmp(command+11, "prefs", 4) == 0) { + _prefs->advert_loc_policy = ADVERT_LOC_PREFS; + savePrefs(); + strcpy(reply, "ok"); + } else { + strcpy(reply, "error"); + } } else if (memcmp(command, "gps", 3) == 0) { LocationProvider * l = sensors.getLocationProvider(); if (l != NULL) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 07523643..68489913 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -8,6 +8,10 @@ #define WITH_BRIDGE #endif +#define ADVERT_LOC_NONE 0 +#define ADVERT_LOC_SHARE 1 +#define ADVERT_LOC_PREFS 2 + struct NodePrefs { // persisted to file float airtime_factor; char node_name[32]; @@ -41,6 +45,7 @@ struct NodePrefs { // persisted to file // Gps settings uint8_t gps_enabled; uint32_t gps_interval; // in seconds + uint8_t advert_loc_policy; }; class CommonCLICallbacks { From c4a2b139308ecd3a03028fb2083a159311624b2e Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sat, 11 Oct 2025 21:52:48 +0200 Subject: [PATCH 154/546] moved HeltecV3Board.h to variant folder --- .../heltec_v3}/HeltecV3Board.h | 18 +++--------------- variants/heltec_v3/platformio.ini | 7 +++++++ variants/heltec_v3/target.h | 2 +- 3 files changed, 11 insertions(+), 16 deletions(-) rename {src/helpers => variants/heltec_v3}/HeltecV3Board.h (88%) diff --git a/src/helpers/HeltecV3Board.h b/variants/heltec_v3/HeltecV3Board.h similarity index 88% rename from src/helpers/HeltecV3Board.h rename to variants/heltec_v3/HeltecV3Board.h index c63ed2d8..afdaf639 100644 --- a/src/helpers/HeltecV3Board.h +++ b/variants/heltec_v3/HeltecV3Board.h @@ -2,16 +2,7 @@ #include <Arduino.h> #include <helpers/RefCountedDigitalPin.h> - -// LoRa radio module pins for Heltec V3 -// Also for Heltec Wireless Tracker/Paper -#define P_LORA_DIO_1 14 -#define P_LORA_NSS 8 -#define P_LORA_RESET RADIOLIB_NC -#define P_LORA_BUSY 13 -#define P_LORA_SCLK 9 -#define P_LORA_MISO 11 -#define P_LORA_MOSI 10 +#include <helpers/ESP32Board.h> // built-ins #ifndef PIN_VBAT_READ // set in platformio.ini for boards like Heltec Wireless Paper (20) @@ -22,9 +13,6 @@ #endif #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH -//#define PIN_LED_BUILTIN 35 - -#include "ESP32Board.h" #include <driver/rtc_io.h> @@ -43,7 +31,7 @@ public: // Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2) pinMode(PIN_ADC_CTRL, INPUT); adc_active_state = !digitalRead(PIN_ADC_CTRL); - + pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive @@ -64,7 +52,7 @@ public: void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index b43c0870..dcf566b3 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -7,6 +7,13 @@ build_flags = -I variants/heltec_v3 -D HELTEC_LORA_V3 -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index b2125664..739aecfe 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/HeltecV3Board.h> +#include <HeltecV3Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/SensorManager.h> From 1979517381ecee7916e1d724a900e3c2c13c246b Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sat, 11 Oct 2025 23:35:10 +0200 Subject: [PATCH 155/546] heltec v2 cleanup --- .../heltec_v2}/HeltecV2Board.h | 14 +------ variants/heltec_v2/platformio.ini | 38 +++++++++++++++++-- variants/heltec_v2/target.h | 2 +- 3 files changed, 37 insertions(+), 17 deletions(-) rename {src/helpers => variants/heltec_v2}/HeltecV2Board.h (85%) diff --git a/src/helpers/HeltecV2Board.h b/variants/heltec_v2/HeltecV2Board.h similarity index 85% rename from src/helpers/HeltecV2Board.h rename to variants/heltec_v2/HeltecV2Board.h index 5bf78778..a6221036 100644 --- a/src/helpers/HeltecV2Board.h +++ b/variants/heltec_v2/HeltecV2Board.h @@ -1,22 +1,12 @@ #pragma once #include <Arduino.h> - -// LoRa radio module pins for Heltec V2 -#define P_LORA_DIO_1 26 // DIO0 -#define P_LORA_NSS 18 -#define P_LORA_RESET RADIOLIB_NC // 14 -#define P_LORA_BUSY RADIOLIB_NC -#define P_LORA_SCLK 5 -#define P_LORA_MISO 19 -#define P_LORA_MOSI 27 +#include <helpers/ESP32Board.h> // built-ins #define PIN_VBAT_READ 37 #define PIN_LED_BUILTIN 25 -#include "ESP32Board.h" - #include <driver/rtc_io.h> class HeltecV2Board : public ESP32Board { @@ -39,7 +29,7 @@ public: void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); - // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 100053ed..539cc52e 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -7,13 +7,20 @@ build_flags = -D HELTEC_LORA_V2 -D RADIO_CLASS=CustomSX1276 -D WRAPPER_CLASS=CustomSX1276Wrapper + -D P_LORA_DIO_1=26 + -D P_LORA_NSS=18 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=RADIOLIB_NC + -D P_LORA_SCLK=5 + -D P_LORA_MISO=19 + -D P_LORA_MOSI=27 + -D P_LORA_TX_LED=25 -D SX127X_CURRENT_LIMIT=120 -D LORA_TX_POWER=20 -D PIN_BOARD_SDA=4 -D PIN_BOARD_SCL=15 -D PIN_USER_BTN=0 -D PIN_OLED_RESET=16 - -D P_LORA_TX_LED=25 build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_v2> lib_deps = @@ -112,7 +119,7 @@ lib_deps = extends = Heltec_lora32_v2 build_flags = ${Heltec_lora32_v2.build_flags} - -D MAX_CONTACTS=170 + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -128,7 +135,7 @@ build_flags = ${Heltec_lora32_v2.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=170 + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -148,7 +155,7 @@ build_flags = ${Heltec_lora32_v2.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=170 + -D MAX_CONTACTS=160 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 @@ -164,3 +171,26 @@ build_src_filter = ${Heltec_lora32_v2.build_src_filter} lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_v2_companion_radio_wifi] +extends = Heltec_lora32_v2 +build_flags = + ${Heltec_lora32_v2.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=160 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_lora32_v2.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_lora32_v2.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 2e5b17de..48d750be 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/HeltecV2Board.h> +#include <HeltecV2Board.h> #include <helpers/radiolib/CustomSX1276Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/SensorManager.h> From 7cb2e0863a8171a300c062547d95697e1c69f009 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sun, 12 Oct 2025 00:13:34 +0200 Subject: [PATCH 156/546] move StationG2Board.h to variants, enable ESM, add companion wifi, cleanup --- .../station_g2}/StationG2Board.h | 17 +-------- variants/station_g2/platformio.ini | 36 +++++++++++++++++-- variants/station_g2/target.cpp | 9 ++++- variants/station_g2/target.h | 6 ++-- 4 files changed, 45 insertions(+), 23 deletions(-) rename {src/helpers => variants/station_g2}/StationG2Board.h (83%) diff --git a/src/helpers/StationG2Board.h b/variants/station_g2/StationG2Board.h similarity index 83% rename from src/helpers/StationG2Board.h rename to variants/station_g2/StationG2Board.h index 2e31571f..a905682c 100644 --- a/src/helpers/StationG2Board.h +++ b/variants/station_g2/StationG2Board.h @@ -1,22 +1,7 @@ #pragma once #include <Arduino.h> - -// LoRa radio module pins for Station G2 -#define P_LORA_DIO_1 48 -#define P_LORA_NSS 11 -#define P_LORA_RESET 21 -#define P_LORA_BUSY 47 -#define P_LORA_SCLK 12 -#define P_LORA_MISO 14 -#define P_LORA_MOSI 13 - -// built-ins -//#define PIN_LED_BUILTIN 35 -//#define PIN_VEXT_EN 36 - -#include "ESP32Board.h" - +#include <helpers/ESP32Board.h> #include <driver/rtc_io.h> class StationG2Board : public ESP32Board { diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 016a46a9..bda8b7ae 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -3,28 +3,40 @@ extends = esp32_base board = station-g2 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/station_g2 + -I src/helpers/ui -D STATION_G2 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=48 + -D P_LORA_NSS=11 + -D P_LORA_RESET=21 + -D P_LORA_BUSY=47 + -D P_LORA_SCLK=12 + -D P_LORA_MISO=14 + -D P_LORA_MOSI=13 -D LORA_TX_POWER=19 ; -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=38 + -D PIN_GPS_RX=7 + -D PIN_GPS_TX=15 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 ; -D SX126X_RX_BOOSTED_GAIN=1 - DO NOT ENABLE THIS! ; https://wiki.uniteng.com/en/meshtastic/station-g2#impact-of-lora-node-dense-areashigh-noise-environments-on-rf-performance - -I src/helpers/ui -D DISPLAY_CLASS=SH1106Display build_src_filter = ${esp32_base.build_src_filter} +<../variants/station_g2> + +<helpers/sensors> +<helpers/ui/SH1106Display.cpp> +<helpers/ui/MomentaryButton.cpp> lib_deps = ${esp32_base.lib_deps} + ${sensor_base.lib_deps} adafruit/Adafruit SH110X @ ~2.1.13 adafruit/Adafruit GFX Library @ ^1.12.1 @@ -172,7 +184,6 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SH1106Display -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 @@ -190,7 +201,6 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=SH1106Display -D MAX_CONTACTS=300 -D MAX_GROUP_CHANNELS=8 -D BLE_PIN_CODE=123456 @@ -205,3 +215,23 @@ build_src_filter = ${Station_G2.build_src_filter} lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Station_G2_companion_radio_wifi] +extends = Station_G2 +build_flags = + ${Station_G2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Station_G2.build_src_filter} + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Station_G2.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 5423af68..c2ee97e2 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -14,7 +14,14 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 3f67af3a..2bf7016d 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -3,10 +3,10 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/StationG2Board.h> +#include <StationG2Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> -#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS #include <helpers/ui/SH1106Display.h> @@ -16,7 +16,7 @@ extern StationG2Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 69cddbca4e66e74fe19e71994360872a6a6a96eb Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sun, 12 Oct 2025 00:32:26 +0200 Subject: [PATCH 157/546] move LilyGoTLoraBoard.h to variants, use template in platformio.ini, cleanup --- .../lilygo_tlora_v2_1}/LilyGoTLoraBoard.h | 4 ++-- variants/lilygo_tlora_v2_1/platformio.ini | 22 ++++--------------- variants/lilygo_tlora_v2_1/target.h | 3 +-- 3 files changed, 7 insertions(+), 22 deletions(-) rename {src/helpers => variants/lilygo_tlora_v2_1}/LilyGoTLoraBoard.h (93%) diff --git a/src/helpers/LilyGoTLoraBoard.h b/variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h similarity index 93% rename from src/helpers/LilyGoTLoraBoard.h rename to variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h index c595740f..545219b2 100644 --- a/src/helpers/LilyGoTLoraBoard.h +++ b/variants/lilygo_tlora_v2_1/LilyGoTLoraBoard.h @@ -1,7 +1,7 @@ #pragma once #include <Arduino.h> -#include "ESP32Board.h" +#include <helpers/ESP32Board.h> // LILYGO T-LoRa V2.1-1.6 board with SX1276 class LilyGoTLoraBoard : public ESP32Board { @@ -9,7 +9,7 @@ public: const char* getManufacturerName() const override { return "LILYGO T-LoRa V2.1-1.6"; } - + uint16_t getBattMilliVolts() override { analogReadResolution(12); diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index e580959c..9ef9734e 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -6,6 +6,8 @@ build_type = release ; Set build type to release board_build.partitions = min_spiffs.csv ; get around 4mb flash limit build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} + -UENV_INCLUDE_GPS -I variants/lilygo_tlora_v2_1 -Os -ffunction-sections -fdata-sections ; Optimize for size -D LILYGO_TLORA ; LILYGO T-LoRa V2.1-1.6 ESP32 with SX1276 @@ -27,28 +29,18 @@ build_flags = -D WRAPPER_CLASS=CustomSX1276Wrapper -D SX127X_CURRENT_LIMIT=120 -D LORA_TX_POWER=20 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_BMP280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 build_src_filter = ${esp32_base.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> +<../variants/lilygo_tlora_v2_1> +<helpers/sensors> lib_deps = ${esp32_base.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.13 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit BMP280 Library @ ^2.6.8 + ${sensor_base.lib_deps} ; === LILYGO T-LoRa V2.1-1.6 with SX1276 environments === [env:LilyGo_TLora_V2_1_1_6_repeater] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> build_flags = ${LilyGo_TLora_V2_1_1_6.build_flags} @@ -73,7 +65,6 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} @@ -89,7 +80,6 @@ build_flags = ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -111,7 +101,6 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} +<helpers/esp32/*.cpp> - +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -149,7 +138,6 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} +<helpers/esp32/*.cpp> - +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -163,7 +151,6 @@ lib_deps = [env:LilyGo_TLora_V2_1_1_6_repeater_bridge_rs232] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<helpers/bridges/RS232Bridge.cpp> +<../examples/simple_repeater> build_flags = @@ -187,7 +174,6 @@ lib_deps = [env:LilyGo_TLora_V2_1_1_6_repeater_bridge_espnow] extends = LilyGo_TLora_V2_1_1_6 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<helpers/bridges/ESPNowBridge.cpp> +<../examples/simple_repeater> build_flags = diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index 380d733b..326a0dee 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -3,10 +3,9 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/LilyGoTLoraBoard.h> +#include <LilyGoTLoraBoard.h> #include <helpers/radiolib/CustomSX1276Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> -#include <helpers/SensorManager.h> #include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS #include <helpers/ui/SSD1306Display.h> From 837e7dcbdbbb08f0ba0514fdac6a5ed056dd2b67 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 12 Oct 2025 12:33:20 +1100 Subject: [PATCH 158/546] * Advert type fix * GPS pref defaults tidy --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 11 ++++++++--- examples/simple_sensor/SensorMesh.cpp | 11 ++++++++--- src/helpers/CommonCLI.cpp | 4 +--- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index de8072f1..eaf0fb2b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -645,6 +645,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc // GPS defaults _prefs.gps_enabled = 0; _prefs.gps_interval = 0; + _prefs.advert_loc_policy = ADVERT_LOC_PREFS; } void MyMesh::begin(FILESYSTEM *fs) { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 00736820..b241788e 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -117,13 +117,13 @@ mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data_len; { if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name); app_data_len = builder.encodeTo(app_data); } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, sensors.node_lat, sensors.node_lon); app_data_len = builder.encodeTo(app_data); } else { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); app_data_len = builder.encodeTo(app_data); } } @@ -620,6 +620,11 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password)); #endif + // GPS defaults + _prefs.gps_enabled = 0; + _prefs.gps_interval = 0; + _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + next_post_idx = 0; next_client_idx = 0; next_push = 0; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 97d95d5f..d365f7fa 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -242,13 +242,13 @@ mesh::Packet* SensorMesh::createSelfAdvert() { uint8_t app_data_len; { if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); + AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name); app_data_len = builder.encodeTo(app_data); } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); + AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, sensors.node_lat, sensors.node_lon); app_data_len = builder.encodeTo(app_data); } else { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); + AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); app_data_len = builder.encodeTo(app_data); } } @@ -690,6 +690,11 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.disable_fwd = true; _prefs.flood_max = 64; _prefs.interference_threshold = 0; // disabled + + // GPS defaults + _prefs.gps_enabled = 0; + _prefs.gps_interval = 0; + _prefs.advert_loc_policy = ADVERT_LOC_PREFS; } void SensorMesh::begin(FILESYSTEM* fs) { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 757180f8..b0229f70 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -67,9 +67,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read(pad, 4); // 152 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 - if (file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)) == -1) { - _prefs->advert_loc_policy = ADVERT_LOC_PREFS; // default value - } // 161 + file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 // 162 // sanitise bad pref values From 93c01807400db0a04fbef2aa2762fa6a717061c6 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 12 Oct 2025 12:49:26 +1100 Subject: [PATCH 159/546] * Refactor: advert_loc_policy now applied in new method CommonCLI::buildAdvertData() --- examples/simple_repeater/MyMesh.cpp | 14 +------------- examples/simple_room_server/MyMesh.cpp | 14 +------------- examples/simple_sensor/SensorMesh.cpp | 14 +------------- src/helpers/CommonCLI.cpp | 14 ++++++++++++++ src/helpers/CommonCLI.h | 1 + 5 files changed, 18 insertions(+), 39 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index eaf0fb2b..6c85b7e0 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -287,19 +287,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name); - app_data_len = builder.encodeTo(app_data); - } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, sensors.node_lat, sensors.node_lon); - app_data_len = builder.encodeTo(app_data); - } else { - AdvertDataBuilder builder(ADV_TYPE_REPEATER, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - } + uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_REPEATER, app_data); return createAdvert(self_id, app_data, app_data_len); } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index b241788e..e4b57f98 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -114,19 +114,7 @@ bool MyMesh::processAck(const uint8_t *data) { mesh::Packet *MyMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name); - app_data_len = builder.encodeTo(app_data); - } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, sensors.node_lat, sensors.node_lon); - app_data_len = builder.encodeTo(app_data); - } else { - AdvertDataBuilder builder(ADV_TYPE_ROOM, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - } + uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_ROOM, app_data); return createAdvert(self_id, app_data, app_data_len); } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index d365f7fa..92ea1889 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -239,19 +239,7 @@ uint8_t SensorMesh::handleRequest(uint8_t perms, uint32_t sender_timestamp, uint mesh::Packet* SensorMesh::createSelfAdvert() { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; - uint8_t app_data_len; - { - if (_prefs.advert_loc_policy == ADVERT_LOC_NONE) { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name); - app_data_len = builder.encodeTo(app_data); - } else if (_prefs.advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, sensors.node_lat, sensors.node_lon); - app_data_len = builder.encodeTo(app_data); - } else { - AdvertDataBuilder builder(ADV_TYPE_SENSOR, _prefs.node_name, _prefs.node_lat, _prefs.node_lon); - app_data_len = builder.encodeTo(app_data); - } - } + uint8_t app_data_len = _cli.buildAdvertData(ADV_TYPE_SENSOR, app_data); return createAdvert(self_id, app_data, app_data_len); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b0229f70..87f20f5a 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -1,6 +1,7 @@ #include <Arduino.h> #include "CommonCLI.h" #include "TxtDataHelpers.h" +#include "AdvertDataHelpers.h" #include <RTClib.h> // Believe it or not, this std C function is busted on some platforms! @@ -160,6 +161,19 @@ void CommonCLI::savePrefs() { _callbacks->savePrefs(); } +uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { + if (_prefs->advert_loc_policy == ADVERT_LOC_NONE) { + AdvertDataBuilder builder(node_type, _prefs->node_name); + return builder.encodeTo(app_data); + } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) { + AdvertDataBuilder builder(node_type, _prefs->node_name, sensors.node_lat, sensors.node_lon); + return builder.encodeTo(app_data); + } else { + AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon); + return builder.encodeTo(app_data); + } +} + void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 68489913..ce567016 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -98,4 +98,5 @@ public: void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); void handleCommand(uint32_t sender_timestamp, const char* command, char* reply); + uint8_t buildAdvertData(uint8_t node_type, uint8_t* app_data); }; From 8426fddcb7af7243f97c665067e6ed4d03910f97 Mon Sep 17 00:00:00 2001 From: Woodie-07 <woodie@woodie.dev> Date: Sun, 12 Oct 2025 16:09:57 +0100 Subject: [PATCH 160/546] workaround for LR1110 shift issue it seems that if the LR1110 radio hears a packet corrupted in a specific way, it'll report a packet of 0 length and with the header error IRQ set. every packet received afterwards will then be shifted to the right by 4 bytes on top of the radio's reported offset. this can occur multiple times with the shift increasing by 4 bytes each time. thus, this patch will read from an additional offset after hearing the trigger packet. transmitting seems to reset the shift - unsure exactly what operation resets it but standby() is called after tx so patch assumes shift is 0 after standby(). more investigation may be needed here. --- platformio.ini | 2 +- src/helpers/radiolib/CustomLR1110.h | 68 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4fe17af9..e72b7b3a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,7 +18,7 @@ monitor_speed = 115200 lib_deps = SPI Wire - jgromes/RadioLib @ ^7.1.2 + jgromes/RadioLib @ ^7.3.0 rweather/Crypto @ ^0.4.0 adafruit/RTClib @ ^2.1.3 melopero/Melopero RV3028 @ ^1.1.0 diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index e82f48f5..0d1fbccc 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -9,6 +9,74 @@ class CustomLR1110 : public LR1110 { public: CustomLR1110(Module *mod) : LR1110(mod) { } + uint8_t shiftCount = 0; + + int16_t standby() override { + // tx resets the shift, standby is called on tx completion + // this might not actually be what resets it, but it seems to work + // more investigation needed + this->shiftCount = 0; + return LR1110::standby(); + } + + size_t getPacketLength(bool update) override { + size_t len = LR1110::getPacketLength(update); + if (len == 0) { + uint32_t irq = getIrqStatus(); + if (irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { + Serial.println(F("got possible bug packet")); + this->shiftCount += 4; // uint8 will loop around to 0 at 256, perfect as rx buffer is 256 bytes + } else { + Serial.println(F("got zero-length packet without header err irq")); + } + } + return len; + } + + int16_t readData(uint8_t *data, size_t len) override { + // check active modem + uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; + int16_t state = getPacketType(&modem); + RADIOLIB_ASSERT(state); + if((modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) && + (modem != RADIOLIB_LR11X0_PACKET_TYPE_GFSK)) { + return(RADIOLIB_ERR_WRONG_MODEM); + } + + // check integrity CRC + uint32_t irq = getIrqStatus(); + int16_t crcState = RADIOLIB_ERR_NONE; + // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) + if((irq & RADIOLIB_LR11X0_IRQ_CRC_ERR) || ((irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID))) { + crcState = RADIOLIB_ERR_CRC_MISMATCH; + } + + // get packet length + // the offset is needed since LR11x0 seems to move the buffer base by 4 bytes on every packet + uint8_t offset = 0; + size_t length = LR1110::getPacketLength(true, &offset); + if((len != 0) && (len < length)) { + // user requested less data than we got, only return what was requested + length = len; + } + + // read packet data + state = readBuffer8(data, length, (uint8_t)(offset + this->shiftCount)); // add shiftCount to offset - only change from radiolib + RADIOLIB_ASSERT(state); + + // clear the Rx buffer + state = clearRxBuffer(); + RADIOLIB_ASSERT(state); + + // clear interrupt flags + state = clearIrqState(RADIOLIB_LR11X0_IRQ_ALL); + + // check if CRC failed - this is done after reading data to give user the option to keep them + RADIOLIB_ASSERT(crcState); + + return(state); + } + RadioLibTime_t getTimeOnAir(size_t len) override { // calculate number of symbols float N_symbol = 0; From c6e5d5021eb9592ebaf47a4f45cae031bea92d97 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sun, 12 Oct 2025 17:16:45 +0200 Subject: [PATCH 161/546] fix: remove VL53L0X because it causes bootloops on esp32c3 --- variants/xiao_c3/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 617f610e..683199d9 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -5,6 +5,7 @@ build_flags = ${esp32_base.build_flags} ${sensor_base.build_flags} -UENV_INCLUDE_GPS + -UENV_INCLUDE_VL53L0X -I variants/xiao_c3 -D ESP32_CPU_FREQ=80 -D PIN_VBAT_READ=D0 From c6b4a584498ea375e6227d405b65fe4c67735017 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 14 Oct 2025 12:31:43 +1100 Subject: [PATCH 162/546] * repeater and room server: enable downgrading permissions on guest login --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6c85b7e0..4bb9a2bb 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -114,6 +114,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr MESH_DEBUG_PRINTLN("Login success!"); client->last_timestamp = sender_timestamp; client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions &= ~0x03; client->permissions |= perms; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index e4b57f98..c1a39a11 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -286,7 +286,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator ClientInfo* client = NULL; - if (data[8] == 0 && !_prefs.allow_read_only) { // blank password, just check if sender is in ACL + if (data[8] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); if (client == NULL) { #if MESH_DEBUG @@ -322,6 +322,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m client->extra.room.push_failures = 0; client->last_activity = getRTCClock()->getCurrentTime(); + client->permissions &= ~0x03; client->permissions |= perm; memcpy(client->shared_secret, secret, PUB_KEY_SIZE); From fa8c31be883be52066eba131b339676e72f8ccb1 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 15 Oct 2025 22:47:55 +1100 Subject: [PATCH 163/546] * fix for RAK12500 GPS (I2C) --- .../sensors/EnvironmentSensorManager.cpp | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index aa51c85a..e7a9f5e5 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -98,6 +98,41 @@ static bool serialGPSFlag = false; static SFE_UBLOX_GNSS ublox_GNSS; #endif +class RAK12500LocationProvider : public LocationProvider { + long _lat = 0; + long _lng = 0; + long _alt = 0; + int _sats = 0; + long _epoch = 0; + bool _fix = false; +public: + long getLatitude() override { return _lat; } + long getLongitude() override { return _lng; } + long getAltitude() override { return _alt; } + long satellitesCount() override { return _sats; } + bool isValid() override { return _fix; } + long getTimestamp() override { return _epoch; } + void sendSentence(const char * sentence) override { } + void reset() override { } + void begin() override { } + void stop() override { } + void loop() override { + if (ublox_GNSS.getGnssFixOk(8)) { + _fix = true; + _lat = ublox_GNSS.getLatitude(2) / 10; + _lng = ublox_GNSS.getLongitude(2) / 10; + _alt = ublox_GNSS.getAltitude(2); + _sats = ublox_GNSS.getSIV(2); + } else { + _fix = false; + } + _epoch = ublox_GNSS.getUnixEpoch(2); + } + bool isEnabled() override { return true; } +}; + +static RAK12500LocationProvider RAK12500_provider; + bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_GPS #ifdef RAK_WISBLOCK_GPS @@ -521,12 +556,22 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ //Try to init RAK12500 on I2C if (ublox_GNSS.begin(Wire) == true){ MESH_DEBUG_PRINTLN("RAK12500 GPS init correctly with pin %i",ioPin); - ublox_GNSS.setI2COutput(COM_TYPE_NMEA); + ublox_GNSS.setI2COutput(COM_TYPE_UBX); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GLONASS); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_BEIDOU); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_IMES); + ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS); + ublox_GNSS.setMeasurementRate(1000); ublox_GNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); gpsResetPin = ioPin; i2cGPSFlag = true; gps_active = true; gps_detected = true; + + _location = &RAK12500_provider; return true; } else if(Serial1){ @@ -583,14 +628,7 @@ void EnvironmentSensorManager::loop() { if (millis() > next_gps_update) { if(gps_active){ #ifdef RAK_WISBLOCK_GPS - if(i2cGPSFlag){ - node_lat = ((double)ublox_GNSS.getLatitude())/10000000.; - node_lon = ((double)ublox_GNSS.getLongitude())/10000000.; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); - node_altitude = ((double)ublox_GNSS.getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); - } - else if (serialGPSFlag && _location->isValid()) { + if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); From d3be6afccb83a4a7f15a64aaded6c6e75c2d9cbe Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 15 Oct 2025 22:51:05 +1100 Subject: [PATCH 164/546] * fix for non-RAK targets --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index e7a9f5e5..29545894 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -96,7 +96,6 @@ static bool serialGPSFlag = false; #define TELEM_RAK12500_ADDRESS 0x42 //RAK12500 Ublox GPS via i2c #include <SparkFun_u-blox_GNSS_Arduino_Library.h> static SFE_UBLOX_GNSS ublox_GNSS; -#endif class RAK12500LocationProvider : public LocationProvider { long _lat = 0; @@ -132,6 +131,7 @@ public: }; static RAK12500LocationProvider RAK12500_provider; +#endif bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_GPS From cd920693ec4c21f184c1512162c578f5fc119d16 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 16 Oct 2025 17:33:22 +1100 Subject: [PATCH 165/546] * UITask: new UI_HAS_JOYSTICK * MomentaryButton: new constructor 'multiclick' param * WIoTrackerL1: now just use joystick, joystick press for KEY_ENTER, no multi-click for snappier UI --- examples/companion_radio/ui-new/UITask.cpp | 31 ++++++++++++++-------- src/helpers/ui/MomentaryButton.cpp | 4 +-- src/helpers/ui/MomentaryButton.h | 2 +- variants/wio-tracker-l1/platformio.ini | 2 ++ variants/wio-tracker-l1/target.cpp | 7 ++--- variants/wio-tracker-l1/target.h | 1 + variants/wio-tracker-l1/variant.h | 3 ++- 7 files changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index c3da0643..b6484a00 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -20,7 +20,11 @@ #define UI_RECENT_LIST_SIZE 4 #endif -#define PRESS_LABEL "long press" +#if UI_HAS_JOYSTICK + #define PRESS_LABEL "press Enter" +#else + #define PRESS_LABEL "long press" +#endif #include "icons.h" @@ -360,7 +364,7 @@ public: display.drawTextCentered(display.width() / 2, 34, "hibernating..."); } else { display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32); - display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate: " PRESS_LABEL); + display.drawTextCentered(display.width() / 2, 64 - 11, "hibernate:" PRESS_LABEL); } } return 5000; // next render after 5000 ms @@ -660,19 +664,13 @@ bool UITask::isButtonPressed() const { void UITask::loop() { char c = 0; -#if defined(PIN_USER_BTN) +#if UI_HAS_JOYSTICK int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); + c = checkDisplayOn(KEY_ENTER); } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); - } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); - } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + c = handleLongPress(KEY_ENTER); // REVISIT: could be mapped to different key code } -#endif -#if defined(WIO_TRACKER_L1) ev = joystick_left.check(); if (ev == BUTTON_EVENT_CLICK) { c = checkDisplayOn(KEY_LEFT); @@ -685,6 +683,17 @@ void UITask::loop() { } else if (ev == BUTTON_EVENT_LONG_PRESS) { c = handleLongPress(KEY_RIGHT); } +#elif defined(PIN_USER_BTN) + int ev = user_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_NEXT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_ENTER); + } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { + c = handleDoubleClick(KEY_PREV); + } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { + c = handleTripleClick(KEY_SELECT); + } #endif #if defined(PIN_USER_BTN_ANA) ev = analog_btn.check(); diff --git a/src/helpers/ui/MomentaryButton.cpp b/src/helpers/ui/MomentaryButton.cpp index 0ea4b027..9d01e5b0 100644 --- a/src/helpers/ui/MomentaryButton.cpp +++ b/src/helpers/ui/MomentaryButton.cpp @@ -2,7 +2,7 @@ #define MULTI_CLICK_WINDOW_MS 280 -MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup) { +MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse, bool pulldownup, bool multiclick) { _pin = pin; _reverse = reverse; _pull = pulldownup; @@ -13,7 +13,7 @@ MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse _threshold = 0; _click_count = 0; _last_click_time = 0; - _multi_click_window = MULTI_CLICK_WINDOW_MS; + _multi_click_window = multiclick ? MULTI_CLICK_WINDOW_MS : 0; _pending_click = false; } diff --git a/src/helpers/ui/MomentaryButton.h b/src/helpers/ui/MomentaryButton.h index 1122e56a..358a343b 100644 --- a/src/helpers/ui/MomentaryButton.h +++ b/src/helpers/ui/MomentaryButton.h @@ -23,7 +23,7 @@ class MomentaryButton { bool isPressed(int level) const; public: - MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false); + MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false, bool pulldownup=false, bool multiclick=true); MomentaryButton(int8_t pin, int long_press_mills, int analog_threshold); void begin(); int check(bool repeat_click=false); // returns one of BUTTON_EVENT_* diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 971a32cb..31c6bcb0 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -63,6 +63,7 @@ build_flags = ${WioTrackerL1.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SH1106Display + -D UI_HAS_JOYSTICK=1 -D OFFLINE_QUEUE_SIZE=256 -D PIN_BUZZER=12 -D QSPIFLASH=1 @@ -91,6 +92,7 @@ build_flags = ${WioTrackerL1.build_flags} -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=SH1106Display + -D UI_HAS_JOYSTICK=1 -D PIN_BUZZER=12 -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 374e53af..e7b73040 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -21,9 +21,10 @@ EnvironmentSensorManager sensors = EnvironmentSensorManager(); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; - MomentaryButton user_btn(PIN_USER_BTN, 1000, true); - MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true); - MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true); + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, false); + MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true, false, false); + MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true, false, false); + MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, false); #endif bool radio_init() { diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index d3021d6d..97e575d8 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -27,6 +27,7 @@ extern EnvironmentSensorManager sensors; extern MomentaryButton user_btn; extern MomentaryButton joystick_left; extern MomentaryButton joystick_right; + extern MomentaryButton back_btn; #endif bool radio_init(); diff --git a/variants/wio-tracker-l1/variant.h b/variants/wio-tracker-l1/variant.h index 7e1b1b86..86ad62ba 100644 --- a/variants/wio-tracker-l1/variant.h +++ b/variants/wio-tracker-l1/variant.h @@ -31,12 +31,13 @@ #define PIN_BUTTON4 (27) // Joystick Left #define PIN_BUTTON5 (28) // Joystick Right #define PIN_BUTTON6 (29) // Joystick Press -#define PIN_USER_BTN PIN_BUTTON1 +#define PIN_BACK_BTN PIN_BUTTON1 #define JOYSTICK_UP PIN_BUTTON2 #define JOYSTICK_DOWN PIN_BUTTON3 #define JOYSTICK_LEFT PIN_BUTTON4 #define JOYSTICK_RIGHT PIN_BUTTON5 #define JOYSTICK_PRESS PIN_BUTTON6 +#define PIN_USER_BTN PIN_BUTTON6 // Buzzer // #define PIN_BUZZER (12) // Buzzer pin (defined per firmware type) From 0e7486552dec3868ca2093917d65039bf43f8f72 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 16 Oct 2025 10:17:23 +0200 Subject: [PATCH 166/546] Add simple BME680 support to RAK with adafruit library --- platformio.ini | 2 ++ .../sensors/EnvironmentSensorManager.cpp | 33 ++++++++++++++++++- .../sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 4fe17af9..7af33747 100644 --- a/platformio.ini +++ b/platformio.ini @@ -124,6 +124,7 @@ build_flags = -D ENV_INCLUDE_INA260=1 -D ENV_INCLUDE_MLX90614=1 -D ENV_INCLUDE_VL53L0X=1 + -D ENV_INCLUDE_BME680=1 lib_deps = adafruit/Adafruit INA3221 Library @ ^1.0.1 adafruit/Adafruit INA219 @ ^1.2.3 @@ -138,3 +139,4 @@ lib_deps = adafruit/Adafruit MLX90614 Library @ ^2.1.5 adafruit/Adafruit_VL53L0X @ ^1.2.4 stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit BME680 Library @ ^2.0.4 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 29545894..e244ef20 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -6,6 +6,14 @@ #define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors #endif +#ifdef ENV_INCLUDE_BME680 +#ifndef TELEM_BME680_ADDRESS +#define TELEM_BME680_ADDRESS 0x76 // BME680 environmental sensor I2C address +#endif +#include <Adafruit_BME680.h> +static Adafruit_BME680 BME680; +#endif + #if ENV_INCLUDE_AHTX0 #define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address #include <Adafruit_AHTX0.h> @@ -286,6 +294,16 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BME680 + if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); + BME680_initialized = true; + } else { + BME680_initialized = false; + MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); + } + #endif + return true; } @@ -415,6 +433,18 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BME680 + if (BME680_initialized) { + if (BME680.performReading()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); + next_available_channel++; + } + } + #endif + } return true; @@ -623,6 +653,7 @@ void EnvironmentSensorManager::stop_gps() { void EnvironmentSensorManager::loop() { static long next_gps_update = 0; + #if ENV_INCLUDE_GPS _location->loop(); if (millis() > next_gps_update) { @@ -647,5 +678,5 @@ void EnvironmentSensorManager::loop() { } next_gps_update = millis() + 1000; } + #endif } -#endif diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 09c6cae4..133d2650 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -20,6 +20,7 @@ protected: bool MLX90614_initialized = false; bool VL53L0X_initialized = false; bool SHT4X_initialized = false; + bool BME680_initialized = false; bool gps_detected = false; bool gps_active = false; From 3c48f016019d611d763493e0de246397d2a7ea98 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 16 Oct 2025 11:29:22 +0200 Subject: [PATCH 167/546] BME680 library doesn't have altitude calculation, we can add it here to match other sensors' --- src/helpers/sensors/EnvironmentSensorManager.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index e244ef20..bb70c0b5 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -8,8 +8,9 @@ #ifdef ENV_INCLUDE_BME680 #ifndef TELEM_BME680_ADDRESS -#define TELEM_BME680_ADDRESS 0x76 // BME680 environmental sensor I2C address +#define TELEM_BME680_ADDRESS 0x76 #endif +#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25) #include <Adafruit_BME680.h> static Adafruit_BME680 BME680; #endif @@ -439,6 +440,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); next_available_channel++; } @@ -680,3 +682,4 @@ void EnvironmentSensorManager::loop() { } #endif } +#endif From 02351abc2d5cffbd68d6c940ee8ac974ce0605b2 Mon Sep 17 00:00:00 2001 From: Woodie-07 <woodie@woodie.dev> Date: Thu, 16 Oct 2025 16:25:18 +0100 Subject: [PATCH 168/546] change println to debug macro in lr1110 patch --- src/helpers/radiolib/CustomLR1110.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 0d1fbccc..f723bb7f 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -1,6 +1,7 @@ #pragma once #include <RadioLib.h> +#include "MeshCore.h" #define LR1110_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received #define LR1110_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received @@ -24,10 +25,10 @@ class CustomLR1110 : public LR1110 { if (len == 0) { uint32_t irq = getIrqStatus(); if (irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { - Serial.println(F("got possible bug packet")); + MESH_DEBUG_PRINTLN("LR1110: got header err, assuming shift"); this->shiftCount += 4; // uint8 will loop around to 0 at 256, perfect as rx buffer is 256 bytes } else { - Serial.println(F("got zero-length packet without header err irq")); + MESH_DEBUG_PRINTLN("LR1110: got zero-length packet without header err irq"); } } return len; From 24ed5b377f35cea6e44e6512e4ffa2442dfb03f6 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Fri, 17 Oct 2025 16:25:58 +0200 Subject: [PATCH 169/546] added custom pio task "Create UF2 file" --- create-uf2.py | 31 +++++++++++++++++++++++++++++++ platformio.ini | 1 + 2 files changed, 32 insertions(+) create mode 100644 create-uf2.py diff --git a/create-uf2.py b/create-uf2.py new file mode 100644 index 00000000..10ec0ed6 --- /dev/null +++ b/create-uf2.py @@ -0,0 +1,31 @@ +#!/usr/bin/python3 + +# Adds PlatformIO post-processing to convert hex files to uf2 files + +import os + +Import("env") + +firmware_hex = "${BUILD_DIR}/${PROGNAME}.hex" +uf2_file = os.environ.get("UF2_FILE_PATH", "${BUILD_DIR}/${PROGNAME}.uf2") + +def create_uf2_action(source, target, env): + uf2_cmd = " ".join( + [ + '"$PYTHONEXE"', + '"$PROJECT_DIR/bin/uf2conv/uf2conv.py"', + '-f', '0xADA52840', + '-c', firmware_hex, + '-o', uf2_file, + ] + ) + env.Execute(uf2_cmd) + +env.AddCustomTarget( + name="create_uf2", + dependencies=firmware_hex, + actions=create_uf2_action, + title="Create UF2 file", + description="Use uf2conv to convert hex binary into uf2", + always_build=True, +) \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 4fe17af9..f107867c 100644 --- a/platformio.ini +++ b/platformio.ini @@ -76,6 +76,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ [nrf52_base] extends = arduino_base platform = nordicnrf52 +extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 From 006af527768f2aebffe3f12cb6dc4fb60cfd6fc7 Mon Sep 17 00:00:00 2001 From: haxwithaxe <spam@haxwithaxe.net> Date: Fri, 17 Oct 2025 14:20:55 -0400 Subject: [PATCH 170/546] Added more polished build.sh usage --- build.sh | 47 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/build.sh b/build.sh index a871b9ab..732dac43 100755 --- a/build.sh +++ b/build.sh @@ -1,12 +1,45 @@ #!/usr/bin/env bash -# usage -# sh build.sh build-firmware RAK_4631_Repeater -# sh build.sh build-firmwares -# sh build.sh build-matching-firmwares RAK_4631 -# sh build.sh build-companion-firmwares -# sh build.sh build-repeater-firmwares -# sh build.sh build-room-server-firmwares +global_usage() { + cat - <<EOF +Usage: +sh build.sh <command> [target] + +Commands: + help|usage|-h|--help: Shows this message. + build-firmware <target>: Build the firmware for the given build target. + build-firmwares: Build all firmwares for all targets. + build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>. + build-companion-firmwares: Build all companion firmwares for all build targets. + build-repeater-firmwares: Build all repeater firmwares for all build targets. + build-room-server-firmwares: Build all chat room server firmwares for all build targets. + +Examples: +Build firmware for the "RAK_4631_Repeater" device target +$ sh build.sh build-firmware RAK_4631_Repeater + +Build all firmwares for device targets containing the string "RAK_4631" +$ sh build.sh build-matching-firmwares <build-match-spec> + +Build all companion firmwares +$ sh build.sh build-companion-firmwares + +Build all repeater firmwares +$ sh build.sh build-repeater-firmwares + +Build all chat room server firmwares +$ sh build.sh build-room-server-firmwares +EOF +} + +# Catch cries for help before doing anything else. +case $1 in + help|usage|-h|--help) + global_usage + exit 1 + ;; +esac + # get a list of pio env names that start with "env:" get_pio_envs() { From 3210475f352c54e795310e908bd0c07a9b2ffa97 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 18 Oct 2025 12:33:43 +0200 Subject: [PATCH 171/546] CommonCli: Remove dependency on target.h --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- src/helpers/CommonCLI.cpp | 26 +++++++++++++------------- src/helpers/CommonCLI.h | 7 ++++--- variants/t1000-e/platformio.ini | 1 + 6 files changed, 21 insertions(+), 19 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4bb9a2bb..374384bd 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -586,7 +586,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index c1a39a11..a9ba7998 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -580,7 +580,7 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; _logging = false; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 92ea1889..8f8a11fe 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -651,7 +651,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 87f20f5a..b8bb698a 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -166,7 +166,7 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { AdvertDataBuilder builder(node_type, _prefs->node_name); return builder.encodeTo(app_data); } else if (_prefs->advert_loc_policy == ADVERT_LOC_SHARE) { - AdvertDataBuilder builder(node_type, _prefs->node_name, sensors.node_lat, sensors.node_lon); + AdvertDataBuilder builder(node_type, _prefs->node_name, _sensors->node_lat, _sensors->node_lon); return builder.encodeTo(app_data); } else { AdvertDataBuilder builder(node_type, _prefs->node_name, _prefs->node_lat, _prefs->node_lon); @@ -533,7 +533,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "%s", _board->getManufacturerName()); } else if (memcmp(command, "sensor get ", 11) == 0) { const char* key = command + 11; - const char* val = sensors.getSettingByKey(key); + const char* val = _sensors->getSettingByKey(key); if (val != NULL) { sprintf(reply, "> %s", val); } else { @@ -545,7 +545,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); const char *key = (num > 0) ? parts[0] : ""; const char *value = (num > 1) ? parts[1] : "null"; - if (sensors.setSettingByKey(key, value)) { + if (_sensors->setSettingByKey(key, value)) { strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); @@ -553,7 +553,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(command, "sensor list", 11) == 0) { char* dp = reply; int start = 0; - int end = sensors.getNumSettings(); + int end = _sensors->getNumSettings(); if (strlen(command) > 11) { start = _atoi(command+12); } @@ -565,8 +565,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch int i; for (i = start; i < end && (dp-reply < 134); i++) { sprintf(dp, "%s=%s\n", - sensors.getSettingName(i), - sensors.getSettingValue(i)); + _sensors->getSettingName(i), + _sensors->getSettingValue(i)); dp = strchr(dp, 0); } if (i < end) { @@ -577,7 +577,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - if (sensors.setSettingByKey("gps", "1")) { + if (_sensors->setSettingByKey("gps", "1")) { _prefs->gps_enabled = 1; savePrefs(); strcpy(reply, "ok"); @@ -585,7 +585,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps off", 7) == 0) { - if (sensors.setSettingByKey("gps", "0")) { + if (_sensors->setSettingByKey("gps", "0")) { _prefs->gps_enabled = 0; savePrefs(); strcpy(reply, "ok"); @@ -593,13 +593,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps sync", 8) == 0) { - LocationProvider * l = sensors.getLocationProvider(); + LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { l->syncTime(); } } else if (memcmp(command, "gps setloc", 10) == 0) { - _prefs->node_lat = sensors.node_lat; - _prefs->node_lon = sensors.node_lon; + _prefs->node_lat = _sensors->node_lat; + _prefs->node_lon = _sensors->node_lon; savePrefs(); strcpy(reply, "ok"); } else if (memcmp(command, "gps advert", 10) == 0) { @@ -633,12 +633,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "error"); } } else if (memcmp(command, "gps", 3) == 0) { - LocationProvider * l = sensors.getLocationProvider(); + LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { bool enabled = l->isEnabled(); // is EN pin on ? bool fix = l->isValid(); // has fix ? int sats = l->satellitesCount(); - bool active = !strcmp(sensors.getSettingByKey("gps"), "1"); + bool active = !strcmp(_sensors->getSettingByKey("gps"), "1"); if (enabled) { sprintf(reply, "on, %s, %s, %d sats", active?"active":"deactivated", diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ce567016..ea59aa92 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -2,7 +2,7 @@ #include "Mesh.h" #include <helpers/IdentityStore.h> -#include <target.h> +#include <helpers/SensorManager.h> #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) #define WITH_BRIDGE @@ -85,6 +85,7 @@ class CommonCLI { NodePrefs* _prefs; CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; + SensorManager* _sensors; char tmp[PRV_KEY_SIZE*2 + 4]; mesh::RTCClock* getRTCClock() { return _rtc; } @@ -92,8 +93,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); public: - CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, NodePrefs* prefs, CommonCLICallbacks* callbacks) - : _board(&board), _rtc(&rtc), _prefs(prefs), _callbacks(callbacks) { } + CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks) + : _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { } void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 6bb3bdb5..555b182f 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -26,6 +26,7 @@ build_flags = ${nrf52_base.build_flags} -D P_LORA_RESET=42 ; P1.10 -D LR11X0_DIO_AS_RF_SWITCH=true -D LR11X0_DIO3_TCXO_VOLTAGE=1.6 + -D ENV_INCLUDE_GPS=1 build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<helpers/nrf52/T1000eBoard.cpp> From f085a9d6c560d598c6692b9e123cb9df50e4b123 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 18 Oct 2025 13:10:17 +0200 Subject: [PATCH 172/546] tracker_l1_eink: set UI_HAS_JOYSTICK --- variants/wio-tracker-l1-eink/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index a41e9e23..deb85f5e 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -49,6 +49,7 @@ build_flags = ${WioTrackerL1Eink.build_flags} -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=GxEPDDisplay + -D UI_HAS_JOYSTICK=1 -D PIN_BUZZER=12 -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 From 7d62a2783652c096c9038edd985ff92431091149 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 18 Oct 2025 13:37:18 +0200 Subject: [PATCH 173/546] uitask: bring back buzzer toggle on tracker l1 --- examples/companion_radio/ui-new/UITask.cpp | 4 ++++ variants/wio-tracker-l1/target.cpp | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index b6484a00..7c75a089 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -683,6 +683,10 @@ void UITask::loop() { } else if (ev == BUTTON_EVENT_LONG_PRESS) { c = handleLongPress(KEY_RIGHT); } + ev = back_btn.check(); + if (ev == BUTTON_EVENT_TRIPLE_CLICK) { + c = handleTripleClick(KEY_SELECT); + } #elif defined(PIN_USER_BTN) int ev = user_btn.check(); if (ev == BUTTON_EVENT_CLICK) { diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index e7b73040..64866de0 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -24,7 +24,7 @@ EnvironmentSensorManager sensors = EnvironmentSensorManager(); MomentaryButton user_btn(PIN_USER_BTN, 1000, true, false, false); MomentaryButton joystick_left(JOYSTICK_LEFT, 1000, true, false, false); MomentaryButton joystick_right(JOYSTICK_RIGHT, 1000, true, false, false); - MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, false); + MomentaryButton back_btn(PIN_BACK_BTN, 1000, true, false, true); #endif bool radio_init() { From ce707923099b8739ac3cb88365797b262605794a Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 18 Oct 2025 14:03:27 +0200 Subject: [PATCH 174/546] lgfx_display: better handle display class construction --- src/helpers/ui/LGFXDisplay.h | 11 +++-------- .../sensecap_indicator-espnow/SCIndicatorDisplay.h | 3 +-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/src/helpers/ui/LGFXDisplay.h b/src/helpers/ui/LGFXDisplay.h index 81d0239f..ad7212ec 100644 --- a/src/helpers/ui/LGFXDisplay.h +++ b/src/helpers/ui/LGFXDisplay.h @@ -1,9 +1,3 @@ - -/* - * Base class for LovyanGFX supported display (works on ESP32 mainly) - * You can extend this class to support your display, providing your own LGFX - */ - #pragma once #include <helpers/ui/DisplayDriver.h> @@ -20,11 +14,12 @@ protected: LGFX_Device* display; LGFX_Sprite buffer; - bool _isOn; + bool _isOn = false; int _color = TFT_WHITE; public: - LGFXDisplay(int w, int h):DisplayDriver(w/UI_ZOOM, h/UI_ZOOM) {_isOn = false;} + LGFXDisplay(int w, int h, LGFX_Device &disp) + : DisplayDriver(w/UI_ZOOM, h/UI_ZOOM), display(&disp) {} bool begin(); bool isOn() override { return _isOn; } void turnOn() override; diff --git a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h index 6a7e3177..aabedd24 100644 --- a/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h +++ b/variants/sensecap_indicator-espnow/SCIndicatorDisplay.h @@ -124,6 +124,5 @@ public: class SCIndicatorDisplay : public LGFXDisplay { LGFX disp; public: - SCIndicatorDisplay() : LGFXDisplay(480, 480) - { display=&disp; } + SCIndicatorDisplay() : LGFXDisplay(480, 480, disp) {} }; From 37dc715a8e5fbec0f0e2cb9537ad418922c9a8b5 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 18 Oct 2025 23:37:58 +0200 Subject: [PATCH 175/546] SensorManager: remove setSettingByKey --- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/CommonCLI.cpp | 6 +++--- src/helpers/SensorManager.h | 10 ---------- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index c45c141d..729c5b7d 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -137,7 +137,7 @@ protected: #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 60ef1e73..f6ce31e5 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -151,7 +151,7 @@ protected: #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index cdc3940c..ff0698dc 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -151,7 +151,7 @@ private: #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingByKey("gps", _prefs.gps_enabled?"1":"0"); + sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif }; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 87f20f5a..77f0b085 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -545,7 +545,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch int num = mesh::Utils::parseTextParts(tmp, parts, 2, ' '); const char *key = (num > 0) ? parts[0] : ""; const char *value = (num > 1) ? parts[1] : "null"; - if (sensors.setSettingByKey(key, value)) { + if (sensors.setSettingValue(key, value)) { strcpy(reply, "ok"); } else { strcpy(reply, "can't find custom var"); @@ -577,7 +577,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } #if ENV_INCLUDE_GPS == 1 } else if (memcmp(command, "gps on", 6) == 0) { - if (sensors.setSettingByKey("gps", "1")) { + if (sensors.setSettingValue("gps", "1")) { _prefs->gps_enabled = 1; savePrefs(); strcpy(reply, "ok"); @@ -585,7 +585,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "gps toggle not found"); } } else if (memcmp(command, "gps off", 7) == 0) { - if (sensors.setSettingByKey("gps", "0")) { + if (sensors.setSettingValue("gps", "0")) { _prefs->gps_enabled = 0; savePrefs(); strcpy(reply, "ok"); diff --git a/src/helpers/SensorManager.h b/src/helpers/SensorManager.h index 38c1d806..89a174c2 100644 --- a/src/helpers/SensorManager.h +++ b/src/helpers/SensorManager.h @@ -34,14 +34,4 @@ public: } return NULL; } - - bool setSettingByKey(const char* key, const char* value) { - int num = getNumSettings(); - for (int i = 0; i < num; i++) { - if (strcmp(getSettingName(i), key) == 0) { - return setSettingValue(key, value); - } - } - return false; - } }; From a421215e848d7ce6e2afc9559bdeeae3fd7a2435 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sat, 18 Oct 2025 23:42:28 +0200 Subject: [PATCH 176/546] all nrf52 devices: force framework-arduinoadafruitnrf52 version to 1.10700.0 --- platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/platformio.ini b/platformio.ini index d4600d00..1200f599 100644 --- a/platformio.ini +++ b/platformio.ini @@ -76,6 +76,8 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ [nrf52_base] extends = arduino_base platform = nordicnrf52 +platform_packages = + framework-arduinoadafruitnrf52 @ 1.10700.0 extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM From a5070077ba058e3dfff7e1668a0a355d00cf07e8 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sun, 19 Oct 2025 00:02:38 +0200 Subject: [PATCH 177/546] equalize RAK with all other nrf52 variants and use newer platform with all important fixes --- boards/rak4631.json | 72 ++++++++++ variants/rak4631/platformio.ini | 3 +- variants/rak4631/variant.cpp | 49 +++++++ variants/rak4631/variant.h | 172 ++++++++++++++++++++++++ variants/rak_wismesh_tag/platformio.ini | 3 +- 5 files changed, 295 insertions(+), 4 deletions(-) create mode 100644 boards/rak4631.json create mode 100644 variants/rak4631/variant.cpp create mode 100644 variants/rak4631/variant.h diff --git a/boards/rak4631.json b/boards/rak4631.json new file mode 100644 index 00000000..8d820fce --- /dev/null +++ b/boards/rak4631.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "WisCore RAK4631 Board", + "mcu": "nrf52840", + "variant": "WisCore_RAK4631_Board", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "WisCore RAK4631 Board", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.rakwireless.com", + "vendor": "RAKwireless" +} diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index a8807e36..9a0504dc 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -1,7 +1,6 @@ [rak4631] extends = nrf52_base -platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak -board = wiscore_rak4631 +board = rak4631 board_check = true build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} diff --git a/variants/rak4631/variant.cpp b/variants/rak4631/variant.cpp new file mode 100644 index 00000000..bd85e971 --- /dev/null +++ b/variants/rak4631/variant.cpp @@ -0,0 +1,49 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" +#include "nrf.h" + +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2);; +} + diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h new file mode 100644 index 00000000..e83d1339 --- /dev/null +++ b/variants/rak4631/variant.h @@ -0,0 +1,172 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK4630_ +#define _VARIANT_RAK4630_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + + /* + * WisBlock Base GPIO definitions + */ + static const uint8_t WB_IO1 = 17; // SLOT_A SLOT_B + static const uint8_t WB_IO2 = 34; // SLOT_A SLOT_B + static const uint8_t WB_IO3 = 21; // SLOT_C + static const uint8_t WB_IO4 = 4; // SLOT_C + static const uint8_t WB_IO5 = 9; // SLOT_D + static const uint8_t WB_IO6 = 10; // SLOT_D + static const uint8_t WB_SW1 = 33; // IO_SLOT + static const uint8_t WB_A0 = 5; // IO_SLOT + static const uint8_t WB_A1 = 31; // IO_SLOT + static const uint8_t WB_I2C1_SDA = 13; // SENSOR_SLOT IO_SLOT + static const uint8_t WB_I2C1_SCL = 14; // SENSOR_SLOT IO_SLOT + static const uint8_t WB_I2C2_SDA = 24; // IO_SLOT + static const uint8_t WB_I2C2_SCL = 25; // IO_SLOT + static const uint8_t WB_SPI_CS = 26; // IO_SLOT + static const uint8_t WB_SPI_CLK = 3; // IO_SLOT + static const uint8_t WB_SPI_MISO = 29; // IO_SLOT + static const uint8_t WB_SPI_MOSI = 30; // IO_SLOT + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Buttons + */ +// RAK4631 has no buttons + +/* + * Analog pins + */ +#define PIN_A0 (5) //(3) +#define PIN_A1 (31) //(4) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + + static const uint8_t A0 = PIN_A0; + static const uint8_t A1 = PIN_A1; + static const uint8_t A2 = PIN_A2; + static const uint8_t A3 = PIN_A3; + static const uint8_t A4 = PIN_A4; + static const uint8_t A5 = PIN_A5; + static const uint8_t A6 = PIN_A6; + static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define PIN_NFC2 (10) + + static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +// TXD1 RXD1 on Base Board +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// TXD0 RXD0 on Base Board +#define PIN_SERIAL2_RX (19) +#define PIN_SERIAL2_TX (20) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 1 + +#define PIN_SPI_MISO (29) +#define PIN_SPI_MOSI (30) +#define PIN_SPI_SCK (3) + + static const uint8_t SS = 26; + static const uint8_t MOSI = PIN_SPI_MOSI; + static const uint8_t MISO = PIN_SPI_MISO; + static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 2 + +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) + +#define PIN_WIRE1_SDA (24) +#define PIN_WIRE1_SCL (25) + + // QSPI Pins + // QSPI occupied by GPIO's + #define PIN_QSPI_SCK 3 // 19 + #define PIN_QSPI_CS 26 // 17 + #define PIN_QSPI_IO0 30 // 20 + #define PIN_QSPI_IO1 29 // 21 + #define PIN_QSPI_IO2 28 // 22 + #define PIN_QSPI_IO3 2 // 23 + + // On-board QSPI Flash + // No onboard flash + #define EXTERNAL_FLASH_DEVICES IS25LP080D + #define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index a55cec19..37593f61 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -1,7 +1,6 @@ [rak_wismesh_tag] extends = nrf52_base -platform = https://github.com/maxgerhardt/platform-nordicnrf52.git#rak -board = wiscore_rak4631 +board = rak4631 board_check = true build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} From 31b8f7252a6078782818cd39a657ba740dcba130 Mon Sep 17 00:00:00 2001 From: Tomas P <tpp@idx.sk> Date: Sun, 19 Oct 2025 20:44:27 +0200 Subject: [PATCH 178/546] Support for Elecrow Thinknode M2 --- boards/ESP32-S3-WROOM-1-N4.json | 39 +++++ variants/thinknode_m2/ThinknodeM2Board.cpp | 32 ++++ variants/thinknode_m2/ThinknodeM2Board.h | 18 +++ variants/thinknode_m2/pins_arduino.h | 28 ++++ variants/thinknode_m2/platformio.ini | 171 +++++++++++++++++++++ variants/thinknode_m2/target.cpp | 57 +++++++ variants/thinknode_m2/target.h | 32 ++++ variants/thinknode_m2/variant.h | 15 ++ 8 files changed, 392 insertions(+) create mode 100644 boards/ESP32-S3-WROOM-1-N4.json create mode 100644 variants/thinknode_m2/ThinknodeM2Board.cpp create mode 100644 variants/thinknode_m2/ThinknodeM2Board.h create mode 100644 variants/thinknode_m2/pins_arduino.h create mode 100644 variants/thinknode_m2/platformio.ini create mode 100644 variants/thinknode_m2/target.cpp create mode 100644 variants/thinknode_m2/target.h create mode 100644 variants/thinknode_m2/variant.h diff --git a/boards/ESP32-S3-WROOM-1-N4.json b/boards/ESP32-S3-WROOM-1-N4.json new file mode 100644 index 00000000..160926b2 --- /dev/null +++ b/boards/ESP32-S3-WROOM-1-N4.json @@ -0,0 +1,39 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D ARDUINO_USB_CDC_ON_BOOT=0", + "-D ARDUINO_USB_MSC_ON_BOOT=0", + "-D ARDUINO_USB_DFU_ON_BOOT=0", + "-D ARDUINO_USB_MODE=0", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "ESP32-S3-WROOM-1-N4" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ESP32-S3-WROOM-1-N4 (4 MB Flash, No PSRAM)", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 524288, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.espressif.com/sites/default/files/documentation/esp32-s3-wroom-1_wroom-1u_datasheet_en.pdf", + "vendor": "Espressif" +} diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp new file mode 100644 index 00000000..3a2f067e --- /dev/null +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -0,0 +1,32 @@ +#include "ThinknodeM2Board.h" + + + +void ThinknodeM2Board::begin() { + ESP32Board::begin(); + pinMode(PIN_VEXT_EN, OUTPUT); // init display + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // pin needs to be high + delay(10); + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // need to do this twice. do not know why.. + pinMode(PIN_STATUS_LED, OUTPUT); // init power led + } + + void ThinknodeM2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_deep_sleep_start(); + } + + void ThinknodeM2Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t ThinknodeM2Board::getBattMilliVolts() { + analogReadResolution(12); + delay(10); + float volts = (analogRead(PIN_VBAT_READ) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096; + analogReadResolution(10); + return volts * 1000; + } + + const char* ThinknodeM2Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; + } \ No newline at end of file diff --git a/variants/thinknode_m2/ThinknodeM2Board.h b/variants/thinknode_m2/ThinknodeM2Board.h new file mode 100644 index 00000000..8011fae6 --- /dev/null +++ b/variants/thinknode_m2/ThinknodeM2Board.h @@ -0,0 +1,18 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/RefCountedDigitalPin.h> +#include <helpers/ESP32Board.h> +#include <driver/rtc_io.h> + +class ThinknodeM2Board : public ESP32Board { + +public: + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; \ No newline at end of file diff --git a/variants/thinknode_m2/pins_arduino.h b/variants/thinknode_m2/pins_arduino.h new file mode 100644 index 00000000..a5dee363 --- /dev/null +++ b/variants/thinknode_m2/pins_arduino.h @@ -0,0 +1,28 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include <stdint.h> +#include <variant.h> + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = GPS_TX; +static const uint8_t RX = GPS_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = P_LORA_NSS; +static const uint8_t SCK = P_LORA_SCLK; +static const uint8_t MOSI = P_LORA_MISO; +static const uint8_t MISO = P_LORA_MOSI; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = PIN_BOARD_SCL; +static const uint8_t SDA = PIN_BOARD_SDA; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini new file mode 100644 index 00000000..0238947f --- /dev/null +++ b/variants/thinknode_m2/platformio.ini @@ -0,0 +1,171 @@ +[ThinkNode_M2] +extends = esp32_base +board = ESP32-S3-WROOM-1-N4 +build_flags = ${esp32_base.build_flags} + -I variants/thinknode_m2 + -D THINKNODE_M2 + -D GPS_RX=44 + -D GPS_TX=43 + -D PIN_VEXT_EN=46 + -D PIN_BUZZER=5 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_BOARD_SCL=15 + -D PIN_BOARD_SDA=16 + -D P_LORA_DIO_1=3 + -D P_LORA_NSS=10 + -D P_LORA_RESET=21 ; RADIOLIB_NC + -D P_LORA_BUSY=14 ; DIO2 = 38 + -D P_LORA_SCLK=12 + -D P_LORA_MISO=13 + -D P_LORA_MOSI=11 + -D PIN_USER_BTN=47 + -D PIN_STATUS_LED=6 + -D PIN_LED=6 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D DISPLAY_CLASS=SH1106Display + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + +<helpers/ui/SH1106Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/ui/buzzer.cpp> + +<../variants/thinknode_m2> +lib_deps = ${esp32_base.lib_deps} + adafruit/Adafruit SH110X @ ~2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + +[env:ThinkNode_M2_Repeater] +extends = ThinkNode_M2 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M2.build_flags} + -D ADVERT_NAME='"Thinknode M2 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M2.lib_deps} + ${esp32_ota.lib_deps} + +; [env:ThinkNode_M2_Repeater_bridge_rs232] +; extends = ThinkNode_M2 +; build_src_filter = ${ThinkNode_M2.build_src_filter} +; +<helpers/bridges/RS232Bridge.cpp> +; +<../examples/simple_repeater/*.cpp> +; build_flags = +; ${ThinkNode_M2.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${ThinkNode_M2.lib_deps} +; ${esp32_ota.lib_deps} + +[env:ThinkNode_M2_Repeater_bridge_espnow] +extends = ThinkNode_M2 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M2.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M2.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M2_room_server] +extends = ThinkNode_M2 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M2.build_flags} + -D ADVERT_NAME='"Thinknode M2 Room Server"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M2.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M2_terminal_chat] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M2_companion_radio_ble] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M2_companion_radio_serial] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D SERIAL_TX=D6 + -D SERIAL_RX=D7 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m2/target.cpp b/variants/thinknode_m2/target.cpp new file mode 100644 index 00000000..cb3c1624 --- /dev/null +++ b/variants/thinknode_m2/target.cpp @@ -0,0 +1,57 @@ +#include <Arduino.h> +#include "target.h" + +ThinknodeM2Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + pinMode(21, INPUT); + pinMode(48, OUTPUT); + #if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/thinknode_m2/target.h b/variants/thinknode_m2/target.h new file mode 100644 index 00000000..b05def8a --- /dev/null +++ b/variants/thinknode_m2/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +//#include <helpers/ESP32Board.h> +#include <ThinknodeM2Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/SH1106Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern ThinknodeM2Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + + \ No newline at end of file diff --git a/variants/thinknode_m2/variant.h b/variants/thinknode_m2/variant.h new file mode 100644 index 00000000..928f56ec --- /dev/null +++ b/variants/thinknode_m2/variant.h @@ -0,0 +1,15 @@ +#define I2C_SCL 15 +#define I2C_SDA 16 +#define PIN_VBAT_READ 17 +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (1.548F) +#define PIN_BUZZER 5 +#define PIN_VEXT_EN_ACTIVE HIGH +#define PIN_VEXT_EN 46 +#define PIN_USER_BTN 47 +#define PIN_LED 6 +#define PIN_STATUS_LED 6 +#define PIN_PWRBTN 4 + + + From 5d495d505a1db06254254f5625a18e96647e46c3 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 21 Oct 2025 00:34:57 +1100 Subject: [PATCH 179/546] Revert Heltec T114 power savings As discussed on discord with @recrof people are having issues, possibly due to these changes. See https://github.com/meshcore-dev/MeshCore/issues/746 This reverts commit a16e011bd21cf3070f04b3513d562add0d090838. --- variants/heltec_t114/T114Board.cpp | 39 ------------------------------ variants/heltec_t114/T114Board.h | 5 ---- 2 files changed, 44 deletions(-) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 3b40e7cf..f8d170b5 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -24,45 +24,6 @@ void T114Board::begin() { pinMode(PIN_VBAT_READ, INPUT); - // Enable SoftDevice low-power mode - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - - // Enable DC/DC converter for better efficiency (REG1 stage) - NRF_POWER->DCDCEN = 1; - - // Power down unused communication peripherals - // UART1 - Not used on T114 - NRF_UARTE1->ENABLE = 0; - - // SPIM2/SPIS2 - Not used (SPI is on SPIM0) - NRF_SPIM2->ENABLE = 0; - NRF_SPIS2->ENABLE = 0; - - // TWI1 (I2C1) - Not used (I2C is on TWI0) - NRF_TWIM1->ENABLE = 0; - NRF_TWIS1->ENABLE = 0; - - // PWM modules - Not used for standard T114 functions - NRF_PWM1->ENABLE = 0; - NRF_PWM2->ENABLE = 0; - NRF_PWM3->ENABLE = 0; - - // PDM (Digital Microphone Interface) - Not used - NRF_PDM->ENABLE = 0; - - // I2S - Not used - NRF_I2S->ENABLE = 0; - - // QSPI - Not used (no external flash) - NRF_QSPI->ENABLE = 0; - - // Disable unused analog peripherals - // SAADC channels - only keep what's needed for battery monitoring - NRF_SAADC->ENABLE = 0; // Re-enable only when needed for measurements - - // COMP - Comparator not used - NRF_COMP->ENABLE = 0; - #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); #endif diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 7a7a654b..49d1ec37 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -27,9 +27,6 @@ public: uint16_t getBattMilliVolts() override { int adcvalue = 0; - - NRF_SAADC->ENABLE = 1; - analogReadResolution(12); analogReference(AR_INTERNAL_3_0); pinMode(PIN_BAT_CTL, OUTPUT); // battery adc can be read only ctrl pin 6 set to high @@ -39,8 +36,6 @@ public: adcvalue = analogRead(PIN_VBAT_READ); digitalWrite(6, 0); - NRF_SAADC->ENABLE = 0; - return (uint16_t)((float)adcvalue * MV_LSB * 4.9); } From ec05d40b3c948aec9e01ac0560204d3388f5336b Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Mon, 20 Oct 2025 18:49:34 +0200 Subject: [PATCH 180/546] Add Seeed Wio WM1110 Dev Board variant --- variants/wio_wm1110/WioWM1110Board.cpp | 75 +++++++++++++ variants/wio_wm1110/WioWM1110Board.h | 58 ++++++++++ variants/wio_wm1110/platformio.ini | 85 +++++++++++++++ variants/wio_wm1110/target.cpp | 110 +++++++++++++++++++ variants/wio_wm1110/target.h | 21 ++++ variants/wio_wm1110/variant.cpp | 92 ++++++++++++++++ variants/wio_wm1110/variant.h | 145 +++++++++++++++++++++++++ 7 files changed, 586 insertions(+) create mode 100644 variants/wio_wm1110/WioWM1110Board.cpp create mode 100644 variants/wio_wm1110/WioWM1110Board.h create mode 100644 variants/wio_wm1110/platformio.ini create mode 100644 variants/wio_wm1110/target.cpp create mode 100644 variants/wio_wm1110/target.h create mode 100644 variants/wio_wm1110/variant.cpp create mode 100644 variants/wio_wm1110/variant.h diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp new file mode 100644 index 00000000..ca3638b3 --- /dev/null +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -0,0 +1,75 @@ +#ifdef WIO_WM1110 + +#include <Arduino.h> +#include <Wire.h> +#include <bluefruit.h> + +#include "WioWM1110Board.h" + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void WioWM1110Board::begin() { + startup_reason = BD_STARTUP_NORMAL; + + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + NRF_POWER->DCDCEN = 1; + + pinMode(BATTERY_PIN, INPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_RED, OUTPUT); + pinMode(SENSOR_POWER_PIN, OUTPUT); + + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, LOW); + digitalWrite(SENSOR_POWER_PIN, LOW); + + Serial1.begin(115200); + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + + delay(10); +} + +bool WioWM1110Board::startOTAUpdate(const char *id, char reply[]) { + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + Bluefruit.setTxPower(4); + Bluefruit.setName("WM1110_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + bledfu.begin(); + + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); + Bluefruit.Advertising.setFastTimeout(30); + Bluefruit.Advertising.start(0); + + strcpy(reply, "OK - started"); + return true; +} + +#endif + diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h new file mode 100644 index 00000000..823acbc7 --- /dev/null +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -0,0 +1,58 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +#ifdef WIO_WM1110 + +#ifdef Serial + #undef Serial +#endif +#define Serial Serial1 + +class WioWM1110Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(LED_GREEN) + void onBeforeTransmit() override { + digitalWrite(LED_RED, HIGH); + } + void onAfterTransmit() override { + digitalWrite(LED_RED, LOW); + } +#endif + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(BATTERY_PIN); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE * 1000.0) / 4096.0; + } + + const char* getManufacturerName() const override { + return "Seeed Wio WM1110"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; + + void enableSensorPower(bool enable) { + digitalWrite(SENSOR_POWER_PIN, enable ? HIGH : LOW); + if (enable) { + delay(100); + } + } +}; + +#endif + diff --git a/variants/wio_wm1110/platformio.ini b/variants/wio_wm1110/platformio.ini new file mode 100644 index 00000000..313430ff --- /dev/null +++ b/variants/wio_wm1110/platformio.ini @@ -0,0 +1,85 @@ +[wio_wm1110] +extends = nrf52_base +board = seeed-xiao-afruitnrf52-nrf52840 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 + -I variants/wio_wm1110 + -D NRF52_PLATFORM + -D WIO_WM1110 +; -D MESH_DEBUG=1 + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LORA_TX_POWER=22 + -D RX_BOOSTED_GAIN=true + -D P_LORA_DIO_1=40 + -D P_LORA_RESET=42 + -D P_LORA_BUSY=43 + -D P_LORA_NSS=44 + -D P_LORA_SCLK=45 + -D P_LORA_MOSI=46 + -D P_LORA_MISO=47 + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 + -D RF_SWITCH_TABLE +build_src_filter = ${nrf52_base.build_src_filter} + +<helpers/*.cpp> + +<helpers/sensors> + +<../variants/wio_wm1110> +debug_tool = jlink +upload_protocol = jlink +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit LIS3DH @ ^1.2.4 + adafruit/Adafruit SHT4x Library @ ^1.0.4 + +[env:wio_wm1110_repeater] +extends = wio_wm1110 +build_flags = + ${wio_wm1110.build_flags} + -D ADVERT_NAME='"WM1110 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${wio_wm1110.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[env:wio_wm1110_room_server] +extends = wio_wm1110 +build_flags = + ${wio_wm1110.build_flags} + -D ADVERT_NAME='"WM1110 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${wio_wm1110.build_src_filter} + +<../examples/simple_room_server/*.cpp> + +[env:wio_wm1110_companion_radio_ble] +extends = wio_wm1110 +board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = + ${wio_wm1110.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D QSPIFLASH=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${wio_wm1110.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${wio_wm1110.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp new file mode 100644 index 00000000..cc302a85 --- /dev/null +++ b/variants/wio_wm1110/target.cpp @@ -0,0 +1,110 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/LocationProvider.h> + +class WM1110LocationProvider : public LocationProvider { +public: + long getLatitude() override { return 0; } + long getLongitude() override { return 0; } + long getAltitude() override { return 0; } + long satellitesCount() override { return 0; } + bool isValid() override { return false; } + long getTimestamp() override { return 0; } + void sendSentence(const char* sentence) override {} + void reset() override {} + void begin() override {} + void stop() override {} + void loop() override {} + bool isEnabled() override { return false; } +}; + +WioWM1110Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock rtc_clock; +WM1110LocationProvider location_provider; +EnvironmentSensorManager sensors(location_provider); + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +#ifdef RF_SWITCH_TABLE +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_LR11X0_DIO7, + RADIOLIB_LR11X0_DIO8, + RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 DIO7 DIO8 + { LR11x0::MODE_STBY, {LOW, LOW, LOW, LOW }}, + { LR11x0::MODE_RX, {HIGH, LOW, LOW, HIGH }}, + { LR11x0::MODE_TX, {HIGH, HIGH, LOW, HIGH }}, + { LR11x0::MODE_TX_HP, {LOW, HIGH, LOW, HIGH }}, + { LR11x0::MODE_TX_HF, {LOW, LOW, LOW, LOW }}, + { LR11x0::MODE_GNSS, {LOW, LOW, HIGH, LOW }}, + { LR11x0::MODE_WIFI, {LOW, LOW, LOW, LOW }}, + END_OF_MODE_TABLE, +}; +#endif + +bool radio_init() { + board.enableSensorPower(true); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.8f; +#endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + +#ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#endif + +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/wio_wm1110/target.h b/variants/wio_wm1110/target.h new file mode 100644 index 00000000..9bd4a22b --- /dev/null +++ b/variants/wio_wm1110/target.h @@ -0,0 +1,21 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include "WioWM1110Board.h" +#include <helpers/radiolib/CustomLR1110Wrapper.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/EnvironmentSensorManager.h> + +extern WioWM1110Board board; +extern WRAPPER_CLASS radio_driver; +extern VolatileRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + diff --git a/variants/wio_wm1110/variant.cpp b/variants/wio_wm1110/variant.cpp new file mode 100644 index 00000000..9691a304 --- /dev/null +++ b/variants/wio_wm1110/variant.cpp @@ -0,0 +1,92 @@ +/* + * variant.cpp - Seeed Wio WM1110 Dev Board + * Pin mapping for nRF52840 + */ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[PINS_COUNT + 1] = +{ + 0, // P0.00 + 1, // P0.01 + 2, // P0.02, AIN0, SENSOR_AIN_0 + 3, // P0.03, AIN1, SENSOR_AIN_1 + 4, // P0.04, AIN2, SENSOR_AIN_2 + 5, // P0.05, AIN3, SENSOR_AIN_3 + 6, // P0.06, PIN_SERIAL2_RX, SENSOR_RXD + 7, // P0.07, SENSOR_POWER_PIN + 8, // P0.08, PIN_SERIAL2_TX, SENSOR_TXD + 9, // P0.09 + 10, // P0.10 + 11, // P0.11, LIS3DH_INT_PIN_1, SENSOR_INT_1 + 12, // P0.12, LIS3DH_INT_PIN_2, SENSOR_INT_2 + 13, // P0.13, LED_GREEN, USER_LED_G + 14, // P0.14, LED_RED, USER_LED_R + 15, // P0.15 + 16, // P0.16 + 17, // P0.17 + 18, // P0.18 + 19, // P0.19 + 20, // P0.20 + 21, // P0.21 + 22, // P0.22, PIN_SERIAL1_RX, DEBUG_RX_PIN + 23, // P0.23 + 24, // P0.24, PIN_SERIAL1_TX, DEBUG_TX_PIN + 25, // P0.25 + 26, // P0.26, PIN_WIRE_SCL, SENSOR_SCL + 27, // P0.27, PIN_WIRE_SDA, SENSOR_SDA + 28, // P0.28, AIN4, SENSOR_AIN_4 + 29, // P0.29, AIN5, SENSOR_AIN_5 + 30, // P0.30, AIN6, SENSOR_AIN_6 + 31, // P0.31, AIN7, SENSOR_AIN_7, BATTERY_PIN + 32, // P1.00 + 33, // P1.01 + 34, // P1.02 + 35, // P1.03 + 36, // P1.04 + 37, // P1.05, LR1110_GNSS_ANT_PIN + 38, // P1.06 + 39, // P1.07 + 40, // P1.08, LORA_DIO_1, LR1110_IRQ_PIN + 41, // P1.09 + 42, // P1.10, LORA_RESET, LR1110_NRESET_PIN + 43, // P1.11, LORA_BUSY, LR1110_BUSY_PIN + 44, // P1.12, PIN_SPI_NSS, LR1110_SPI_NSS_PIN + 45, // P1.13, PIN_SPI_SCK, LR1110_SPI_SCK_PIN + 46, // P1.14, PIN_SPI_MOSI, LR1110_SPI_MOSI_PIN + 47, // P1.15, PIN_SPI_MISO, LR1110_SPI_MISO_PIN + 255, // NRFX_SPIM_PIN_NOT_USED +}; + +void initVariant() +{ + // All pins output HIGH by default. + // https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/fab7d30a997a1dfeef9d1d59bfb549adda73815a/cores/nRF5/wiring.c#L65-L69 + + // Set analog input pins + pinMode(BATTERY_PIN, INPUT); + pinMode(SENSOR_AIN_0, INPUT); + pinMode(SENSOR_AIN_1, INPUT); + pinMode(SENSOR_AIN_2, INPUT); + pinMode(SENSOR_AIN_3, INPUT); + pinMode(SENSOR_AIN_4, INPUT); + pinMode(SENSOR_AIN_5, INPUT); + pinMode(SENSOR_AIN_6, INPUT); + + // Sensor interrupts as inputs + pinMode(LIS3DH_INT_PIN_1, INPUT); + pinMode(LIS3DH_INT_PIN_2, INPUT); + + // Set output pins + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_RED, OUTPUT); + pinMode(SENSOR_POWER_PIN, OUTPUT); + + // Initialize outputs to safe states + digitalWrite(LED_GREEN, HIGH); // Power indicator LED on + digitalWrite(LED_RED, LOW); + digitalWrite(SENSOR_POWER_PIN, LOW); // Sensors powered off initially +} + diff --git a/variants/wio_wm1110/variant.h b/variants/wio_wm1110/variant.h new file mode 100644 index 00000000..cc72c328 --- /dev/null +++ b/variants/wio_wm1110/variant.h @@ -0,0 +1,145 @@ +/* + * variant.h - Seeed Wio WM1110 Dev Board + * nRF52840 + LR1110 (LoRa + GNSS + WiFi Scanner) + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define BATTERY_PIN (31) // AIN7 +#define BATTERY_IMMUTABLE +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION (14) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX (22) +#define PIN_SERIAL1_TX (24) + +#define PIN_SERIAL2_RX (6) +#define PIN_SERIAL2_TX (8) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define HAS_WIRE (1) +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (27) +#define PIN_WIRE_SCL (26) +#define I2C_NO_RESCAN + +#define SENSOR_POWER_PIN (7) + +#define HAS_LIS3DH (1) +#define LIS3DH_INT_PIN_1 (11) +#define LIS3DH_INT_PIN_2 (12) + +#define HAS_SHT41 (1) + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) +#define PIN_SPI_MOSI (46) +#define PIN_SPI_SCK (45) +#define PIN_SPI_NSS (44) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_BUILTIN (13) +#define LED_GREEN (13) +#define LED_RED (14) +#define LED_BLUE LED_RED +#define LED_PIN LED_GREEN + +#define LED_STATE_ON HIGH + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (-1) +#define BUTTON_PIN PIN_BUTTON1 + +//////////////////////////////////////////////////////////////////////////////// +// LR1110 LoRa Radio + GNSS + WiFi + +#define LORA_DIO_1 (40) // P1.8 - LR1110_IRQ_PIN +#define LORA_NSS (PIN_SPI_NSS) // P1.12 +#define LORA_RESET (42) // P1.10 - LR1110_NRESET_PIN +#define LORA_BUSY (43) // P1.11 - LR1110_BUSY_PIN +#define LORA_SCLK (PIN_SPI_SCK) // P1.13 +#define LORA_MISO (PIN_SPI_MISO) // P1.15 +#define LORA_MOSI (PIN_SPI_MOSI) // P1.14 +#define LORA_CS PIN_SPI_NSS // P1.12 + +// LR1110 specific settings +#define LR11X0_DIO_AS_RF_SWITCH true +#define LR11X0_DIO3_TCXO_VOLTAGE 1.8 +#define LR1110_GNSS_ANT_PIN (37) // P1.5 + +// Pin aliases for LR1110 driver compatibility +#define LR1110_IRQ_PIN LORA_DIO_1 +#define LR1110_NRESET_PIN LORA_RESET +#define LR1110_BUSY_PIN LORA_BUSY +#define LR1110_SPI_NSS_PIN LORA_CS +#define LR1110_SPI_SCK_PIN LORA_SCLK +#define LR1110_SPI_MOSI_PIN LORA_MOSI +#define LR1110_SPI_MISO_PIN LORA_MISO + +//////////////////////////////////////////////////////////////////////////////// +// Analog Input Pins + +#define SENSOR_AIN_0 (2) +#define SENSOR_AIN_1 (3) +#define SENSOR_AIN_2 (4) +#define SENSOR_AIN_3 (5) +#define SENSOR_AIN_4 (28) +#define SENSOR_AIN_5 (29) +#define SENSOR_AIN_6 (30) +#define SENSOR_AIN_7 (31) + +static const uint8_t A0 = SENSOR_AIN_0; +static const uint8_t A1 = SENSOR_AIN_1; +static const uint8_t A2 = SENSOR_AIN_2; +static const uint8_t A3 = SENSOR_AIN_3; +static const uint8_t A4 = SENSOR_AIN_4; +static const uint8_t A5 = SENSOR_AIN_5; +static const uint8_t A6 = SENSOR_AIN_6; +static const uint8_t A7 = SENSOR_AIN_7; + +//////////////////////////////////////////////////////////////////////////////// +// GPS/GNSS + +#define HAS_GPS 0 +#define PIN_GPS_TX (-1) +#define PIN_GPS_RX (-1) +#define GPS_EN (-1) +#define GPS_RESET (-1) + From 0920dc66637cc9da0abc2228b9241c1abc5c1eb9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 21 Oct 2025 12:23:45 +0200 Subject: [PATCH 181/546] Fix reversed GPS PINs on G2 and enable timesync --- variants/station_g2/platformio.ini | 4 ++-- variants/station_g2/target.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index bda8b7ae..bc00bae7 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -21,8 +21,8 @@ build_flags = -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=38 - -D PIN_GPS_RX=7 - -D PIN_GPS_TX=15 + -D PIN_GPS_RX=15 + -D PIN_GPS_TX=7 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index c2ee97e2..3f0c1404 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -17,7 +17,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include <helpers/sensors/MicroNMEALocationProvider.h> - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; From 87677fda76dc5c83cbc6feac8f74d041fab2597e Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Wed, 22 Oct 2025 15:15:29 +0200 Subject: [PATCH 182/546] allow spreading factor from 5 and bandwidth from 7.8kHz --- examples/companion_radio/MyMesh.cpp | 4 ++-- src/helpers/CommonCLI.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3772fc1b..02f1a21d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -706,8 +706,8 @@ void MyMesh::begin(bool has_display) { _prefs.rx_delay_base = constrain(_prefs.rx_delay_base, 0, 20.0f); _prefs.airtime_factor = constrain(_prefs.airtime_factor, 0, 9.0f); _prefs.freq = constrain(_prefs.freq, 400.0f, 2500.0f); - _prefs.bw = constrain(_prefs.bw, 62.5f, 500.0f); - _prefs.sf = constrain(_prefs.sf, 7, 12); + _prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f); + _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b8bb698a..32ef632d 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -77,8 +77,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->direct_tx_delay_factor = constrain(_prefs->direct_tx_delay_factor, 0, 2.0f); _prefs->airtime_factor = constrain(_prefs->airtime_factor, 0, 9.0f); _prefs->freq = constrain(_prefs->freq, 400.0f, 2500.0f); - _prefs->bw = constrain(_prefs->bw, 62.5f, 500.0f); - _prefs->sf = constrain(_prefs->sf, 7, 12); + _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); + _prefs->sf = constrain(_prefs->sf, 5, 12); _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); From a38418e09a450d9564db82b614285406c45c83eb Mon Sep 17 00:00:00 2001 From: Matthias Wientapper <m.wientapper@gmx.de> Date: Wed, 22 Oct 2025 20:01:15 +0200 Subject: [PATCH 183/546] * Add display of IP address to companion screen --- examples/companion_radio/ui-new/UITask.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a089..38c09781 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,6 +2,8 @@ #include <helpers/TxtDataHelpers.h> #include "../MyMesh.h" #include "target.h" +#include <WiFi.h> + #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds @@ -129,6 +131,8 @@ class HomeScreen : public UIScreen { bool sensors_scroll = false; int sensors_scroll_offset = 0; int next_sensors_refresh = 0; + + char ipStr[20]; void refresh_sensors() { if (millis() > next_sensors_refresh) { @@ -192,10 +196,17 @@ public: sprintf(tmp, "MSG: %d", _task->getMsgCount()); display.drawTextCentered(display.width() / 2, 20, tmp); + #ifdef WIFI_SSID + IPAddress ip = WiFi.localIP(); + snprintf(ipStr, sizeof(ipStr), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + display.setTextSize(1); + display.drawTextCentered(display.width() / 2, 54, ipStr); + #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); display.setTextSize(1); display.drawTextCentered(display.width() / 2, 43, "< Connected >"); + } else if (the_mesh.getBLEPin() != 0) { // BT pin display.setColor(DisplayDriver::RED); display.setTextSize(2); From ac1513129627e505544264b8ff46282a4a46f3c1 Mon Sep 17 00:00:00 2001 From: Wesley Ellis <tahnok@gmail.com> Date: Wed, 22 Oct 2025 16:17:06 -0400 Subject: [PATCH 184/546] Add support for bmp085/bmp180 temperature/pressure sensor --- platformio.ini | 2 ++ .../sensors/EnvironmentSensorManager.cpp | 29 +++++++++++++++++++ .../sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 32 insertions(+) diff --git a/platformio.ini b/platformio.ini index 1200f599..56a51719 100644 --- a/platformio.ini +++ b/platformio.ini @@ -128,6 +128,7 @@ build_flags = -D ENV_INCLUDE_MLX90614=1 -D ENV_INCLUDE_VL53L0X=1 -D ENV_INCLUDE_BME680=1 + -D ENV_INCLUDE_BMP085=1 lib_deps = adafruit/Adafruit INA3221 Library @ ^1.0.1 adafruit/Adafruit INA219 @ ^1.2.3 @@ -143,3 +144,4 @@ lib_deps = adafruit/Adafruit_VL53L0X @ ^1.2.4 stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit BME680 Library @ ^2.0.4 + adafruit/Adafruit BMP085 Library @ ^1.2.4 diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index bb70c0b5..41d50e92 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -15,6 +15,12 @@ static Adafruit_BME680 BME680; #endif +#ifdef ENV_INCLUDE_BMP085 +#define TELEM_BMP085_SEALEVELPRESSURE_HPA (1013.25) +#include <Adafruit_BMP085.h> +static Adafruit_BMP085 BMP085; +#endif + #if ENV_INCLUDE_AHTX0 #define TELEM_AHTX_ADDRESS 0x38 // AHT10, AHT20 temperature and humidity sensor I2C address #include <Adafruit_AHTX0.h> @@ -305,6 +311,21 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BMP085 + // first arg is MODE + // 0: ULTRALOWPOWER + // 1: STANDARD + // 2: HIGHRES + // 3: ULTRAHIGHRES + if (BMP085.begin(1, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found sensor BMP085"); + BMP085_initialized = true; + } else { + BMP085_initialized = false; + MESH_DEBUG_PRINTLN("BMP085 was not found at I2C address %02X", 0x77); + } + #endif + return true; } @@ -447,6 +468,14 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BMP085 + if (BMP085_initialized) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP085.readPressure() / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100)); + } + #endif + } return true; diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 133d2650..5f1c08e2 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -21,6 +21,7 @@ protected: bool VL53L0X_initialized = false; bool SHT4X_initialized = false; bool BME680_initialized = false; + bool BMP085_initialized = false; bool gps_detected = false; bool gps_active = false; From 4cfbd3bad556abfdb03d5a00023b28536842b59f Mon Sep 17 00:00:00 2001 From: Wesley Ellis <tahnok@gmail.com> Date: Wed, 22 Oct 2025 16:53:11 -0400 Subject: [PATCH 185/546] Switch BMP085 mode to 0 for ULTRALOWPOWER --- src/helpers/sensors/EnvironmentSensorManager.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 41d50e92..98339105 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -312,12 +312,9 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_BMP085 - // first arg is MODE - // 0: ULTRALOWPOWER - // 1: STANDARD - // 2: HIGHRES - // 3: ULTRAHIGHRES - if (BMP085.begin(1, TELEM_WIRE)) { + // First argument is MODE (aka oversampling) + // choose ULTRALOWPOWER + if (BMP085.begin(0, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found sensor BMP085"); BMP085_initialized = true; } else { From 8ca3ed28cf49ed4ee4788454044e9e86eb94a0b9 Mon Sep 17 00:00:00 2001 From: kallanreed <3761006+kallanreed@users.noreply.github.com> Date: Wed, 22 Oct 2025 15:59:43 -0700 Subject: [PATCH 186/546] set PIN_GPS_EN in wismesh tag companion --- variants/rak_wismesh_tag/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 37593f61..081cb0d0 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -94,6 +94,7 @@ build_flags = -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 + -D PIN_GPS_EN=34 ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak_wismesh_tag.build_src_filter} From 2e249e24dcec86e184f0e354a9e3e9b4ce73879b Mon Sep 17 00:00:00 2001 From: Winston Lowe <wel97459@gmail.com> Date: Wed, 22 Oct 2025 23:55:51 -0700 Subject: [PATCH 187/546] Updated CayenneLPP to 1.6.1 --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 11814a22..916bb464 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ lib_deps = rweather/Crypto @ ^0.4.0 adafruit/RTClib @ ^2.1.3 melopero/Melopero RV3028 @ ^1.1.0 - electroniccats/CayenneLPP @ 1.4.0 + electroniccats/CayenneLPP @ 1.6.1 build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 -D LORA_FREQ=869.525 -D LORA_BW=250 From f1824e68b9e64384b941b06fec6ea59596ac09df Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Thu, 23 Oct 2025 23:24:40 +1300 Subject: [PATCH 188/546] increase repeater max uptime from 49 days to 136 years --- examples/simple_repeater/MyMesh.cpp | 9 ++++++++- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 374384bd..f328c752 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -149,7 +149,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t stats.n_packets_recv = radio_driver.getPacketsRecv(); stats.n_packets_sent = radio_driver.getPacketsSent(); stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.total_up_time_secs = uptime_millis / 1000; stats.n_sent_flood = getNumSentFlood(); stats.n_sent_direct = getNumSentDirect(); stats.n_recv_flood = getNumRecvFlood(); @@ -594,6 +594,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc , bridge(&_prefs, _mgr, &rtc) #endif { + last_millis = 0; + uptime_millis = 0; next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; set_radio_at = revert_radio_at = 0; @@ -891,4 +893,9 @@ void MyMesh::loop() { acl.save(_fs); dirty_contacts_expiry = 0; } + + // update uptime + uint32_t now = millis(); + uptime_millis += now - last_millis; + last_millis = now; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index c45c141d..a9ab251e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -78,6 +78,8 @@ struct NeighbourInfo { class MyMesh : public mesh::Mesh, public CommonCLICallbacks { FILESYSTEM* _fs; + uint32_t last_millis; + uint64_t uptime_millis; unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; From 273a54f104f3462754aeba6e8ace7b3a694b1f5a Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Thu, 23 Oct 2025 23:29:08 +1300 Subject: [PATCH 189/546] increase room server max uptime from 49 days to 136 years --- examples/simple_room_server/MyMesh.cpp | 9 ++++++++- examples/simple_room_server/MyMesh.h | 2 ++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index a9ba7998..5d245ba1 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -144,7 +144,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t stats.n_packets_recv = radio_driver.getPacketsRecv(); stats.n_packets_sent = radio_driver.getPacketsSent(); stats.total_air_time_secs = getTotalAirTime() / 1000; - stats.total_up_time_secs = _ms->getMillis() / 1000; + stats.total_up_time_secs = uptime_millis / 1000; stats.n_sent_flood = getNumSentFlood(); stats.n_sent_direct = getNumSentDirect(); stats.n_recv_flood = getNumRecvFlood(); @@ -581,6 +581,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + last_millis = 0; + uptime_millis = 0; next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; _logging = false; @@ -858,4 +860,9 @@ void MyMesh::loop() { } // TODO: periodically check for OLD/inactive entries in known_clients[], and evict + + // update uptime + uint32_t now = millis(); + uptime_millis += now - last_millis; + last_millis = now; } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 60ef1e73..88e30bf2 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -88,6 +88,8 @@ struct PostInfo { class MyMesh : public mesh::Mesh, public CommonCLICallbacks { FILESYSTEM* _fs; + uint32_t last_millis; + uint64_t uptime_millis; unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; From dfb4497c7aab34d2fac2e0964a2b37f2a0cd6958 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 23 Oct 2025 21:44:52 +1100 Subject: [PATCH 190/546] * T114: enabled GPS page in UITask --- variants/heltec_t114/platformio.ini | 1 + variants/heltec_t114/target.cpp | 3 +-- variants/heltec_t114/target.h | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 53fd5e02..c482a30a 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -170,6 +170,7 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 + -D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI ; -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index d2fa6c4c..5b786437 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -74,11 +74,10 @@ bool T114SensorManager::begin() { if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); - digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed } else { MESH_DEBUG_PRINTLN("No GPS detected"); - digitalWrite(GPS_EN, LOW); } + digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed return true; } diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 1876aadc..6306cd69 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -30,6 +30,7 @@ public: bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; void loop() override; + LocationProvider* getLocationProvider() override { return gps_detected ? _location : NULL; } int getNumSettings() const override; const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; From 2981fc70e10102b2b9eabbcd3b9ee7c2d608362f Mon Sep 17 00:00:00 2001 From: Woodie-07 <woodie@woodie.dev> Date: Fri, 24 Oct 2025 20:12:02 +0100 Subject: [PATCH 191/546] new workaround --- src/helpers/radiolib/CustomLR1110.h | 69 +++-------------------------- 1 file changed, 7 insertions(+), 62 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index f723bb7f..cdf3cc7c 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -10,74 +10,19 @@ class CustomLR1110 : public LR1110 { public: CustomLR1110(Module *mod) : LR1110(mod) { } - uint8_t shiftCount = 0; - - int16_t standby() override { - // tx resets the shift, standby is called on tx completion - // this might not actually be what resets it, but it seems to work - // more investigation needed - this->shiftCount = 0; - return LR1110::standby(); - } - size_t getPacketLength(bool update) override { size_t len = LR1110::getPacketLength(update); - if (len == 0) { - uint32_t irq = getIrqStatus(); - if (irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { - MESH_DEBUG_PRINTLN("LR1110: got header err, assuming shift"); - this->shiftCount += 4; // uint8 will loop around to 0 at 256, perfect as rx buffer is 256 bytes - } else { - MESH_DEBUG_PRINTLN("LR1110: got zero-length packet without header err irq"); - } + if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { + // we've just recieved a corrupted packet + // this may have triggered a bug causing subsequent packets to be shifted + // call standby() to return radio to known-good state + // recvRaw will call startReceive() to restart rx + MESH_DEBUG_PRINTLN("LR1110: got header err, calling standby()"); + standby(); } return len; } - int16_t readData(uint8_t *data, size_t len) override { - // check active modem - uint8_t modem = RADIOLIB_LR11X0_PACKET_TYPE_NONE; - int16_t state = getPacketType(&modem); - RADIOLIB_ASSERT(state); - if((modem != RADIOLIB_LR11X0_PACKET_TYPE_LORA) && - (modem != RADIOLIB_LR11X0_PACKET_TYPE_GFSK)) { - return(RADIOLIB_ERR_WRONG_MODEM); - } - - // check integrity CRC - uint32_t irq = getIrqStatus(); - int16_t crcState = RADIOLIB_ERR_NONE; - // Report CRC mismatch when there's a payload CRC error, or a header error and no valid header (to avoid false alarm from previous packet) - if((irq & RADIOLIB_LR11X0_IRQ_CRC_ERR) || ((irq & RADIOLIB_LR11X0_IRQ_HEADER_ERR) && !(irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID))) { - crcState = RADIOLIB_ERR_CRC_MISMATCH; - } - - // get packet length - // the offset is needed since LR11x0 seems to move the buffer base by 4 bytes on every packet - uint8_t offset = 0; - size_t length = LR1110::getPacketLength(true, &offset); - if((len != 0) && (len < length)) { - // user requested less data than we got, only return what was requested - length = len; - } - - // read packet data - state = readBuffer8(data, length, (uint8_t)(offset + this->shiftCount)); // add shiftCount to offset - only change from radiolib - RADIOLIB_ASSERT(state); - - // clear the Rx buffer - state = clearRxBuffer(); - RADIOLIB_ASSERT(state); - - // clear interrupt flags - state = clearIrqState(RADIOLIB_LR11X0_IRQ_ALL); - - // check if CRC failed - this is done after reading data to give user the option to keep them - RADIOLIB_ASSERT(crcState); - - return(state); - } - RadioLibTime_t getTimeOnAir(size_t len) override { // calculate number of symbols float N_symbol = 0; From 0e259a63ed07099e9a31c377f077a3ab5dfd9793 Mon Sep 17 00:00:00 2001 From: Woodie-07 <woodie@woodie.dev> Date: Sat, 25 Oct 2025 22:12:30 +0100 Subject: [PATCH 192/546] lr1110 irq fixes fix incorrect irqs used in isReceiving. also remove getTimeOnAir override as fixed upstream --- src/helpers/radiolib/CustomLR1110.h | 60 +---------------------------- 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index cdf3cc7c..2e536de5 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -3,9 +3,6 @@ #include <RadioLib.h> #include "MeshCore.h" -#define LR1110_IRQ_HAS_PREAMBLE 0b0000000100 // 4 4 valid LoRa header received -#define LR1110_IRQ_HEADER_VALID 0b0000010000 // 4 4 valid LoRa header received - class CustomLR1110 : public LR1110 { public: CustomLR1110(Module *mod) : LR1110(mod) { } @@ -22,63 +19,10 @@ class CustomLR1110 : public LR1110 { } return len; } - - RadioLibTime_t getTimeOnAir(size_t len) override { - // calculate number of symbols - float N_symbol = 0; - if(this->codingRate <= RADIOLIB_LR11X0_LORA_CR_4_8_SHORT) { - // legacy coding rate - nice and simple - // get SF coefficients - float coeff1 = 0; - int16_t coeff2 = 0; - int16_t coeff3 = 0; - if(this->spreadingFactor < 7) { - // SF5, SF6 - coeff1 = 6.25; - coeff2 = 4*this->spreadingFactor; - coeff3 = 4*this->spreadingFactor; - } else if(this->spreadingFactor < 11) { - // SF7. SF8, SF9, SF10 - coeff1 = 4.25; - coeff2 = 4*this->spreadingFactor + 8; - coeff3 = 4*this->spreadingFactor; - } else { - // SF11, SF12 - coeff1 = 4.25; - coeff2 = 4*this->spreadingFactor + 8; - coeff3 = 4*(this->spreadingFactor - 2); - } - - // get CRC length - int16_t N_bitCRC = 16; - if(this->crcTypeLoRa == RADIOLIB_LR11X0_LORA_CRC_DISABLED) { - N_bitCRC = 0; - } - - // get header length - int16_t N_symbolHeader = 20; - if(this->headerType == RADIOLIB_LR11X0_LORA_HEADER_IMPLICIT) { - N_symbolHeader = 0; - } - - // calculate number of LoRa preamble symbols - NO! Lora preamble is already in symbols - // uint32_t N_symbolPreamble = (this->preambleLengthLoRa & 0x0F) * (uint32_t(1) << ((this->preambleLengthLoRa & 0xF0) >> 4)); - - // calculate the number of symbols - nope - // N_symbol = (float)N_symbolPreamble + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4); - // calculate the number of symbols - using only preamblelora because it's already in symbols - N_symbol = (float)preambleLengthLoRa + coeff1 + 8.0f + ceilf((float)RADIOLIB_MAX((int16_t)(8 * len + N_bitCRC - coeff2 + N_symbolHeader), (int16_t)0) / (float)coeff3) * (float)(this->codingRate + 4); - } else { - // long interleaving - not needed for this modem - } - - // get time-on-air in us - return(((uint32_t(1) << this->spreadingFactor) / this->bandwidthKhz) * N_symbol * 1000.0f); -} - + bool isReceiving() { uint16_t irq = getIrqStatus(); - bool detected = ((irq & LR1110_IRQ_HEADER_VALID) || (irq & LR1110_IRQ_HAS_PREAMBLE)); + bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); return detected; } }; \ No newline at end of file From f339c74bb489e2394583f427642c21dbae3b5625 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper <m.wientapper@gmx.de> Date: Mon, 27 Oct 2025 17:58:29 +0100 Subject: [PATCH 193/546] * Add #ifdef, reuse variable --- examples/companion_radio/ui-new/UITask.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 38c09781..35218ac2 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2,8 +2,9 @@ #include <helpers/TxtDataHelpers.h> #include "../MyMesh.h" #include "target.h" -#include <WiFi.h> - +#ifdef WIFI_SSID + #include <WiFi.h> +#endif #ifndef AUTO_OFF_MILLIS #define AUTO_OFF_MILLIS 15000 // 15 seconds @@ -132,8 +133,6 @@ class HomeScreen : public UIScreen { int sensors_scroll_offset = 0; int next_sensors_refresh = 0; - char ipStr[20]; - void refresh_sensors() { if (millis() > next_sensors_refresh) { sensors_lpp.reset(); @@ -198,9 +197,9 @@ public: #ifdef WIFI_SSID IPAddress ip = WiFi.localIP(); - snprintf(ipStr, sizeof(ipStr), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); display.setTextSize(1); - display.drawTextCentered(display.width() / 2, 54, ipStr); + display.drawTextCentered(display.width() / 2, 54, tmp); #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); From d4eb04d6e96ad2e776c5e1d7ec00a5efb6c67239 Mon Sep 17 00:00:00 2001 From: WattleFoxxo <wattle@wattlefoxxo.com> Date: Wed, 29 Oct 2025 15:20:31 +1100 Subject: [PATCH 194/546] Switch xiao rp2040 to std init --- variants/xiao_rp2040/target.cpp | 30 ++++-------------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/variants/xiao_rp2040/target.cpp b/variants/xiao_rp2040/target.cpp index a801aae8..b7c19975 100644 --- a/variants/xiao_rp2040/target.cpp +++ b/variants/xiao_rp2040/target.cpp @@ -20,34 +20,12 @@ SensorManager sensors; bool radio_init() { rtc_clock.begin(Wire); -#ifdef SX126X_DIO3_TCXO_VOLTAGE - float tcxo = SX126X_DIO3_TCXO_VOLTAGE; +#if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); #else - float tcxo = 1.6f; + return radio.std_init(); #endif - int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 8, tcxo); - - if (status != RADIOLIB_ERR_NONE) { - Serial.print("ERROR: radio init failed: "); - Serial.println(status); - return false; // fail - } - - radio.setCRC(1); - -#ifdef SX126X_CURRENT_LIMIT - radio.setCurrentLimit(SX126X_CURRENT_LIMIT); -#endif - -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio.setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif - -#ifdef SX126X_RX_BOOSTED_GAIN - radio.setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif - - return true; // success } uint32_t radio_get_rng_seed() { From 81ab9446824aef47a3cb252f87d963c97784ddc4 Mon Sep 17 00:00:00 2001 From: Michael Hart <michaelhart@michaelhart.me> Date: Tue, 7 Oct 2025 09:45:45 -0700 Subject: [PATCH 195/546] Adds serial commands to get stats - Added formatStatsReply, formatRadioStatsReply, and formatPacketStatsReply methods in MyMesh for both simple_repeater, simple_room_server, and simple_sensor. - Updated CommonCLI to handle new stats commands. --- examples/simple_repeater/MyMesh.cpp | 13 +++++++ examples/simple_repeater/MyMesh.h | 4 ++ examples/simple_room_server/MyMesh.cpp | 13 +++++++ examples/simple_room_server/MyMesh.h | 4 ++ examples/simple_sensor/SensorMesh.cpp | 13 +++++++ examples/simple_sensor/SensorMesh.h | 4 ++ src/helpers/CommonCLI.cpp | 6 +++ src/helpers/CommonCLI.h | 3 ++ src/helpers/StatsFormatHelper.h | 54 ++++++++++++++++++++++++++ 9 files changed, 114 insertions(+) create mode 100644 src/helpers/StatsFormatHelper.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index f328c752..abd284ff 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -787,6 +787,19 @@ void MyMesh::removeNeighbor(const uint8_t *pubkey, int key_len) { #endif } +void MyMesh::formatStatsReply(char *reply) { + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index a9ab251e..694e8ff9 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -30,6 +30,7 @@ #include <helpers/IdentityStore.h> #include <helpers/SimpleMeshTables.h> #include <helpers/StaticPoolPacketManager.h> +#include <helpers/StatsFormatHelper.h> #include <helpers/TxtDataHelpers.h> #ifdef WITH_BRIDGE @@ -183,6 +184,9 @@ public: void setTxPower(uint8_t power_dbm) override; void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; + void formatStatsReply(char *reply) override; + void formatRadioStatsReply(char *reply) override; + void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 5d245ba1..592861bf 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -729,6 +729,19 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } +void MyMesh::formatStatsReply(char *reply) { + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { while (*command == ' ') command++; // skip leading spaces diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 88e30bf2..d149b37b 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -18,6 +18,7 @@ #include <helpers/AdvertDataHelpers.h> #include <helpers/TxtDataHelpers.h> #include <helpers/CommonCLI.h> +#include <helpers/StatsFormatHelper.h> #include <helpers/ClientACL.h> #include <RTClib.h> #include <target.h> @@ -192,6 +193,9 @@ public: void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } + void formatStatsReply(char *reply) override; + void formatRadioStatsReply(char *reply) override; + void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 8f8a11fe..f914a6b6 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -769,6 +769,19 @@ void SensorMesh::setTxPower(uint8_t power_dbm) { radio_set_tx_power(power_dbm); } +void SensorMesh::formatStatsReply(char *reply) { + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); +} + +void SensorMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void SensorMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + float SensorMesh::getTelemValue(uint8_t channel, uint8_t type) { auto buf = telemetry.getBuffer(); uint8_t size = telemetry.getSize(); diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index cdc3940c..ba55bc70 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -20,6 +20,7 @@ #include <helpers/AdvertDataHelpers.h> #include <helpers/TxtDataHelpers.h> #include <helpers/CommonCLI.h> +#include <helpers/StatsFormatHelper.h> #include <helpers/ClientACL.h> #include <RTClib.h> #include <target.h> @@ -69,6 +70,9 @@ public: void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } + void formatStatsReply(char *reply) override; + void formatRadioStatsReply(char *reply) override; + void formatPacketStatsReply(char *reply) override; mesh::LocalIdentity& getSelfId() override { return self_id; } void saveIdentity(const mesh::LocalIdentity& new_id) override; void clearStats() override { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b8bb698a..eac2698e 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -663,6 +663,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (sender_timestamp == 0 && memcmp(command, "log", 3) == 0) { _callbacks->dumpLogFile(); strcpy(reply, " EOF"); + } else if (sender_timestamp == 0 && memcmp(command, "stats-packets", 13) == 0 && (command[13] == 0 || command[13] == ' ')) { + _callbacks->formatPacketStatsReply(reply); + } else if (sender_timestamp == 0 && memcmp(command, "stats-radio", 11) == 0 && (command[11] == 0 || command[11] == ' ')) { + _callbacks->formatRadioStatsReply(reply); + } else if (sender_timestamp == 0 && memcmp(command, "stats-core", 10) == 0 && (command[10] == 0 || command[10] == ' ')) { + _callbacks->formatStatsReply(reply); } else { strcpy(reply, "Unknown command"); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ea59aa92..3cfca46c 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -66,6 +66,9 @@ public: virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { // no op by default }; + virtual void formatStatsReply(char *reply) = 0; + virtual void formatRadioStatsReply(char *reply) = 0; + virtual void formatPacketStatsReply(char *reply) = 0; virtual mesh::LocalIdentity& getSelfId() = 0; virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0; virtual void clearStats() = 0; diff --git a/src/helpers/StatsFormatHelper.h b/src/helpers/StatsFormatHelper.h new file mode 100644 index 00000000..d0107f3b --- /dev/null +++ b/src/helpers/StatsFormatHelper.h @@ -0,0 +1,54 @@ +#pragma once + +#include "Mesh.h" + +class StatsFormatHelper { +public: + static void formatCoreStats(char* reply, + mesh::MainBoard& board, + mesh::MillisecondClock& ms, + uint16_t err_flags, + mesh::PacketManager* mgr) { + sprintf(reply, + "{\"battery_mv\":%u,\"uptime_secs\":%u,\"errors\":%u,\"queue_len\":%u}", + board.getBattMilliVolts(), + ms.getMillis() / 1000, + err_flags, + mgr->getOutboundCount(0xFFFFFFFF) + ); + } + + template<typename RadioDriverType> + static void formatRadioStats(char* reply, + mesh::Radio* radio, + RadioDriverType& driver, + uint32_t total_air_time_ms, + uint32_t total_rx_air_time_ms) { + sprintf(reply, + "{\"noise_floor\":%d,\"last_rssi\":%d,\"last_snr\":%.2f,\"tx_air_secs\":%u,\"rx_air_secs\":%u}", + (int16_t)radio->getNoiseFloor(), + (int16_t)driver.getLastRSSI(), + driver.getLastSNR(), + total_air_time_ms / 1000, + total_rx_air_time_ms / 1000 + ); + } + + template<typename RadioDriverType> + static void formatPacketStats(char* reply, + RadioDriverType& driver, + uint32_t n_sent_flood, + uint32_t n_sent_direct, + uint32_t n_recv_flood, + uint32_t n_recv_direct) { + sprintf(reply, + "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}", + driver.getPacketsRecv(), + driver.getPacketsSent(), + n_sent_flood, + n_sent_direct, + n_recv_flood, + n_recv_direct + ); + } +}; From 1bbc2151f10b4ee07344ee613234bb63d6fdd8c0 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Wed, 29 Oct 2025 10:32:39 +0100 Subject: [PATCH 196/546] remove vision master boards because of issues with display drivers --- variants/heltec_e213/platformio.ini | 12 ++++++------ variants/heltec_e290/platformio.ini | 12 ++++++------ variants/heltec_t190/platformio.ini | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 74598a2d..41824e1c 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -40,7 +40,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E213_companion_radio_ble] +[env:Heltec_E213_companion_ble] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -60,7 +60,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_companion_radio_usb] +[env:Heltec_E213_companion_usb] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -78,7 +78,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_repeater] +[env:Heltec_E213_rptr] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -95,7 +95,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E213_repeater_bridge_rs232] +; [env:Heltec_E213_rptr_bridge_rs232] ; extends = Heltec_E213_base ; build_flags = ; ${Heltec_E213_base.build_flags} @@ -119,7 +119,7 @@ lib_deps = ; ${Heltec_E213_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E213_repeater_bridge_espnow] +[env:Heltec_E213_rptr_bridge_espnow] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -141,7 +141,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E213_room_server] +[env:Heltec_E213_room_svr] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 3289c975..ccc81b4e 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -34,7 +34,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E290_companion_radio_ble] +[env:Heltec_E290_companion_ble] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -54,7 +54,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_companion_radio_usb] +[env:Heltec_E290_companion_usb] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -74,7 +74,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_repeater] +[env:Heltec_E290_rptr] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -91,7 +91,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E290_repeater_bridge_rs232] +; [env:Heltec_E290_rptr_bridge_rs232] ; extends = Heltec_E290_base ; build_flags = ; ${Heltec_E290_base.build_flags} @@ -115,7 +115,7 @@ lib_deps = ; ${Heltec_E290_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E290_repeater_bridge_espnow] +[env:Heltec_E290_rptr_bridge_espnow] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -137,7 +137,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E290_room_server] +[env:Heltec_E290_room_svr] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 72a40dec..b27e7f2b 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -47,7 +47,7 @@ lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 -[env:Heltec_T190_companion_radio_ble] +[env:Heltec_T190_companion_ble] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -65,7 +65,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_companion_radio_usb] +[env:Heltec_T190_companion_usb] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -81,7 +81,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_repeater] +[env:Heltec_T190_rptr] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -96,7 +96,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_T190_repeater_bridge_rs232] +; [env:Heltec_T190_rptr_bridge_rs232] ; extends = Heltec_T190_base ; build_flags = ; ${Heltec_T190_base.build_flags} @@ -118,7 +118,7 @@ lib_deps = ; ${Heltec_T190_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_T190_repeater_bridge_espnow] +[env:Heltec_T190_rptr_bridge_espnow] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -138,7 +138,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_T190_room_server] +[env:Heltec_T190_room_svr] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} From 1c052d8ad2097bbb72816d9e07dc93b2e0924090 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Wed, 29 Oct 2025 13:14:27 +0100 Subject: [PATCH 197/546] use different strategy in renaming the envs in order to avoid building --- variants/heltec_e213/platformio.ini | 12 ++++++------ variants/heltec_e290/platformio.ini | 10 +++++----- variants/heltec_t190/platformio.ini | 12 ++++++------ 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 41824e1c..93bdc000 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -40,7 +40,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E213_companion_ble] +[env:Heltec_E213_companion_radio_ble_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -60,7 +60,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_companion_usb] +[env:Heltec_E213_companion_radio_usb_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -78,7 +78,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E213_rptr] +[env:Heltec_E213_repeater_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -95,7 +95,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E213_rptr_bridge_rs232] +; [env:Heltec_E213_repeater_bridge_rs232_] ; extends = Heltec_E213_base ; build_flags = ; ${Heltec_E213_base.build_flags} @@ -119,7 +119,7 @@ lib_deps = ; ${Heltec_E213_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E213_rptr_bridge_espnow] +[env:Heltec_E213_repeater_bridge_espnow_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} @@ -141,7 +141,7 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E213_room_svr] +[env:Heltec_E213_room_server_] extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index ccc81b4e..2039b526 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -34,7 +34,7 @@ lib_deps = ${esp32_base.lib_deps} https://github.com/Quency-D/heltec-eink-modules/archive/563dd41fd850a1bc3039b8723da4f3a20fe1c800.zip -[env:Heltec_E290_companion_ble] +[env:Heltec_E290_companion_ble_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -54,7 +54,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_companion_usb] +[env:Heltec_E290_companion_usb_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -91,7 +91,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_E290_rptr_bridge_rs232] +; [env:Heltec_E290_repeater_bridge_rs232_] ; extends = Heltec_E290_base ; build_flags = ; ${Heltec_E290_base.build_flags} @@ -115,7 +115,7 @@ lib_deps = ; ${Heltec_E290_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_E290_rptr_bridge_espnow] +[env:Heltec_E290_repeater_bridge_espnow_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} @@ -137,7 +137,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_E290_room_svr] +[env:Heltec_E290_room_server_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index b27e7f2b..01e9dfc9 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -47,7 +47,7 @@ lib_deps = ${esp32_base.lib_deps} adafruit/Adafruit GFX Library @ ^1.12.1 -[env:Heltec_T190_companion_ble] +[env:Heltec_T190_companion_radio_ble_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -65,7 +65,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_companion_usb] +[env:Heltec_T190_companion_radio_usb_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -81,7 +81,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_T190_rptr] +[env:Heltec_T190_repeater_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -96,7 +96,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -; [env:Heltec_T190_rptr_bridge_rs232] +; [env:Heltec_T190_repeater_bridge_rs232_] ; extends = Heltec_T190_base ; build_flags = ; ${Heltec_T190_base.build_flags} @@ -118,7 +118,7 @@ lib_deps = ; ${Heltec_T190_base.lib_deps} ; ${esp32_ota.lib_deps} -[env:Heltec_T190_rptr_bridge_espnow] +[env:Heltec_T190_repeater_bridge_espnow_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} @@ -138,7 +138,7 @@ lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} -[env:Heltec_T190_room_svr] +[env:Heltec_T190_room_server_] extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} From 377f9ff67dc4902609904199f10f98cf7dbc747b Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Wed, 29 Oct 2025 13:22:11 +0100 Subject: [PATCH 198/546] renamed esp32c6 variants, so they are not included in release. added disclaimer about pioarduino builds --- platformio.ini | 1 + variants/lilygo_tlora_c6/platformio.ini | 6 +++--- variants/xiao_c6/platformio.ini | 20 ++++++++++---------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/platformio.ini b/platformio.ini index b5bce215..b1dd2367 100644 --- a/platformio.ini +++ b/platformio.ini @@ -67,6 +67,7 @@ lib_deps = file://arch/esp32/AsyncElegantOTA ; esp32c6 uses arduino framework 3.x +; WARNING: experimental. pioarduino on esp32c6 needs work - it's not considered stable and has issues. [esp32c6_base] extends = esp32_base platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 308c85eb..88a811b5 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -30,7 +30,7 @@ build_flags = build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/lilygo_tlora_c6> -[env:LilyGo_Tlora_C6_repeater] +[env:LilyGo_Tlora_C6_repeater_] extends = tlora_c6 build_src_filter = ${tlora_c6.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -47,7 +47,7 @@ lib_deps = ${tlora_c6.lib_deps} ; ${esp32_ota.lib_deps} -[env:LilyGo_Tlora_C6_room_server] +[env:LilyGo_Tlora_C6_room_server_] extends = tlora_c6 build_src_filter = ${tlora_c6.build_src_filter} +<../examples/simple_room_server> @@ -64,7 +64,7 @@ lib_deps = ${tlora_c6.lib_deps} ; ${esp32_ota.lib_deps} -[env:LilyGo_Tlora_C6_companion_radio_ble] +[env:LilyGo_Tlora_C6_companion_radio_ble_] extends = tlora_c6 build_flags = ${tlora_c6.build_flags} -D MAX_CONTACTS=300 diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 6e963432..9c971083 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -30,7 +30,7 @@ build_src_filter = ${esp32c6_base.build_src_filter} +<../variants/xiao_c6> +<XiaoC6Board.cpp> -[env:Xiao_C6_repeater] +[env:Xiao_C6_repeater_] extends = Xiao_C6 build_src_filter = ${Xiao_C6.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -47,7 +47,7 @@ lib_deps = ${Xiao_C6.lib_deps} ; ${esp32_ota.lib_deps} -[env:Xiao_C6_companion_radio_ble] +[env:Xiao_C6_companion_radio_ble_] extends = Xiao_C6 build_flags = ${Xiao_C6.build_flags} -D MAX_CONTACTS=300 @@ -90,10 +90,10 @@ build_flags = -D SX126X_RX_BOOSTED_GAIN=1 -D USE_XIAO_ESP32C6_EXTERNAL_ANTENNA=1 -[env:Meshimi_repeater] +[env:Meshimi_repeater_] extends = Meshimi build_src_filter = ${Meshimi.build_src_filter} - +<../examples/simple_repeater/*.cpp> + +<../examples/simple_repeater/*.cpp> build_flags = ${Meshimi.build_flags} -D ADVERT_NAME='"Meshimi Repeater"' @@ -104,7 +104,7 @@ build_flags = lib_deps = ${Meshimi.lib_deps} -[env:Meshimi_companion_radio_ble] +[env:Meshimi_companion_radio_ble_] extends = Meshimi build_flags = ${Meshimi.build_flags} -D MAX_CONTACTS=300 @@ -115,9 +115,9 @@ build_flags = ${Meshimi.build_flags} -D ENABLE_PRIVATE_KEY_IMPORT=1 -D ENABLE_PRIVATE_KEY_EXPORT=1 build_src_filter = ${Meshimi.build_src_filter} - +<helpers/esp32/*.cpp> - -<helpers/esp32/ESPNOWRadio.cpp> - +<../examples/companion_radio/*.cpp> + +<helpers/esp32/*.cpp> + -<helpers/esp32/ESPNOWRadio.cpp> + +<../examples/companion_radio/*.cpp> lib_deps = ${Meshimi.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -147,7 +147,7 @@ build_flags = -USX126X_DIO2_AS_RF_SWITCH -USX126X_DIO3_TCXO_VOLTAGE -[env:WHY2025_badge_repeater] +[env:WHY2025_badge_repeater_] extends = WHY2025_badge build_src_filter = ${WHY2025_badge.build_src_filter} +<../examples/simple_repeater/*.cpp> @@ -164,7 +164,7 @@ lib_deps = ${WHY2025_badge.lib_deps} ; ${esp32_ota.lib_deps} -[env:WHY2025_badge_companion_radio_ble] +[env:WHY2025_badge_companion_radio_ble_] extends = WHY2025_badge build_flags = ${WHY2025_badge.build_flags} -D MAX_CONTACTS=300 From 4aef69662074674da99e431887c5202b154351aa Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Wed, 29 Oct 2025 13:27:26 +0100 Subject: [PATCH 199/546] missed one definition --- variants/heltec_e290/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 2039b526..201b3631 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -74,7 +74,7 @@ lib_deps = ${Heltec_E290_base.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:Heltec_E290_rptr] +[env:Heltec_E290_repeater_] extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} From 8cbcd2271d586997dd3f92011e6c024477c36798 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 29 Oct 2025 23:35:46 +1100 Subject: [PATCH 200/546] * experimental: retransmit delay, removing the 6 'slots' --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_room_server/MyMesh.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index abd284ff..9a974565 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -397,11 +397,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 592861bf..cc657800 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -262,11 +262,11 @@ const char *MyMesh::getLogDateTime() { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 6) * t; + return getRNG()->nextInt(0, 5*t); } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { From 80f0405600d4510f89742b9694f8802150324fb1 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 30 Oct 2025 00:03:10 +1100 Subject: [PATCH 201/546] * direct.txdelay default now 0.2 (was zero) --- examples/simple_repeater/MyMesh.cpp | 1 + examples/simple_room_server/MyMesh.cpp | 1 + examples/simple_sensor/SensorMesh.cpp | 1 + 3 files changed, 3 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9a974565..9c9471f5 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -610,6 +610,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f + _prefs.direct_tx_delay_factor = 0.2f; // was zero StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index cc657800..a6ccb140 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -593,6 +593,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // off by default, was 10.0 _prefs.tx_delay_factor = 0.5f; // was 0.25f; + _prefs.direct_tx_delay_factor = 0.2f; // was zero StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index f914a6b6..6564c4ef 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -664,6 +664,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f + _prefs.direct_tx_delay_factor = 0.2f; // was zero StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; From 3d9378d91eab4ff94e6225e8ac6d357743ee9684 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 30 Oct 2025 16:45:50 +1100 Subject: [PATCH 202/546] * Fix for VolatileRTCClock wrapping around to initial synced time every 49 days --- examples/companion_radio/main.cpp | 1 + examples/simple_repeater/main.cpp | 1 + examples/simple_room_server/main.cpp | 1 + examples/simple_secure_chat/main.cpp | 3 ++- examples/simple_sensor/main.cpp | 1 + src/MeshCore.h | 5 +++++ src/helpers/ArduinoHelpers.h | 16 ++++++++++++---- src/helpers/AutoDiscoverRTCClock.h | 4 ++++ 8 files changed, 27 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 89adca59..82c8c21d 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -227,4 +227,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7db0e7d4..5843df74 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -114,4 +114,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 8f6b6d58..1a3b4d6e 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -110,4 +110,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index eac35898..da1bac5b 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -548,7 +548,7 @@ public: StdRNG fast_rng; SimpleMeshTables tables; -MyMesh the_mesh(radio_driver, fast_rng, *new VolatileRTCClock(), tables); // TODO: test with 'rtc_clock' in target.cpp +MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables); void halt() { while (1) ; @@ -587,4 +587,5 @@ void setup() { void loop() { the_mesh.loop(); + rtc_clock.tick(); } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index 2dacd1b4..a5fcc148 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -144,4 +144,5 @@ void loop() { #ifdef DISPLAY_CLASS ui_task.loop(); #endif + rtc_clock.tick(); } diff --git a/src/MeshCore.h b/src/MeshCore.h index 5c7e1760..94bf351d 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -72,6 +72,11 @@ public: */ virtual void setCurrentTime(uint32_t time) = 0; + /** + * override in classes that need to periodically update internal state + */ + virtual void tick() { /* no op */} + uint32_t getCurrentTimeUnique() { uint32_t t = getCurrentTime(); if (t <= last_unique) { diff --git a/src/helpers/ArduinoHelpers.h b/src/helpers/ArduinoHelpers.h index a736c9b0..97596daa 100644 --- a/src/helpers/ArduinoHelpers.h +++ b/src/helpers/ArduinoHelpers.h @@ -4,11 +4,19 @@ #include <Arduino.h> class VolatileRTCClock : public mesh::RTCClock { - long millis_offset; + uint32_t base_time; + uint64_t accumulator; + unsigned long prev_millis; public: - VolatileRTCClock() { millis_offset = 1715770351; } // 15 May 2024, 8:50pm - uint32_t getCurrentTime() override { return (millis()/1000 + millis_offset); } - void setCurrentTime(uint32_t time) override { millis_offset = time - millis()/1000; } + VolatileRTCClock() { base_time = 1715770351; accumulator = 0; prev_millis = millis(); } // 15 May 2024, 8:50pm + uint32_t getCurrentTime() override { return base_time + accumulator/1000; } + void setCurrentTime(uint32_t time) override { base_time = time; accumulator = 0; prev_millis = millis(); } + + void tick() override { + unsigned long now = millis(); + accumulator += (now - prev_millis); + prev_millis = now; + } }; class ArduinoMillis : public mesh::MillisecondClock { diff --git a/src/helpers/AutoDiscoverRTCClock.h b/src/helpers/AutoDiscoverRTCClock.h index 02eedf52..11364cd8 100644 --- a/src/helpers/AutoDiscoverRTCClock.h +++ b/src/helpers/AutoDiscoverRTCClock.h @@ -14,4 +14,8 @@ public: void begin(TwoWire& wire); uint32_t getCurrentTime() override; void setCurrentTime(uint32_t time) override; + + void tick() override { + _fallback->tick(); // is typically VolatileRTCClock, which now needs tick() + } }; From f3b20d5e70e67780decc64c69a7e34b5b14356cc Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 30 Oct 2025 08:35:01 +0100 Subject: [PATCH 203/546] t114 gps --- variants/heltec_t114/platformio.ini | 5 +++++ variants/heltec_t114/target.cpp | 2 +- variants/heltec_t114/variant.h | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index c482a30a..91ca78cd 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -29,6 +29,11 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D DISPLAY_CLASS=NullDisplayDriver -D ST7789 + -D PIN_GPS_RX=39 + -D PIN_GPS_TX=37 + -D PIN_GPS_EN=21 + -D PIN_GPS_RESET=38 + -D PIN_GPS_RESET_ACTIVE=LOW build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<../variants/heltec_t114> diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 5b786437..c3341103 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); T114SensorManager sensors = T114SensorManager(nmea); #ifdef DISPLAY_CLASS diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index a0fd2e4f..b3f760bb 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -117,6 +117,8 @@ #define GPS_EN (21) #define GPS_RESET (38) +#define PIN_GPS_RX (39) // This is for bits going TOWARDS the GPS +#define PIN_GPS_TX (37) // This is for bits going TOWARDS the CPU //////////////////////////////////////////////////////////////////////////////// // TFT From 96e786fa9e17b1f35406830aa812c60aa812f283 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 30 Oct 2025 19:11:04 +1100 Subject: [PATCH 204/546] * FIX: for divide by zero crash --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_room_server/MyMesh.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9c9471f5..5e62ab6d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -397,11 +397,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index a6ccb140..4d953c9c 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -262,11 +262,11 @@ const char *MyMesh::getLogDateTime() { uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); - return getRNG()->nextInt(0, 5*t); + return getRNG()->nextInt(0, 5*t + 1); } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { From 5088444f858a3fe7135ed5f77892be69285c76a6 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Thu, 30 Oct 2025 16:33:02 +0100 Subject: [PATCH 205/546] Update Wio WM1110 configuration to disable GPS and clean up location provider code --- variants/wio_wm1110/platformio.ini | 1 + variants/wio_wm1110/target.cpp | 20 +------------------- 2 files changed, 2 insertions(+), 19 deletions(-) diff --git a/variants/wio_wm1110/platformio.ini b/variants/wio_wm1110/platformio.ini index 313430ff..ec65e706 100644 --- a/variants/wio_wm1110/platformio.ini +++ b/variants/wio_wm1110/platformio.ini @@ -24,6 +24,7 @@ build_flags = ${nrf52_base.build_flags} -D LR11X0_DIO_AS_RF_SWITCH=true -D LR11X0_DIO3_TCXO_VOLTAGE=1.8 -D RF_SWITCH_TABLE + -D ENV_INCLUDE_GPS=0 build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp index cc302a85..c659d708 100644 --- a/variants/wio_wm1110/target.cpp +++ b/variants/wio_wm1110/target.cpp @@ -1,23 +1,6 @@ #include <Arduino.h> #include "target.h" #include <helpers/ArduinoHelpers.h> -#include <helpers/sensors/LocationProvider.h> - -class WM1110LocationProvider : public LocationProvider { -public: - long getLatitude() override { return 0; } - long getLongitude() override { return 0; } - long getAltitude() override { return 0; } - long satellitesCount() override { return 0; } - bool isValid() override { return false; } - long getTimestamp() override { return 0; } - void sendSentence(const char* sentence) override {} - void reset() override {} - void begin() override {} - void stop() override {} - void loop() override {} - bool isEnabled() override { return false; } -}; WioWM1110Board board; @@ -26,8 +9,7 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; -WM1110LocationProvider location_provider; -EnvironmentSensorManager sensors(location_provider); +EnvironmentSensorManager sensors; #ifndef LORA_CR #define LORA_CR 5 From 0b8159c6e51e0bdd2098b19c049d8fdd8de0afc5 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 31 Oct 2025 11:42:36 +1100 Subject: [PATCH 206/546] refactor DataStore to use openRead() and openWrite() refactored loadPrefsInt(), loadContacts(), loadChannels(), getBlobByKey() and putBlobByKey() to use openRead() and openWrite() --- examples/companion_radio/DataStore.cpp | 40 ++++---------------------- 1 file changed, 6 insertions(+), 34 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index f1adb05e..4441d291 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -197,11 +197,7 @@ void DataStore::loadPrefs(NodePrefs& prefs, double& node_lat, double& node_lon) } void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& node_lat, double& node_lon) { -#if defined(RP2040_PLATFORM) - File file = _fs->open(filename, "r"); -#else - File file = _fs->open(filename); -#endif + File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; @@ -262,16 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ } void DataStore::loadContacts(DataStoreHost* host) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - if (_getContactsChannelsFS()->exists("/contacts3")) { - File file = _getContactsChannelsFS()->open("/contacts3"); -#elif defined(RP2040_PLATFORM) - if (_fs->exists("/contacts3")) { - File file = _fs->open("/contacts3", "r"); -#else - if (_fs->exists("/contacts3")) { - File file = _fs->open("/contacts3", "r", false); -#endif +File file = openRead(_getContactsChannelsFS(), "/contacts3"); if (file) { bool full = false; while (!full) { @@ -299,7 +286,6 @@ void DataStore::loadContacts(DataStoreHost* host) { } file.close(); } - } } void DataStore::saveContacts(DataStoreHost* host) { @@ -332,16 +318,7 @@ void DataStore::saveContacts(DataStoreHost* host) { } void DataStore::loadChannels(DataStoreHost* host) { -#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - if (_getContactsChannelsFS()->exists("/channels2")) { - File file = _getContactsChannelsFS()->open("/channels2"); -#elif defined(RP2040_PLATFORM) - if (_fs->exists("/channels2")) { - File file = _fs->open("/channels2", "r"); -#else - if (_fs->exists("/channels2")) { - File file = _fs->open("/channels2", "r", false); -#endif + File file = openRead(_getContactsChannelsFS(), "/channels2"); if (file) { bool full = false; uint8_t channel_idx = 0; @@ -363,7 +340,6 @@ void DataStore::loadChannels(DataStoreHost* host) { } file.close(); } - } } void DataStore::saveChannels(DataStoreHost* host) { @@ -520,7 +496,7 @@ void DataStore::migrateToSecondaryFS() { } uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { - File file = _getContactsChannelsFS()->open("/adv_blobs"); + File file = openRead(_getContactsChannelsFS(), "/adv_blobs"); uint8_t len = 0; // 0 = not found if (file) { BlobRec tmp; @@ -539,7 +515,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { if (len < PUB_KEY_SIZE+4+SIGNATURE_SIZE || len > MAX_ADVERT_PKT_LEN) return false; checkAdvBlobFile(); - File file = _getContactsChannelsFS()->open("/adv_blobs", FILE_O_WRITE); + File file = openWrite(_getContactsChannelsFS(), "/adv_blobs"); if (file) { uint32_t pos = 0, found_pos = 0; uint32_t min_timestamp = 0xFFFFFFFF; @@ -583,11 +559,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b sprintf(path, "/bl/%s", fname); if (_fs->exists(path)) { -#if defined(RP2040_PLATFORM) - File f = _fs->open(path, "r"); -#else - File f = _fs->open(path); -#endif + File f = openRead(_fs, path); if (f) { int len = f.read(dest_buf, 255); // currently MAX 255 byte blob len supported!! f.close(); From 52a3df4977f1a775324814f3115d539a4f6bd6f8 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 31 Oct 2025 15:06:29 +1100 Subject: [PATCH 207/546] revert pubBlobByKey() change --- examples/companion_radio/DataStore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 4441d291..eac027b8 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -515,7 +515,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { if (len < PUB_KEY_SIZE+4+SIGNATURE_SIZE || len > MAX_ADVERT_PKT_LEN) return false; checkAdvBlobFile(); - File file = openWrite(_getContactsChannelsFS(), "/adv_blobs"); + File file = _getContactsChannelsFS()->open("/adv_blobs", FILE_O_WRITE); if (file) { uint32_t pos = 0, found_pos = 0; uint32_t min_timestamp = 0xFFFFFFFF; From 7abe6c969382f989e8e160f617073de0e58c65aa Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 31 Oct 2025 16:54:58 +1100 Subject: [PATCH 208/546] * Upping max channel hash conflicts to 4 (was 2) --- src/Mesh.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index b055d811..a480a9c3 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -201,9 +201,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (i + 2 >= pkt->payload_len) { MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): incomplete data packet", getLogDateTime()); } else if (!_tables->hasSeen(pkt)) { - // scan channels DB, for all matching hashes of 'channel_hash' (max 2 matches supported ATM) - GroupChannel channels[2]; - int num = searchChannelsByHash(&channel_hash, channels, 2); + // scan channels DB, for all matching hashes of 'channel_hash' (max 4 matches supported ATM) + GroupChannel channels[4]; + int num = searchChannelsByHash(&channel_hash, channels, 4); // for each matching channel, try to decrypt data for (int j = 0; j < num; j++) { // decrypt, checking MAC is valid From 7755400a357dcc32eeaded3124735688e5787e93 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 31 Oct 2025 20:40:22 +1100 Subject: [PATCH 209/546] * Companion: Now using transport codes { 0, 0 } when Share contact zero hop. * Repeater: onAdvertRecv(), adverts via Share now NOT added to neighbours table --- examples/simple_repeater/MyMesh.cpp | 11 +++++++++-- src/Mesh.cpp | 13 +++++++++++++ src/Mesh.h | 6 ++++++ src/helpers/BaseChatMesh.cpp | 15 ++++++++++++--- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5e62ab6d..1afad18b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -448,12 +448,19 @@ void MyMesh::getPeerSharedSecret(uint8_t *dest_secret, int peer_idx) { } } +static bool isShare(const mesh::Packet *packet) { + if (packet->hasTransportCodes()) { + return packet->transport_codes[0] == 0 && packet->transport_codes[1] == 0; // codes { 0, 0 } means 'send to nowhere' + } + return false; +} + void MyMesh::onAdvertRecv(mesh::Packet *packet, const mesh::Identity &id, uint32_t timestamp, const uint8_t *app_data, size_t app_data_len) { mesh::Mesh::onAdvertRecv(packet, id, timestamp, app_data, app_data_len); // chain to super impl - // if this a zero hop advert, add it to neighbours - if (packet->path_len == 0) { + // if this a zero hop advert (and not via 'Share'), add it to neighbours + if (packet->path_len == 0 && !isShare(packet)) { AdvertDataParser parser(app_data, app_data_len); if (parser.isValid() && parser.getType() == ADV_TYPE_REPEATER) { // just keep neigbouring Repeaters putNeighbour(id, timestamp, packet->getSNR()); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index a480a9c3..53dc74f5 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -645,4 +645,17 @@ void Mesh::sendZeroHop(Packet* packet, uint32_t delay_millis) { sendPacket(packet, 0, delay_millis); } +void Mesh::sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_TRANSPORT_DIRECT; + packet->transport_codes[0] = transport_codes[0]; + packet->transport_codes[1] = transport_codes[1]; + + packet->path_len = 0; // path_len of zero means Zero Hop + + _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + + sendPacket(packet, 0, delay_millis); +} + } \ No newline at end of file diff --git a/src/Mesh.h b/src/Mesh.h index a8fdb2a4..cbf1c9cf 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -196,6 +196,12 @@ public: */ void sendZeroHop(Packet* packet, uint32_t delay_millis=0); + /** + * \brief send a locally-generated Packet to just neigbor nodes (zero hops), with specific transort codes + * \param transport_codes array of 2 codes to attach to packet + */ + void sendZeroHop(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + }; } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index e03dd088..9b1eb1ce 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -68,9 +68,16 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } // save a copy of raw advert packet (to support "Share..." function) - int plen = packet->writeTo(temp_buf); + int plen; + { + uint8_t save = packet->header; + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_FLOOD; // make sure transport codes are NOT saved + plen = packet->writeTo(temp_buf); + packet->header = save; + } putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); - + bool is_new = false; if (from == NULL) { if (!isAutoAddEnabled()) { @@ -405,7 +412,9 @@ bool BaseChatMesh::shareContactZeroHop(const ContactInfo& contact) { if (packet == NULL) return false; // no Packets available packet->readFrom(temp_buf, plen); // restore Packet from 'blob' - sendZeroHop(packet); + uint16_t codes[2]; + codes[0] = codes[1] = 0; // { 0, 0 } means 'send this nowhere' + sendZeroHop(packet, codes); return true; // success } From c13b4ae4816c6008d866b22e382955883953cde5 Mon Sep 17 00:00:00 2001 From: Adam Mealings <adam2872@live.co.uk> Date: Fri, 31 Oct 2025 13:04:59 +0000 Subject: [PATCH 210/546] Analogue button delay based on millis --- examples/companion_radio/ui-new/UITask.cpp | 21 ++++++++++++--------- examples/companion_radio/ui-new/UITask.h | 4 ++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a089..086f8259 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -700,15 +700,18 @@ void UITask::loop() { } #endif #if defined(PIN_USER_BTN_ANA) - ev = analog_btn.check(); - if (ev == BUTTON_EVENT_CLICK) { - c = checkDisplayOn(KEY_NEXT); - } else if (ev == BUTTON_EVENT_LONG_PRESS) { - c = handleLongPress(KEY_ENTER); - } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { - c = handleDoubleClick(KEY_PREV); - } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { - c = handleTripleClick(KEY_SELECT); + if (abs(millis() - _analogue_pin_read_millis) > 10) { + ev = analog_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + c = checkDisplayOn(KEY_NEXT); + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + c = handleLongPress(KEY_ENTER); + } else if (ev == BUTTON_EVENT_DOUBLE_CLICK) { + c = handleDoubleClick(KEY_PREV); + } else if (ev == BUTTON_EVENT_TRIPLE_CLICK) { + c = handleTripleClick(KEY_SELECT); + } + _analogue_pin_read_millis = millis(); } #endif #if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index c24d33a4..32d5f3a0 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -40,6 +40,10 @@ class UITask : public AbstractUITask { int last_led_increment = 0; #endif +#ifdef PIN_USER_BTN_ANA + unsigned long _analogue_pin_read_millis = millis(); +#endif + UIScreen* splash; UIScreen* home; UIScreen* msg_preview; From ff4fa7be31d5668126ce84aa770da420042f71b0 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 31 Oct 2025 14:42:16 +0100 Subject: [PATCH 211/546] Add ESP32-S3-Zero board configuration and Nibble Screen Connect variant --- boards/esp32-s3-zero.json | 40 +++++ variants/nibble_screen_connect/platformio.ini | 159 ++++++++++++++++++ variants/nibble_screen_connect/target.cpp | 49 ++++++ variants/nibble_screen_connect/target.h | 30 ++++ 4 files changed, 278 insertions(+) create mode 100644 boards/esp32-s3-zero.json create mode 100644 variants/nibble_screen_connect/platformio.ini create mode 100644 variants/nibble_screen_connect/target.cpp create mode 100644 variants/nibble_screen_connect/target.h diff --git a/boards/esp32-s3-zero.json b/boards/esp32-s3-zero.json new file mode 100644 index 00000000..7a9dbc53 --- /dev/null +++ b/boards/esp32-s3-zero.json @@ -0,0 +1,40 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld" + }, + "core": "esp32", + "extra_flags": [ + "-D ARDUINO_USB_CDC_ON_BOOT=1", + "-D ARDUINO_USB_MSC_ON_BOOT=0", + "-D ARDUINO_USB_DFU_ON_BOOT=0", + "-D ARDUINO_USB_MODE=1", + "-D ARDUINO_RUNNING_CORE=1", + "-D ARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "esp32s3" + }, + "connectivity": ["wifi", "bluetooth"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "ESP32-S3-Zero", + "upload": { + "flash_size": "4MB", + "maximum_ram_size": 327680, + "maximum_size": 4194304, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://www.espressif.com", + "vendor": "Espressif" +} + diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini new file mode 100644 index 00000000..e18bcde1 --- /dev/null +++ b/variants/nibble_screen_connect/platformio.ini @@ -0,0 +1,159 @@ +[nibble_screen_connect_base] +extends = esp32_base +board = esp32-s3-zero +build_flags = + ${esp32_base.build_flags} + -I variants/nibble_screen_connect + -D NIBBLE_SCREEN_CONNECT + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=10 + -D P_LORA_RESET=6 + -D P_LORA_BUSY=5 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + -D PIN_USER_BTN=1 + -D PIN_BOARD_SDA=8 + -D PIN_BOARD_SCL=7 + -D HAS_NEOPIXEL + -D NEOPIXEL_COUNT=1 + -D NEOPIXEL_DATA=21 + -D NEOPIXEL_TYPE=(NEO_GRB+NEO_KHZ800) + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/nibble_screen_connect> +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + adafruit/Adafruit NeoPixel @ ^1.12.3 + +[env:nibble_screen_connect_repeater] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_repeater_bridge_espnow] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_terminal_chat] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_room_server] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Nibble Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_room_server> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + ${esp32_ota.lib_deps} + +[env:nibble_screen_connect_companion_radio_usb] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_companion_radio_ble] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_companion_radio_wifi] +extends = nibble_screen_connect_base +build_flags = + ${nibble_screen_connect_base.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=300 + -D MAX_GROUP_CHANNELS=8 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${nibble_screen_connect_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + diff --git a/variants/nibble_screen_connect/target.cpp b/variants/nibble_screen_connect/target.cpp new file mode 100644 index 00000000..1980e039 --- /dev/null +++ b/variants/nibble_screen_connect/target.cpp @@ -0,0 +1,49 @@ +#include <Arduino.h> +#include "target.h" + +ESP32Board board; + +static SPIClass spi; +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + return radio.std_init(&spi); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} + diff --git a/variants/nibble_screen_connect/target.h b/variants/nibble_screen_connect/target.h new file mode 100644 index 00000000..66e69901 --- /dev/null +++ b/variants/nibble_screen_connect/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/ESP32Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern ESP32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + From d0caa3be0447bc3eecc3d2a301c56021ba6a3734 Mon Sep 17 00:00:00 2001 From: Devin Carraway <git@devin.com> Date: Fri, 31 Oct 2025 22:11:24 -0700 Subject: [PATCH 212/546] Fix the sample RAK repeater build target name The actual target doesn't capitalize the 'r' in repeater. --- build.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 732dac43..f2127941 100755 --- a/build.sh +++ b/build.sh @@ -15,8 +15,8 @@ Commands: build-room-server-firmwares: Build all chat room server firmwares for all build targets. Examples: -Build firmware for the "RAK_4631_Repeater" device target -$ sh build.sh build-firmware RAK_4631_Repeater +Build firmware for the "RAK_4631_repeater" device target +$ sh build.sh build-firmware RAK_4631_repeater Build all firmwares for device targets containing the string "RAK_4631" $ sh build.sh build-matching-firmwares <build-match-spec> From 03fc94901477b4c7fc15fa6757d1a1940d7eaa34 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 3 Nov 2025 14:23:32 +1100 Subject: [PATCH 213/546] * setting up framework for Regions, TransportKeys, etc --- examples/simple_repeater/MyMesh.cpp | 22 +++++++++++++-- examples/simple_repeater/MyMesh.h | 5 ++++ src/helpers/RegionMap.cpp | 35 +++++++++++++++++++++++ src/helpers/RegionMap.h | 39 +++++++++++++++++++++++++ src/helpers/TransportKeyStore.cpp | 44 +++++++++++++++++++++++++++++ src/helpers/TransportKeyStore.h | 23 +++++++++++++++ 6 files changed, 166 insertions(+), 2 deletions(-) create mode 100644 src/helpers/RegionMap.cpp create mode 100644 src/helpers/RegionMap.h create mode 100644 src/helpers/TransportKeyStore.cpp create mode 100644 src/helpers/TransportKeyStore.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 1afad18b..21f5a76b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -404,6 +404,23 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { return getRNG()->nextInt(0, 5*t + 1); } +mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { + if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { + auto region = region_map.findMatch(pkt, REGION_ALLOW_FLOOD); + if (region == NULL) { + MESH_DEBUG_PRINTLN("onRecvPacket: unknown transport code for FLOOD packet"); + return ACTION_RELEASE; + } + } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { + if ((region_map.getWildcard().flags & REGION_ALLOW_FLOOD) == 0) { + MESH_DEBUG_PRINTLN("onRecvPacket: wildcard FLOOD packet not allowed"); + return ACTION_RELEASE; + } + } + // otherwise do normal processing + return mesh::Mesh::onRecvPacket(pkt); +} + void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, uint8_t *data, size_t len) { if (packet->getPayloadType() == PAYLOAD_TYPE_ANON_REQ) { // received an initial request by a possible admin @@ -593,7 +610,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store) #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif @@ -652,8 +669,9 @@ void MyMesh::begin(FILESYSTEM *fs) { _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + // TODO: key_store.begin(); + region_map.load(_fs); #if defined(WITH_BRIDGE) if (_prefs.bridge_enabled) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 694e8ff9..7e34ef5f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -32,6 +32,7 @@ #include <helpers/StaticPoolPacketManager.h> #include <helpers/StatsFormatHelper.h> #include <helpers/TxtDataHelpers.h> +#include <helpers/RegionMap.h> #ifdef WITH_BRIDGE extern AbstractBridge* bridge; @@ -87,6 +88,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; ClientACL acl; + TransportKeyStore key_store; + RegionMap region_map; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS NeighbourInfo neighbours[MAX_NEIGHBOURS]; @@ -144,6 +147,8 @@ protected: } #endif + mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; + void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp new file mode 100644 index 00000000..4c270ff8 --- /dev/null +++ b/src/helpers/RegionMap.cpp @@ -0,0 +1,35 @@ +#include "RegionMap.h" +#include <SHA256.h> + +void RegionMap::load(FILESYSTEM* _fs) { + // TODO +} +void RegionMap::save(FILESYSTEM* _fs) { + // TODO +} + +RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (region->flags & mask) { // does region allow this? (per 'mask' param) + TransportKey keys[4]; + int num = _store->loadKeysFor(region->name, region->id, keys, 4); + for (int j = 0; j < num; j++) { + uint16_t code = keys[j].calcTransportCode(packet); + if (packet->transport_codes[0] == code) { // a match!! + return region; + } + } + } + } + return NULL; // no matches +} + +const RegionEntry* RegionMap::findName(const char* name) const { + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (strcmp(name, region->name) == 0) return region; + } + return NULL; // not found +} + diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h new file mode 100644 index 00000000..4620a745 --- /dev/null +++ b/src/helpers/RegionMap.h @@ -0,0 +1,39 @@ +#pragma once + +#include <Arduino.h> // needed for PlatformIO +#include <Packet.h> +#include "TransportKeyStore.h" + +#ifndef MAX_REGION_ENTRIES + #define MAX_REGION_ENTRIES 32 +#endif + +#define REGION_ALLOW_FLOOD 0x01 + +struct RegionEntry { + uint16_t id; + uint16_t parent; + uint8_t flags; + char name[31]; +}; + +class RegionMap { + TransportKeyStore* _store; + uint16_t next_id; + uint16_t num_regions; + RegionEntry regions[MAX_REGION_ENTRIES]; + RegionEntry wildcard; + +public: + RegionMap(TransportKeyStore& store) : _store(&store) { + next_id = 1; num_regions = 0; + wildcard.id = wildcard.parent = 0; + wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood + } + void load(FILESYSTEM* _fs); + void save(FILESYSTEM* _fs); + + RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); + const RegionEntry& getWildcard() const { return wildcard; } + const RegionEntry* findName(const char* name) const; +}; diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp new file mode 100644 index 00000000..973ad703 --- /dev/null +++ b/src/helpers/TransportKeyStore.cpp @@ -0,0 +1,44 @@ +#include "TransportKeyStore.h" +#include <SHA256.h> + +uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { + uint16_t code; + SHA256 sha; + sha.resetHMAC(key, sizeof(key)); + uint8_t type = packet->getPayloadType(); + sha.update(&type, 1); + sha.update(packet->payload, packet->payload_len); + sha.finalizeHMAC(key, sizeof(key), &code, 2); + return code; +} + +int TransportKeyStore::loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num) { + int n = 0; + for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache + if (cache_ids[i] == id) { + keys[n++] = cache_keys[i]; + } + } + if (n > 0) return n; // cache hit! + + if (*name == '#') { // is a publicly-known hashtag region + SHA256 sha; + sha.update(name, strlen(name)); + sha.finalize(&keys[0], sizeof(keys[0].key)); + n = 1; + } else { + // TODO: retrieve from difficult-to-copy keystore + } + + // store in cache (if room) + for (int i = 0; i < n; i++) { + if (num_cache < MAX_TKS_ENTRIES) { + cache_ids[num_cache] = id; + cache_keys[num_cache] = keys[i]; + num_cache++; + } else { + // TODO: evict oldest cache entry + } + } + return n; +} diff --git a/src/helpers/TransportKeyStore.h b/src/helpers/TransportKeyStore.h new file mode 100644 index 00000000..f25ed53f --- /dev/null +++ b/src/helpers/TransportKeyStore.h @@ -0,0 +1,23 @@ +#pragma once + +#include <Arduino.h> // needed for PlatformIO +#include <Packet.h> +#include <helpers/IdentityStore.h> + +struct TransportKey { + uint8_t key[16]; + + uint16_t calcTransportCode(const mesh::Packet* packet) const; +}; + +#define MAX_TKS_ENTRIES 16 + +class TransportKeyStore { + uint16_t cache_ids[MAX_TKS_ENTRIES]; + TransportKey cache_keys[MAX_TKS_ENTRIES]; + int num_cache; + +public: + TransportKeyStore() { num_cache = 0; } + int loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num); +}; From f797744f7c4148e1f77888dbb121451c6745f0c0 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 3 Nov 2025 18:14:44 +1100 Subject: [PATCH 214/546] * misc RegionMap and key store methods --- src/helpers/RegionMap.cpp | 67 ++++++++++++++++++++++++++++-- src/helpers/RegionMap.h | 8 +++- src/helpers/TransportKeyStore.cpp | 68 +++++++++++++++++++++++-------- src/helpers/TransportKeyStore.h | 9 +++- 4 files changed, 130 insertions(+), 22 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 4c270ff8..db9ea2d5 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -1,4 +1,5 @@ #include "RegionMap.h" +#include <helpers/TxtDataHelpers.h> #include <SHA256.h> void RegionMap::load(FILESYSTEM* _fs) { @@ -8,12 +9,36 @@ void RegionMap::save(FILESYSTEM* _fs) { // TODO } +RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) { + auto region = findByName(name); + if (region) { + if (region->id == parent_id) return NULL; // ERROR: invalid parent! + + region->parent = parent_id; // re-parent / move this region in the hierarchy + } else { + if (num_regions >= MAX_REGION_ENTRIES) return NULL; // full! + + region = ®ions[num_regions++]; // alloc new RegionEntry + region->flags = 0; + region->id = next_id++; + StrHelper::strncpy(region->name, name, sizeof(region->name)); + region->parent = parent_id; + } + return region; +} + RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (region->flags & mask) { // does region allow this? (per 'mask' param) + if ((region->flags & mask) == mask) { // does region allow this? (per 'mask' param) TransportKey keys[4]; - int num = _store->loadKeysFor(region->name, region->id, keys, 4); + int num; + if (region->name[0] == '#') { // auto hashtag region + _store->getAutoKeyFor(region->id, region->name, keys[0]); + num = 1; + } else { + num = _store->loadKeysFor(region->id, keys, 4); + } for (int j = 0; j < num; j++) { uint16_t code = keys[j].calcTransportCode(packet); if (packet->transport_codes[0] == code) { // a match!! @@ -25,7 +50,7 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { return NULL; // no matches } -const RegionEntry* RegionMap::findName(const char* name) const { +RegionEntry* RegionMap::findByName(const char* name) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if (strcmp(name, region->name) == 0) return region; @@ -33,3 +58,39 @@ const RegionEntry* RegionMap::findName(const char* name) const { return NULL; // not found } +RegionEntry* RegionMap::findById(uint16_t id) { + if (id == 0) return &wildcard; // special root Region + + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (region->id == id) return region; + } + return NULL; // not found +} + +bool RegionMap::removeRegion(const RegionEntry& region) { + if (region.id == 0) return false; // failed (cannot remove the wildcard Region) + + int i; // first check region has no child regions + for (i = 0; i < num_regions; i++) { + if (regions[i].parent == region.id) return false; // failed (must remove child Regions first) + } + + i = 0; + while (i < num_regions) { + if (region.id == regions[i].id) break; + i++; + } + if (i >= num_regions) return false; // failed (not found) + + num_regions--; // remove from regions array + while (i + 1 < num_regions) { + regions[i] = regions[i + 1]; + } + return true; // success +} + +bool RegionMap::clear() { + num_regions = 0; + return true; // success +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 4620a745..69c5220b 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -33,7 +33,11 @@ public: void load(FILESYSTEM* _fs); void save(FILESYSTEM* _fs); + RegionEntry* putRegion(const char* name, uint16_t parent_id); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); - const RegionEntry& getWildcard() const { return wildcard; } - const RegionEntry* findName(const char* name) const; + RegionEntry& getWildcard() { return wildcard; } + RegionEntry* findByName(const char* name); + RegionEntry* findById(uint16_t id); + bool removeRegion(const RegionEntry& region); + bool clear(); }; diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp index 973ad703..4f689a12 100644 --- a/src/helpers/TransportKeyStore.cpp +++ b/src/helpers/TransportKeyStore.cpp @@ -12,7 +12,32 @@ uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { return code; } -int TransportKeyStore::loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num) { +void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) { + if (num_cache < MAX_TKS_ENTRIES) { + cache_ids[num_cache] = id; + cache_keys[num_cache] = key; + num_cache++; + } else { + // TODO: evict oldest cache entry + } +} + +void TransportKeyStore::getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest) { + for (int i = 0; i < num_cache; i++) { // first, check cache + if (cache_ids[i] == id) { // cache hit! + dest = cache_keys[i]; + return; + } + } + // calc key for publicly-known hashtag region name + SHA256 sha; + sha.update(name, strlen(name)); + sha.finalize(&dest.key, sizeof(dest.key)); + + putCache(id, dest); +} + +int TransportKeyStore::loadKeysFor(uint16_t id, TransportKey keys[], int max_num) { int n = 0; for (int i = 0; i < num_cache && n < max_num; i++) { // first, check cache if (cache_ids[i] == id) { @@ -21,24 +46,35 @@ int TransportKeyStore::loadKeysFor(const char* name, uint16_t id, TransportKey k } if (n > 0) return n; // cache hit! - if (*name == '#') { // is a publicly-known hashtag region - SHA256 sha; - sha.update(name, strlen(name)); - sha.finalize(&keys[0], sizeof(keys[0].key)); - n = 1; - } else { - // TODO: retrieve from difficult-to-copy keystore - } + // TODO: retrieve from difficult-to-copy keystore // store in cache (if room) for (int i = 0; i < n; i++) { - if (num_cache < MAX_TKS_ENTRIES) { - cache_ids[num_cache] = id; - cache_keys[num_cache] = keys[i]; - num_cache++; - } else { - // TODO: evict oldest cache entry - } + putCache(id, keys[i]); } return n; } + +bool TransportKeyStore::saveKeysFor(uint16_t id, const TransportKey keys[], int num) { + invalidateCache(); + + // TODO: update hardware keystore + + return false; // failed +} + +bool TransportKeyStore::removeKeys(uint16_t id) { + invalidateCache(); + + // TODO: remove from hardware keystore + + return false; // failed +} + +bool TransportKeyStore::clear() { + invalidateCache(); + + // TODO: clear hardware keystore + + return false; // failed +} diff --git a/src/helpers/TransportKeyStore.h b/src/helpers/TransportKeyStore.h index f25ed53f..fc9d2532 100644 --- a/src/helpers/TransportKeyStore.h +++ b/src/helpers/TransportKeyStore.h @@ -17,7 +17,14 @@ class TransportKeyStore { TransportKey cache_keys[MAX_TKS_ENTRIES]; int num_cache; + void putCache(uint16_t id, const TransportKey& key); + void invalidateCache() { num_cache = 0; } + public: TransportKeyStore() { num_cache = 0; } - int loadKeysFor(const char* name, uint16_t id, TransportKey keys[], int max_num); + void getAutoKeyFor(uint16_t id, const char* name, TransportKey& dest); + int loadKeysFor(uint16_t id, TransportKey keys[], int max_num); + bool saveKeysFor(uint16_t id, const TransportKey keys[], int num); + bool removeKeys(uint16_t id); + bool clear(); }; From ecd30f4d364d190beac467224063d56ed681c5f2 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 3 Nov 2025 22:53:14 +1100 Subject: [PATCH 215/546] * new CLI commands: region, region load, region save, region get, region allow --- examples/simple_repeater/MyMesh.cpp | 83 +++++++++++++++++- examples/simple_repeater/MyMesh.h | 4 +- examples/simple_repeater/main.cpp | 4 +- src/helpers/RegionMap.cpp | 128 ++++++++++++++++++++++++++-- src/helpers/RegionMap.h | 22 +++-- 5 files changed, 219 insertions(+), 22 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 21f5a76b..2cd81ef6 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -610,7 +610,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store) #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif @@ -624,6 +624,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc dirty_contacts_expiry = 0; set_radio_at = revert_radio_at = 0; _logging = false; + region_load_active = false; #if MAX_NEIGHBOURS memset(neighbours, 0, sizeof(neighbours)); @@ -846,9 +847,46 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } +static bool is_name_char(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= 'z') || c == '-' || c == '.' || c == '_' || c == '#'; +} + void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { - while (*command == ' ') - command++; // skip leading spaces + if (region_load_active) { + if (*command == 0) { // empty line, signal to terminate 'load' operation + region_map = temp_map; // copy over the temp instance as new current map + region_load_active = false; + + sprintf(reply, "OK - loaded %d regions", region_map.getCount()); + } else { + char *np = command; + while (*np == ' ') np++; // skip indent + int indent = np - command; + + char *ep = np; + while (is_name_char(*ep)) ep++; + if (*ep) { *ep++ = 0; } // set null terminator for end of name + + while (*ep && *ep != 'F') ep++; // look for (optional flags) + + if (indent > 0 && indent < 8) { + auto parent = load_stack[indent - 1]; + if (parent) { + auto old = region_map.findByName(np); + auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists) + if (nw) { + nw->flags = old ? old->flags : (*ep == 'F' ? REGION_ALLOW_FLOOD : 0); // carry-over flags from curr + + load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's + } + } + } + reply[0] = 0; + } + return; + } + + while (*command == ' ') command++; // skip leading spaces if (strlen(command) > 4 && command[2] == '|') { // optional prefix (for companion radio CLI) memcpy(reply, command, 3); // reflect the prefix back @@ -890,6 +928,45 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply Serial.printf("\n"); } reply[0] = 0; + } else if (memcmp(command, "region", 6) == 0) { + reply[0] = 0; + + const char* parts[4]; + int n = mesh::Utils::parseTextParts(command, parts, 4, ' '); + if (n == 1 && sender_timestamp == 0) { + region_map.exportTo(Serial); + } else if (n >= 2 && strcmp(parts[1], "load") == 0) { + temp_map.resetFrom(region_map); // rebuild regions in a temp instance + memset(load_stack, 0, sizeof(load_stack)); + load_stack[0] = &temp_map.getWildcard(); + region_load_active = true; + } else if (n >= 2 && strcmp(parts[1], "save") == 0) { + bool success = region_map.save(_fs); + strcpy(reply, success ? "OK" : "Err - save failed"); + } else if (n >= 3 && strcmp(parts[1], "allow") == 0) { + auto region = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard(); + if (region) { + strcpy(reply, "OK"); + if (strcmp(parts[2], "F") == 0) { + region->flags = REGION_ALLOW_FLOOD; + } else if (strcmp(parts[2], "0") == 0) { + region->flags = 0; + } else { + sprintf(reply, "Err - invalid flag: %s", parts[2]); + } + } else { + strcpy(reply, "Err - unknown region"); + } + } else if (n >= 3 && strcmp(parts[1], "get") == 0) { + auto region = region_map.findByNamePrefix(parts[2]); + if (region) { + sprintf(reply, " %s %s", region->name, region->flags == REGION_ALLOW_FLOOD ? "F" : ""); + } else { + strcpy(reply, "Err - unknown region"); + } + } else { + strcpy(reply, "Err - ??"); + } } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7e34ef5f..9ab93747 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -89,7 +89,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t reply_data[MAX_PACKET_PAYLOAD]; ClientACL acl; TransportKeyStore key_store; - RegionMap region_map; + RegionMap region_map, temp_map; + RegionEntry* load_stack[8]; + bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS NeighbourInfo neighbours[MAX_NEIGHBOURS]; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 5843df74..7387e77e 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -91,14 +91,16 @@ void loop() { if (c != '\n') { command[len++] = c; command[len] = 0; + Serial.print(c); } - Serial.print(c); + if (c == '\r') break; } if (len == sizeof(command)-1) { // command buffer full command[sizeof(command)-1] = '\r'; } if (len > 0 && command[len - 1] == '\r') { // received complete line + Serial.print('\n'); command[len - 1] = 0; // replace newline with C string null terminator char reply[160]; the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial! diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index db9ea2d5..7b6456b4 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -2,25 +2,106 @@ #include <helpers/TxtDataHelpers.h> #include <SHA256.h> -void RegionMap::load(FILESYSTEM* _fs) { - // TODO -} -void RegionMap::save(FILESYSTEM* _fs) { - // TODO +RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { + next_id = 1; num_regions = 0; + wildcard.id = wildcard.parent = 0; + wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood + strcpy(wildcard.name, "(*)"); } -RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id) { +static File openWrite(FILESYSTEM* _fs, const char* filename) { + #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) + _fs->remove(filename); + return _fs->open(filename, FILE_O_WRITE); + #elif defined(RP2040_PLATFORM) + return _fs->open(filename, "w"); + #else + return _fs->open(filename, "w", true); + #endif +} + +bool RegionMap::load(FILESYSTEM* _fs) { + if (_fs->exists("/regions2")) { + #if defined(RP2040_PLATFORM) + File file = _fs->open("/regions2", "r"); + #else + File file = _fs->open("/regions2"); + #endif + + if (file) { + uint8_t pad[128]; + + num_regions = 0; next_id = 1; + + bool success = file.read(pad, 7) == 7; // reserved header + success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); + success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); + + if (success) { + while (num_regions < MAX_REGION_ENTRIES) { + auto r = ®ions[num_regions]; + + success = file.read((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id); + success = success && file.read((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent); + success = success && file.read((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name); + success = success && file.read((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags); + success = success && file.read(pad, sizeof(pad)) == sizeof(pad); + + if (!success) break; // EOF + + if (r->id >= next_id) { // make sure next_id is valid + next_id = r->id + 1; + } + num_regions++; + } + } + file.close(); + return true; + } + } + return false; // failed +} + +bool RegionMap::save(FILESYSTEM* _fs) { + File file = openWrite(_fs, "/regions2"); + if (file) { + uint8_t pad[128]; + memset(pad, 0, sizeof(pad)); + + bool success = file.write(pad, 7) == 7; // reserved header + success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); + success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); + + if (success) { + for (int i = 0; i < num_regions; i++) { + auto r = ®ions[i]; + + success = file.write((uint8_t *) &r->id, sizeof(r->id)) == sizeof(r->id); + success = success && file.write((uint8_t *) &r->parent, sizeof(r->parent)) == sizeof(r->parent); + success = success && file.write((uint8_t *) r->name, sizeof(r->name)) == sizeof(r->name); + success = success && file.write((uint8_t *) &r->flags, sizeof(r->flags)) == sizeof(r->flags); + success = success && file.write(pad, sizeof(pad)) == sizeof(pad); + if (!success) break; // write failed + } + } + file.close(); + return true; + } + return false; // failed +} + +RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) { auto region = findByName(name); if (region) { if (region->id == parent_id) return NULL; // ERROR: invalid parent! region->parent = parent_id; // re-parent / move this region in the hierarchy } else { - if (num_regions >= MAX_REGION_ENTRIES) return NULL; // full! + if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full! region = ®ions[num_regions++]; // alloc new RegionEntry region->flags = 0; - region->id = next_id++; + region->id = id == 0 ? next_id++ : id; StrHelper::strncpy(region->name, name, sizeof(region->name)); region->parent = parent_id; } @@ -58,6 +139,14 @@ RegionEntry* RegionMap::findByName(const char* name) { return NULL; // not found } +RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if (memcmp(prefix, region->name, strlen(prefix)) == 0) return region; + } + return NULL; // not found +} + RegionEntry* RegionMap::findById(uint16_t id) { if (id == 0) return &wildcard; // special root Region @@ -94,3 +183,26 @@ bool RegionMap::clear() { num_regions = 0; return true; // success } + +void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out) const { + for (int i = 0; i < indent; i++) { + out.print(' '); + } + + if (parent->flags & REGION_ALLOW_FLOOD) { + out.printf("%s F\n", parent->name); + } else { + out.printf("%s\n", parent->name); + } + + for (int i = 0; i < num_regions; i++) { + auto r = ®ions[i]; + if (r->parent == parent->id) { + printChildRegions(indent + 1, r, out); + } + } +} + +void RegionMap::exportTo(Stream& out) const { + printChildRegions(0, &wildcard, out); // recursive +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 69c5220b..3858bfbd 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -24,20 +24,24 @@ class RegionMap { RegionEntry regions[MAX_REGION_ENTRIES]; RegionEntry wildcard; -public: - RegionMap(TransportKeyStore& store) : _store(&store) { - next_id = 1; num_regions = 0; - wildcard.id = wildcard.parent = 0; - wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood - } - void load(FILESYSTEM* _fs); - void save(FILESYSTEM* _fs); + void printChildRegions(int indent, const RegionEntry* parent, Stream& out) const; - RegionEntry* putRegion(const char* name, uint16_t parent_id); +public: + RegionMap(TransportKeyStore& store); + + bool load(FILESYSTEM* _fs); + bool save(FILESYSTEM* _fs); + + RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); RegionEntry& getWildcard() { return wildcard; } RegionEntry* findByName(const char* name); + RegionEntry* findByNamePrefix(const char* prefix); RegionEntry* findById(uint16_t id); bool removeRegion(const RegionEntry& region); bool clear(); + void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } + int getCount() const { return num_regions; } + + void exportTo(Stream& out) const; }; From d9ff3a4d02d25005eac27c40bd58b6c43f29c9ed Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 4 Nov 2025 01:21:56 +1100 Subject: [PATCH 216/546] * Mesh: new sendFlood() overload with transport codes. * BaseChatMesh: sendFloodScoped(), for overriding with some outbound 'scope' / TransportKey * companion: new 'send_scope' variable. --- examples/companion_radio/MyMesh.cpp | 24 ++++++++++++++++++++++ examples/companion_radio/MyMesh.h | 6 ++++++ src/Mesh.cpp | 25 +++++++++++++++++++++++ src/Mesh.h | 6 ++++++ src/helpers/BaseChatMesh.cpp | 31 ++++++++++++++++++----------- src/helpers/BaseChatMesh.h | 3 +++ src/helpers/TransportKeyStore.cpp | 7 +++++++ src/helpers/TransportKeyStore.h | 1 + 8 files changed, 91 insertions(+), 12 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 02f1a21d..b8ddbfa7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -378,6 +378,29 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe #endif } +void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { + // TODO: dynamic send_scope, depending on recipient and current 'home' Region + if (send_scope.isNull()) { + sendFlood(pkt, delay_millis); + } else { + uint16_t codes[2]; + codes[0] = send_scope.calcTransportCode(pkt); + codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? + sendFlood(pkt, codes, delay_millis); + } +} +void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { + // TODO: have per-channel send_scope + if (send_scope.isNull()) { + sendFlood(pkt, delay_millis); + } else { + uint16_t codes[2]; + codes[0] = send_scope.calcTransportCode(pkt); + codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? + sendFlood(pkt, codes, delay_millis); + } +} + void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, const char *text) { markConnectionActive(from); // in case this is from a server, and we have a connection @@ -663,6 +686,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe sign_data = NULL; dirty_contacts_expiry = 0; memset(advert_paths, 0, sizeof(advert_paths)); + memset(send_scope.key, 0, sizeof(send_scope.key)); // defaults memset(&_prefs, 0, sizeof(_prefs)); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e6400871..bfb8bb18 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -68,6 +68,7 @@ #endif #include <helpers/BaseChatMesh.h> +#include <helpers/TransportKeyStore.h> /* -------------------------------------------------------------------------------------- */ @@ -107,6 +108,9 @@ protected: int calcRxDelay(float score, uint32_t air_time) const override; uint8_t getExtraAckTransmitCount() const override; + void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; + void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; + void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; @@ -191,6 +195,8 @@ private: uint32_t sign_data_len; unsigned long dirty_contacts_expiry; + TransportKey send_scope; + uint8_t cmd_frame[MAX_FRAME_SIZE + 1]; uint8_t out_frame[MAX_FRAME_SIZE + 1]; CayenneLPP telemetry; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 53dc74f5..1ee6ce5c 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -610,6 +610,31 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { sendPacket(packet, pri, delay_millis); } +void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { + if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { + MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); + return; + } + + packet->header &= ~PH_ROUTE_MASK; + packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD; + packet->transport_codes[0] = transport_codes[0]; + packet->transport_codes[1] = transport_codes[1]; + packet->path_len = 0; + + _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us + + uint8_t pri; + if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { + pri = 2; + } else if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT) { + pri = 3; // de-prioritie these + } else { + pri = 1; + } + sendPacket(packet, pri, delay_millis); +} + void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uint32_t delay_millis) { packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_DIRECT; diff --git a/src/Mesh.h b/src/Mesh.h index cbf1c9cf..e96043e8 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -186,6 +186,12 @@ public: */ void sendFlood(Packet* packet, uint32_t delay_millis=0); + /** + * \brief send a locally-generated Packet with flood routing + * \param transport_codes array of 2 codes to attach to packet + */ + void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + /** * \brief send a locally-generated Packet with Direct routing */ diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 9b1eb1ce..b4072657 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -9,6 +9,13 @@ #define TXT_ACK_DELAY 200 #endif +void BaseChatMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { + sendFlood(pkt, delay_millis); +} +void BaseChatMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { + sendFlood(pkt, delay_millis); +} + mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name) { uint8_t app_data[MAX_ADVERT_DATA_SIZE]; uint8_t app_data_len; @@ -34,7 +41,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { if (dest.out_path_len < 0) { mesh::Packet* ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); + if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY); } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { @@ -175,7 +182,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path, TXT_ACK_DELAY); + if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash); } @@ -186,7 +193,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (packet->isRouteFlood()) { // let this sender know path TO here, so they can use sendDirect() (NOTE: no ACK as extra) mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, 0, NULL, 0); - if (path) sendFlood(path); + if (path) sendFloodScoped(from, path); } } else if (flags == TXT_TYPE_SIGNED_PLAIN) { if (timestamp > from.sync_since) { // make sure 'sync_since' is up-to-date @@ -202,7 +209,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path, TXT_ACK_DELAY); + if (path) sendFloodScoped(from, path, TXT_ACK_DELAY); } else { sendAckTo(from, ack_hash); } @@ -218,14 +225,14 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(from.id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, temp_buf, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFloodScoped(from, path, SERVER_RESPONSE_DELAY); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); if (reply) { if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY); } } } @@ -346,7 +353,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, int rc; if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; } else { @@ -372,7 +379,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; } else { @@ -398,7 +405,7 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& chan auto pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len); if (pkt) { - sendFlood(pkt); + sendFloodScoped(channel, pkt); return true; } return false; @@ -460,7 +467,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; } else { @@ -487,7 +494,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; } else { @@ -514,7 +521,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); if (recipient.out_path_len < 0) { - sendFlood(pkt); + sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; } else { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 9392001e..76b0dd1c 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -107,6 +107,9 @@ protected: virtual void onContactResponse(const ContactInfo& contact, const uint8_t* data, uint8_t len) = 0; virtual void handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len); + virtual void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0); + virtual void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0); + // storage concepts, for sub-classes to override/implement virtual int getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { return 0; } // not implemented virtual bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], int len) { return false; } diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp index 4f689a12..8b7c891b 100644 --- a/src/helpers/TransportKeyStore.cpp +++ b/src/helpers/TransportKeyStore.cpp @@ -12,6 +12,13 @@ uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { return code; } +bool TransportKey::isNull() const { + for (int i = 0; i < sizeof(key); i++) { + if (key[i]) return false; + } + return true; // key is all zeroes +} + void TransportKeyStore::putCache(uint16_t id, const TransportKey& key) { if (num_cache < MAX_TKS_ENTRIES) { cache_ids[num_cache] = id; diff --git a/src/helpers/TransportKeyStore.h b/src/helpers/TransportKeyStore.h index fc9d2532..e3ba1524 100644 --- a/src/helpers/TransportKeyStore.h +++ b/src/helpers/TransportKeyStore.h @@ -8,6 +8,7 @@ struct TransportKey { uint8_t key[16]; uint16_t calcTransportCode(const mesh::Packet* packet) const; + bool isNull() const; }; #define MAX_TKS_ENTRIES 16 From 397d280c3bdf524134726f644111108a3bc1f5f9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:25:31 +0100 Subject: [PATCH 217/546] stop OLED powering on every message if connected to phone --- examples/companion_radio/ui-new/UITask.cpp | 9 ++++++++- examples/companion_radio/ui-orig/UITask.cpp | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 7c75a089..886823ff 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -596,9 +596,16 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - if (!_display->isOn()) _display->turnOn(); + // Only turn on display if it's off AND not connected to phone via BLE + // If connected to phone, user will see messages there, so don't wake the OLED + if (!_display->isOn() && !hasConnection()) { + _display->turnOn(); + } + // Always extend auto-off timer and trigger refresh if display is on + if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _next_refresh = 100; // trigger refresh + } } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d..20d45bec 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -136,9 +136,16 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i StrHelper::strncpy(_msg, text, sizeof(_msg)); if (_display != NULL) { - if (!_display->isOn()) _display->turnOn(); + // Only turn on display if it's off AND not connected to phone via BLE + // If connected to phone, user will see messages there, so don't wake the OLED + if (!_display->isOn() && !hasConnection()) { + _display->turnOn(); + } + // Always extend auto-off timer and trigger refresh if display is on + if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _need_refresh = true; + } } } From eae16cfc5f0690cb20bcce92918c04f39d348e9c Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:39:35 +0100 Subject: [PATCH 218/546] less unnecessary comments, less lines of code :) --- examples/companion_radio/ui-new/UITask.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 886823ff..27734135 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -596,12 +596,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i setCurrScreen(msg_preview); if (_display != NULL) { - // Only turn on display if it's off AND not connected to phone via BLE - // If connected to phone, user will see messages there, so don't wake the OLED if (!_display->isOn() && !hasConnection()) { _display->turnOn(); } - // Always extend auto-off timer and trigger refresh if display is on if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _next_refresh = 100; // trigger refresh From 99a34731693432e844db03622ded3b739de705dc Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Mon, 3 Nov 2025 21:41:11 +0100 Subject: [PATCH 219/546] even less comments \o/ --- examples/companion_radio/ui-orig/UITask.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 20d45bec..3b36e45d 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -136,12 +136,9 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i StrHelper::strncpy(_msg, text, sizeof(_msg)); if (_display != NULL) { - // Only turn on display if it's off AND not connected to phone via BLE - // If connected to phone, user will see messages there, so don't wake the OLED if (!_display->isOn() && !hasConnection()) { _display->turnOn(); } - // Always extend auto-off timer and trigger refresh if display is on if (_display->isOn()) { _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _need_refresh = true; From 5c80334dbda5a09fd44104a70168fe79a1bf0fea Mon Sep 17 00:00:00 2001 From: David Hall <david@datajack.org> Date: Mon, 3 Nov 2025 21:00:43 +0000 Subject: [PATCH 220/546] Fix manufacturer name on Seeed Xiao S3 WIO --- variants/xiao_s3_wio/XiaoS3WIOBoard.h | 13 +++++++++++++ variants/xiao_s3_wio/target.cpp | 3 ++- variants/xiao_s3_wio/target.h | 3 ++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 variants/xiao_s3_wio/XiaoS3WIOBoard.h diff --git a/variants/xiao_s3_wio/XiaoS3WIOBoard.h b/variants/xiao_s3_wio/XiaoS3WIOBoard.h new file mode 100644 index 00000000..7ae06a35 --- /dev/null +++ b/variants/xiao_s3_wio/XiaoS3WIOBoard.h @@ -0,0 +1,13 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/ESP32Board.h> + +class XiaoS3WIOBoard : public ESP32Board { +public: + XiaoS3WIOBoard() { } + + const char* getManufacturerName() const override { + return "Xiao S3 WIO"; + } +}; diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index ed8584ff..f7de9244 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -1,7 +1,8 @@ #include <Arduino.h> #include "target.h" +#include "XiaoS3WIOBoard.h" -ESP32Board board; +XiaoS3WIOBoard board; #if defined(P_LORA_SCLK) static SPIClass spi; diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index f184c757..b84ab42b 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -11,8 +11,9 @@ #include <helpers/ui/SSD1306Display.h> #include <helpers/ui/MomentaryButton.h> #endif +#include "XiaoS3WIOBoard.h" -extern ESP32Board board; +extern XiaoS3WIOBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; From c3dbec41bac0730b778efd8202cd2d29466c0b9b Mon Sep 17 00:00:00 2001 From: David Hall <david@datajack.org> Date: Mon, 3 Nov 2025 21:02:08 +0000 Subject: [PATCH 221/546] Fix manufacturer name on Seeed Xiao S3 WIO --- variants/xiao_s3_wio/target.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index f7de9244..41f25da6 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -1,6 +1,5 @@ #include <Arduino.h> #include "target.h" -#include "XiaoS3WIOBoard.h" XiaoS3WIOBoard board; From 04c0c40b391e5e9d78ce83dba5e539b30e4c1443 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Tue, 4 Nov 2025 23:58:32 +0100 Subject: [PATCH 222/546] set max contacts to 350 and channels to 40 for esp32c3, s3 and c6 --- variants/ebyte_eora_s3/platformio.ini | 10 ++++---- variants/generic_espnow/platformio.ini | 4 ++-- variants/heltec_ct62/platformio.ini | 8 +++---- variants/heltec_e213/platformio.ini | 8 +++---- variants/heltec_e290/platformio.ini | 8 +++---- variants/heltec_t190/platformio.ini | 8 +++---- variants/heltec_tracker/platformio.ini | 4 ++-- variants/heltec_tracker_v2/platformio.ini | 14 +++++------ variants/heltec_v3/platformio.ini | 24 +++++++++---------- variants/heltec_v4/platformio.ini | 14 +++++------ variants/heltec_wireless_paper/platformio.ini | 4 ++-- variants/lilygo_t3s3/platformio.ini | 10 ++++---- variants/lilygo_t3s3_sx1276/platformio.ini | 10 ++++---- .../platformio.ini | 4 ++-- variants/lilygo_tlora_c6/platformio.ini | 4 ++-- variants/meshadventurer/platformio.ini | 8 +++---- .../sensecap_indicator-espnow/platformio.ini | 4 ++-- variants/station_g2/platformio.ini | 12 +++++----- variants/thinknode_m2/platformio.ini | 12 +++++----- variants/xiao_c3/platformio.ini | 12 +++++----- variants/xiao_c6/platformio.ini | 12 +++++----- variants/xiao_s3_wio/platformio.ini | 16 ++++++------- 22 files changed, 105 insertions(+), 105 deletions(-) diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini index df622a34..bdf6bba3 100644 --- a/variants/ebyte_eora_s3/platformio.ini +++ b/variants/ebyte_eora_s3/platformio.ini @@ -64,7 +64,7 @@ lib_deps = extends = Ebyte_EoRa-S3 build_flags = ${Ebyte_EoRa-S3.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -99,8 +99,8 @@ build_flags = ${Ebyte_EoRa-S3.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} @@ -118,8 +118,8 @@ build_flags = ${Ebyte_EoRa-S3.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/generic_espnow/platformio.ini b/variants/generic_espnow/platformio.ini index c40833dc..cdeed076 100644 --- a/variants/generic_espnow/platformio.ini +++ b/variants/generic_espnow/platformio.ini @@ -26,7 +26,7 @@ build_src_filter = ${esp32_base.build_src_filter} extends = Generic_ESPNOW build_flags = ${Generic_ESPNOW.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 build_src_filter = ${Generic_ESPNOW.build_src_filter} +<../examples/simple_secure_chat/main.cpp> @@ -54,7 +54,7 @@ lib_deps = extends = Generic_ESPNOW build_flags = ${Generic_ESPNOW.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=8 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 1b6121e4..1f2e330a 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -97,8 +97,8 @@ build_flags = ${Heltec_ct62.build_flags} ; -D ARDUINO_USB_MODE=1 ; -D ARDUINO_USB_CDC_ON_BOOT=1 - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -115,8 +115,8 @@ build_flags = ${Heltec_ct62.build_flags} ; -D ARDUINO_USB_MODE=1 ; -D ARDUINO_USB_CDC_ON_BOOT=1 - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D BLE_PIN_CODE=123456 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 93bdc000..caba3a30 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -45,8 +45,8 @@ extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 @@ -65,8 +65,8 @@ extends = Heltec_E213_base build_flags = ${Heltec_E213_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${Heltec_E213_base.build_src_filter} diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 201b3631..0c07c592 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -39,8 +39,8 @@ extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E290Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 @@ -59,8 +59,8 @@ extends = Heltec_E290_base build_flags = ${Heltec_E290_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E290Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 01e9dfc9..8d21c523 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -52,8 +52,8 @@ extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -70,8 +70,8 @@ extends = Heltec_T190_base build_flags = ${Heltec_T190_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${Heltec_T190_base.build_src_filter} +<helpers/esp32/*.cpp> diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 19ab49a8..4f48ac21 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -43,8 +43,8 @@ build_flags = -I examples/companion_radio/ui-new -D DISPLAY_ROTATION=1 -D DISPLAY_CLASS=ST7735Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; HWT will use display for pin -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index c4a79d9e..61ccd4f4 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -120,7 +120,7 @@ lib_deps = extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -135,8 +135,8 @@ extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -154,8 +154,8 @@ extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 @@ -179,8 +179,8 @@ extends = Heltec_tracker_v2 build_flags = ${Heltec_tracker_v2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=ST7735Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index dcf566b3..36c6386f 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -125,7 +125,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -140,8 +140,8 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -159,8 +159,8 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 @@ -183,8 +183,8 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' @@ -304,8 +304,8 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -323,7 +323,7 @@ extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} -D MAX_CONTACTS=140 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -336,8 +336,8 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 2c06b978..c26a5bc6 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -112,7 +112,7 @@ lib_deps = extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -127,8 +127,8 @@ extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 @@ -146,8 +146,8 @@ extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 @@ -170,8 +170,8 @@ extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 20bf4c64..c9ad758b 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -38,8 +38,8 @@ extends = Heltec_Wireless_Paper_base build_flags = ${Heltec_Wireless_Paper_base.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=E213Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_DEBUG_LOGGING=1 diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index a97a692a..0f01c9b7 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -102,7 +102,7 @@ lib_deps = extends = LilyGo_T3S3_sx1262 build_flags = ${LilyGo_T3S3_sx1262.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -137,8 +137,8 @@ build_flags = ${LilyGo_T3S3_sx1262.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} @@ -156,8 +156,8 @@ build_flags = ${LilyGo_T3S3_sx1262.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index eee8bdbd..f544be11 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -100,7 +100,7 @@ lib_deps = extends = LilyGo_T3S3_sx1276 build_flags = ${LilyGo_T3S3_sx1276.build_flags} - -D MAX_CONTACTS=300 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 @@ -136,8 +136,8 @@ build_flags = ${LilyGo_T3S3_sx1276.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} @@ -155,8 +155,8 @@ build_flags = ${LilyGo_T3S3_sx1276.build_flags} -I examples/companion_radio/ui-new -D DISPLAY_CLASS=SSD1306Display - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index ef31e6e1..2d2a095a 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -116,8 +116,8 @@ extends = T_Beam_S3_Supreme_SX1262 build_flags = ${T_Beam_S3_Supreme_SX1262.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index 88a811b5..b29cd036 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -67,8 +67,8 @@ lib_deps = [env:LilyGo_Tlora_C6_companion_radio_ble_] extends = tlora_c6 build_flags = ${tlora_c6.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index c52f4b7b..18b64ac3 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -190,7 +190,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -212,7 +212,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -275,7 +275,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1268Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 lib_deps = @@ -297,7 +297,7 @@ build_flags = -D WRAPPER_CLASS=CustomSX1268Wrapper -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/sensecap_indicator-espnow/platformio.ini b/variants/sensecap_indicator-espnow/platformio.ini index 064f3ae8..e643d033 100644 --- a/variants/sensecap_indicator-espnow/platformio.ini +++ b/variants/sensecap_indicator-espnow/platformio.ini @@ -37,8 +37,8 @@ extends =SenseCapIndicator-ESPNow build_flags = ${SenseCapIndicator-ESPNow.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D ESPNOW_DEBUG_LOGGING=1 diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index bda8b7ae..9ee8f829 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -184,8 +184,8 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} @@ -201,8 +201,8 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -221,8 +221,8 @@ extends = Station_G2 build_flags = ${Station_G2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index 0238947f..fb691d92 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -118,8 +118,8 @@ lib_deps = extends = ThinkNode_M2 build_flags = ${ThinkNode_M2.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M2.build_src_filter} @@ -133,8 +133,8 @@ extends = ThinkNode_M2 build_flags = ${ThinkNode_M2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 @@ -155,8 +155,8 @@ extends = ThinkNode_M2 build_flags = ${ThinkNode_M2.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D SERIAL_TX=D6 -D SERIAL_RX=D7 ; -D MESH_PACKET_LOGGING=1 diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index 683199d9..76b72174 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -73,8 +73,8 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<helpers/esp32/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 @@ -92,8 +92,8 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<helpers/esp32/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 @@ -110,8 +110,8 @@ build_src_filter = ${Xiao_esp32_C3.build_src_filter} +<helpers/esp32/*.cpp> build_flags = ${Xiao_esp32_C3.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 9c971083..8f02dc87 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -50,8 +50,8 @@ lib_deps = [env:Xiao_C6_companion_radio_ble_] extends = Xiao_C6 build_flags = ${Xiao_C6.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -107,8 +107,8 @@ lib_deps = [env:Meshimi_companion_radio_ble_] extends = Meshimi build_flags = ${Meshimi.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 @@ -167,8 +167,8 @@ lib_deps = [env:WHY2025_badge_companion_radio_ble_] extends = WHY2025_badge build_flags = ${WHY2025_badge.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D BLE_DEBUG_LOGGING=1 -D OFFLINE_QUEUE_SIZE=256 diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index e5cc4c8c..95a54012 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -107,8 +107,8 @@ lib_deps = extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_S3_WIO.build_src_filter} @@ -122,8 +122,8 @@ extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 @@ -144,8 +144,8 @@ extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D DISPLAY_CLASS=SSD1306Display -D OFFLINE_QUEUE_SIZE=256 @@ -168,8 +168,8 @@ extends = Xiao_S3_WIO build_flags = ${Xiao_S3_WIO.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=300 - -D MAX_GROUP_CHANNELS=8 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display -D SERIAL_TX=D6 -D SERIAL_RX=D7 From 9ebeb477aa3313123323a967c8981a6ab96ea82f Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 5 Nov 2025 14:34:44 +1100 Subject: [PATCH 223/546] * RegionMap: inverted 'flags' to _deny_ bits * Mesh: new filterRecvFloodPacket() for overriding * repeater CLI: 'allow' -> 'allowf' or 'denyf' --- examples/companion_radio/MyMesh.cpp | 6 +++++ examples/companion_radio/MyMesh.h | 1 + examples/simple_repeater/MyMesh.cpp | 38 +++++++++++++++-------------- examples/simple_repeater/MyMesh.h | 2 +- src/Mesh.cpp | 2 ++ src/Mesh.h | 5 ++++ src/helpers/RegionMap.cpp | 12 ++++----- src/helpers/RegionMap.h | 3 ++- 8 files changed, 43 insertions(+), 26 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index b8ddbfa7..3c882d18 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -378,6 +378,12 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe #endif } +bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) { + // REVISIT: try to determine which Region (from transport_codes[1]) that Sender is indicating for replies/responses + // if unknown, fallback to finding Region from transport_codes[0], the 'scope' used by Sender + return false; +} + void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: dynamic send_scope, depending on recipient and current 'home' Region if (send_scope.isNull()) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index bfb8bb18..fb0fb479 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -107,6 +107,7 @@ protected: int getInterferenceThreshold() const override; int calcRxDelay(float score, uint32_t air_time) const override; uint8_t getExtraAckTransmitCount() const override; + bool filterRecvFloodPacket(mesh::Packet* packet) override; void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2cd81ef6..dce0f18b 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -404,21 +404,21 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { return getRNG()->nextInt(0, 5*t + 1); } -mesh::DispatcherAction MyMesh::onRecvPacket(mesh::Packet* pkt) { +bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { - auto region = region_map.findMatch(pkt, REGION_ALLOW_FLOOD); + auto region = region_map.findMatch(pkt, REGION_DENY_FLOOD); if (region == NULL) { MESH_DEBUG_PRINTLN("onRecvPacket: unknown transport code for FLOOD packet"); - return ACTION_RELEASE; + return true; } } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { - if ((region_map.getWildcard().flags & REGION_ALLOW_FLOOD) == 0) { + if (region_map.getWildcard().flags & REGION_DENY_FLOOD) { MESH_DEBUG_PRINTLN("onRecvPacket: wildcard FLOOD packet not allowed"); - return ACTION_RELEASE; + return true; } } // otherwise do normal processing - return mesh::Mesh::onRecvPacket(pkt); + return false; } void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const mesh::Identity &sender, @@ -867,7 +867,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply while (is_name_char(*ep)) ep++; if (*ep) { *ep++ = 0; } // set null terminator for end of name - while (*ep && *ep != 'F') ep++; // look for (optional flags) + while (*ep && *ep != 'F') ep++; // look for (optional) flags if (indent > 0 && indent < 8) { auto parent = load_stack[indent - 1]; @@ -875,7 +875,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply auto old = region_map.findByName(np); auto nw = temp_map.putRegion(np, parent->id, old ? old->id : 0); // carry-over the current ID (if name already exists) if (nw) { - nw->flags = old ? old->flags : (*ep == 'F' ? REGION_ALLOW_FLOOD : 0); // carry-over flags from curr + nw->flags = old ? old->flags : (*ep == 'F' ? 0 : REGION_DENY_FLOOD); // carry-over flags from curr load_stack[indent] = nw; // keep pointers to parent regions, to resolve parent_id's } @@ -943,24 +943,26 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (n >= 2 && strcmp(parts[1], "save") == 0) { bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); - } else if (n >= 3 && strcmp(parts[1], "allow") == 0) { - auto region = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard(); + } else if (n >= 2 && strcmp(parts[1], "allowf") == 0) { + auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); if (region) { + region->flags &= ~REGION_DENY_FLOOD; + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - unknown region"); + } + } else if (n >= 2 && strcmp(parts[1], "denyf") == 0) { + auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); + if (region) { + region->flags |= REGION_DENY_FLOOD; strcpy(reply, "OK"); - if (strcmp(parts[2], "F") == 0) { - region->flags = REGION_ALLOW_FLOOD; - } else if (strcmp(parts[2], "0") == 0) { - region->flags = 0; - } else { - sprintf(reply, "Err - invalid flag: %s", parts[2]); - } } else { strcpy(reply, "Err - unknown region"); } } else if (n >= 3 && strcmp(parts[1], "get") == 0) { auto region = region_map.findByNamePrefix(parts[2]); if (region) { - sprintf(reply, " %s %s", region->name, region->flags == REGION_ALLOW_FLOOD ? "F" : ""); + sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); } else { strcpy(reply, "Err - unknown region"); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 9ab93747..83331541 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -149,7 +149,7 @@ protected: } #endif - mesh::DispatcherAction onRecvPacket(mesh::Packet* pkt) override; + bool filterRecvFloodPacket(mesh::Packet* pkt) override; void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override; int searchPeersByHash(const uint8_t* hash) override; diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 1ee6ce5c..71b8eaee 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -90,6 +90,8 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return ACTION_RELEASE; // this node is NOT the next hop (OR this packet has already been forwarded), so discard. } + if (pkt->isRouteFlood() && filterRecvFloodPacket(pkt)) return ACTION_RELEASE; + DispatcherAction action = ACTION_RELEASE; switch (pkt->getPayloadType()) { diff --git a/src/Mesh.h b/src/Mesh.h index e96043e8..70fa80f3 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -43,6 +43,11 @@ protected: */ DispatcherAction routeRecvPacket(Packet* packet); + /** + * \returns true, if given packet should be NOT be processed. + */ + virtual bool filterRecvFloodPacket(Packet* packet) { return false; } + /** * \brief Check whether this packet should be forwarded (re-transmitted) or not. * Is sub-classes responsibility to make sure given packet is only transmitted ONCE (by this node) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7b6456b4..074084ec 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -5,7 +5,7 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { next_id = 1; num_regions = 0; wildcard.id = wildcard.parent = 0; - wildcard.flags = REGION_ALLOW_FLOOD; // default behaviour, allow flood + wildcard.flags = 0; // default behaviour, allow flood and direct strcpy(wildcard.name, "(*)"); } @@ -100,7 +100,7 @@ RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t if (id == 0 && num_regions >= MAX_REGION_ENTRIES) return NULL; // full! region = ®ions[num_regions++]; // alloc new RegionEntry - region->flags = 0; + region->flags = REGION_DENY_FLOOD; // DENY by default region->id = id == 0 ? next_id++ : id; StrHelper::strncpy(region->name, name, sizeof(region->name)); region->parent = parent_id; @@ -111,7 +111,7 @@ RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if ((region->flags & mask) == mask) { // does region allow this? (per 'mask' param) + if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param) TransportKey keys[4]; int num; if (region->name[0] == '#') { // auto hashtag region @@ -189,10 +189,10 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& out.print(' '); } - if (parent->flags & REGION_ALLOW_FLOOD) { - out.printf("%s F\n", parent->name); - } else { + if (parent->flags & REGION_DENY_FLOOD) { out.printf("%s\n", parent->name); + } else { + out.printf("%s F\n", parent->name); } for (int i = 0; i < num_regions; i++) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 3858bfbd..5ad9df29 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -8,7 +8,8 @@ #define MAX_REGION_ENTRIES 32 #endif -#define REGION_ALLOW_FLOOD 0x01 +#define REGION_DENY_FLOOD 0x01 +#define REGION_DENY_DIRECT 0x02 // reserved for future struct RegionEntry { uint16_t id; From 937865c8fd8d3e82cf5078bc77226d725c556344 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 5 Nov 2025 14:56:18 +1100 Subject: [PATCH 224/546] * companion: new CMD_SET_FLOOD_SCOPE (54) --- examples/companion_radio/MyMesh.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3c882d18..904cd871 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -50,6 +50,7 @@ #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 +#define CMD_SET_FLOOD_SCOPE 54 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -1515,6 +1516,13 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_FILE_IO_ERROR); } + } else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE && len >= 2 && cmd_frame[1] == 0) { + if (len >= 2 + 16) { + memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set curr scope TransportKey + } else { + memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null + } + writeOKFrame(); } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); From 3ef53e64a1fd0f7b654e949f3caf16d3a760c1ba Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 5 Nov 2025 15:34:23 +1100 Subject: [PATCH 225/546] * is_name_char() bug fix --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index dce0f18b..0bfb7c89 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -848,7 +848,7 @@ void MyMesh::clearStats() { } static bool is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= 'z') || c == '-' || c == '.' || c == '_' || c == '#'; + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; } void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { From 82b4c1e6b0771031d53c6c301567cb021577a9cd Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 00:56:54 +1100 Subject: [PATCH 226/546] * new PAYLOAD_TYPE_CONTROL (11) * repeater: onControlDataRecv(), now responds to new CTL_TYPE_NODE_DISCOVER_REQ (zero hop only) * node prefs: new discovery_mod_timestamp (will be set to affect when node should respond to DISCOVERY_REQ's ) --- examples/simple_repeater/MyMesh.cpp | 32 +++++++++++++++++++++++++++++ examples/simple_repeater/MyMesh.h | 1 + src/Mesh.cpp | 24 ++++++++++++++++++++++ src/Mesh.h | 6 ++++++ src/Packet.h | 1 + src/helpers/CommonCLI.cpp | 6 ++++-- src/helpers/CommonCLI.h | 1 + 7 files changed, 69 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 0bfb7c89..74fca86c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -607,6 +607,38 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t return false; } +#define CTL_TYPE_NODE_DISCOVER_REQ 0x80 +#define CTL_TYPE_NODE_DISCOVER_RESP 0x90 + +void MyMesh::onControlDataRecv(mesh::Packet* packet) { + uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) { + // TODO: apply rate limiting to these! + int i = 1; + uint8_t filter = packet->payload[i++]; + uint32_t tag; + memcpy(&tag, &packet->payload[i], 4); i += 4; + uint32_t since; + if (packet->payload_len >= i+4) { // optional since field + memcpy(&since, &packet->payload[i], 4); i += 4; + } else { + since = 0; + } + + if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) { + uint8_t data[6 + PUB_KEY_SIZE]; + data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type + data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) + memcpy(&data[2], &tag, 4); // include tag from request, for client to match to + memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); + auto resp = createControlData(data, sizeof(data)); + if (resp) { + sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + } + } + } +} + MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 83331541..fd0b4d6a 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -157,6 +157,7 @@ protected: void onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len); void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + void onControlDataRecv(mesh::Packet* packet) override; public: MyMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables); diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 71b8eaee..f1271574 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -68,6 +68,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { return ACTION_RELEASE; } + if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) { + if (pkt->path_len == 0) { + onControlDataRecv(pkt); + } + // just zero-hop control packets allowed (for this subset of payloads) + return ACTION_RELEASE; + } + if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { @@ -589,6 +597,22 @@ Packet* Mesh::createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags) { return packet; } +Packet* Mesh::createControlData(const uint8_t* data, size_t len) { + if (len > sizeof(Packet::payload)) return NULL; // invalid arg + + Packet* packet = obtainNewPacket(); + if (packet == NULL) { + MESH_DEBUG_PRINTLN("%s Mesh::createControlData(): error, packet pool empty", getLogDateTime()); + return NULL; + } + packet->header = (PAYLOAD_TYPE_CONTROL << PH_TYPE_SHIFT); // ROUTE_TYPE_* set later + + memcpy(packet->payload, data, len); + packet->payload_len = len; + + return packet; +} + void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); diff --git a/src/Mesh.h b/src/Mesh.h index 70fa80f3..bc1ac54d 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -133,6 +133,11 @@ protected: */ virtual void onPathRecv(Packet* packet, Identity& sender, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { } + /** + * \brief A control packet has been received. + */ + virtual void onControlDataRecv(Packet* packet) { } + /** * \brief A packet with PAYLOAD_TYPE_RAW_CUSTOM has been received. */ @@ -185,6 +190,7 @@ public: Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len); Packet* createRawData(const uint8_t* data, size_t len); Packet* createTrace(uint32_t tag, uint32_t auth_code, uint8_t flags = 0); + Packet* createControlData(const uint8_t* data, size_t len); /** * \brief send a locally-generated Packet with flood routing diff --git a/src/Packet.h b/src/Packet.h index e52ab526..42d73f41 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -27,6 +27,7 @@ namespace mesh { #define PAYLOAD_TYPE_PATH 0x08 // returned path (prefixed with dest/src hashes, MAC) (enc data: path, extra) #define PAYLOAD_TYPE_TRACE 0x09 // trace a path, collecting SNI for each hop #define PAYLOAD_TYPE_MULTIPART 0x0A // packet is one of a set of packets +#define PAYLOAD_TYPE_CONTROL 0x0B // a control/discovery packet //... #define PAYLOAD_TYPE_RAW_CUSTOM 0x0F // custom packet as raw bytes, for applications with custom encryption, payloads, etc diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 88327aa8..caed368b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -69,7 +69,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 - // 162 + file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 + // 166 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -146,7 +147,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 - // 162 + file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 + // 166 file.close(); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 3cfca46c..a665e014 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -46,6 +46,7 @@ struct NodePrefs { // persisted to file uint8_t gps_enabled; uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; + uint32_t discovery_mod_timestamp; }; class CommonCLICallbacks { From 7419ed71f7a57f7bf6f2ec157d5222fa1fa2283c Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 12:27:25 +1100 Subject: [PATCH 227/546] * region filtering now applied in allowPacketForward() --- examples/simple_repeater/MyMesh.cpp | 22 ++++++++++++++-------- examples/simple_repeater/MyMesh.h | 1 + src/Mesh.h | 1 + 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 74fca86c..2fb01029 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -306,6 +306,10 @@ File MyMesh::openAppend(const char *fname) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && recv_pkt_region == NULL) { + MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); + return false; + } return true; } @@ -405,19 +409,19 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { } bool MyMesh::filterRecvFloodPacket(mesh::Packet* pkt) { + // just try to determine region for packet (apply later in allowPacketForward()) if (pkt->getRouteType() == ROUTE_TYPE_TRANSPORT_FLOOD) { - auto region = region_map.findMatch(pkt, REGION_DENY_FLOOD); - if (region == NULL) { - MESH_DEBUG_PRINTLN("onRecvPacket: unknown transport code for FLOOD packet"); - return true; - } + recv_pkt_region = region_map.findMatch(pkt, REGION_DENY_FLOOD); } else if (pkt->getRouteType() == ROUTE_TYPE_FLOOD) { if (region_map.getWildcard().flags & REGION_DENY_FLOOD) { - MESH_DEBUG_PRINTLN("onRecvPacket: wildcard FLOOD packet not allowed"); - return true; + recv_pkt_region = NULL; + } else { + recv_pkt_region = ®ion_map.getWildcard(); } + } else { + recv_pkt_region = NULL; } - // otherwise do normal processing + // do normal processing return false; } @@ -973,6 +977,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply load_stack[0] = &temp_map.getWildcard(); region_load_active = true; } else if (n >= 2 && strcmp(parts[1], "save") == 0) { + _prefs.discovery_mod_timestamp = rtc_clock.getCurrentTime(); // this node is now 'modified' (for discovery info) + savePrefs(); bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); } else if (n >= 2 && strcmp(parts[1], "allowf") == 0) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index fd0b4d6a..45001597 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -91,6 +91,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8]; + RegionEntry* recv_pkt_region; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS diff --git a/src/Mesh.h b/src/Mesh.h index bc1ac54d..00f7ed00 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -44,6 +44,7 @@ protected: DispatcherAction routeRecvPacket(Packet* packet); /** + * \brief Called _before_ the packet is dispatched to the on..Recv() methods. * \returns true, if given packet should be NOT be processed. */ virtual bool filterRecvFloodPacket(Packet* packet) { return false; } From cf547da85735f86df5836e6803708845a7677ac8 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 17:28:45 +1100 Subject: [PATCH 228/546] * RegionMap: get/set Home Region * repeater: admin CLI, changed "allowf *", "denyf *", added "home" --- examples/simple_repeater/MyMesh.cpp | 21 ++++++++++++++----- src/helpers/RegionMap.cpp | 32 +++++++++++++++++++++-------- src/helpers/RegionMap.h | 4 +++- 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 2fb01029..7489d611 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -637,7 +637,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); auto resp = createControlData(data, sizeof(data)); if (resp) { - sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } } } @@ -981,16 +981,16 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply savePrefs(); bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); - } else if (n >= 2 && strcmp(parts[1], "allowf") == 0) { - auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); + } else if (n >= 3 && strcmp(parts[1], "allowf") == 0) { + auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); if (region) { region->flags &= ~REGION_DENY_FLOOD; strcpy(reply, "OK"); } else { strcpy(reply, "Err - unknown region"); } - } else if (n >= 2 && strcmp(parts[1], "denyf") == 0) { - auto region = n >= 3 ? region_map.findByNamePrefix(parts[2]) : ®ion_map.getWildcard(); + } else if (n >= 3 && strcmp(parts[1], "denyf") == 0) { + auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); if (region) { region->flags |= REGION_DENY_FLOOD; strcpy(reply, "OK"); @@ -1004,6 +1004,17 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - unknown region"); } + } else if (n >= 3 && strcmp(parts[1], "home") == 0) { + auto home = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + if (home) { + region_map.setHomeRegion(home); + sprintf(reply, " home is now %s", home->name); + } else { + strcpy(reply, "Err - unknown region"); + } + } else if (n == 2 && strcmp(parts[1], "home") == 0) { + auto home = region_map.getHomeRegion(); + sprintf(reply, " home is %s", home ? home->name : "*"); } else { strcpy(reply, "Err - ??"); } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 074084ec..c6221db0 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -3,10 +3,10 @@ #include <SHA256.h> RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { - next_id = 1; num_regions = 0; + next_id = 1; num_regions = 0; home_id = 0; wildcard.id = wildcard.parent = 0; wildcard.flags = 0; // default behaviour, allow flood and direct - strcpy(wildcard.name, "(*)"); + strcpy(wildcard.name, "*"); } static File openWrite(FILESYSTEM* _fs, const char* filename) { @@ -31,9 +31,10 @@ bool RegionMap::load(FILESYSTEM* _fs) { if (file) { uint8_t pad[128]; - num_regions = 0; next_id = 1; + num_regions = 0; next_id = 1; home_id = 0; - bool success = file.read(pad, 7) == 7; // reserved header + bool success = file.read(pad, 5) == 5; // reserved header + success = success && file.read((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id); success = success && file.read((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); success = success && file.read((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); @@ -68,7 +69,8 @@ bool RegionMap::save(FILESYSTEM* _fs) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); - bool success = file.write(pad, 7) == 7; // reserved header + bool success = file.write(pad, 5) == 5; // reserved header + success = success && file.write((uint8_t *) &home_id, sizeof(home_id)) == sizeof(home_id); success = success && file.write((uint8_t *) &wildcard.flags, sizeof(wildcard.flags)) == sizeof(wildcard.flags); success = success && file.write((uint8_t *) &next_id, sizeof(next_id)) == sizeof(next_id); @@ -140,11 +142,15 @@ RegionEntry* RegionMap::findByName(const char* name) { } RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { + RegionEntry* partial = NULL; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (memcmp(prefix, region->name, strlen(prefix)) == 0) return region; + if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one + if (memcmp(prefix, region->name, strlen(prefix)) == 0) { + partial = region; + } } - return NULL; // not found + return partial; } RegionEntry* RegionMap::findById(uint16_t id) { @@ -157,6 +163,14 @@ RegionEntry* RegionMap::findById(uint16_t id) { return NULL; // not found } +RegionEntry* RegionMap::getHomeRegion() { + return findById(home_id); +} + +void RegionMap::setHomeRegion(const RegionEntry* home) { + home_id = home ? home->id : 0; +} + bool RegionMap::removeRegion(const RegionEntry& region) { if (region.id == 0) return false; // failed (cannot remove the wildcard Region) @@ -190,9 +204,9 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& } if (parent->flags & REGION_DENY_FLOOD) { - out.printf("%s\n", parent->name); + out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : ""); } else { - out.printf("%s F\n", parent->name); + out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : ""); } for (int i = 0; i < num_regions; i++) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 5ad9df29..c3f897c5 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -20,7 +20,7 @@ struct RegionEntry { class RegionMap { TransportKeyStore* _store; - uint16_t next_id; + uint16_t next_id, home_id; uint16_t num_regions; RegionEntry regions[MAX_REGION_ENTRIES]; RegionEntry wildcard; @@ -39,6 +39,8 @@ public: RegionEntry* findByName(const char* name); RegionEntry* findByNamePrefix(const char* prefix); RegionEntry* findById(uint16_t id); + RegionEntry* getHomeRegion(); // NOTE: can be NULL + void setHomeRegion(const RegionEntry* home); bool removeRegion(const RegionEntry& region); bool clear(); void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } From 09eab330a2f3f22008948aa244a81e29462a985d Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 20:15:01 +1100 Subject: [PATCH 229/546] * repeater: onAnonDataRecv(), now rejecting non-ASCII password (preparing for future request codes) * repeater: DISCOVER requests now with a simple RateLimiter (max 4, every 2 minutes) --- examples/simple_repeater/MyMesh.cpp | 15 +++++++++++---- examples/simple_repeater/MyMesh.h | 2 ++ examples/simple_repeater/RateLimiter.h | 23 +++++++++++++++++++++++ 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 examples/simple_repeater/RateLimiter.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 7489d611..9d313d21 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -433,7 +433,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m memcpy(×tamp, data, 4); data[len] = 0; // ensure null terminator - uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + uint8_t reply_len; + if (data[0] == 0 || data[0] >= ' ') { // is password, ie. a login request + reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + //} else if (data[0] == ANON_REQ_TYPE_*) { // future type codes + // TODO + } else { + reply_len = 0; // unknown request type + } if (reply_len == 0) return; // invalid request @@ -616,8 +623,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t void MyMesh::onControlDataRecv(mesh::Packet* packet) { uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits - if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) { - // TODO: apply rate limiting to these! + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) { int i = 1; uint8_t filter = packet->payload[i++]; uint32_t tag; @@ -646,7 +652,8 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store) + _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), + discover_limiter(4, 120) // max 4 every 2 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 45001597..86fac3f4 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -33,6 +33,7 @@ #include <helpers/StatsFormatHelper.h> #include <helpers/TxtDataHelpers.h> #include <helpers/RegionMap.h> +#include "RateLimiter.h" #ifdef WITH_BRIDGE extern AbstractBridge* bridge; @@ -92,6 +93,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; + RateLimiter discover_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS diff --git a/examples/simple_repeater/RateLimiter.h b/examples/simple_repeater/RateLimiter.h new file mode 100644 index 00000000..a6633c0a --- /dev/null +++ b/examples/simple_repeater/RateLimiter.h @@ -0,0 +1,23 @@ +#pragma once + +#include <stdint.h> + +class RateLimiter { + uint32_t _start_timestamp; + uint32_t _secs; + uint16_t _maximum, _count; + +public: + RateLimiter(uint16_t maximum, uint32_t secs): _maximum(maximum), _secs(secs), _start_timestamp(0), _count(0) { } + + bool allow(uint32_t now) { + if (now < _start_timestamp + _secs) { + _count++; + if (_count > _maximum) return false; // deny + } else { // time window now expired + _start_timestamp = now; + _count = 1; + } + return true; + } +}; \ No newline at end of file From 256848208d6303b2f11f16d5026d027a3be7c0e8 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 20:22:40 +1100 Subject: [PATCH 230/546] * repeater: onAnonDataRecv(), future code check bug fix (offset 4) * sensor: onAnonDataRecv(), future request code provision --- examples/simple_repeater/MyMesh.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 9 ++++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 9d313d21..a996cbf6 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -434,9 +434,9 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; - if (data[0] == 0 || data[0] >= ' ') { // is password, ie. a login request + if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); - //} else if (data[0] == ANON_REQ_TYPE_*) { // future type codes + //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { reply_len = 0; // unknown request type diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 6564c4ef..58bce766 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -449,7 +449,14 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con memcpy(×tamp, data, 4); data[len] = 0; // ensure null terminator - uint8_t reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + uint8_t reply_len; + if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request + reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes + // TODO + } else { + reply_len = 0; // unknown request type + } if (reply_len == 0) return; // invalid request From ddac13ae80c74960a0879975741236f9021dc2cd Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 21:40:52 +1100 Subject: [PATCH 231/546] * repeater: CLI, added "region put" and "region remove" commands --- examples/simple_repeater/MyMesh.cpp | 29 ++++++++++++++++++++++++----- src/helpers/RegionMap.cpp | 13 ++++++++++++- src/helpers/RegionMap.h | 2 ++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index a996cbf6..b06ae42a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -890,10 +890,6 @@ void MyMesh::clearStats() { ((SimpleMeshTables *)getTables())->resetStats(); } -static bool is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; -} - void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { if (region_load_active) { if (*command == 0) { // empty line, signal to terminate 'load' operation @@ -907,7 +903,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply int indent = np - command; char *ep = np; - while (is_name_char(*ep)) ep++; + while (RegionMap::is_name_char(*ep)) ep++; if (*ep) { *ep++ = 0; } // set null terminator for end of name while (*ep && *ep != 'F') ep++; // look for (optional) flags @@ -1022,6 +1018,29 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (n == 2 && strcmp(parts[1], "home") == 0) { auto home = region_map.getHomeRegion(); sprintf(reply, " home is %s", home ? home->name : "*"); + } else if (n >= 3 && strcmp(parts[1], "put") == 0) { + auto parent = n >= 4 ? region_map.findByNamePrefix(parts[3]) : ®ion_map.getWildcard(); + if (parent == NULL) { + strcpy(reply, "Err - unknown parent"); + } else { + auto region = region_map.putRegion(parts[2], parent->id); + if (region == NULL) { + strcpy(reply, "Err - unable to put"); + } else { + strcpy(reply, "OK"); + } + } + } else if (n >= 3 && strcmp(parts[1], "remove") == 0) { + auto region = region_map.findByName(parts[2]); + if (region) { + if (region_map.removeRegion(*region)) { + strcpy(reply, "OK"); + } else { + strcpy(reply, "Err - not empty"); + } + } else { + strcpy(reply, "Err - not found"); + } } else { strcpy(reply, "Err - ??"); } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index c6221db0..7d1c08e6 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -9,6 +9,10 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { strcpy(wildcard.name, "*"); } +bool RegionMap::is_name_char(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; +} + static File openWrite(FILESYSTEM* _fs, const char* filename) { #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) _fs->remove(filename); @@ -93,6 +97,12 @@ bool RegionMap::save(FILESYSTEM* _fs) { } RegionEntry* RegionMap::putRegion(const char* name, uint16_t parent_id, uint16_t id) { + const char* sp = name; // check for illegal name chars + while (*sp) { + if (!is_name_char(*sp)) return NULL; // error + sp++; + } + auto region = findByName(name); if (region) { if (region->id == parent_id) return NULL; // ERROR: invalid parent! @@ -187,8 +197,9 @@ bool RegionMap::removeRegion(const RegionEntry& region) { if (i >= num_regions) return false; // failed (not found) num_regions--; // remove from regions array - while (i + 1 < num_regions) { + while (i < num_regions) { regions[i] = regions[i + 1]; + i++; } return true; // success } diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index c3f897c5..50513be1 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -30,6 +30,8 @@ class RegionMap { public: RegionMap(TransportKeyStore& store); + static bool is_name_char(char c); + bool load(FILESYSTEM* _fs); bool save(FILESYSTEM* _fs); From 4a5404d997ce89dae793e47b498f076b9a244d76 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 22:10:20 +1100 Subject: [PATCH 232/546] * companion: added CMD_SEND_CONTROL_DATA, and PUSH_CODE_CONTROL_DATA --- examples/companion_radio/MyMesh.cpp | 30 +++++++++++++++++++++++++++++ examples/companion_radio/MyMesh.h | 1 + 2 files changed, 31 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 904cd871..16be3e9c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -51,6 +51,7 @@ #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 #define CMD_SET_FLOOD_SCOPE 54 +#define CMD_SEND_CONTROL_DATA 55 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -100,6 +101,7 @@ #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C #define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D +#define PUSH_CODE_CONTROL_DATA 0x8E #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -626,6 +628,26 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t i return BaseChatMesh::onContactPathRecv(contact, in_path, in_path_len, out_path, out_path_len, extra_type, extra, extra_len); } +void MyMesh::onControlDataRecv(mesh::Packet *packet) { + if (packet->payload_len + 4 > sizeof(out_frame)) { + MESH_DEBUG_PRINTLN("onControlDataRecv(), payload_len too long: %d", packet->payload_len); + return; + } + int i = 0; + out_frame[i++] = PUSH_CODE_CONTROL_DATA; + out_frame[i++] = (int8_t)(_radio->getLastSNR() * 4); + out_frame[i++] = (int8_t)(_radio->getLastRSSI()); + out_frame[i++] = packet->path_len; + memcpy(&out_frame[i], packet->payload, packet->payload_len); + i += packet->payload_len; + + if (_serial->isConnected()) { + _serial->writeFrame(out_frame, i); + } else { + MESH_DEBUG_PRINTLN("onControlDataRecv(), data received while app offline"); + } +} + void MyMesh::onRawDataRecv(mesh::Packet *packet) { if (packet->payload_len + 4 > sizeof(out_frame)) { MESH_DEBUG_PRINTLN("onRawDataRecv(), payload_len too long: %d", packet->payload_len); @@ -1523,6 +1545,14 @@ void MyMesh::handleCmdFrame(size_t len) { memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null } writeOKFrame(); + } else if (cmd_frame[0] == CMD_SEND_CONTROL_DATA && len >= 2 && (cmd_frame[1] & 0x80) != 0) { + auto resp = createControlData(&cmd_frame[1], len - 1); + if (resp) { + sendZeroHop(resp); + writeOKFrame(); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index fb0fb479..927f22e4 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -133,6 +133,7 @@ protected: uint8_t onContactRequest(const ContactInfo &contact, uint32_t sender_timestamp, const uint8_t *data, uint8_t len, uint8_t *reply) override; void onContactResponse(const ContactInfo &contact, const uint8_t *data, uint8_t len) override; + void onControlDataRecv(mesh::Packet *packet) override; void onRawDataRecv(mesh::Packet *packet) override; void onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) override; From 2e63499ae594e14842a3be35f5678389b748ad0a Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 6 Nov 2025 22:51:17 +1100 Subject: [PATCH 233/546] * companion: protocol ver bumped to 8. --- examples/companion_radio/MyMesh.cpp | 6 +++--- examples/companion_radio/MyMesh.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 16be3e9c..598d535f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -50,8 +50,8 @@ #define CMD_SEND_BINARY_REQ 50 #define CMD_FACTORY_RESET 51 #define CMD_SEND_PATH_DISCOVERY_REQ 52 -#define CMD_SET_FLOOD_SCOPE 54 -#define CMD_SEND_CONTROL_DATA 55 +#define CMD_SET_FLOOD_SCOPE 54 // v8+ +#define CMD_SEND_CONTROL_DATA 55 // v8+ #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -101,7 +101,7 @@ #define PUSH_CODE_TELEMETRY_RESPONSE 0x8B #define PUSH_CODE_BINARY_RESPONSE 0x8C #define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D -#define PUSH_CODE_CONTROL_DATA 0x8E +#define PUSH_CODE_CONTROL_DATA 0x8E // v8+ #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 927f22e4..f2b56e5e 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,7 +5,7 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 7 +#define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "2 Oct 2025" From 06825030e5d48098857a63a26165cb88d0221218 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Thu, 6 Nov 2025 22:36:37 +0100 Subject: [PATCH 234/546] sensor: copy control data code from repeater --- examples/simple_sensor/SensorMesh.cpp | 32 +++++++++++++++++++++++++++ examples/simple_sensor/SensorMesh.h | 1 + 2 files changed, 33 insertions(+) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 58bce766..2ab2081f 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -617,6 +617,38 @@ bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t return false; } +#define CTL_TYPE_NODE_DISCOVER_REQ 0x80 +#define CTL_TYPE_NODE_DISCOVER_RESP 0x90 + +void SensorMesh::onControlDataRecv(mesh::Packet* packet) { + uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6) { + // TODO: apply rate limiting to these! + int i = 1; + uint8_t filter = packet->payload[i++]; + uint32_t tag; + memcpy(&tag, &packet->payload[i], 4); i += 4; + uint32_t since; + if (packet->payload_len >= i+4) { // optional since field + memcpy(&since, &packet->payload[i], 4); i += 4; + } else { + since = 0; + } + + if ((filter & (1 << ADV_TYPE_SENSOR)) != 0 && _prefs.discovery_mod_timestamp >= since) { + uint8_t data[6 + PUB_KEY_SIZE]; + data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_SENSOR; // low 4-bits for node type + data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) + memcpy(&data[2], &tag, 4); // include tag from request, for client to match to + memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); + auto resp = createControlData(data, sizeof(data)); + if (resp) { + sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + } + } + } +} + bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { int i = matching_peer_indexes[sender_idx]; if (i < 0 || i >= acl.getNumClients()) { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ba55bc70..0fb00dc1 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -125,6 +125,7 @@ protected: void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override; void onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_idx, const uint8_t* secret, uint8_t* data, size_t len) override; bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; + void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); From 963290ea159c00edf4283bafd71464b27c8d95dc Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 7 Nov 2025 14:42:06 +1100 Subject: [PATCH 235/546] * repeater: various "region" CLI changes * transport codes 0000 and FFFF reserved --- examples/simple_repeater/MyMesh.cpp | 17 +++++++++++------ src/helpers/RegionMap.cpp | 4 ++++ src/helpers/TransportKeyStore.cpp | 5 +++++ src/helpers/TxtDataHelpers.cpp | 7 +++++++ src/helpers/TxtDataHelpers.h | 1 + 5 files changed, 28 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b06ae42a..cae05b20 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -892,7 +892,7 @@ void MyMesh::clearStats() { void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply) { if (region_load_active) { - if (*command == 0) { // empty line, signal to terminate 'load' operation + if (StrHelper::isBlank(command)) { // empty/blank line, signal to terminate 'load' operation region_map = temp_map; // copy over the temp instance as new current map region_load_active = false; @@ -908,7 +908,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply while (*ep && *ep != 'F') ep++; // look for (optional) flags - if (indent > 0 && indent < 8) { + if (indent > 0 && indent < 8 && strlen(np) > 0) { auto parent = load_stack[indent - 1]; if (parent) { auto old = region_map.findByName(np); @@ -985,7 +985,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply bool success = region_map.save(_fs); strcpy(reply, success ? "OK" : "Err - save failed"); } else if (n >= 3 && strcmp(parts[1], "allowf") == 0) { - auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + auto region = region_map.findByNamePrefix(parts[2]); if (region) { region->flags &= ~REGION_DENY_FLOOD; strcpy(reply, "OK"); @@ -993,7 +993,7 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply strcpy(reply, "Err - unknown region"); } } else if (n >= 3 && strcmp(parts[1], "denyf") == 0) { - auto region = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + auto region = region_map.findByNamePrefix(parts[2]); if (region) { region->flags |= REGION_DENY_FLOOD; strcpy(reply, "OK"); @@ -1003,12 +1003,17 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else if (n >= 3 && strcmp(parts[1], "get") == 0) { auto region = region_map.findByNamePrefix(parts[2]); if (region) { - sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); + auto parent = region_map.findById(region->parent); + if (parent && parent->id != 0) { + sprintf(reply, " %s (%s) %s", region->name, parent->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); + } else { + sprintf(reply, " %s %s", region->name, (region->flags & REGION_DENY_FLOOD) ? "" : "F"); + } } else { strcpy(reply, "Err - unknown region"); } } else if (n >= 3 && strcmp(parts[1], "home") == 0) { - auto home = strcmp(parts[2], "*") == 0 ? ®ion_map.getWildcard() : region_map.findByNamePrefix(parts[2]); + auto home = region_map.findByNamePrefix(parts[2]); if (home) { region_map.setHomeRegion(home); sprintf(reply, " home is now %s", home->name); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 7d1c08e6..36844615 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -144,6 +144,8 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { } RegionEntry* RegionMap::findByName(const char* name) { + if (strcmp(name, "*") == 0) return &wildcard; + for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if (strcmp(name, region->name) == 0) return region; @@ -152,6 +154,8 @@ RegionEntry* RegionMap::findByName(const char* name) { } RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { + if (strcmp(prefix, "*") == 0) return &wildcard; + RegionEntry* partial = NULL; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; diff --git a/src/helpers/TransportKeyStore.cpp b/src/helpers/TransportKeyStore.cpp index 8b7c891b..f34610b6 100644 --- a/src/helpers/TransportKeyStore.cpp +++ b/src/helpers/TransportKeyStore.cpp @@ -9,6 +9,11 @@ uint16_t TransportKey::calcTransportCode(const mesh::Packet* packet) const { sha.update(&type, 1); sha.update(packet->payload, packet->payload_len); sha.finalizeHMAC(key, sizeof(key), &code, 2); + if (code == 0) { // reserve codes 0000 and FFFF + code++; + } else if (code == 0xFFFF) { + code--; + } return code; } diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 0044fd28..224eb873 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -19,6 +19,13 @@ void StrHelper::strzcpy(char* dest, const char* src, size_t buf_sz) { } } +bool StrHelper::isBlank(const char* str) { + while (*str) { + if (*str++ != ' ') return false; + } + return true; +} + #include <Arduino.h> union int32_Float_t diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 3154766c..89789990 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -12,4 +12,5 @@ public: static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); + static bool isBlank(const char* str); }; From 62d7ce110b912b55a5e1fd5107ecfabaac7cb694 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 7 Nov 2025 15:49:01 +1100 Subject: [PATCH 236/546] * packet format docs updated --- docs/packet_structure.md | 4 +++ docs/payloads.md | 56 +++++++++++++++++++++++++++++++--------- 2 files changed, 48 insertions(+), 12 deletions(-) diff --git a/docs/packet_structure.md b/docs/packet_structure.md index aa260855..92c410be 100644 --- a/docs/packet_structure.md +++ b/docs/packet_structure.md @@ -44,6 +44,10 @@ bit 0 means the lowest bit (1s place) | `0x08` | `PAYLOAD_TYPE_PATH` | Returned path. | | `0x09` | `PAYLOAD_TYPE_TRACE` | trace a path, collecting SNI for each hop. | | `0x0A` | `PAYLOAD_TYPE_MULTIPART` | packet is part of a sequence of packets. | +| `0x0B` | `PAYLOAD_TYPE_CONTROL` | control packet data (unencrypted) | +| `0x0C` | . | reserved | +| `0x0D` | . | reserved | +| `0x0E` | . | reserved | | `0x0F` | `PAYLOAD_TYPE_RAW_CUSTOM` | Custom packet (raw bytes, custom encryption). | ## Payload Version Values diff --git a/docs/payloads.md b/docs/payloads.md index 4d00f930..f66088f7 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -11,6 +11,7 @@ Inside of each [meshcore packet](./packet_structure.md) is a payload, identified * Group text message (unverified). * Group datagram (unverified). * Multi-part packet +* Control data packet * Custom packet (raw bytes, custom encryption). This document defines the structure of each of these payload types. @@ -57,7 +58,7 @@ Appdata Flags # Acknowledgement -An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement will be sent in the "extra" payload (see [Returned Path](#returned-path)) and not as a discrete ackowledgement. CLI commands do not require an acknowledgement, neither discrete nor extra. +An acknowledgement that a message was received. Note that for returned path messages, an acknowledgement can be sent in the "extra" payload (see [Returned Path](#returned-path)) instead of as a separate ackowledgement packet. CLI commands do not cause acknowledgement responses, neither discrete nor extra. | Field | Size (bytes) | Description | |----------|--------------|------------------------------------------------------------| @@ -140,13 +141,13 @@ Request data about sensors on the node, including battery level. ## Plain text message -| Field | Size (bytes) | Description | -|-----------------|-----------------|--------------------------------------------------------------| -| timestamp | 4 | send time (unix timestamp) | -| flags + attempt | 1 | upper six bits are flags (see below), lower two bits are attempt number (0..3) | -| message | rest of payload | the message content, see next table | +| Field | Size (bytes) | Description | +|--------------------|-----------------|--------------------------------------------------------------| +| timestamp | 4 | send time (unix timestamp) | +| txt_type + attempt | 1 | upper six bits are txt_type (see below), lower two bits are attempt number (0..3) | +| message | rest of payload | the message content, see next table | -Flags +txt_type | Value | Description | Message content | |--------|---------------------------|------------------------------------------------------------| @@ -163,13 +164,20 @@ Flags | cipher MAC | 2 | MAC for encrypted data in next field | | ciphertext | rest of payload | encrypted message, see below for details | -Plaintext message +## Room server login | Field | Size (bytes) | Description | |----------------|-----------------|-------------------------------------------------------------------------------| -| timestamp | 4 | send time (unix timestamp) | -| sync timestamp | 4 | NOTE: room server only! - sender's "sync messages SINCE x" timestamp | -| password | rest of message | password for repeater/room | +| timestamp | 4 | sender time (unix timestamp) | +| sync timestamp | 4 | sender's "sync messages SINCE x" timestamp | +| password | rest of message | password for room | + +## Repeater/Sensor login + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| password | rest of message | password for repeater/sensor | # Group text message / datagram @@ -182,7 +190,31 @@ Plaintext message The plaintext contained in the ciphertext matches the format described in [plain text message](#plain-text-message). Specifically, it consists of a four byte timestamp, a flags byte, and the message. The flags byte will generally be `0x00` because it is a "plain text message". The message will be of the form `<sender name>: <message body>` (eg., `user123: I'm on my way`). -TODO: describe what datagram looks like +# Control data + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | upper 4 bits is sub_type | +| data | rest of payload | typically unencrypted data | + +## DISCOVER_REQ (sub_type) + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | 0x8 (upper 4 bits) | +| type_filter | 1 | bit for each ADV_TYPE_* | +| tag | 4 | randomly generate by sender | +| since | 4 | (optional) epoch timestamp (0 by default) | + +## DISCOVER_RESP (sub_type) + +| Field | Size (bytes) | Description | +|--------------|-----------------|--------------------------------------------| +| flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) | +| snr | 1 | signed, SNR*4 | +| tag | 4 | reflected back from DISCOVER_REQ | +| pubkey | 32 | node's ID | + # Custom packet From 1520f4d28e3b0e512155567bdcfd12ac54922c94 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 7 Nov 2025 19:31:09 +1100 Subject: [PATCH 237/546] * repeater, DISCOVER_REQ, flags lowest bit now for 'prefix_only' responses --- docs/payloads.md | 14 +++++++------- examples/simple_repeater/MyMesh.cpp | 3 ++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/payloads.md b/docs/payloads.md index f66088f7..5a41e69c 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -199,12 +199,12 @@ The plaintext contained in the ciphertext matches the format described in [plain ## DISCOVER_REQ (sub_type) -| Field | Size (bytes) | Description | -|--------------|-----------------|--------------------------------------------| -| flags | 1 | 0x8 (upper 4 bits) | -| type_filter | 1 | bit for each ADV_TYPE_* | -| tag | 4 | randomly generate by sender | -| since | 4 | (optional) epoch timestamp (0 by default) | +| Field | Size (bytes) | Description | +|--------------|-----------------|----------------------------------------------| +| flags | 1 | 0x8 (upper 4 bits), prefix_only (lowest bit) | +| type_filter | 1 | bit for each ADV_TYPE_* | +| tag | 4 | randomly generate by sender | +| since | 4 | (optional) epoch timestamp (0 by default) | ## DISCOVER_RESP (sub_type) @@ -213,7 +213,7 @@ The plaintext contained in the ciphertext matches the format described in [plain | flags | 1 | 0x9 (upper 4 bits), node_type (lower 4) | | snr | 1 | signed, SNR*4 | | tag | 4 | reflected back from DISCOVER_REQ | -| pubkey | 32 | node's ID | +| pubkey | 8 or 32 | node's ID (or prefix) | # Custom packet diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index cae05b20..622b73a6 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -636,12 +636,13 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { } if ((filter & (1 << ADV_TYPE_REPEATER)) != 0 && _prefs.discovery_mod_timestamp >= since) { + bool prefix_only = packet->payload[0] & 1; uint8_t data[6 + PUB_KEY_SIZE]; data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_REPEATER; // low 4-bits for node type data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) memcpy(&data[2], &tag, 4); // include tag from request, for client to match to memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); - auto resp = createControlData(data, sizeof(data)); + auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE); if (resp) { sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } From c0a51aff66ee53949e6a499d270cd00e6809dab7 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:47:18 +0100 Subject: [PATCH 238/546] change ADC_MULTIPLIER to better reflect battery voltage --- variants/thinknode_m2/variant.h | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/variants/thinknode_m2/variant.h b/variants/thinknode_m2/variant.h index 928f56ec..223bfbb5 100644 --- a/variants/thinknode_m2/variant.h +++ b/variants/thinknode_m2/variant.h @@ -1,8 +1,8 @@ #define I2C_SCL 15 #define I2C_SDA 16 -#define PIN_VBAT_READ 17 +#define PIN_VBAT_READ 17 #define AREF_VOLTAGE (3.0) -#define ADC_MULTIPLIER (1.548F) +#define ADC_MULTIPLIER (1.509F) #define PIN_BUZZER 5 #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 @@ -10,6 +10,3 @@ #define PIN_LED 6 #define PIN_STATUS_LED 6 #define PIN_PWRBTN 4 - - - From 429f82106bbb16a353d0aa5e86be8fbf18aa2972 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:48:57 +0100 Subject: [PATCH 239/546] tweak getBattMilliVolts to report battery more accurately --- variants/thinknode_m2/ThinknodeM2Board.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 3a2f067e..332f1f67 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -19,14 +19,21 @@ void ThinknodeM2Board::begin() { enterDeepSleep(0); } - uint16_t ThinknodeM2Board::getBattMilliVolts() { + uint16_t ThinknodeM2Board::getBattMilliVolts() { analogReadResolution(12); - delay(10); - float volts = (analogRead(PIN_VBAT_READ) * ADC_MULTIPLIER * AREF_VOLTAGE) / 4096; - analogReadResolution(10); - return volts * 1000; - } + analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); + + uint32_t mv = 0; + for (int i = 0; i < 8; ++i) { + mv += analogReadMilliVolts(PIN_VBAT_READ); + delayMicroseconds(200); + } + mv /= 8; + + analogReadResolution(10); + return static_cast<uint16_t>(mv * ADC_MULTIPLIER ); +} const char* ThinknodeM2Board::getManufacturerName() const { return "Elecrow ThinkNode M2"; - } \ No newline at end of file + } From a0bf66f9d83dbe75353d665de827f503d533c600 Mon Sep 17 00:00:00 2001 From: Tomas P <128642216+tpp-at-idx@users.noreply.github.com> Date: Fri, 7 Nov 2025 09:50:21 +0100 Subject: [PATCH 240/546] Fix for display not coming on after poweron --- variants/thinknode_m2/ThinknodeM2Board.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/variants/thinknode_m2/ThinknodeM2Board.cpp b/variants/thinknode_m2/ThinknodeM2Board.cpp index 332f1f67..05965103 100644 --- a/variants/thinknode_m2/ThinknodeM2Board.cpp +++ b/variants/thinknode_m2/ThinknodeM2Board.cpp @@ -3,12 +3,13 @@ void ThinknodeM2Board::begin() { + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle + delay(20); // allow power rail to discharge + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on + delay(120); // give display time to bias on cold boot ESP32Board::begin(); - pinMode(PIN_VEXT_EN, OUTPUT); // init display - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // pin needs to be high - delay(10); - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // need to do this twice. do not know why.. - pinMode(PIN_STATUS_LED, OUTPUT); // init power led + pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { From 00e0635ab5400c9fc78bf05c729ddb1f234f3a9a Mon Sep 17 00:00:00 2001 From: brad <bradley.ferguson@gmail.com> Date: Sat, 8 Nov 2025 20:05:46 -0600 Subject: [PATCH 241/546] add variant files for ikoka handheld (nrf52 with e22 radio) --- .../ikoka_handheld_nrf/IkokaNrf52Board.cpp | 100 ++++++++++++ variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 52 ++++++ variants/ikoka_handheld_nrf/platformio.ini | 103 ++++++++++++ variants/ikoka_handheld_nrf/target.cpp | 46 ++++++ variants/ikoka_handheld_nrf/target.h | 29 ++++ variants/ikoka_handheld_nrf/variant.cpp | 84 ++++++++++ variants/ikoka_handheld_nrf/variant.h | 148 ++++++++++++++++++ 7 files changed, 562 insertions(+) create mode 100644 variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp create mode 100644 variants/ikoka_handheld_nrf/IkokaNrf52Board.h create mode 100644 variants/ikoka_handheld_nrf/platformio.ini create mode 100644 variants/ikoka_handheld_nrf/target.cpp create mode 100644 variants/ikoka_handheld_nrf/target.h create mode 100644 variants/ikoka_handheld_nrf/variant.cpp create mode 100644 variants/ikoka_handheld_nrf/variant.h diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp new file mode 100644 index 00000000..44940b8f --- /dev/null +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -0,0 +1,100 @@ +#ifdef IKOKA_NRF52 + +#include <Arduino.h> +#include <Wire.h> +#include <bluefruit.h> + +#include "IkokaNrf52Board.h" + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void IkokaNrf52Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + // ensure we have pull ups on the screen i2c, this isn't always available + // in hardware and it should only be 20k ohms. Disable the pullups if we + // are using the rotated lcd breakout board + #if defined(DISPLAY_CLASS) && DISPLAY_ROTATION == 0 + pinMode(PIN_WIRE_SDA, INPUT_PULLUP); + pinMode(PIN_WIRE_SCL, INPUT_PULLUP); + #endif + + pinMode(PIN_VBAT, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, HIGH); + + // required button pullup is handled as part of button initilization + // in target.cpp + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, HIGH); +#endif + + delay(10); // give sx1262 some time to power up +} + +bool IkokaNrf52Board::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("XIAO_NRF52_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + + return true; +} + +#endif diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h new file mode 100644 index 00000000..9dfc3833 --- /dev/null +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -0,0 +1,52 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +#ifdef IKOKA_NRF52 + +class IkokaNrf52Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + // Please read befor going further ;) + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + + // We can't drive VBAT_ENABLE to HIGH as long + // as we don't know wether we are charging or not ... + // this is a 3mA loss (4/1500) + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char* getManufacturerName() const override { + return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; + +#endif diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini new file mode 100644 index 00000000..e4643c38 --- /dev/null +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -0,0 +1,103 @@ +[ikoka_nrf52] +extends = Xiao_nrf52 +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + densaugeo/base64 @ ~1.4.0 +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I lib/nrf52/s140_nrf52_7.3.0_API/include + -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 + -I variants/ikoka_handheld_nrf + -UENV_INCLUDE_GPS + -D IKOKA_NRF52 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_TX_LED=11 + -D P_LORA_DIO_1=D1 + -D P_LORA_RESET=D2 + -D P_LORA_BUSY=D3 + -D P_LORA_NSS=D4 + -D SX126X_RXEN=D5 + -D SX126X_TXEN=RADIOLIB_NC + -D SX126X_DIO2_AS_RF_SWITCH=1 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/ikoka_handheld_nrf> + +<helpers/sensors> + +# larger screen has a different driver, this is for the 0.96 inch +[ikoka_nrf52_ssd1306_companion] +lib_deps = ${ikoka_nrf52.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 +build_flags = ${ikoka_nrf52.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D DISPLAY_ROTATION=0 + -D PIN_WIRE_SCL=D6 + -D PIN_WIRE_SDA=D7 + -D PIN_USER_BTN=D0 + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D QSPIFLASH=1 + -I examples/companion_radio/ui-new +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/companion_radio/ui-new/UITask.cpp> + +<../examples/companion_radio/*.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D BLE_PIN_CODE=123456 + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D BLE_PIN_CODE=123456 + -D LORA_TX_POWER=20 + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] +extends = ikoka_nrf52 +build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} + -D LORA_TX_POWER=20 + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + +[env:ikoka_handheld_nrf_e22_30dbm_repeater] +extends = ikoka_nrf52 +build_flags = + ${ikoka_nrf52.build_flags} + -D ADVERT_NAME='"ikoka_handheld Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<../examples/simple_repeater/*.cpp> + +[env:ikoka_handheld_nrf_e22_30dbm_room_server] +extends = ikoka_nrf52 +build_flags = + ${ikoka_nrf52.build_flags} + -D ADVERT_NAME='"ikoka_handheld Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_nrf52.build_src_filter} + +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp new file mode 100644 index 00000000..efa6669f --- /dev/null +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -0,0 +1,46 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> + +IkokaNrf52Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/ikoka_handheld_nrf/target.h b/variants/ikoka_handheld_nrf/target.h new file mode 100644 index 00000000..a28ca81a --- /dev/null +++ b/variants/ikoka_handheld_nrf/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <IkokaNrf52Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/EnvironmentSensorManager.h> + +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + + +extern IkokaNrf52Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_handheld_nrf/variant.cpp b/variants/ikoka_handheld_nrf/variant.cpp new file mode 100644 index 00000000..38a94c89 --- /dev/null +++ b/variants/ikoka_handheld_nrf/variant.cpp @@ -0,0 +1,84 @@ +#include "variant.h" + +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // D0 .. D10 + 2, // D0 is P0.02 (A0) + 3, // D1 is P0.03 (A1) + 28, // D2 is P0.28 (A2) + 29, // D3 is P0.29 (A3) + 4, // D4 is P0.04 (A4,SDA) + 5, // D5 is P0.05 (A5,SCL) + 43, // D6 is P1.11 (TX) + 44, // D7 is P1.12 (RX) + 45, // D8 is P1.13 (SCK) + 46, // D9 is P1.14 (MISO) + 47, // D10 is P1.15 (MOSI) + + // LEDs + 26, // D11 is P0.26 (LED RED) + 6, // D12 is P0.06 (LED BLUE) + 30, // D13 is P0.30 (LED GREEN) + 14, // D14 is P0.14 (READ_BAT) + + // LSM6DS3TR + 40, // D15 is P1.08 (6D_PWR) + 27, // D16 is P0.27 (6D_I2C_SCL) + 7, // D17 is P0.07 (6D_I2C_SDA) + 11, // D18 is P0.11 (6D_INT1) + + // MIC + 42, // D19 is P1.10 (MIC_PWR) + 32, // D20 is P1.00 (PDM_CLK) + 16, // D21 is P0.16 (PDM_DATA) + + // BQ25100 + 13, // D22 is P0.13 (HICHG) + 17, // D23 is P0.17 (~CHG) + + // + 21, // D24 is P0.21 (QSPI_SCK) + 25, // D25 is P0.25 (QSPI_CSN) + 20, // D26 is P0.20 (QSPI_SIO_0 DI) + 24, // D27 is P0.24 (QSPI_SIO_1 DO) + 22, // D28 is P0.22 (QSPI_SIO_2 WP) + 23, // D29 is P0.23 (QSPI_SIO_3 HOLD) + + // NFC + 9, // D30 is P0.09 (NFC1) + 10, // D31 is P0.10 (NFC2) + + // VBAT + 31, // D32 is P0.31 (VBAT) +}; + +void initVariant() { + // Disable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + pinMode(VBAT_ENABLE, OUTPUT); + // digitalWrite(VBAT_ENABLE, HIGH); + // This was taken from Seeed github butis not coherent with the doc, + // VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V + // This induces a 3mA current in the resistors :( but it's better than burning the nrf + digitalWrite(VBAT_ENABLE, LOW); + + // disable xiao charging current, the handheld uses a tp4056 to charge + // instead of the onboard xiao charging circuit. This charges at a max of + // 780ma instead of 100ma. In theory you could enable both, but in practice + // fire is scary. + pinMode(PIN_CHARGING_CURRENT, OUTPUT); + digitalWrite(PIN_CHARGING_CURRENT, HIGH); + + pinMode(PIN_QSPI_CS, OUTPUT); + digitalWrite(PIN_QSPI_CS, HIGH); + + pinMode(LED_RED, OUTPUT); + digitalWrite(LED_RED, HIGH); + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); +} diff --git a/variants/ikoka_handheld_nrf/variant.h b/variants/ikoka_handheld_nrf/variant.h new file mode 100644 index 00000000..8e6a8ed1 --- /dev/null +++ b/variants/ikoka_handheld_nrf/variant.h @@ -0,0 +1,148 @@ +#ifndef _SEEED_XIAO_NRF52840_H_ +#define _SEEED_XIAO_NRF52840_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +//#define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" +{ +#endif // __cplusplus + +#define PINS_COUNT (33) +#define NUM_DIGITAL_PINS (33) +#define NUM_ANALOG_INPUTS (8) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (LED_RED) +#define LED_PWR (PINS_COUNT) +#define PIN_NEOPIXEL (PINS_COUNT) +#define NEOPIXEL_NUM (0) + +#define LED_BUILTIN (PIN_LED) + +#define LED_RED (11) +#define LED_GREEN (13) +#define LED_BLUE (12) + +#define LED_STATE_ON (1) // State when LED is litted + +// Buttons +#define PIN_BUTTON1 (PINS_COUNT) + +// Digital PINs +static const uint8_t D0 = 0 ; +static const uint8_t D1 = 1 ; +static const uint8_t D2 = 2 ; +static const uint8_t D3 = 3 ; +static const uint8_t D4 = 4 ; +static const uint8_t D5 = 5 ; +static const uint8_t D6 = 6 ; +static const uint8_t D7 = 7 ; +static const uint8_t D8 = 8 ; +static const uint8_t D9 = 9 ; +static const uint8_t D10 = 10; + +#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define PIN_CHARGING_CURRENT (22) // Battery Charging current + // https://wiki.seedstudio.com/XIAO_BLE#battery-charging-current +// Analog pins +#define PIN_A0 (0) +#define PIN_A1 (1) +#define PIN_A2 (2) +#define PIN_A3 (3) +#define PIN_A4 (4) +#define PIN_A5 (5) +#define PIN_VBAT (32) // Read the BAT voltage. + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + +#define BAT_NOT_CHARGING (23) // LOW when charging + +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; + +#define ADC_RESOLUTION (12) + +// Other pins +#define PIN_NFC1 (30) +#define PIN_NFC2 (31) + +// Serial interfaces +#define PIN_SERIAL1_RX (7) +#define PIN_SERIAL1_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO (9) +#define PIN_SPI_MOSI (10) +#define PIN_SPI_SCK (8) + +#define PIN_SPI1_MISO (25) +#define PIN_SPI1_MOSI (26) +#define PIN_SPI1_SCK (29) + +// Lora SPI is on SPI0 +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (6) // 4 and 5 are used for the sx1262 ! +#define PIN_WIRE_SCL (7) // use WIRE1_SDA + +static const uint8_t SDA = (6); +static const uint8_t SCL = (7); + +//#define PIN_WIRE1_SDA (17) +//#define PIN_WIRE1_SCL (16) +#define PIN_LSM6DS3TR_C_POWER (15) +#define PIN_LSM6DS3TR_C_INT1 (18) + +// PDM Interfaces +#define PIN_PDM_PWR (19) +#define PIN_PDM_CLK (20) +#define PIN_PDM_DIN (21) + +// QSPI Pins +#define PIN_QSPI_SCK (24) +#define PIN_QSPI_CS (25) +#define PIN_QSPI_IO0 (26) +#define PIN_QSPI_IO1 (27) +#define PIN_QSPI_IO2 (28) +#define PIN_QSPI_IO3 (29) + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES (P25Q16H) +#define EXTERNAL_FLASH_USE_QSPI + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From b31d3e7b5f90c7b6f562079df02e4a88cf93c97c Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 9 Nov 2025 16:17:49 +1100 Subject: [PATCH 242/546] * added StrHelper::fromHex() --- src/helpers/TxtDataHelpers.cpp | 20 ++++++++++++++++++++ src/helpers/TxtDataHelpers.h | 1 + 2 files changed, 21 insertions(+) diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 224eb873..09e86c67 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -139,3 +139,23 @@ const char* StrHelper::ftoa(float f) { } return tmp; } + +uint32_t StrHelper::fromHex(const char* src) { + uint32_t n = 0; + while (*src) { + if (*src >= '0' && *src <= '9') { + n <<= 4; + n |= (*src - '0'); + } else if (*src >= 'A' && *src <= 'F') { + n <<= 4; + n |= (*src - 'A' + 10); + } else if (*src >= 'a' && *src <= 'f') { + n <<= 4; + n |= (*src - 'a' + 10); + } else { + break; // non-hex char encountered, stop parsing + } + src++; + } + return n; +} diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 89789990..387e09b9 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -13,4 +13,5 @@ public: static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); static bool isBlank(const char* str); + static uint32_t fromHex(const char* src); }; From ab0721d6dfaa66aa1271196ee7b7abe64c880686 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 9 Nov 2025 16:36:23 +1100 Subject: [PATCH 243/546] * fix: repeater and room server telemetry requests now return all telemetry for _READ & _WRITE ACL permissions. --- examples/simple_repeater/MyMesh.cpp | 5 ++++- examples/simple_room_server/MyMesh.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 622b73a6..4136818c 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -170,7 +170,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry); + if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { + perm_mask = 0x00; // just base telemetry allowed + } + sensors.querySensors(perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 4d953c9c..de06b4c6 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -165,7 +165,10 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); // query other sensors -- target specific - sensors.querySensors((sender->isAdmin() ? 0xFF : 0x00) & perm_mask, telemetry); + if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { + perm_mask = 0x00; // just base telemetry allowed + } + sensors.querySensors(perm_mask, telemetry); uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); From df4dab8509081a08010229af207ac2eae288bab8 Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Fri, 7 Nov 2025 22:16:48 -0800 Subject: [PATCH 244/546] Add statistics commands and response handling in MyMesh - Introduced new commands for retrieving statistics: CMD_GET_STATS_CORE, CMD_GET_STATS_RADIO, and CMD_GET_STATS_PACKETS. - Implemented corresponding response handling methods to format and send statistics data. - Updated MyMesh constructor to initialize new member variables for managing statistics. - Included StatsFormatHelper for formatting statistics replies. --- examples/companion_radio/MyMesh.cpp | 62 ++++++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 8 ++++ 2 files changed, 69 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 598d535f..fa9edc8c 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -52,6 +52,9 @@ #define CMD_SEND_PATH_DISCOVERY_REQ 52 #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ +#define CMD_GET_STATS_CORE 56 +#define CMD_GET_STATS_RADIO 57 +#define CMD_GET_STATS_PACKETS 58 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -77,6 +80,9 @@ #define RESP_CODE_CUSTOM_VARS 21 #define RESP_CODE_ADVERT_PATH 22 #define RESP_CODE_TUNING_PARAMS 23 +#define RESP_CODE_STATS_CORE 24 +#define RESP_CODE_STATS_RADIO 25 +#define RESP_CODE_STATS_PACKETS 26 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -704,7 +710,7 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) - : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), + : BaseChatMesh(radio, *(_ms_clock = new ArduinoMillis()), rng, rtc, *(_pkt_mgr = new StaticPoolPacketManager(16)), tables), _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; @@ -1529,6 +1535,45 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); } + } else if (cmd_frame[0] == CMD_GET_STATS_CORE) { + char json_reply[160]; + formatStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_CORE; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } + } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { + char json_reply[160]; + formatRadioStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_RADIO; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } + } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { + char json_reply[160]; + formatPacketStatsReply(json_reply); + int i = 0; + out_frame[i++] = RESP_CODE_STATS_PACKETS; + int json_len = strlen(json_reply); + if (i + json_len <= MAX_FRAME_SIZE) { + memcpy(&out_frame[i], json_reply, json_len); + i += json_len; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { @@ -1565,6 +1610,21 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } +void MyMesh::formatStatsReply(char *reply) { + // Use StatsFormatHelper + // Note: err_flags is private in Dispatcher, so we use 0 + StatsFormatHelper::formatCoreStats(reply, board, *_ms_clock, 0, _pkt_mgr); +} + +void MyMesh::formatRadioStatsReply(char *reply) { + StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); +} + +void MyMesh::formatPacketStatsReply(char *reply) { + StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), + getNumRecvFlood(), getNumRecvDirect()); +} + void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f2b56e5e..ef451d5d 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -69,6 +69,7 @@ #include <helpers/BaseChatMesh.h> #include <helpers/TransportKeyStore.h> +#include <helpers/StatsFormatHelper.h> /* -------------------------------------------------------------------------------------- */ @@ -170,6 +171,11 @@ private: void checkCLIRescueCmd(); void checkSerialInterface(); + // Stats methods + void formatStatsReply(char *reply); + void formatRadioStatsReply(char *reply); + void formatPacketStatsReply(char *reply); + // helpers, short-cuts void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } @@ -178,6 +184,8 @@ private: private: DataStore* _store; NodePrefs _prefs; + mesh::PacketManager* _pkt_mgr; // stored for stats access + mesh::MillisecondClock* _ms_clock; // stored for stats access uint32_t pending_login; uint32_t pending_status; uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ From c9aa536ca632db7f28029ba1a4a66bc4bdccda92 Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Sat, 8 Nov 2025 20:44:42 -0800 Subject: [PATCH 245/546] Reverted MyMesh constructor for simplicity. Updated formatStatsReply method to use new member variables for statistics handling. Removed excess variable creation --- examples/companion_radio/MyMesh.cpp | 5 ++--- examples/companion_radio/MyMesh.h | 2 -- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index fa9edc8c..6fe165c5 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -710,7 +710,7 @@ uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t void MyMesh::onSendTimeout() {} MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui) - : BaseChatMesh(radio, *(_ms_clock = new ArduinoMillis()), rng, rtc, *(_pkt_mgr = new StaticPoolPacketManager(16)), tables), + : BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables), _serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) { _iter_started = false; _cli_rescue = false; @@ -1612,8 +1612,7 @@ void MyMesh::enterCLIRescue() { void MyMesh::formatStatsReply(char *reply) { // Use StatsFormatHelper - // Note: err_flags is private in Dispatcher, so we use 0 - StatsFormatHelper::formatCoreStats(reply, board, *_ms_clock, 0, _pkt_mgr); + StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); } void MyMesh::formatRadioStatsReply(char *reply) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index ef451d5d..61a25e14 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -184,8 +184,6 @@ private: private: DataStore* _store; NodePrefs _prefs; - mesh::PacketManager* _pkt_mgr; // stored for stats access - mesh::MillisecondClock* _ms_clock; // stored for stats access uint32_t pending_login; uint32_t pending_status; uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ From 80d6dd4367c2882a5b576ed1394bb4c2151b290e Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Sun, 9 Nov 2025 11:28:11 -0800 Subject: [PATCH 246/546] Update statistics handling to use binary frames instead of JSON formatting for consistency with other companion commands. Added documentation of frame structure with code examples. --- docs/stats_binary_frames.md | 225 ++++++++++++++++++++++++++++ examples/companion_radio/MyMesh.cpp | 62 ++++---- 2 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 docs/stats_binary_frames.md diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md new file mode 100644 index 00000000..d4327ea6 --- /dev/null +++ b/docs/stats_binary_frames.md @@ -0,0 +1,225 @@ +# Stats Binary Frame Structures + +Binary frame structures for companion radio stats commands. All multi-byte integers use little-endian byte order. + +## Command Codes + +| Command | Code | Description | +|---------|------|-------------| +| `CMD_GET_STATS_CORE` | 56 | Get core device statistics | +| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics | +| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics | + +## Response Codes + +| Response | Code | Description | +|----------|------|-------------| +| `RESP_CODE_STATS_CORE` | 24 | Core stats response | +| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response | +| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response | + +--- + +## RESP_CODE_STATS_CORE (24) + +**Total Frame Size:** 10 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | +| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | +| 7 | 2 | uint16_t | errors | Error flags bitmask | - | +| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | + +### Example Structure (C/C++) + +```c +struct StatsCore { + uint8_t response_code; // 0x18 + uint16_t battery_mv; + uint32_t uptime_secs; + uint16_t errors; + uint8_t queue_len; +} __attribute__((packed)); +``` + +--- + +## RESP_CODE_STATS_RADIO (25) + +**Total Frame Size:** 13 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - | +| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | +| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | +| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | +| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | +| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | + +### Example Structure (C/C++) + +```c +struct StatsRadio { + uint8_t response_code; // 0x19 + int16_t noise_floor; + int8_t last_rssi; + int8_t last_snr; // Divide by 4.0 to get actual SNR in dB + uint32_t tx_air_secs; + uint32_t rx_air_secs; +} __attribute__((packed)); +``` + +--- + +## RESP_CODE_STATS_PACKETS (26) + +**Total Frame Size:** 25 bytes + +| Offset | Size | Type | Field Name | Description | Range/Notes | +|--------|------|------|------------|-------------|-------------| +| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - | +| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | +| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | +| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | +| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | +| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | +| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | + +### Notes + +- Counters are cumulative from boot and may wrap. +- `recv = flood_rx + direct_rx` +- `sent = flood_tx + direct_tx` + +### Example Structure (C/C++) + +```c +struct StatsPackets { + uint8_t response_code; // 0x1A + uint32_t recv; + uint32_t sent; + uint32_t flood_tx; + uint32_t direct_tx; + uint32_t flood_rx; + uint32_t direct_rx; +} __attribute__((packed)); +``` + +--- + +## Usage Example (Python) + +```python +import struct + +def parse_stats_core(frame): + """Parse RESP_CODE_STATS_CORE frame (10 bytes)""" + response_code, battery_mv, uptime_secs, errors, queue_len = \ + struct.unpack('<B H I H B', frame) + return { + 'battery_mv': battery_mv, + 'uptime_secs': uptime_secs, + 'errors': errors, + 'queue_len': queue_len + } + +def parse_stats_radio(frame): + """Parse RESP_CODE_STATS_RADIO frame (13 bytes)""" + response_code, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \ + struct.unpack('<B h b b I I', frame) + return { + 'noise_floor': noise_floor, + 'last_rssi': last_rssi, + 'last_snr': last_snr / 4.0, # Unscale SNR + 'tx_air_secs': tx_air_secs, + 'rx_air_secs': rx_air_secs + } + +def parse_stats_packets(frame): + """Parse RESP_CODE_STATS_PACKETS frame (25 bytes)""" + response_code, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \ + struct.unpack('<B I I I I I I', frame) + return { + 'recv': recv, + 'sent': sent, + 'flood_tx': flood_tx, + 'direct_tx': direct_tx, + 'flood_rx': flood_rx, + 'direct_rx': direct_rx + } +``` + +--- + +## Usage Example (JavaScript/TypeScript) + +```typescript +interface StatsCore { + battery_mv: number; + uptime_secs: number; + errors: number; + queue_len: number; +} + +interface StatsRadio { + noise_floor: number; + last_rssi: number; + last_snr: number; + tx_air_secs: number; + rx_air_secs: number; +} + +interface StatsPackets { + recv: number; + sent: number; + flood_tx: number; + direct_tx: number; + flood_rx: number; + direct_rx: number; +} + +function parseStatsCore(buffer: ArrayBuffer): StatsCore { + const view = new DataView(buffer); + return { + battery_mv: view.getUint16(1, true), + uptime_secs: view.getUint32(3, true), + errors: view.getUint16(7, true), + queue_len: view.getUint8(9) + }; +} + +function parseStatsRadio(buffer: ArrayBuffer): StatsRadio { + const view = new DataView(buffer); + return { + noise_floor: view.getInt16(1, true), + last_rssi: view.getInt8(3), + last_snr: view.getInt8(4) / 4.0, // Unscale SNR + tx_air_secs: view.getUint32(5, true), + rx_air_secs: view.getUint32(9, true) + }; +} + +function parseStatsPackets(buffer: ArrayBuffer): StatsPackets { + const view = new DataView(buffer); + return { + recv: view.getUint32(1, true), + sent: view.getUint32(5, true), + flood_tx: view.getUint32(9, true), + direct_tx: view.getUint32(13, true), + flood_rx: view.getUint32(17, true), + direct_rx: view.getUint32(21, true) + }; +} +``` + +--- + +## Field Size Considerations + +- Packet counters (uint32_t): May wrap after extended high-traffic operation. +- Time fields (uint32_t): Max ~136 years. +- SNR (int8_t, scaled by 4): Range -32 to +31.75 dB, 0.25 dB precision. + diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6fe165c5..6fc9a433 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1536,44 +1536,46 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_NOT_FOUND); } } else if (cmd_frame[0] == CMD_GET_STATS_CORE) { - char json_reply[160]; - formatStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_CORE; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + uint16_t battery_mv = board.getBattMilliVolts(); + uint32_t uptime_secs = _ms->getMillis() / 1000; + uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); + memcpy(&out_frame[i], &battery_mv, 2); i += 2; + memcpy(&out_frame[i], &uptime_secs, 4); i += 4; + memcpy(&out_frame[i], &_err_flags, 2); i += 2; + out_frame[i++] = queue_len; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { - char json_reply[160]; - formatRadioStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_RADIO; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); + int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); + int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision + uint32_t tx_air_secs = getTotalAirTime() / 1000; + uint32_t rx_air_secs = getReceiveAirTime() / 1000; + memcpy(&out_frame[i], &noise_floor, 2); i += 2; + out_frame[i++] = last_rssi; + out_frame[i++] = last_snr; + memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; + memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { - char json_reply[160]; - formatPacketStatsReply(json_reply); int i = 0; out_frame[i++] = RESP_CODE_STATS_PACKETS; - int json_len = strlen(json_reply); - if (i + json_len <= MAX_FRAME_SIZE) { - memcpy(&out_frame[i], json_reply, json_len); - i += json_len; - _serial->writeFrame(out_frame, i); - } else { - writeErrFrame(ERR_CODE_TABLE_FULL); - } + uint32_t recv = radio_driver.getPacketsRecv(); + uint32_t sent = radio_driver.getPacketsSent(); + uint32_t n_sent_flood = getNumSentFlood(); + uint32_t n_sent_direct = getNumSentDirect(); + uint32_t n_recv_flood = getNumRecvFlood(); + uint32_t n_recv_direct = getNumRecvDirect(); + memcpy(&out_frame[i], &recv, 4); i += 4; + memcpy(&out_frame[i], &sent, 4); i += 4; + memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; + memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; + memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { From 39f83efbfe9c34129996f35eec6de916b890762c Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Sun, 9 Nov 2025 11:39:47 -0800 Subject: [PATCH 247/546] Remove unused statistics formatting methods and associated header includes from MyMesh class. Whoops. --- examples/companion_radio/MyMesh.cpp | 14 -------------- examples/companion_radio/MyMesh.h | 6 ------ 2 files changed, 20 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6fc9a433..140f58c6 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1612,20 +1612,6 @@ void MyMesh::enterCLIRescue() { Serial.println("========= CLI Rescue ========="); } -void MyMesh::formatStatsReply(char *reply) { - // Use StatsFormatHelper - StatsFormatHelper::formatCoreStats(reply, board, *_ms, _err_flags, _mgr); -} - -void MyMesh::formatRadioStatsReply(char *reply) { - StatsFormatHelper::formatRadioStats(reply, _radio, radio_driver, getTotalAirTime(), getReceiveAirTime()); -} - -void MyMesh::formatPacketStatsReply(char *reply) { - StatsFormatHelper::formatPacketStats(reply, radio_driver, getNumSentFlood(), getNumSentDirect(), - getNumRecvFlood(), getNumRecvDirect()); -} - void MyMesh::checkCLIRescueCmd() { int len = strlen(cli_command); while (Serial.available() && len < sizeof(cli_command)-1) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 61a25e14..f2b56e5e 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -69,7 +69,6 @@ #include <helpers/BaseChatMesh.h> #include <helpers/TransportKeyStore.h> -#include <helpers/StatsFormatHelper.h> /* -------------------------------------------------------------------------------------- */ @@ -171,11 +170,6 @@ private: void checkCLIRescueCmd(); void checkSerialInterface(); - // Stats methods - void formatStatsReply(char *reply); - void formatRadioStatsReply(char *reply); - void formatPacketStatsReply(char *reply); - // helpers, short-cuts void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } From b59d1999e6f6d79aa673e136cc52319229444bc0 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 11 Nov 2025 20:08:05 +1100 Subject: [PATCH 248/546] * Sensor: DISCOVER_REQ, prefix_only support --- examples/simple_sensor/SensorMesh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 2ab2081f..20d9921b 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -636,14 +636,15 @@ void SensorMesh::onControlDataRecv(mesh::Packet* packet) { } if ((filter & (1 << ADV_TYPE_SENSOR)) != 0 && _prefs.discovery_mod_timestamp >= since) { + bool prefix_only = packet->payload[0] & 1; uint8_t data[6 + PUB_KEY_SIZE]; data[0] = CTL_TYPE_NODE_DISCOVER_RESP | ADV_TYPE_SENSOR; // low 4-bits for node type data[1] = packet->_snr; // let sender know the inbound SNR ( x 4) memcpy(&data[2], &tag, 4); // include tag from request, for client to match to memcpy(&data[6], self_id.pub_key, PUB_KEY_SIZE); - auto resp = createControlData(data, sizeof(data)); + auto resp = createControlData(data, prefix_only ? 6 + 8 : 6 + PUB_KEY_SIZE); if (resp) { - sendZeroHop(resp, getRetransmitDelay(resp)); // apply random delay, as multiple nodes can respond to this + sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } } } From b0ce00652f45057493eb9893ec549e1bfaa5fbca Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:00:27 +0100 Subject: [PATCH 249/546] Fix RAK4631 GPS UART pin macros --- variants/rak4631/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 9a0504dc..7db67abf 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -9,8 +9,8 @@ build_flags = ${nrf52_base.build_flags} -D RAK_BOARD -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 - -D PIN_GPS_TX=16 - -D PIN_GPS_RX=15 + -D PIN_GPS_TX=PIN_SERIAL1_RX + -D PIN_GPS_RX=PIN_SERIAL1_TX -D PIN_GPS_EN=-1 -D PIN_OLED_RESET=-1 -D RADIO_CLASS=CustomSX1262 From 750e955f193780b8eb1065b21691eb7e704b4377 Mon Sep 17 00:00:00 2001 From: fdlamotte <florent@frizoncorrea.fr> Date: Thu, 13 Nov 2025 10:39:20 +0100 Subject: [PATCH 250/546] Update library.json to latest libs and version --- library.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library.json b/library.json index 572c55b0..aa37cb6e 100644 --- a/library.json +++ b/library.json @@ -1,14 +1,14 @@ { "name": "MeshCore", - "version" : "1.8.0", + "version" : "1.10.0", "dependencies": { "SPI": "*", "Wire": "*", - "jgromes/RadioLib": "^7.1.2", + "jgromes/RadioLib": "^7.3.0", "rweather/Crypto": "^0.4.0", "adafruit/RTClib": "^2.1.3", "melopero/Melopero RV3028": "^1.1.0", - "electroniccats/CayenneLPP": "1.4.0" + "electroniccats/CayenneLPP": "1.6.1" }, "build": { "extraScript": "build_as_lib.py" From 91e9fcea4b76307d0e109730e848ddb819df9360 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 13 Nov 2025 20:45:38 +1100 Subject: [PATCH 251/546] * ver 1.10.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f2b56e5e..927ec65e 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "2 Oct 2025" +#define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.9.1" +#define FIRMWARE_VERSION "v1.10.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 00720bf7..d8a20486 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -68,11 +68,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "2 Oct 2025" + #define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.1" + #define FIRMWARE_VERSION "v1.10.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f0dec42b..8641caaf 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "2 Oct 2025" + #define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.1" + #define FIRMWARE_VERSION "v1.10.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index cc14f596..00d9c698 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "2 Oct 2025" + #define FIRMWARE_BUILD_DATE "13 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.9.1" + #define FIRMWARE_VERSION "v1.10.0" #endif #define FIRMWARE_ROLE "sensor" From 16c294ce6029a0a924762bf8825f8543e1703834 Mon Sep 17 00:00:00 2001 From: Stephan Rodemeier <stephan.rodemeier@mailbox.org> Date: Thu, 13 Nov 2025 22:25:55 +0100 Subject: [PATCH 252/546] Allow SF smaller than 7 to be saved --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/CommonCLI.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 598d535f..c0075a22 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1128,7 +1128,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t sf = cmd_frame[i++]; uint8_t cr = cmd_frame[i++]; - if (freq >= 300000 && freq <= 2500000 && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && + if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && bw <= 500000) { _prefs.sf = sf; _prefs.cr = cr; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index fc9fd5eb..93773cce 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -233,7 +233,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { + if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f && temp_timeout_mins > 0) { _callbacks->applyTempRadioParams(freq, bw, sf, cr, temp_timeout_mins); sprintf(reply, "OK - temp params for %d mins", temp_timeout_mins); } else { @@ -411,7 +411,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch float bw = num > 1 ? atof(parts[1]) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; - if (freq >= 300.0f && freq <= 2500.0f && sf >= 7 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { + if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { _prefs->sf = sf; _prefs->cr = cr; _prefs->freq = freq; From 850d57a8f243dce0193008ee46b2f276d4fa5919 Mon Sep 17 00:00:00 2001 From: zach <zacharysylvester81@gmail.com> Date: Fri, 14 Nov 2025 21:51:28 -0700 Subject: [PATCH 253/546] Refactor float conversion in CommonCLI to use strtof for improved precision and add ftoa3 function for formatting floats with three decimal places in TxtDataHelpers to fix display issue in app and repeater config ui in web REPO: 1. Flash a repeater 2. Connect over lora 3. Set bw to 42.7 KHZ It will revert back due to converting a double to a float. REPO2: 1.Flash Repeater 2. Apply float fix 3. Set to say 20.8 4. try to get value via app or web cli repeater config It wil show blank because it doesnt return a good value. It would be something like 20.7999992 which the app and web apps dont like so the ftoa3 rounds it and returns a 3 decimal point float --- src/helpers/CommonCLI.cpp | 10 +++++----- src/helpers/TxtDataHelpers.cpp | 13 +++++++++++++ src/helpers/TxtDataHelpers.h | 1 + 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93773cce..481d9ca7 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -228,8 +228,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(tmp, &command[10]); const char *parts[5]; int num = mesh::Utils::parseTextParts(tmp, parts, 5); - float freq = num > 0 ? atof(parts[0]) : 0.0f; - float bw = num > 1 ? atof(parts[1]) : 0.0f; + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0; @@ -284,7 +284,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "radio", 5) == 0) { char freq[16], bw[16]; strcpy(freq, StrHelper::ftoa(_prefs->freq)); - strcpy(bw, StrHelper::ftoa(_prefs->bw)); + strcpy(bw, StrHelper::ftoa3(_prefs->bw)); sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr); } else if (memcmp(config, "rxdelay", 7) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base)); @@ -407,8 +407,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(tmp, &config[6]); const char *parts[4]; int num = mesh::Utils::parseTextParts(tmp, parts, 4); - float freq = num > 0 ? atof(parts[0]) : 0.0f; - float bw = num > 1 ? atof(parts[1]) : 0.0f; + float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f; + float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f; uint8_t sf = num > 2 ? atoi(parts[2]) : 0; uint8_t cr = num > 3 ? atoi(parts[3]) : 0; if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) { diff --git a/src/helpers/TxtDataHelpers.cpp b/src/helpers/TxtDataHelpers.cpp index 09e86c67..d327931f 100644 --- a/src/helpers/TxtDataHelpers.cpp +++ b/src/helpers/TxtDataHelpers.cpp @@ -140,6 +140,19 @@ const char* StrHelper::ftoa(float f) { return tmp; } +const char* StrHelper::ftoa3(float f) { + static char s[16]; + int v = (int)(f * 1000.0f + (f >= 0 ? 0.5f : -0.5f)); // rounded ×1000 + int w = v / 1000; // whole + int d = abs(v % 1000); // decimals + snprintf(s, sizeof(s), "%d.%03d", w, d); + for (int i = strlen(s) - 1; i > 0 && s[i] == '0'; i--) + s[i] = 0; + int L = strlen(s); + if (s[L - 1] == '.') s[L - 1] = 0; + return s; +} + uint32_t StrHelper::fromHex(const char* src) { uint32_t n = 0; while (*src) { diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 387e09b9..6ab84d39 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -12,6 +12,7 @@ public: static void strncpy(char* dest, const char* src, size_t buf_sz); static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls static const char* ftoa(float f); + static const char* ftoa3(float f); //Converts float to string with 3 decimal places static bool isBlank(const char* str); static uint32_t fromHex(const char* src); }; From 2058af8453540ff864e414df774509c443f4971d Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sun, 16 Nov 2025 15:55:03 +1100 Subject: [PATCH 254/546] initial support: Keepteen LT1 --- boards/keepteen_lt1.json | 79 ++++++++++++ variants/keepteen_lt1/KeepteenLT1Board.cpp | 75 ++++++++++++ variants/keepteen_lt1/KeepteenLT1Board.h | 51 ++++++++ variants/keepteen_lt1/platformio.ini | 132 +++++++++++++++++++++ variants/keepteen_lt1/target.cpp | 51 ++++++++ variants/keepteen_lt1/target.h | 30 +++++ variants/keepteen_lt1/variant.cpp | 22 ++++ variants/keepteen_lt1/variant.h | 74 ++++++++++++ 8 files changed, 514 insertions(+) create mode 100644 boards/keepteen_lt1.json create mode 100644 variants/keepteen_lt1/KeepteenLT1Board.cpp create mode 100644 variants/keepteen_lt1/KeepteenLT1Board.h create mode 100644 variants/keepteen_lt1/platformio.ini create mode 100644 variants/keepteen_lt1/target.cpp create mode 100644 variants/keepteen_lt1/target.h create mode 100644 variants/keepteen_lt1/variant.cpp create mode 100644 variants/keepteen_lt1/variant.h diff --git a/boards/keepteen_lt1.json b/boards/keepteen_lt1.json new file mode 100644 index 00000000..c23b0b88 --- /dev/null +++ b/boards/keepteen_lt1.json @@ -0,0 +1,79 @@ +{ + "build": { + "arduino":{ + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x00B3" + ], + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "Keepteen LT1", + "mcu": "nrf52840", + "variant": "Keepteen LT1", + "variants_dir": "variants", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino", + "zephyr" + ], + "name": "Keepteen LT1", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "http://www.keepteen.com/", + "vendor": "Keepteen" + } \ No newline at end of file diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp new file mode 100644 index 00000000..46bff1fc --- /dev/null +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -0,0 +1,75 @@ +#include <Arduino.h> +#include "KeepteenLT1Board.h" + +#include <bluefruit.h> +#include <Wire.h> + +static BLEDfu bledfu; + +void KeepteenLT1Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + btn_prev_state = HIGH; + + pinMode(PIN_VBAT_READ, INPUT); + + #if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); + #endif + + Wire.begin(); +} + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +bool KeepteenLT1Board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("KeepteenLT1_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h new file mode 100644 index 00000000..9892638b --- /dev/null +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -0,0 +1,51 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +class KeepteenLT1Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint8_t getStartupReason() const override { return startup_reason; } + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + return (ADC_MULTIPLIER * raw); + } + + const char* getManufacturerName() const override { + return "Keepteen LT1"; + } + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } +#endif + + void reboot() override { + NVIC_SystemReset(); + } + + void powerOff() override { + sd_power_system_off(); + } + + bool startOTAUpdate(const char* id, char reply[]) override; +}; diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini new file mode 100644 index 00000000..96a55eb7 --- /dev/null +++ b/variants/keepteen_lt1/platformio.ini @@ -0,0 +1,132 @@ +[KeepteenLT1] +extends = nrf52_base +board = keepteen_lt1 +build_flags = ${nrf52_base.build_flags} + -I variants/keepteen_lt1 + -D KEEPTEEN_LT1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_BOARD_SDA=34 + -D PIN_BOARD_SCL=36 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<helpers/sensors> + +<../variants/keepteen_lt1> +lib_deps= ${nrf52_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + stevemarple/MicroNMEA @ ^2.0.6 + +[env:KeepteenLT1_repeater] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_repeater> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> +build_flags = + ${KeepteenLT1.build_flags} + -D ADVERT_NAME='"KeepteenLT1 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_room_server] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_room_server> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> +build_flags = ${KeepteenLT1.build_flags} + -D ADVERT_NAME='"KeepteenLT1 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_terminal_chat] +extends = KeepteenLT1 +build_flags = ${KeepteenLT1.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/RTClib @ ^2.1.3 + +[env:KeepteenLT1_companion_radio_usb] +extends = KeepteenLT1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = ${KeepteenLT1.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + +[env:KeepteenLT1_companion_radio_ble] +extends = KeepteenLT1 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = ${KeepteenLT1.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = ${KeepteenLT1.lib_deps} + adafruit/RTClib @ ^2.1.3 + densaugeo/base64 @ ~1.4.0 + +; [env:KeepteenLT1_sensor] +; extends = KeepteenLT1 +; build_flags = +; ${KeepteenLT1.build_flags} +; -D ADVERT_NAME='"KeepteenLT1 Sensor"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D DISPLAY_CLASS=SSD1306Display +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${KeepteenLT1.build_src_filter} +; +<helpers/ui/SSD1306Display.cpp> +; +<helpers/ui/MomentaryButton.cpp> +; +<../examples/simple_sensor> +; lib_deps = +; ${KeepteenLT1.lib_deps} diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp new file mode 100644 index 00000000..e72abf08 --- /dev/null +++ b/variants/keepteen_lt1/target.cpp @@ -0,0 +1,51 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> + +KeepteenLT1Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/keepteen_lt1/target.h b/variants/keepteen_lt1/target.h new file mode 100644 index 00000000..0f1aa756 --- /dev/null +++ b/variants/keepteen_lt1/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <KeepteenLT1Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +#include <helpers/sensors/EnvironmentSensorManager.h> + +extern KeepteenLT1Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/keepteen_lt1/variant.cpp b/variants/keepteen_lt1/variant.cpp new file mode 100644 index 00000000..47a20f13 --- /dev/null +++ b/variants/keepteen_lt1/variant.cpp @@ -0,0 +1,22 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() +{ + // set LED pin as output and set it low for off + pinMode(PIN_LED, OUTPUT); + digitalWrite(PIN_LED, LOW); + + // set INPUT_PULLUP for user button + pinMode(PIN_USER_BTN, INPUT_PULLUP); + +} + diff --git a/variants/keepteen_lt1/variant.h b/variants/keepteen_lt1/variant.h new file mode 100644 index 00000000..a2b63fad --- /dev/null +++ b/variants/keepteen_lt1/variant.h @@ -0,0 +1,74 @@ +#ifndef _KEEPTEEN_LT1_H_ +#define _KEEPTEEN_LT1_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) +#define USE_LFRC + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED (15) // Blue LED +#define PIN_LED2 (13) // Maybe red power LED? +#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit +#define LED_BUILTIN PIN_LED +#define P_LORA_TX_LED PIN_LED +#define LED_STATE_ON 1 + +// Buttons +#define PIN_BUTTON1 (32) // Menu / User Button +#define PIN_USER_BTN PIN_BUTTON1 + +// Analog pins +#define PIN_VBAT_READ (31) +#define AREF_VOLTAGE (3.6F) +#define ADC_MULTIPLIER (1.535F) +#define ADC_RESOLUTION (12) + +// Serial interfaces +#define PIN_SERIAL1_RX (22) +#define PIN_SERIAL1_TX (20) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (2) +#define PIN_SPI_MOSI (38) +#define PIN_SPI_SCK (43) + +// Lora Pins +#define P_LORA_BUSY (29) +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI +#define P_LORA_NSS (45) +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_DIO_1 (10) +#define P_LORA_RESET (9) +#define SX126X_RXEN RADIOLIB_NC +#define SX126X_TXEN RADIOLIB_NC +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (34) +#define PIN_WIRE_SCL (36) +#define I2C_NO_RESCAN + +// GPS L76KB +#define GPS_BAUDRATE 9600 +#define PIN_GPS_TX PIN_SERIAL1_RX +#define PIN_GPS_RX PIN_SERIAL1_TX +#define PIN_GPS_EN (24) + +#endif // _KEEPTEEN_LT1_H_ \ No newline at end of file From bc2256f232c476ada4d4a1bee3863c25ce05f02f Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sun, 16 Nov 2025 16:17:11 +1100 Subject: [PATCH 255/546] Keepteen LT1: remove terminal_chat and sensor targets --- variants/keepteen_lt1/platformio.ini | 33 +--------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini index 96a55eb7..cb3ea9c8 100644 --- a/variants/keepteen_lt1/platformio.ini +++ b/variants/keepteen_lt1/platformio.ini @@ -56,19 +56,6 @@ build_flags = ${KeepteenLT1.build_flags} lib_deps = ${KeepteenLT1.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:KeepteenLT1_terminal_chat] -extends = KeepteenLT1 -build_flags = ${KeepteenLT1.build_flags} - -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${KeepteenLT1.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${KeepteenLT1.lib_deps} - densaugeo/base64 @ ~1.4.0 - adafruit/RTClib @ ^2.1.3 - [env:KeepteenLT1_companion_radio_usb] extends = KeepteenLT1 board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld @@ -111,22 +98,4 @@ build_src_filter = ${KeepteenLT1.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${KeepteenLT1.lib_deps} adafruit/RTClib @ ^2.1.3 - densaugeo/base64 @ ~1.4.0 - -; [env:KeepteenLT1_sensor] -; extends = KeepteenLT1 -; build_flags = -; ${KeepteenLT1.build_flags} -; -D ADVERT_NAME='"KeepteenLT1 Sensor"' -; -D ADVERT_LAT=0.0 -; -D ADVERT_LON=0.0 -; -D ADMIN_PASSWORD='"password"' -; -D DISPLAY_CLASS=SSD1306Display -; ; -D MESH_PACKET_LOGGING=1 -; ; -D MESH_DEBUG=1 -; build_src_filter = ${KeepteenLT1.build_src_filter} -; +<helpers/ui/SSD1306Display.cpp> -; +<helpers/ui/MomentaryButton.cpp> -; +<../examples/simple_sensor> -; lib_deps = -; ${KeepteenLT1.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file From 3dd6dc02ea49eef565b58c34c21399e9dcc9beae Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 16 Nov 2025 16:55:16 +0100 Subject: [PATCH 256/546] xiao_s3: use environment sensor manager and add sensor role --- variants/xiao_s3_wio/platformio.ini | 24 ++++++++++++++++++++++++ variants/xiao_s3_wio/target.cpp | 2 +- variants/xiao_s3_wio/target.h | 4 ++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 95a54012..04a14db2 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -4,7 +4,9 @@ board = seeed_xiao_esp32s3 board_check = true board_build.mcu = esp32s3 build_flags = ${esp32_base.build_flags} + ${sensor_base.build_flags} -I variants/xiao_s3_wio + -UENV_INCLUDE_GPS -D SEEED_XIAO_S3 -D P_LORA_DIO_1=39 -D P_LORA_NSS=41 @@ -15,6 +17,8 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=9 -D PIN_USER_BTN=21 -D PIN_STATUS_LED=48 + -D PIN_BOARD_SDA=D4 + -D PIN_BOARD_SCL=D5 -D SX126X_RXEN=38 -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=true @@ -26,6 +30,10 @@ build_flags = ${esp32_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 build_src_filter = ${esp32_base.build_src_filter} +<../variants/xiao_s3_wio> + +<helpers/sensors> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} [env:Xiao_S3_WIO_repeater] extends = Xiao_S3_WIO @@ -185,3 +193,19 @@ lib_deps = ${Xiao_S3_WIO.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Xiao_S3_WIO_sensor] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + +<../examples/simple_sensor> +build_flags = + ${Xiao_S3_WIO.build_flags} + -D ADVERT_NAME='"XiaoS3 sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_S3_WIO.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 41f25da6..26cd27ac 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -14,7 +14,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index b84ab42b..c3227368 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -6,7 +6,7 @@ #include <helpers/ESP32Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> -#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS #include <helpers/ui/SSD1306Display.h> #include <helpers/ui/MomentaryButton.h> @@ -16,7 +16,7 @@ extern XiaoS3WIOBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 838e83b3b54790e91bf180ccdae669b13092ea59 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sun, 16 Nov 2025 17:07:33 +0100 Subject: [PATCH 257/546] xiao_s3: relocate serial pins on repeater_bridge_rs232 * conflicts with i2c pins that are documented on the same pins * this is a commented target --- variants/xiao_s3_wio/platformio.ini | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 04a14db2..8979edc2 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -65,8 +65,9 @@ lib_deps = ; -D ADMIN_PASSWORD='"password"' ; -D MAX_NEIGHBOURS=50 ; -D WITH_RS232_BRIDGE=Serial2 -; -D WITH_RS232_BRIDGE_RX=5 -; -D WITH_RS232_BRIDGE_TX=6 +; RS232 bridge Pins have been relocated from 5,6 which is the i2c bus on xiao_s3 +; -D WITH_RS232_BRIDGE_RX=3 +; -D WITH_RS232_BRIDGE_TX=4 ; -D BRIDGE_DEBUG=1 ; ; -D MESH_PACKET_LOGGING=1 ; ; -D MESH_DEBUG=1 From a3c9a07377d1895f78c776049ebd2bd99b90f4ae Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Mon, 17 Nov 2025 09:57:36 -0800 Subject: [PATCH 258/546] Modify CMD_GET_STATS with sub-types for core, radio, and packet statistics. Consolidated to a single RESP_CODE_STATS with a second byte to identify response structure. Updated documentation and examples to reflect the new command structure and response parsing. --- docs/stats_binary_frames.md | 201 ++++++++++++++++++++-------- examples/companion_radio/MyMesh.cpp | 103 +++++++------- 2 files changed, 200 insertions(+), 104 deletions(-) diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md index d4327ea6..1b409912 100644 --- a/docs/stats_binary_frames.md +++ b/docs/stats_binary_frames.md @@ -6,37 +6,53 @@ Binary frame structures for companion radio stats commands. All multi-byte integ | Command | Code | Description | |---------|------|-------------| -| `CMD_GET_STATS_CORE` | 56 | Get core device statistics | -| `CMD_GET_STATS_RADIO` | 57 | Get radio statistics | -| `CMD_GET_STATS_PACKETS` | 58 | Get packet statistics | +| `CMD_GET_STATS` | 56 | Get statistics (2-byte command: code + sub-type) | + +### Stats Sub-Types + +The `CMD_GET_STATS` command uses a 2-byte frame structure: +- **Byte 0:** `CMD_GET_STATS` (56) +- **Byte 1:** Stats sub-type: + - `STATS_TYPE_CORE` (0) - Get core device statistics + - `STATS_TYPE_RADIO` (1) - Get radio statistics + - `STATS_TYPE_PACKETS` (2) - Get packet statistics ## Response Codes | Response | Code | Description | |----------|------|-------------| -| `RESP_CODE_STATS_CORE` | 24 | Core stats response | -| `RESP_CODE_STATS_RADIO` | 25 | Radio stats response | -| `RESP_CODE_STATS_PACKETS` | 26 | Packet stats response | +| `RESP_CODE_STATS` | 24 | Statistics response (2-byte response: code + sub-type) | + +### Stats Response Sub-Types + +The `RESP_CODE_STATS` response uses a 2-byte header structure: +- **Byte 0:** `RESP_CODE_STATS` (24) +- **Byte 1:** Stats sub-type (matches command sub-type): + - `STATS_TYPE_CORE` (0) - Core device statistics response + - `STATS_TYPE_RADIO` (1) - Radio statistics response + - `STATS_TYPE_PACKETS` (2) - Packet statistics response --- -## RESP_CODE_STATS_CORE (24) +## RESP_CODE_STATS + STATS_TYPE_CORE (24, 0) -**Total Frame Size:** 10 bytes +**Total Frame Size:** 11 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| | 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | -| 1 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | -| 3 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | -| 7 | 2 | uint16_t | errors | Error flags bitmask | - | -| 9 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | +| 1 | 1 | uint8_t | stats_type | Always `0x00` (STATS_TYPE_CORE) | - | +| 2 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 | +| 4 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 | +| 8 | 2 | uint16_t | errors | Error flags bitmask | - | +| 10 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 | ### Example Structure (C/C++) ```c struct StatsCore { uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x00 (STATS_TYPE_CORE) uint16_t battery_mv; uint32_t uptime_secs; uint16_t errors; @@ -46,24 +62,26 @@ struct StatsCore { --- -## RESP_CODE_STATS_RADIO (25) +## RESP_CODE_STATS + STATS_TYPE_RADIO (24, 1) -**Total Frame Size:** 13 bytes +**Total Frame Size:** 14 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| -| 0 | 1 | uint8_t | response_code | Always `0x19` (25) | - | -| 1 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | -| 3 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | -| 4 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | -| 5 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | -| 9 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 1 | uint8_t | stats_type | Always `0x01` (STATS_TYPE_RADIO) | - | +| 2 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 | +| 4 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 | +| 5 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB | +| 6 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 | +| 10 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 | ### Example Structure (C/C++) ```c struct StatsRadio { - uint8_t response_code; // 0x19 + uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x01 (STATS_TYPE_RADIO) int16_t noise_floor; int8_t last_rssi; int8_t last_snr; // Divide by 4.0 to get actual SNR in dB @@ -74,19 +92,20 @@ struct StatsRadio { --- -## RESP_CODE_STATS_PACKETS (26) +## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2) -**Total Frame Size:** 25 bytes +**Total Frame Size:** 26 bytes | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| -| 0 | 1 | uint8_t | response_code | Always `0x1A` (26) | - | -| 1 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | -| 5 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | -| 9 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | -| 13 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | -| 17 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | -| 21 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | +| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - | +| 1 | 1 | uint8_t | stats_type | Always `0x02` (STATS_TYPE_PACKETS) | - | +| 2 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 | +| 6 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 | +| 10 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 | +| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | +| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | +| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | ### Notes @@ -98,7 +117,8 @@ struct StatsRadio { ```c struct StatsPackets { - uint8_t response_code; // 0x1A + uint8_t response_code; // 0x18 + uint8_t stats_type; // 0x02 (STATS_TYPE_PACKETS) uint32_t recv; uint32_t sent; uint32_t flood_tx; @@ -110,15 +130,38 @@ struct StatsPackets { --- -## Usage Example (Python) +## Command Usage Example (Python) + +```python +# Send CMD_GET_STATS command +def send_get_stats_core(serial_interface): + """Send command to get core stats""" + cmd = bytes([56, 0]) # CMD_GET_STATS (56) + STATS_TYPE_CORE (0) + serial_interface.write(cmd) + +def send_get_stats_radio(serial_interface): + """Send command to get radio stats""" + cmd = bytes([56, 1]) # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1) + serial_interface.write(cmd) + +def send_get_stats_packets(serial_interface): + """Send command to get packet stats""" + cmd = bytes([56, 2]) # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2) + serial_interface.write(cmd) +``` + +--- + +## Response Parsing Example (Python) ```python import struct def parse_stats_core(frame): - """Parse RESP_CODE_STATS_CORE frame (10 bytes)""" - response_code, battery_mv, uptime_secs, errors, queue_len = \ - struct.unpack('<B H I H B', frame) + """Parse RESP_CODE_STATS + STATS_TYPE_CORE frame (11 bytes)""" + response_code, stats_type, battery_mv, uptime_secs, errors, queue_len = \ + struct.unpack('<B B H I H B', frame) + assert response_code == 24 and stats_type == 0, "Invalid response type" return { 'battery_mv': battery_mv, 'uptime_secs': uptime_secs, @@ -127,9 +170,10 @@ def parse_stats_core(frame): } def parse_stats_radio(frame): - """Parse RESP_CODE_STATS_RADIO frame (13 bytes)""" - response_code, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \ - struct.unpack('<B h b b I I', frame) + """Parse RESP_CODE_STATS + STATS_TYPE_RADIO frame (14 bytes)""" + response_code, stats_type, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \ + struct.unpack('<B B h b b I I', frame) + assert response_code == 24 and stats_type == 1, "Invalid response type" return { 'noise_floor': noise_floor, 'last_rssi': last_rssi, @@ -139,9 +183,10 @@ def parse_stats_radio(frame): } def parse_stats_packets(frame): - """Parse RESP_CODE_STATS_PACKETS frame (25 bytes)""" - response_code, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \ - struct.unpack('<B I I I I I I', frame) + """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)""" + response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \ + struct.unpack('<B B I I I I I I', frame) + assert response_code == 24 and stats_type == 2, "Invalid response type" return { 'recv': recv, 'sent': sent, @@ -154,7 +199,34 @@ def parse_stats_packets(frame): --- -## Usage Example (JavaScript/TypeScript) +## Command Usage Example (JavaScript/TypeScript) + +```typescript +// Send CMD_GET_STATS command +const CMD_GET_STATS = 56; +const STATS_TYPE_CORE = 0; +const STATS_TYPE_RADIO = 1; +const STATS_TYPE_PACKETS = 2; + +function sendGetStatsCore(serialInterface: SerialPort): void { + const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_CORE]); + serialInterface.write(cmd); +} + +function sendGetStatsRadio(serialInterface: SerialPort): void { + const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_RADIO]); + serialInterface.write(cmd); +} + +function sendGetStatsPackets(serialInterface: SerialPort): void { + const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_PACKETS]); + serialInterface.write(cmd); +} +``` + +--- + +## Response Parsing Example (JavaScript/TypeScript) ```typescript interface StatsCore { @@ -183,34 +255,49 @@ interface StatsPackets { function parseStatsCore(buffer: ArrayBuffer): StatsCore { const view = new DataView(buffer); + const response_code = view.getUint8(0); + const stats_type = view.getUint8(1); + if (response_code !== 24 || stats_type !== 0) { + throw new Error('Invalid response type'); + } return { - battery_mv: view.getUint16(1, true), - uptime_secs: view.getUint32(3, true), - errors: view.getUint16(7, true), - queue_len: view.getUint8(9) + battery_mv: view.getUint16(2, true), + uptime_secs: view.getUint32(4, true), + errors: view.getUint16(8, true), + queue_len: view.getUint8(10) }; } function parseStatsRadio(buffer: ArrayBuffer): StatsRadio { const view = new DataView(buffer); + const response_code = view.getUint8(0); + const stats_type = view.getUint8(1); + if (response_code !== 24 || stats_type !== 1) { + throw new Error('Invalid response type'); + } return { - noise_floor: view.getInt16(1, true), - last_rssi: view.getInt8(3), - last_snr: view.getInt8(4) / 4.0, // Unscale SNR - tx_air_secs: view.getUint32(5, true), - rx_air_secs: view.getUint32(9, true) + noise_floor: view.getInt16(2, true), + last_rssi: view.getInt8(4), + last_snr: view.getInt8(5) / 4.0, // Unscale SNR + tx_air_secs: view.getUint32(6, true), + rx_air_secs: view.getUint32(10, true) }; } function parseStatsPackets(buffer: ArrayBuffer): StatsPackets { const view = new DataView(buffer); + const response_code = view.getUint8(0); + const stats_type = view.getUint8(1); + if (response_code !== 24 || stats_type !== 2) { + throw new Error('Invalid response type'); + } return { - recv: view.getUint32(1, true), - sent: view.getUint32(5, true), - flood_tx: view.getUint32(9, true), - direct_tx: view.getUint32(13, true), - flood_rx: view.getUint32(17, true), - direct_rx: view.getUint32(21, true) + recv: view.getUint32(2, true), + sent: view.getUint32(6, true), + flood_tx: view.getUint32(10, true), + direct_tx: view.getUint32(14, true), + flood_rx: view.getUint32(18, true), + direct_rx: view.getUint32(22, true) }; } ``` diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 140f58c6..bcc56817 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -52,9 +52,12 @@ #define CMD_SEND_PATH_DISCOVERY_REQ 52 #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ -#define CMD_GET_STATS_CORE 56 -#define CMD_GET_STATS_RADIO 57 -#define CMD_GET_STATS_PACKETS 58 +#define CMD_GET_STATS 56 // v8+, second byte is stats type + +// Stats sub-types for CMD_GET_STATS +#define STATS_TYPE_CORE 0 +#define STATS_TYPE_RADIO 1 +#define STATS_TYPE_PACKETS 2 #define RESP_CODE_OK 0 #define RESP_CODE_ERR 1 @@ -80,9 +83,7 @@ #define RESP_CODE_CUSTOM_VARS 21 #define RESP_CODE_ADVERT_PATH 22 #define RESP_CODE_TUNING_PARAMS 23 -#define RESP_CODE_STATS_CORE 24 -#define RESP_CODE_STATS_RADIO 25 -#define RESP_CODE_STATS_PACKETS 26 +#define RESP_CODE_STATS 24 // v8+, second byte is stats type #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -1535,47 +1536,55 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); } - } else if (cmd_frame[0] == CMD_GET_STATS_CORE) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_CORE; - uint16_t battery_mv = board.getBattMilliVolts(); - uint32_t uptime_secs = _ms->getMillis() / 1000; - uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); - memcpy(&out_frame[i], &battery_mv, 2); i += 2; - memcpy(&out_frame[i], &uptime_secs, 4); i += 4; - memcpy(&out_frame[i], &_err_flags, 2); i += 2; - out_frame[i++] = queue_len; - _serial->writeFrame(out_frame, i); - } else if (cmd_frame[0] == CMD_GET_STATS_RADIO) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_RADIO; - int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); - int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); - int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision - uint32_t tx_air_secs = getTotalAirTime() / 1000; - uint32_t rx_air_secs = getReceiveAirTime() / 1000; - memcpy(&out_frame[i], &noise_floor, 2); i += 2; - out_frame[i++] = last_rssi; - out_frame[i++] = last_snr; - memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; - memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; - _serial->writeFrame(out_frame, i); - } else if (cmd_frame[0] == CMD_GET_STATS_PACKETS) { - int i = 0; - out_frame[i++] = RESP_CODE_STATS_PACKETS; - uint32_t recv = radio_driver.getPacketsRecv(); - uint32_t sent = radio_driver.getPacketsSent(); - uint32_t n_sent_flood = getNumSentFlood(); - uint32_t n_sent_direct = getNumSentDirect(); - uint32_t n_recv_flood = getNumRecvFlood(); - uint32_t n_recv_direct = getNumRecvDirect(); - memcpy(&out_frame[i], &recv, 4); i += 4; - memcpy(&out_frame[i], &sent, 4); i += 4; - memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; - memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; - memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; - memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; - _serial->writeFrame(out_frame, i); + } else if (cmd_frame[0] == CMD_GET_STATS && len >= 2) { + uint8_t stats_type = cmd_frame[1]; + if (stats_type == STATS_TYPE_CORE) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_CORE; + uint16_t battery_mv = board.getBattMilliVolts(); + uint32_t uptime_secs = _ms->getMillis() / 1000; + uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF); + memcpy(&out_frame[i], &battery_mv, 2); i += 2; + memcpy(&out_frame[i], &uptime_secs, 4); i += 4; + memcpy(&out_frame[i], &_err_flags, 2); i += 2; + out_frame[i++] = queue_len; + _serial->writeFrame(out_frame, i); + } else if (stats_type == STATS_TYPE_RADIO) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_RADIO; + int16_t noise_floor = (int16_t)_radio->getNoiseFloor(); + int8_t last_rssi = (int8_t)radio_driver.getLastRSSI(); + int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision + uint32_t tx_air_secs = getTotalAirTime() / 1000; + uint32_t rx_air_secs = getReceiveAirTime() / 1000; + memcpy(&out_frame[i], &noise_floor, 2); i += 2; + out_frame[i++] = last_rssi; + out_frame[i++] = last_snr; + memcpy(&out_frame[i], &tx_air_secs, 4); i += 4; + memcpy(&out_frame[i], &rx_air_secs, 4); i += 4; + _serial->writeFrame(out_frame, i); + } else if (stats_type == STATS_TYPE_PACKETS) { + int i = 0; + out_frame[i++] = RESP_CODE_STATS; + out_frame[i++] = STATS_TYPE_PACKETS; + uint32_t recv = radio_driver.getPacketsRecv(); + uint32_t sent = radio_driver.getPacketsSent(); + uint32_t n_sent_flood = getNumSentFlood(); + uint32_t n_sent_direct = getNumSentDirect(); + uint32_t n_recv_flood = getNumRecvFlood(); + uint32_t n_recv_direct = getNumRecvDirect(); + memcpy(&out_frame[i], &recv, 4); i += 4; + memcpy(&out_frame[i], &sent, 4); i += 4; + memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; + memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; + memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + _serial->writeFrame(out_frame, i); + } else { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type + } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { bool success = _store->formatFileSystem(); if (success) { From 88a614194324bc49b338beee61b96a16b0f5b1b3 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Tue, 18 Nov 2025 15:36:25 +0100 Subject: [PATCH 259/546] fix: move bme680 detection before bme280 --- .../sensors/EnvironmentSensorManager.cpp | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 98339105..79dc87e5 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -178,6 +178,16 @@ bool EnvironmentSensorManager::begin() { } #endif + #if ENV_INCLUDE_BME680 + if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { + MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); + BME680_initialized = true; + } else { + BME680_initialized = false; + MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); + } + #endif + #if ENV_INCLUDE_BME280 if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); @@ -301,16 +311,6 @@ bool EnvironmentSensorManager::begin() { } #endif - #if ENV_INCLUDE_BME680 - if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) { - MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); - BME680_initialized = true; - } else { - BME680_initialized = false; - MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); - } - #endif - #if ENV_INCLUDE_BMP085 // First argument is MODE (aka oversampling) // choose ULTRALOWPOWER @@ -344,6 +344,19 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif + #if ENV_INCLUDE_BME680 + if (BME680_initialized) { + if (BME680.performReading()) { + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); + telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); + next_available_channel++; + } + } + #endif + #if ENV_INCLUDE_BME280 if (BME280_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); @@ -452,19 +465,6 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen } #endif - #if ENV_INCLUDE_BME680 - if (BME680_initialized) { - if (BME680.performReading()) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); - telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); - telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); - next_available_channel++; - } - } - #endif - #if ENV_INCLUDE_BMP085 if (BMP085_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature()); @@ -563,7 +563,7 @@ void EnvironmentSensorManager::initBasicGPS() { gps_active = false; //Set GPS visibility off until setting is changed } -// gps code for rak might be moved to MicroNMEALoactionProvider +// gps code for rak might be moved to MicroNMEALoactionProvider // or make a new location provider ... #ifdef RAK_WISBLOCK_GPS void EnvironmentSensorManager::rakGPSInit(){ From 310618e6899337b37fb3a9268f8e6571bf1f760c Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Wed, 19 Nov 2025 11:43:52 +0800 Subject: [PATCH 260/546] add heltec_v4 tft expansion box --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 265 +++++++++++++++++++++++---- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 267 insertions(+), 44 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index c9da0cf8..4e7fd10a 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,10 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -15,12 +19,18 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - _isOn = true; + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - _isOn = false; + if (_isOn) { + if (_peripher_power) _peripher_power->release(); + _isOn = false; + } } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index 1a3a9602..d843da85 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,6 +5,7 @@ #include <Adafruit_GFX.h> #define SSD1306_NO_SPLASH #include <Adafruit_SSD1306.h> +#include <helpers/RefCountedDigitalPin.h> #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -18,10 +19,16 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; + RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(128, 64, &Wire, PIN_OLED_RESET), + _peripher_power(peripher_power) + { + _isOn = false; + } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 87f9b8ad..a686c0c8 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -25,10 +25,13 @@ bool ST7789LCDDisplay::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + pinMode(PIN_TFT_RST, OUTPUT); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); digitalWrite(PIN_TFT_RST, HIGH); // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index a8077148..5b960ca1 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include <helpers/RefCountedDigitalPin.h> class ST7789LCDDisplay : public DisplayDriver { - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ public: { _isOn = false; } -#elif LILYGO_TDECK +#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f143db36..92f93437 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,5 +86,9 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - return "Heltec V4"; + #ifdef HELTEC_LORA_V4_TFT + return "Heltec V4 TFT"; + #else + return "Heltec V4 OLED"; + #endif } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc6..ba759009 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,11 +20,9 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VEXT_EN_ACTIVE=LOW -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -47,10 +45,44 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:heltec_v4_repeater] +[heltec_v4_oled] extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D ENV_PIN_SDA=4 + -D ENV_PIN_SCL=3 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = ${Heltec_lora32_v4.lib_deps} + +[heltec_v4_tft] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_TFT + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=3 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=18 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=21 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=15 + -D PIN_TFT_DC=16 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=33 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = + ${Heltec_lora32_v4.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_v4_repeater] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -59,18 +91,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -81,18 +113,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -101,50 +133,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_room_server> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -155,20 +187,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -178,21 +210,21 @@ build_flags = -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} - -D ADVERT_NAME='"Heltec v3 Sensor"' + ${heltec_v4_oled.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -201,9 +233,172 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_sensor> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + + +[env:heltec_v4_tft_repeater] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + + +[env:heltec_v4_tft_repeater_bridge_espnow] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_room_server] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_terminal_chat] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_usb] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_ble] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=ST7789LCDDisplay + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_wifi] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_sensor] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7789LCDDisplay +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_tft.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 015c3a8e..0d2bd497 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; + DISPLAY_CLASS display(&(board.periph_power)); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index a153b2af..00d2adab 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,7 +9,11 @@ #include <helpers/SensorManager.h> #include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS - #include <helpers/ui/SSD1306Display.h> +#ifdef HELTEC_LORA_V4_OLED + #include <helpers/ui/SSD1306Display.h> +#elif defined(HELTEC_LORA_V4_TFT) + #include <helpers/ui/ST7789LCDDisplay.h> +#endif #include <helpers/ui/MomentaryButton.h> #endif From ed9655e14e40d9e4f5361631c1ddc0d3a70de999 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 21 Nov 2025 12:48:33 +1100 Subject: [PATCH 261/546] rename faketec to promicro --- variants/promicro/platformio.ini | 64 ++++++++++++++++---------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index b1c0c4ea..90962d27 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -1,9 +1,9 @@ -[Faketec] +[Promicro] extends = nrf52_base board = promicro_nrf52840 build_flags = ${nrf52_base.build_flags} -I variants/promicro - -D FAKETEC + -D PROMICRO -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 @@ -34,14 +34,14 @@ lib_deps= ${nrf52_base.lib_deps} adafruit/Adafruit BMP280 Library@^2.6.8 stevemarple/MicroNMEA @ ^2.0.6 -[env:Faketec_repeater] -extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +[env:ProMicro_repeater] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_repeater> +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> build_flags = - ${Faketec.build_flags} + ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -50,16 +50,16 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:Faketec_room_server] -extends = Faketec -build_src_filter = ${Faketec.build_src_filter} +[env:ProMicro_room_server] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_room_server> +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -68,47 +68,47 @@ build_flags = ${Faketec.build_flags} -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 -[env:Faketec_terminal_chat] -extends = Faketec -build_flags = ${Faketec.build_flags} +[env:ProMicro_terminal_chat] +extends = Promicro +build_flags = ${Promicro.build_flags} -D MAX_CONTACTS=100 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} densaugeo/base64 @ ~1.4.0 adafruit/RTClib @ ^2.1.3 -[env:Faketec_companion_radio_usb] -extends = Faketec +[env:ProMicro_companion_radio_usb] +extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[env:Faketec_companion_radio_ble] -extends = Faketec +[env:ProMicro_companion_radio_ble] +extends = Promicro board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld board_upload.maximum_size = 712704 -build_flags = ${Faketec.build_flags} +build_flags = ${Promicro.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -118,20 +118,20 @@ build_flags = ${Faketec.build_flags} -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = ${Faketec.lib_deps} +lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 -[env:Faketec_sensor] -extends = Faketec +[env:ProMicro_sensor] +extends = Promicro build_flags = - ${Faketec.build_flags} + ${Promicro.build_flags} -D ADVERT_NAME='"Faketec Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -139,9 +139,9 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Faketec.build_src_filter} +build_src_filter = ${Promicro.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/simple_sensor> lib_deps = - ${Faketec.lib_deps} + ${Promicro.lib_deps} From 2bd47de3b9824d99450baa4d1d90c9729c13032f Mon Sep 17 00:00:00 2001 From: zaquaz <zaquaz@zaquaz.com> Date: Thu, 20 Nov 2025 18:55:39 -0800 Subject: [PATCH 262/546] Added buzzer config persistance accross restart --- examples/companion_radio/DataStore.cpp | 7 +++++++ examples/companion_radio/MyMesh.h | 5 +++-- examples/companion_radio/NodePrefs.h | 1 + examples/companion_radio/ui-new/UITask.cpp | 3 +++ examples/companion_radio/ui-orig/UITask.cpp | 3 +++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index eac027b8..058389fe 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -200,6 +200,11 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; + + // Initialize defaults for any missing fields (backward compatibility) + memset(&_prefs, 0, sizeof(_prefs)); + node_lat = 0.0; + node_lon = 0.0; file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 @@ -221,6 +226,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.close(); } @@ -252,6 +258,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 + file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.close(); } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 927ec65e..9c22532d 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -152,6 +152,9 @@ protected: pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0; } +public: + void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } + private: void writeOKFrame(); void writeErrFrame(uint8_t err_code); @@ -171,11 +174,9 @@ private: void checkSerialInterface(); // helpers, short-cuts - void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); } void saveChannels() { _store->saveChannels(this); } void saveContacts() { _store->saveContacts(this); } -private: DataStore* _store; NodePrefs _prefs; uint32_t pending_login; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index bfde7218..13c9f884 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -24,4 +24,5 @@ struct NodePrefs { // persisted to file float rx_delay_base; uint32_t ble_pin; uint8_t advert_loc_policy; + uint8_t buzzer_quiet; }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 086f8259..9213df12 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -532,6 +532,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #ifdef PIN_BUZZER buzzer.begin(); + buzzer.quiet(_node_prefs->buzzer_quiet); #endif #ifdef PIN_VIBRATION @@ -871,6 +872,8 @@ void UITask::toggleBuzzer() { buzzer.quiet(true); showAlert("Buzzer: OFF", 800); } + _node_prefs->buzzer_quiet = buzzer.isQuiet(); + the_mesh.savePrefs(); _next_refresh = 0; // trigger refresh #endif } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d..f7838d68 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -56,6 +56,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #ifdef PIN_BUZZER buzzer.begin(); + buzzer.quiet(_node_prefs->buzzer_quiet); #endif // Initialize digital button if available @@ -394,6 +395,8 @@ void UITask::handleButtonTriplePress() { buzzer.quiet(true); sprintf(_alert, "Buzzer: OFF"); } + _node_prefs->buzzer_quiet = buzzer.isQuiet(); + the_mesh.savePrefs(); _need_refresh = true; #endif } From b33d226c583bc601787454e512cca3f938200de4 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 21 Nov 2025 15:44:31 +1100 Subject: [PATCH 263/546] * proposal for 'Extended Trace' packets. Using 'flags' byte, lower 2 bits, for path hash size. --- examples/companion_radio/MyMesh.cpp | 52 ++++++++++++++++++----------- src/Identity.h | 3 ++ src/Mesh.cpp | 9 ++--- 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c0075a22..0afd11de 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -670,6 +670,11 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) { void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags, const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) { + uint8_t path_sz = flags & 0x03; // NEW v1.11+ + if (12 + path_len + (path_len >> path_sz) + 1 > sizeof(out_frame)) { + MESH_DEBUG_PRINTLN("onTraceRecv(), path_len is too long: %d", (uint32_t)path_len); + return; + } int i = 0; out_frame[i++] = PUSH_CODE_TRACE_DATA; out_frame[i++] = 0; // reserved @@ -681,8 +686,9 @@ void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, i += 4; memcpy(&out_frame[i], path_hashes, path_len); i += path_len; - memcpy(&out_frame[i], path_snrs, path_len); - i += path_len; + + memcpy(&out_frame[i], path_snrs, path_len >> path_sz); + i += path_len >> path_sz; out_frame[i++] = (int8_t)(packet->getSNR() * 4); // extra/final SNR (to this node) if (_serial->isConnected()) { @@ -1446,25 +1452,31 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_BAD_STATE); } - } else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PATH_SIZE) { - uint32_t tag, auth; - memcpy(&tag, &cmd_frame[1], 4); - memcpy(&auth, &cmd_frame[5], 4); - auto pkt = createTrace(tag, auth, cmd_frame[9]); - if (pkt) { - uint8_t path_len = len - 10; - sendDirect(pkt, &cmd_frame[10], path_len); - - uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); - uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); - - out_frame[0] = RESP_CODE_SENT; - out_frame[1] = 0; - memcpy(&out_frame[2], &tag, 4); - memcpy(&out_frame[6], &est_timeout, 4); - _serial->writeFrame(out_frame, 10); + } else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PACKET_PAYLOAD-5) { + uint8_t path_len = len - 10; + uint8_t flags = cmd_frame[9]; + uint8_t path_sz = flags & 0x03; // NEW v1.11+ + if ((path_len >> path_sz) > MAX_PATH_SIZE || (path_len % (1 << path_sz)) != 0) { // make sure is multiple of path_sz + writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { - writeErrFrame(ERR_CODE_TABLE_FULL); + uint32_t tag, auth; + memcpy(&tag, &cmd_frame[1], 4); + memcpy(&auth, &cmd_frame[5], 4); + auto pkt = createTrace(tag, auth, flags); + if (pkt) { + sendDirect(pkt, &cmd_frame[10], path_len); + + uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); + uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); + + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } else { + writeErrFrame(ERR_CODE_TABLE_FULL); + } } } else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) { diff --git a/src/Identity.h b/src/Identity.h index 42fb9d9a..60e8783b 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -23,6 +23,9 @@ public: bool isHashMatch(const uint8_t* hash) const { return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0; } + bool isHashMatch(const uint8_t* hash, uint8_t len) const { + return memcmp(hash, pub_key, len) == 0; + } /** * \brief Performs Ed25519 signature verification. diff --git a/src/Mesh.cpp b/src/Mesh.cpp index f1271574..1bda9e96 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -52,14 +52,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { uint32_t auth_code; memcpy(&auth_code, &pkt->payload[i], 4); i += 4; uint8_t flags = pkt->payload[i++]; + uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size uint8_t len = pkt->payload_len - i; - if (pkt->path_len >= len) { // TRACE has reached end of given path + uint8_t offset = pkt->path_len << path_sz; + if (offset >= len) { // TRACE has reached end of given path onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len); - } else if (self_id.isHashMatch(&pkt->payload[i + pkt->path_len]) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { + } else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) { // append SNR (Not hash!) - pkt->path[pkt->path_len] = (int8_t) (pkt->getSNR()*4); - pkt->path_len += PATH_HASH_SIZE; + pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4); uint32_t d = getDirectRetransmitDelay(pkt); return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable? From 031fa1e704da52c8d31dba1799fbf19b65ed14ba Mon Sep 17 00:00:00 2001 From: Winston Lowe <wel97459@gmail.com> Date: Thu, 20 Nov 2025 21:58:42 -0800 Subject: [PATCH 264/546] Changed uint to a uint8_t --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 4 ++-- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/BaseChatMesh.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4136818c..becbb759 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -541,7 +541,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index de06b4c6..78588f0f 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -394,7 +394,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) { MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 20d9921b..ca07f496 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -550,7 +550,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin()) { // a CLI command uint32_t sender_timestamp; memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = (data[4] >> 2); // message attempt number, and other flags + uint8_t flags = (data[4] >> 2); // message attempt number, and other flags if (sender_timestamp > from->last_timestamp) { // prevent replay attacks if (flags == TXT_TYPE_PLAIN) { @@ -608,7 +608,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i } } -bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) { +bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len) { MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from "); #ifdef MESH_DEBUG mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE); diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 00d9c698..1b0d83b3 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -127,7 +127,7 @@ protected: bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; - virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len); + virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len); void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); private: FILESYSTEM* _fs; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b4072657..4ab3e03b 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -166,7 +166,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { uint32_t timestamp; memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong) - uint flags = data[4] >> 2; // message attempt number, and other flags + uint8_t flags = data[4] >> 2; // message attempt number, and other flags // len can be > original length, but 'text' will be padded with zeroes data[len] = 0; // need to make a C string again, with null terminator From 454f6b2583496cc4937b111ab4c7d6eb3b79e9e7 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 21 Nov 2025 17:57:49 +1100 Subject: [PATCH 265/546] rename adverts --- variants/promicro/platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 90962d27..78ea5fa1 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -42,7 +42,7 @@ build_src_filter = ${Promicro.build_src_filter} +<helpers/ui/MomentaryButton.cpp> build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Repeater"' + -D ADVERT_NAME='"ProMicro Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -60,7 +60,7 @@ build_src_filter = ${Promicro.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Room"' + -D ADVERT_NAME='"ProMicro Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -132,7 +132,7 @@ lib_deps = ${Promicro.lib_deps} extends = Promicro build_flags = ${Promicro.build_flags} - -D ADVERT_NAME='"Faketec Sensor"' + -D ADVERT_NAME='"ProMicro Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' From 5a3ea64a97dc0cc419ee553d0da5c4c7656b7858 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 21 Nov 2025 18:15:30 +1100 Subject: [PATCH 266/546] Repeater: add adc.multiplier setting --- examples/simple_repeater/MyMesh.cpp | 4 ++++ src/MeshCore.h | 2 ++ src/helpers/CommonCLI.cpp | 14 ++++++++++++-- src/helpers/CommonCLI.h | 1 + variants/promicro/PromicroBoard.h | 14 +++++++++++++- 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 4136818c..091d7901 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -710,6 +710,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; + + _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier } void MyMesh::begin(FILESYSTEM *fs) { @@ -733,6 +735,8 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif diff --git a/src/MeshCore.h b/src/MeshCore.h index 94bf351d..d65c2b93 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -42,6 +42,8 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual void setAdcMultiplier(float multiplier) {}; + virtual float getAdcMultiplier() const { return 1.0f; } virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93773cce..b33d71aa 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -70,7 +70,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - // 166 + file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 + // 170 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -83,6 +84,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->cr = constrain(_prefs->cr, 5, 8); _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); + _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); @@ -148,7 +150,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 - // 166 + file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 + // 170 file.close(); } @@ -331,6 +334,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); #endif + } else if (memcmp(config, "adc.multiplier", 14) == 0) { + sprintf(reply, "> %s", StrHelper::ftoa(_prefs->adc_multiplier)); } else { sprintf(reply, "??: %s", config); } @@ -523,6 +528,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch savePrefs(); strcpy(reply, "OK"); #endif + } else if (memcmp(config, "adc.multiplier ", 15) == 0) { + _prefs->adc_multiplier = atof(&config[15]); + _board->setAdcMultiplier(_prefs->adc_multiplier); + savePrefs(); + strcpy(reply, "OK"); } else { sprintf(reply, "unknown config: %s", config); } diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index a665e014..068783ab 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -47,6 +47,7 @@ struct NodePrefs { // persisted to file uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; + float adc_multiplier; }; class CommonCLICallbacks { diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index e4b67415..9dfb7b2f 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -23,6 +23,7 @@ class PromicroBoard : public mesh::MainBoard { protected: uint8_t startup_reason; uint8_t btn_prev_state; + float adc_mult = ADC_MULTIPLIER; public: void begin(); @@ -39,7 +40,18 @@ public: raw += analogRead(PIN_VBAT_READ); } raw = raw / BATTERY_SAMPLES; - return (ADC_MULTIPLIER * raw); + return (adc_mult * raw); + } + + void setAdcMultiplier(float multiplier) override { + if (multiplier == 0.0f) { + adc_mult = ADC_MULTIPLIER;} + else { + adc_mult = multiplier; + } + } + float getAdcMultiplier() const override { + return adc_mult; } const char* getManufacturerName() const override { From e13c064487f11a99eaad173b556e0a4dc92a1350 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 21 Nov 2025 21:46:55 +1100 Subject: [PATCH 267/546] add board.setAdcMultiplier to room server and sensor --- examples/simple_room_server/MyMesh.cpp | 2 ++ examples/simple_sensor/SensorMesh.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index de06b4c6..7b575e6f 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -641,6 +641,8 @@ void MyMesh::begin(FILESYSTEM *fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 20d9921b..96a3791d 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -740,6 +740,8 @@ void SensorMesh::begin(FILESYSTEM* fs) { updateAdvertTimer(); updateFloodAdvertTimer(); + board.setAdcMultiplier(_prefs.adc_multiplier); + #if ENV_INCLUDE_GPS == 1 applyGpsPrefs(); #endif From fc93d84fb8eac695fe79f602ad0e70d59dd07823 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 21 Nov 2025 23:44:17 +1100 Subject: [PATCH 268/546] tweaks get/set adcMultiplier logic --- src/MeshCore.h | 4 ++-- src/helpers/CommonCLI.cpp | 21 +++++++++++++++++---- variants/promicro/PromicroBoard.h | 9 +++++++-- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/src/MeshCore.h b/src/MeshCore.h index d65c2b93..11a6a5b4 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -42,8 +42,8 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; - virtual void setAdcMultiplier(float multiplier) {}; - virtual float getAdcMultiplier() const { return 1.0f; } + virtual bool setAdcMultiplier(float multiplier) { return false; }; + virtual float getAdcMultiplier() const { return 0.0f; } virtual const char* getManufacturerName() const = 0; virtual void onBeforeTransmit() { } virtual void onAfterTransmit() { } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index b33d71aa..17b2b753 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -335,7 +335,12 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %s", _prefs->bridge_secret); #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { - sprintf(reply, "> %s", StrHelper::ftoa(_prefs->adc_multiplier)); + float adc_mult = _board->getAdcMultiplier(); + if (adc_mult == 0.0f) { + strcpy(reply, "Error: unsupported by this board"); + } else { + sprintf(reply, "> %.3f", adc_mult); + } } else { sprintf(reply, "??: %s", config); } @@ -530,9 +535,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch #endif } else if (memcmp(config, "adc.multiplier ", 15) == 0) { _prefs->adc_multiplier = atof(&config[15]); - _board->setAdcMultiplier(_prefs->adc_multiplier); - savePrefs(); - strcpy(reply, "OK"); + if (_board->setAdcMultiplier(_prefs->adc_multiplier)) { + savePrefs(); + if (_prefs->adc_multiplier == 0.0f) { + strcpy(reply, "OK - using default board multiplier"); + } else { + sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier); + } + } else { + _prefs->adc_multiplier = 0.0f; + strcpy(reply, "Error: unsupported by this board"); + }; } else { sprintf(reply, "unknown config: %s", config); } diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 9dfb7b2f..dc20e550 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -43,15 +43,20 @@ public: return (adc_mult * raw); } - void setAdcMultiplier(float multiplier) override { + bool setAdcMultiplier(float multiplier) override { if (multiplier == 0.0f) { adc_mult = ADC_MULTIPLIER;} else { adc_mult = multiplier; } + return true; } float getAdcMultiplier() const override { - return adc_mult; + if (adc_mult == 0.0f) { + return ADC_MULTIPLIER; + } else { + return adc_mult; + } } const char* getManufacturerName() const override { From 07e7e2d44bfd68abbe87f73a853b04d76b37ddf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com> Date: Sat, 22 Nov 2025 02:06:44 +0100 Subject: [PATCH 269/546] companion: Suspend radio when hibernating MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should significantly reduce power consumption in hibernation. Fixes: #1014 Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com> Signed-off-by: Frieder Schrempf <frieder@fris.de> # generalize for all radios and UIs --- examples/companion_radio/ui-new/UITask.cpp | 1 + examples/companion_radio/ui-orig/UITask.cpp | 6 ++++-- src/helpers/radiolib/CustomSX1262Wrapper.h | 3 +++ src/helpers/radiolib/RadioLibWrappers.h | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 086f8259..d8d778c3 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -650,6 +650,7 @@ void UITask::shutdown(bool restart){ _board->reboot(); } else { _display->turnOff(); + radio_driver.powerOff(); _board->powerOff(); } } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 045c955d..89dda116 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -292,10 +292,12 @@ void UITask::shutdown(bool restart){ #endif // PIN_BUZZER - if (restart) + if (restart) { _board->reboot(); - else + } else { + radio_driver.powerOff(); _board->powerOff(); + } } void UITask::loop() { diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 119f6dce..1afee5e8 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -19,4 +19,7 @@ public: int sf = ((CustomSX1262 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + virtual void powerOff() override { + ((CustomSX1262 *)_radio)->sleep(false); + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 25cc5358..3c26d372 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -21,6 +21,7 @@ public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } void begin() override; + virtual void powerOff() { _radio->sleep(); } int recvRaw(uint8_t* bytes, int sz) override; uint32_t getEstAirtimeFor(int len_bytes) override; bool startSendRaw(const uint8_t* bytes, int len) override; From 0f565323a092fbf13354a1d908e9564694c0f113 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Wed, 19 Nov 2025 11:40:00 +0100 Subject: [PATCH 270/546] variants: WisMesh Tag: Enable DC/DC regulator According to the documentation and experiments on other boards using NRF52 + SX1262 this reduces the power consumption significantly. This assumes that the hardware actually has the inductor for the internal DC/DC regulator populated which is very likely. Even if not, it won't hurt. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 68ce2fd8..28f6f713 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -21,6 +21,8 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void RAKWismeshTagBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; + pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); From b9b82fcf1bfa57ce4978b9e3e466343dc79e156f Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Thu, 20 Nov 2025 09:09:33 +0100 Subject: [PATCH 271/546] variants: WisMesh Tag: Enable status LED Use the blue LED as status LED. This prevents the blue LED from being always-on and draining the battery. Instead use it as status LED with blink patterns as other companion devices do. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/rak_wismesh_tag/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/rak_wismesh_tag/variant.h b/variants/rak_wismesh_tag/variant.h index b0e51efc..3b8e079f 100644 --- a/variants/rak_wismesh_tag/variant.h +++ b/variants/rak_wismesh_tag/variant.h @@ -66,7 +66,7 @@ #define LED_BLUE (36) #define LED_GREEN (35) -//#define PIN_STATUS_LED LED_BLUE +#define PIN_STATUS_LED LED_BLUE #define LED_BUILTIN LED_GREEN #define LED_PIN LED_GREEN #define LED_STATE_ON HIGH From 11f119a7fb7f868d276d495abd7c8755db234e08 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Wed, 19 Nov 2025 12:01:07 +0100 Subject: [PATCH 272/546] variants: XIAO NRF52: Enable DC/DC regulator This reduces the power consumption by approximately 25%. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 03bb674e..a709b1c3 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -23,6 +23,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void XiaoNrf52Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); From c76d337a00e6db7ef110e25366bd9a9b8726f5cb Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Wed, 19 Nov 2025 17:25:05 +0100 Subject: [PATCH 273/546] variants: XIAO NRF52: Enable user button The Xiao nRF52840 combined with the Wio-SX1262 is often used for cheap and compact DIY companion nodes. The Wio actually has an onboard pushbutton that can be used as user button. Enable support for the button. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 4 ++++ variants/xiao_nrf52/platformio.ini | 7 +++++++ variants/xiao_nrf52/target.cpp | 4 ++++ variants/xiao_nrf52/target.h | 5 +++++ variants/xiao_nrf52/variant.h | 2 +- 5 files changed, 21 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index a709b1c3..396880ab 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -29,6 +29,10 @@ void XiaoNrf52Board::begin() { pinMode(VBAT_ENABLE, OUTPUT); digitalWrite(VBAT_ENABLE, HIGH); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT); +#endif + #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); #endif diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 888e9493..edbf6275 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -26,10 +26,13 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_WIRE_SCL=D6 -D PIN_WIRE_SDA=D7 + -D PIN_USER_BTN=PIN_BUTTON1 + -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<../variants/xiao_nrf52> + +<helpers/ui/NullDisplayDriver.cpp> debug_tool = jlink upload_protocol = nrfutil lib_deps = ${nrf52_base.lib_deps} @@ -41,6 +44,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 @@ -52,6 +56,7 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -62,6 +67,7 @@ board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = ${Xiao_nrf52.build_flags} + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D QSPIFLASH=1 @@ -70,6 +76,7 @@ build_flags = build_src_filter = ${Xiao_nrf52.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${Xiao_nrf52.lib_deps} densaugeo/base64 @ ~1.4.0 diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index 41142eb6..c9c02d21 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -2,6 +2,10 @@ #include "target.h" #include <helpers/ArduinoHelpers.h> +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; +#endif + XiaoNrf52Board board; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index 86f546b8..e1ea2a6b 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -9,6 +9,11 @@ #include <helpers/ArduinoHelpers.h> #include <helpers/sensors/EnvironmentSensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/NullDisplayDriver.h> + extern DISPLAY_CLASS display; +#endif + extern XiaoNrf52Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index c54f3c2f..c888a833 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -38,7 +38,7 @@ extern "C" #define LED_STATE_ON (1) // State when LED is litted // Buttons -#define PIN_BUTTON1 (PINS_COUNT) +#define PIN_BUTTON1 (0) // Digital PINs static const uint8_t D0 = 0 ; From 4a8dcb4906ba4452e6fc0e99898bb5cd67d4cad3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Wed, 19 Nov 2025 17:26:15 +0100 Subject: [PATCH 274/546] variants: XIAO NRF52: Support power-off via user button Add the necessary code to properly power-off the Xiao + Wio companions. This way we can achieve around 15 microamps of power consumption in the off state. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/xiao_nrf52/XiaoNrf52Board.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index b229507a..f3766012 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -46,6 +46,24 @@ public: NVIC_SystemReset(); } + void powerOff() override { + // set led on and wait for button release before poweroff + digitalWrite(PIN_LED, LOW); +#ifdef PIN_USER_BTN + while(digitalRead(PIN_USER_BTN) == LOW); +#endif + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_BLUE, HIGH); + digitalWrite(PIN_LED, HIGH); + +#ifdef PIN_USER_BTN + // configure button press to wake up when in powered off state + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); +#endif + + sd_power_system_off(); + } + bool startOTAUpdate(const char* id, char reply[]) override; }; From 048bd268a100bb1aad4dbfaff0a5d51cff242695 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Thu, 20 Nov 2025 10:58:14 +0100 Subject: [PATCH 275/546] companion: ui: Respect LED_STATE_ON for status LED The current logic only works for active high LEDs. Some boards need an active low level control and therefore they set LED_STATE_ON to 0. Take this into account and use the correct LED pattern for both cases. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- examples/companion_radio/ui-new/UITask.cpp | 2 +- examples/companion_radio/ui-orig/UITask.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index d8d778c3..f12667c7 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -618,7 +618,7 @@ void UITask::userLedHandler() { led_state = 0; next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment; } - digitalWrite(PIN_STATUS_LED, led_state); + digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON); } #endif } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 89dda116..1fef8572 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -269,7 +269,7 @@ void UITask::userLedHandler() { state = 0; next_change = cur_time + LED_CYCLE_MILLIS - last_increment; } - digitalWrite(PIN_STATUS_LED, state); + digitalWrite(PIN_STATUS_LED, state == LED_STATE_ON); } #endif } From 5235516dc7033f69d285eb91a86b49fce3378636 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Thu, 20 Nov 2025 10:59:37 +0100 Subject: [PATCH 276/546] variants: XIAO NRF52: Enable status LED Fix the active state of the LEDs (active low) and enable the status LED. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/xiao_nrf52/variant.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index c888a833..3f4d7afe 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -34,8 +34,9 @@ extern "C" #define LED_RED (11) #define LED_GREEN (13) #define LED_BLUE (12) +#define PIN_STATUS_LED (LED_BLUE) -#define LED_STATE_ON (1) // State when LED is litted +#define LED_STATE_ON (0) // State when LED is on // Buttons #define PIN_BUTTON1 (0) From 32d622d96964881463c9f7bbdd5b301862078da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20=C5=A0karvada?= <jskarvad@redhat.com> Date: Sat, 22 Nov 2025 02:06:44 +0100 Subject: [PATCH 277/546] variants: Heltec T114: Disable LED and GPS when powering off MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This should reduce power consumption in hibernation. Signed-off-by: Jaroslav Škarvada <jskarvad@redhat.com> --- variants/heltec_t114/T114Board.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 49d1ec37..0f7fc47f 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -48,6 +48,13 @@ public: } void powerOff() override { + #ifdef LED_PIN + digitalWrite(LED_PIN, HIGH); + #endif + #if ENV_INCLUDE_GPS == 1 + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, LOW); + #endif sd_power_system_off(); } From 7723a4cb34e5a8b380e378fb2c2170975dc85efe Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Sat, 22 Nov 2025 10:31:47 +0100 Subject: [PATCH 278/546] variants: Heltec T114: Enable DC/DC regulator According to the documentation and experiments on other boards using NRF52 + SX1262 this reduces the power consumption significantly. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/heltec_t114/T114Board.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index f8d170b5..f46a1a84 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -21,6 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void T114Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; + NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); From d84e61546692be65c5c2f311fe4057b95b445ca1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Sun, 23 Nov 2025 14:25:38 +0000 Subject: [PATCH 279/546] Add devcontainer configuration for vscode --- .devcontainer/devcontainer.json | 42 +++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .devcontainer/devcontainer.json diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 00000000..b734fe6b --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,42 @@ +{ + "name": "MeshCore", + "image": "mcr.microsoft.com/devcontainers/python:3-bookworm", + "features": { + "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { + "packages": [ + "sudo" + ] + } + }, + "runArgs": [ + "--network=host", + "--privileged", + "--volume", + "/dev/bus/usb:/dev/bus/usb" + ], + "postCreateCommand": { + "platformio": "pipx install platformio" + }, + "customizations": { + "vscode": { + "settings": { + "platformio-ide.disablePIOHomeStartup": true, + "editor.formatOnSave": false, + "workbench.colorCustomizations": { + "titleBar.activeBackground": "#0d1a2b", + "titleBar.activeForeground": "#ffffff", + "titleBar.inactiveBackground": "#0d1a2b99", + "titleBar.inactiveForeground": "#ffffff99" + } + }, + "extensions": [ + "platformio.platformio-ide", + "github.vscode-github-actions", + "GitHub.vscode-pull-request-github" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] + } + } +} \ No newline at end of file From dc58f0ea83ad12653075fef31b9afe4175d1671d Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 24 Nov 2025 22:56:55 +1100 Subject: [PATCH 280/546] * BUG FIX: repeater remote admin, flood login should invalidate the client->out_path --- examples/simple_repeater/MyMesh.cpp | 8 ++++++-- examples/simple_repeater/MyMesh.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 091d7901..a9be6c40 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -82,7 +82,7 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn #endif } -uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client = NULL; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); @@ -123,6 +123,10 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } } + if (is_flood) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -438,7 +442,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request - reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index d8a20486..98bce787 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -113,7 +113,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 0e903de72cf6740ab5be070b3fd97dd0e0b3884f Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 25 Nov 2025 15:09:51 +1100 Subject: [PATCH 281/546] * BUG FIX: same remote login fix as repeater --- examples/simple_sensor/SensorMesh.cpp | 8 ++++++-- examples/simple_sensor/SensorMesh.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 96a3791d..6116c4de 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -326,7 +326,7 @@ int SensorMesh::getAGCResetInterval() const { return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds } -uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) { ClientInfo* client; if (data[0] == 0) { // blank password, just check if sender is in ACL client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); @@ -359,6 +359,10 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } + if (is_flood) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp reply_data[4] = RESP_SERVER_LOGIN_OK; @@ -451,7 +455,7 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con data[len] = 0; // ensure null terminator uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request - reply_len = handleLoginReq(sender, secret, timestamp, &data[4]); + reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes // TODO } else { diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 00d9c698..627ef549 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -148,7 +148,7 @@ private: uint8_t pending_sf; uint8_t pending_cr; - uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 30ccc1fa0148c7af036cd05a8eecd444f644b83b Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 25 Nov 2025 15:12:48 +1100 Subject: [PATCH 282/546] * BUG FIX: remote login fix same as repeater --- examples/simple_room_server/MyMesh.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 7b575e6f..89505a3b 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -332,6 +332,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); } + if (packet->isRouteFlood()) { + client->out_path_len = -1; // need to rediscover out_path + } + uint32_t now = getRTCClock()->getCurrentTimeUnique(); memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp // TODO: maybe reply with count of messages waiting to be synced for THIS client? From eafbd85d1706698cbc09ca674d49743a9bda4445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Tue, 25 Nov 2025 11:53:21 +0000 Subject: [PATCH 283/546] Add RAK4631 support for rs232 bridge --- src/helpers/bridges/RS232Bridge.cpp | 2 ++ variants/rak4631/platformio.ini | 44 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 554e8fcc..77332855 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -15,6 +15,8 @@ void RS232Bridge::begin() { #if defined(ESP32) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); +#elif defined(RAK_4631) + ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 7db67abf..37800c06 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -45,6 +45,50 @@ build_src_filter = ${rak4631.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> +[env:RAK_4631_repeater_bridge_rs232_tx0_rx0] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=19 + -D WITH_RS232_BRIDGE_TX=20 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater> + +[env:RAK_4631_repeater_bridge_rs232_tx1_rx1] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial1 + -D WITH_RS232_BRIDGE_RX=15 + -D WITH_RS232_BRIDGE_TX=16 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater> + [env:RAK_4631_room_server] extends = rak4631 build_flags = From baedddb25dead095d439ce458ed7439f10ffde44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Tue, 25 Nov 2025 16:19:28 +0000 Subject: [PATCH 284/546] Rename RS232 bridge environments and update build flags for Serial1 and Serial2 --- variants/rak4631/platformio.ini | 51 +++++++++++++++++---------------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 37800c06..b3357855 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -45,29 +45,7 @@ build_src_filter = ${rak4631.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> -[env:RAK_4631_repeater_bridge_rs232_tx0_rx0] -extends = rak4631 -build_flags = - ${rak4631.build_flags} - -D DISPLAY_CLASS=SSD1306Display - -D ADVERT_NAME='"RS232 Bridge"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 - -D WITH_RS232_BRIDGE=Serial2 - -D WITH_RS232_BRIDGE_RX=19 - -D WITH_RS232_BRIDGE_TX=20 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -; -D CORE_DEBUG_LEVEL=3 -build_src_filter = ${rak4631.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> - +<helpers/bridges/RS232Bridge.cpp> - +<../examples/simple_repeater> - -[env:RAK_4631_repeater_bridge_rs232_tx1_rx1] +[env:RAK_4631_repeater_bridge_rs232_serial1] extends = rak4631 build_flags = ${rak4631.build_flags} @@ -78,8 +56,31 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 -D WITH_RS232_BRIDGE=Serial1 - -D WITH_RS232_BRIDGE_RX=15 - -D WITH_RS232_BRIDGE_TX=16 + -D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX + -D WITH_RS232_BRIDGE_TX=PIN_SERIAL1_TX + -UENV_INCLUDE_GPS +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D CORE_DEBUG_LEVEL=3 +build_src_filter = ${rak4631.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater> + +[env:RAK_4631_repeater_bridge_rs232_serial2] +extends = rak4631 +build_flags = + ${rak4631.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX + -D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 5b7d73866cd16d3248fb4598ab0e0185414efdc4 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Tue, 25 Nov 2025 19:41:01 +0100 Subject: [PATCH 285/546] fix building issues with heltec wireless paper and heltec tracker --- variants/heltec_tracker/platformio.ini | 8 ++++++++ variants/heltec_tracker/target.h | 2 +- variants/heltec_wireless_paper/platformio.ini | 12 ++++++++++-- variants/heltec_wireless_paper/target.h | 2 +- 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 4f48ac21..797eafdc 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -6,6 +6,14 @@ build_flags = -I variants/heltec_tracker -D HELTEC_LORA_V3 -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 8ac5eb72..23fab16e 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/HeltecV3Board.h> +#include <../heltec_v3/HeltecV3Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/SensorManager.h> diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index c9ad758b..9cf76153 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -5,12 +5,20 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER + -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=RADIOLIB_NC + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D P_LORA_TX_LED=18 - ; -D PIN_BOARD_SDA=17 - ; -D PIN_BOARD_SCL=18 + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=45 -D PIN_VBAT_READ=20 diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index 65b972d0..b89c486f 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include <helpers/HeltecV3Board.h> +#include <../heltec_v3/HeltecV3Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/SensorManager.h> From e98c79ae480190e9452aa45f33de1d4a1aa47fba Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Tue, 25 Nov 2025 19:45:51 +0100 Subject: [PATCH 286/546] added missing NonBlockingRTTTL dependency, added USB and WIFI companions --- variants/thinknode_m2/platformio.ini | 41 +++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index fb691d92..b2ebca73 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -145,10 +145,49 @@ build_src_filter = ${ThinkNode_M2.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> - lib_deps = ${ThinkNode_M2.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M2_companion_radio_usb] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M2_companion_radio_wifi] +extends = ThinkNode_M2 +build_flags = + ${ThinkNode_M2.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M2.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 [env:ThinkNode_M2_companion_radio_serial] extends = ThinkNode_M2 From 6c7b5390e2d8fdcfacebb358d4740b06d9ad4d29 Mon Sep 17 00:00:00 2001 From: zaquaz <zaquaz@zaquaz.com> Date: Wed, 26 Nov 2025 18:37:56 -0800 Subject: [PATCH 287/546] Remove default setting, since it is handled in MyMesh --- examples/companion_radio/DataStore.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 058389fe..00e25cb2 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -200,11 +200,6 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no File file = openRead(_fs, filename); if (file) { uint8_t pad[8]; - - // Initialize defaults for any missing fields (backward compatibility) - memset(&_prefs, 0, sizeof(_prefs)); - node_lat = 0.0; - node_lon = 0.0; file.read((uint8_t *)&_prefs.airtime_factor, sizeof(float)); // 0 file.read((uint8_t *)_prefs.node_name, sizeof(_prefs.node_name)); // 4 From 3ddfdd477b0402eaa71d1600770d9e50731b1fa7 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 27 Nov 2025 21:34:52 +1100 Subject: [PATCH 288/546] Revert "add heltec_v4 tft expansion box" This reverts commit 310618e6899337b37fb3a9268f8e6571bf1f760c. --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 267 ++++----------------------- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 45 insertions(+), 268 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 4e7fd10a..c9da0cf8 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,10 +7,6 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { - if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); - _isOn = true; - } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -19,18 +15,12 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); - _isOn = true; - } + _isOn = true; } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - if (_isOn) { - if (_peripher_power) _peripher_power->release(); - _isOn = false; - } + _isOn = false; } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index d843da85..1a3a9602 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,7 +5,6 @@ #include <Adafruit_GFX.h> #define SSD1306_NO_SPLASH #include <Adafruit_SSD1306.h> -#include <helpers/RefCountedDigitalPin.h> #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -19,16 +18,10 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; - RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), - display(128, 64, &Wire, PIN_OLED_RESET), - _peripher_power(peripher_power) - { - _isOn = false; - } + SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index a686c0c8..87f9b8ad 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -25,13 +25,10 @@ bool ST7789LCDDisplay::begin() { pinMode(PIN_TFT_LEDA_CTL, OUTPUT); digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - pinMode(PIN_TFT_RST, OUTPUT); - digitalWrite(PIN_TFT_RST, LOW); - delay(10); digitalWrite(PIN_TFT_RST, HIGH); // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #ifdef LILYGO_TDECK displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index 5b960ca1..a8077148 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include <helpers/RefCountedDigitalPin.h> class ST7789LCDDisplay : public DisplayDriver { - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #ifdef LILYGO_TDECK SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ public: { _isOn = false; } -#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) +#elif LILYGO_TDECK ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 92f93437..f143db36 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,9 +86,5 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - #ifdef HELTEC_LORA_V4_TFT - return "Heltec V4 TFT"; - #else - return "Heltec V4 OLED"; - #endif + return "Heltec V4"; } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ba759009..c26a5bc6 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,9 +20,11 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=LOW + -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -45,44 +47,10 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[heltec_v4_oled] -extends = Heltec_lora32_v4 -build_flags = - ${Heltec_lora32_v4.build_flags} - -D HELTEC_LORA_V4_OLED - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 -build_src_filter= ${Heltec_lora32_v4.build_src_filter} -lib_deps = ${Heltec_lora32_v4.lib_deps} - -[heltec_v4_tft] -extends = Heltec_lora32_v4 -build_flags = - ${Heltec_lora32_v4.build_flags} - -D HELTEC_LORA_V4_TFT - -D PIN_BOARD_SDA=4 - -D PIN_BOARD_SCL=3 - -D DISPLAY_SCALE_X=2.5 - -D DISPLAY_SCALE_Y=3.75 - -D PIN_TFT_RST=18 - -D PIN_TFT_VDD_CTL=-1 - -D PIN_TFT_LEDA_CTL=21 - -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH - -D PIN_TFT_CS=15 - -D PIN_TFT_DC=16 - -D PIN_TFT_SCL=17 - -D PIN_TFT_SDA=33 -build_src_filter= ${Heltec_lora32_v4.build_src_filter} -lib_deps = - ${Heltec_lora32_v4.lib_deps} - adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 - [env:heltec_v4_repeater] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -91,18 +59,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -113,18 +81,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -133,50 +101,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_room_server> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -187,20 +155,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} + ${Heltec_lora32_v4.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -210,21 +178,21 @@ build_flags = -D WIFI_PWD='"mypwd"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${heltec_v4_oled.lib_deps} + ${Heltec_lora32_v4.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = heltec_v4_oled +extends = Heltec_lora32_v4 build_flags = - ${heltec_v4_oled.build_flags} - -D ADVERT_NAME='"Heltec v4 Sensor"' + ${Heltec_lora32_v4.build_flags} + -D ADVERT_NAME='"Heltec v3 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -233,172 +201,9 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_oled.build_src_filter} +build_src_filter = ${Heltec_lora32_v4.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_sensor> lib_deps = - ${heltec_v4_oled.lib_deps} - ${esp32_ota.lib_deps} - - -[env:heltec_v4_tft_repeater] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"Heltec Repeater"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/ui/ST7789LCDDisplay.cpp> - +<../examples/simple_repeater> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - bakercp/CRC32 @ ^2.0.0 - - -[env:heltec_v4_tft_repeater_bridge_espnow] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"ESPNow Bridge"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 - -D WITH_ESPNOW_BRIDGE=1 -; -D BRIDGE_DEBUG=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/bridges/ESPNowBridge.cpp> - +<helpers/ui/ST7789LCDDisplay.cpp> - +<../examples/simple_repeater> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - -[env:heltec_v4_tft_room_server] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D DISPLAY_CLASS=ST7789LCDDisplay - -D ADVERT_NAME='"Heltec Room"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ROOM_PASSWORD='"hello"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/ui/ST7789LCDDisplay.cpp> - +<../examples/simple_room_server> -lib_deps = - ${heltec_v4_tft.lib_deps} - ${esp32_ota.lib_deps} - -[env:heltec_v4_tft_terminal_chat] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_usb] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=ST7789LCDDisplay -; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 -; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/ui/ST7789LCDDisplay.cpp> - +<helpers/ui/MomentaryButton.cpp> - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_ble] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D DISPLAY_CLASS=ST7789LCDDisplay - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 ; dynamic, random PIN - -D AUTO_SHUTDOWN_MILLIVOLTS=3400 - -D BLE_DEBUG_LOGGING=1 - -D OFFLINE_QUEUE_SIZE=256 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/ui/ST7789LCDDisplay.cpp> - +<helpers/ui/MomentaryButton.cpp> - +<helpers/esp32/*.cpp> - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_companion_radio_wifi] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=ST7789LCDDisplay - -D WIFI_DEBUG_LOGGING=1 - -D WIFI_SSID='"myssid"' - -D WIFI_PWD='"mypwd"' -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/ui/ST7789LCDDisplay.cpp> - +<helpers/ui/MomentaryButton.cpp> - +<helpers/esp32/*.cpp> - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${heltec_v4_tft.lib_deps} - densaugeo/base64 @ ~1.4.0 - -[env:heltec_v4_tft_sensor] -extends = heltec_v4_tft -build_flags = - ${heltec_v4_tft.build_flags} - -D ADVERT_NAME='"Heltec v4 Sensor"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ENV_PIN_SDA=3 - -D ENV_PIN_SCL=4 - -D DISPLAY_CLASS=ST7789LCDDisplay -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 -build_src_filter = ${heltec_v4_tft.build_src_filter} - +<helpers/ui/ST7789LCDDisplay.cpp> - +<../examples/simple_sensor> -lib_deps = - ${heltec_v4_tft.lib_deps} + ${Heltec_lora32_v4.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 0d2bd497..015c3a8e 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display(&(board.periph_power)); + DISPLAY_CLASS display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index 00d2adab..a153b2af 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,11 +9,7 @@ #include <helpers/SensorManager.h> #include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS -#ifdef HELTEC_LORA_V4_OLED - #include <helpers/ui/SSD1306Display.h> -#elif defined(HELTEC_LORA_V4_TFT) - #include <helpers/ui/ST7789LCDDisplay.h> -#endif + #include <helpers/ui/SSD1306Display.h> #include <helpers/ui/MomentaryButton.h> #endif From d0f6def4f9b4d03ea226a9b2246aa594081a5049 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Thu, 27 Nov 2025 21:49:04 +0100 Subject: [PATCH 289/546] thinknode_m5: initial port --- src/helpers/ui/GxEPDDisplay.cpp | 8 + variants/thinknode_m5/ThinknodeM5Board.cpp | 40 ++++ variants/thinknode_m5/ThinknodeM5Board.h | 18 ++ variants/thinknode_m5/pins_arduino.h | 28 +++ variants/thinknode_m5/platformio.ini | 217 +++++++++++++++++++++ variants/thinknode_m5/target.cpp | 57 ++++++ variants/thinknode_m5/target.h | 32 +++ variants/thinknode_m5/variant.h | 21 ++ 8 files changed, 421 insertions(+) create mode 100644 variants/thinknode_m5/ThinknodeM5Board.cpp create mode 100644 variants/thinknode_m5/ThinknodeM5Board.h create mode 100644 variants/thinknode_m5/pins_arduino.h create mode 100644 variants/thinknode_m5/platformio.ini create mode 100644 variants/thinknode_m5/target.cpp create mode 100644 variants/thinknode_m5/target.h create mode 100644 variants/thinknode_m5/variant.h diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index 34e31e30..a8a9b209 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -5,9 +5,17 @@ #define DISPLAY_ROTATION 3 #endif +#ifdef ESP32 + SPIClass SPI1 = SPIClass(FSPI); +#endif + bool GxEPDDisplay::begin() { display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#ifdef ESP32 + SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS); +#else SPI1.begin(); +#endif display.init(115200, true, 2, false); display.setRotation(DISPLAY_ROTATION); setTextSize(1); // Default to size 1 diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp new file mode 100644 index 00000000..64744019 --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -0,0 +1,40 @@ +#include "ThinknodeM5Board.h" + + + +void ThinknodeM5Board::begin() { + pinMode(PIN_VEXT_EN, OUTPUT); + digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle + delay(20); // allow power rail to discharge + digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on + delay(120); // give display time to bias on cold boot + ESP32Board::begin(); + pinMode(PIN_STATUS_LED, OUTPUT); // init power led + } + + void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_deep_sleep_start(); + } + + void ThinknodeM5Board::powerOff() { + enterDeepSleep(0); + } + + uint16_t ThinknodeM5Board::getBattMilliVolts() { + analogReadResolution(12); + analogSetPinAttenuation(PIN_VBAT_READ, ADC_11db); + + uint32_t mv = 0; + for (int i = 0; i < 8; ++i) { + mv += analogReadMilliVolts(PIN_VBAT_READ); + delayMicroseconds(200); + } + mv /= 8; + + analogReadResolution(10); + return static_cast<uint16_t>(mv * ADC_MULTIPLIER ); +} + + const char* ThinknodeM5Board::getManufacturerName() const { + return "Elecrow ThinkNode M2"; + } diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h new file mode 100644 index 00000000..58a3ae30 --- /dev/null +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -0,0 +1,18 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/RefCountedDigitalPin.h> +#include <helpers/ESP32Board.h> +#include <driver/rtc_io.h> + +class ThinknodeM5Board : public ESP32Board { + +public: + + void begin(); + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override ; + +}; \ No newline at end of file diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h new file mode 100644 index 00000000..a5dee363 --- /dev/null +++ b/variants/thinknode_m5/pins_arduino.h @@ -0,0 +1,28 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include <stdint.h> +#include <variant.h> + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = GPS_TX; +static const uint8_t RX = GPS_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = P_LORA_NSS; +static const uint8_t SCK = P_LORA_SCLK; +static const uint8_t MOSI = P_LORA_MISO; +static const uint8_t MISO = P_LORA_MOSI; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = PIN_BOARD_SCL; +static const uint8_t SDA = PIN_BOARD_SDA; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini new file mode 100644 index 00000000..54ad8bd5 --- /dev/null +++ b/variants/thinknode_m5/platformio.ini @@ -0,0 +1,217 @@ +[ThinkNode_M5] +extends = esp32_base +board = ESP32-S3-WROOM-1-N4 +build_flags = ${esp32_base.build_flags} + -I variants/thinknode_m5 + -D THINKNODE_M5 + -D GPS_RX=19 + -D GPS_TX=20 + -D PIN_VEXT_EN=46 + -D PIN_BUZZER=9 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_BOARD_SCL=1 + -D PIN_BOARD_SDA=2 + -D P_LORA_DIO_1=4 + -D P_LORA_NSS=17 + -D P_LORA_RESET=6 ; RADIOLIB_NC + -D P_LORA_BUSY=5 ; DIO2 = 38 + -D P_LORA_SCLK=16 + -D P_LORA_MISO=7 + -D P_LORA_MOSI=15 + -D PIN_USER_BTN=21 + -D PIN_STATUS_LED=1 + -D LED_STATE_ON=HIGH + -D PIN_LED=3 + -D DISPLAY_ROTATION=4 + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 + -D EINK_SCALE_X=1.5625f + -D EINK_SCALE_Y=1.5625f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + -D MESH_DEBUG=1 +build_src_filter = ${esp32_base.build_src_filter} + +<helpers/ui/MomentaryButton.cpp> + +<helpers/ui/GxEPDDisplay.cpp> + +<helpers/ui/buzzer.cpp> + +<../variants/thinknode_m5> +lib_deps = ${esp32_base.lib_deps} + zinggjm/GxEPD2 @ 1.6.2 + bakercp/CRC32 @ ^2.0.0 + +[env:ThinkNode_M5_Repeater] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +; [env:ThinkNode_M5_Repeater_bridge_rs232] +; extends = ThinkNode_M5 +; build_src_filter = ${ThinkNode_M5.build_src_filter} +; +<helpers/bridges/RS232Bridge.cpp> +; +<../examples/simple_repeater/*.cpp> +; build_flags = +; ${ThinkNode_M5.build_flags} +; -D ADVERT_NAME='"RS232 Bridge"' +; -D ADVERT_LAT=0.0 +; -D ADVERT_LON=0.0 +; -D ADMIN_PASSWORD='"password"' +; -D MAX_NEIGHBOURS=8 +; -D WITH_RS232_BRIDGE=Serial2 +; -D WITH_RS232_BRIDGE_RX=5 +; -D WITH_RS232_BRIDGE_TX=6 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; lib_deps = +; ${ThinkNode_M5.lib_deps} +; ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_Repeater_bridge_espnow] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<../examples/simple_repeater/*.cpp> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=8 + -D WITH_ESPNOW_BRIDGE=1 + -D WITH_ESPNOW_BRIDGE_SECRET='"shared-secret"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_room_server] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${ThinkNode_M5.build_flags} + -D ADVERT_NAME='"Thinknode M2 Room Server"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${ThinkNode_M5.lib_deps} + ${esp32_ota.lib_deps} + +[env:ThinkNode_M5_terminal_chat] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M5_companion_radio_ble] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_usb] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_wifi] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"Livebox-633C"' + -D WIFI_PWD='"vvQUHGSxsWd7fKMYSr"' +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_companion_radio_serial] +extends = ThinkNode_M5 +build_flags = + ${ThinkNode_M5.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D SERIAL_TX=D6 + -D SERIAL_RX=D7 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M5.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp new file mode 100644 index 00000000..c65696c2 --- /dev/null +++ b/variants/thinknode_m5/target.cpp @@ -0,0 +1,57 @@ +#include <Arduino.h> +#include "target.h" + +ThinknodeM5Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); +// pinMode(21, INPUT); +// pinMode(48, OUTPUT); + #if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h new file mode 100644 index 00000000..75b68ae4 --- /dev/null +++ b/variants/thinknode_m5/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +//#include <helpers/ESP32Board.h> +#include <ThinknodeM5Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/GxEPDDisplay.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern ThinknodeM5Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); + + \ No newline at end of file diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h new file mode 100644 index 00000000..6118bd75 --- /dev/null +++ b/variants/thinknode_m5/variant.h @@ -0,0 +1,21 @@ +#define I2C_SCL 1 +#define I2C_SDA 2 +#define PIN_VBAT_READ 8 +#define AREF_VOLTAGE (3.0) +#define ADC_MULTIPLIER (2.11F) +#define PIN_BUZZER 9 +#define PIN_VEXT_EN_ACTIVE HIGH +#define PIN_VEXT_EN 46 +#define PIN_USER_BTN 21 +#define PIN_LED 3 +#define PIN_STATUS_LED 1 +#define PIN_PWRBTN 14 + +#define PIN_DISPLAY_MISO (-1) +#define PIN_DISPLAY_MOSI (45) +#define PIN_DISPLAY_SCLK (38) +#define PIN_DISPLAY_CS (39) +#define PIN_DISPLAY_DC (40) +#define PIN_DISPLAY_RST (41) +#define PIN_DISPLAY_BUSY (42) +#define DISP_BACKLIGHT (5) \ No newline at end of file From 24edd3cf209b9a8362d103b535d2f18c7958ae56 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Thu, 27 Nov 2025 22:55:21 +0100 Subject: [PATCH 290/546] thinknode_m5: add pca9557 expander --- variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 7 ++++--- variants/thinknode_m5/target.cpp | 6 ++++++ variants/thinknode_m5/target.h | 3 +++ variants/thinknode_m5/variant.h | 6 +++--- 5 files changed, 17 insertions(+), 8 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 64744019..2aabb0e4 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,7 +1,6 @@ #include "ThinknodeM5Board.h" - void ThinknodeM5Board::begin() { pinMode(PIN_VEXT_EN, OUTPUT); digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle @@ -9,7 +8,7 @@ void ThinknodeM5Board::begin() { digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on delay(120); // give display time to bias on cold boot ESP32Board::begin(); - pinMode(PIN_STATUS_LED, OUTPUT); // init power led + // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 54ad8bd5..a78b1ba2 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -19,9 +19,9 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 - -D PIN_STATUS_LED=1 - -D LED_STATE_ON=HIGH - -D PIN_LED=3 +# -D PIN_STATUS_LED=1 ; leds are on PCA !!! +# -D LED_STATE_ON=HIGH +# -D PIN_LED=3 -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -45,6 +45,7 @@ build_src_filter = ${esp32_base.build_src_filter} lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 + maxpromer/PCA9557-arduino [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index c65696c2..2c388b58 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,6 +15,7 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; +PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -26,6 +27,11 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); + Wire1.begin(48, 47); + expander.pinMode(4, OUTPUT); // eink + expander.pinMode(5, OUTPUT); // peripherals + expander.digitalWrite(4, HIGH); + expander.digitalWrite(5, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 75b68ae4..7fa749f8 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,11 +12,14 @@ #include <helpers/ui/GxEPDDisplay.h> #include <helpers/ui/MomentaryButton.h> #endif +#include <Wire.h> +#include <PCA9557.h> extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; +extern PCA9557 expander; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 6118bd75..d312fcbf 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -7,8 +7,8 @@ #define PIN_VEXT_EN_ACTIVE HIGH #define PIN_VEXT_EN 46 #define PIN_USER_BTN 21 -#define PIN_LED 3 -#define PIN_STATUS_LED 1 +//#define PIN_LED 3 +//#define PIN_STATUS_LED 1 #define PIN_PWRBTN 14 #define PIN_DISPLAY_MISO (-1) @@ -18,4 +18,4 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -#define DISP_BACKLIGHT (5) \ No newline at end of file +//#define DISP_BACKLIGHT (5) \ No newline at end of file From dfec6d3483432159c34d734ad1d31b60f3e6b28b Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Fri, 28 Nov 2025 09:57:58 +0100 Subject: [PATCH 291/546] thinknode_m5: tx_led --- variants/thinknode_m5/ThinknodeM5Board.cpp | 16 ++++++++++------ variants/thinknode_m5/ThinknodeM5Board.h | 9 +++++++++ variants/thinknode_m5/platformio.ini | 4 ++-- variants/thinknode_m5/target.cpp | 8 ++------ variants/thinknode_m5/target.h | 2 -- variants/thinknode_m5/variant.h | 3 ++- 6 files changed, 25 insertions(+), 17 deletions(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 2aabb0e4..f178caad 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -1,14 +1,18 @@ #include "ThinknodeM5Board.h" +PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - pinMode(PIN_VEXT_EN, OUTPUT); - digitalWrite(PIN_VEXT_EN, !PIN_VEXT_EN_ACTIVE); // force power cycle - delay(20); // allow power rail to discharge - digitalWrite(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE); // turn backlight back on - delay(120); // give display time to bias on cold boot + // Start expander + Wire1.begin(48, 47); + expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink + expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals + expander.pinMode(EXP_PIN_LED, OUTPUT); // peripherals + expander.digitalWrite(EXP_PIN_POWER, HIGH); + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); + expander.digitalWrite(EXP_PIN_LED, LOW); + ESP32Board::begin(); - // pinMode(PIN_STATUS_LED, OUTPUT); // init power led } void ThinknodeM5Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/thinknode_m5/ThinknodeM5Board.h b/variants/thinknode_m5/ThinknodeM5Board.h index 58a3ae30..3c120027 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.h +++ b/variants/thinknode_m5/ThinknodeM5Board.h @@ -4,6 +4,9 @@ #include <helpers/RefCountedDigitalPin.h> #include <helpers/ESP32Board.h> #include <driver/rtc_io.h> +#include <PCA9557.h> + +extern PCA9557 expander; class ThinknodeM5Board : public ESP32Board { @@ -15,4 +18,10 @@ public: uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; + void onBeforeTransmit() override { + expander.digitalWrite(EXP_PIN_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + expander.digitalWrite(EXP_PIN_LED, LOW); // turn TX LED off + } }; \ No newline at end of file diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index a78b1ba2..11f56377 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -6,11 +6,10 @@ build_flags = ${esp32_base.build_flags} -D THINKNODE_M5 -D GPS_RX=19 -D GPS_TX=20 - -D PIN_VEXT_EN=46 -D PIN_BUZZER=9 - -D PIN_VEXT_EN_ACTIVE=HIGH -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 + -D P_LORA_EN=46 -D P_LORA_DIO_1=4 -D P_LORA_NSS=17 -D P_LORA_RESET=6 ; RADIOLIB_NC @@ -19,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH # -D PIN_LED=3 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index 2c388b58..fdc5ca7a 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -15,7 +15,6 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); SensorManager sensors; -PCA9557 expander (0x18, &Wire1); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -27,11 +26,8 @@ bool radio_init() { rtc_clock.begin(Wire); // pinMode(21, INPUT); // pinMode(48, OUTPUT); - Wire1.begin(48, 47); - expander.pinMode(4, OUTPUT); // eink - expander.pinMode(5, OUTPUT); // peripherals - expander.digitalWrite(4, HIGH); - expander.digitalWrite(5, HIGH); + pinMode(P_LORA_EN, OUTPUT); + digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); return radio.std_init(&spi); diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 7fa749f8..c3584a70 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -12,8 +12,6 @@ #include <helpers/ui/GxEPDDisplay.h> #include <helpers/ui/MomentaryButton.h> #endif -#include <Wire.h> -#include <PCA9557.h> extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index d312fcbf..7ee5f5cc 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -18,4 +18,5 @@ #define PIN_DISPLAY_DC (40) #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) -//#define DISP_BACKLIGHT (5) \ No newline at end of file +#define EXP_PIN_BACKLIGHT (5) +#define EXP_PIN_POWER (4) \ No newline at end of file From ee4e87c3ee545821a9b017e73525a3f943d9f2ba Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Fri, 28 Nov 2025 10:33:19 +0100 Subject: [PATCH 292/546] thinknode_m5: manage baclight --- examples/companion_radio/ui-new/UITask.cpp | 6 +++++- src/helpers/ui/GxEPDDisplay.cpp | 9 +++++++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 3 +-- variants/thinknode_m5/platformio.ini | 3 +++ variants/thinknode_m5/target.cpp | 2 -- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 16751d20..fe26277f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -716,10 +716,14 @@ void UITask::loop() { _analogue_pin_read_millis = millis(); } #endif -#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN) +#if defined(BACKLIGHT_BTN) if (millis() > next_backlight_btn_check) { bool touch_state = digitalRead(PIN_BUTTON2); +#if defined(DISP_BACKLIGHT) digitalWrite(DISP_BACKLIGHT, !touch_state); +#elif defined(EXP_PIN_BACKLIGHT) + expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state); +#endif next_backlight_btn_check = millis() + 300; } #endif diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index a8a9b209..ad47754b 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -1,6 +1,11 @@ #include "GxEPDDisplay.h" +#ifdef EXP_PIN_BACKLIGHT + #include <PCA9557.h> + extern PCA9557 expander; +#endif + #ifndef DISPLAY_ROTATION #define DISPLAY_ROTATION 3 #endif @@ -35,6 +40,8 @@ void GxEPDDisplay::turnOn() { if (!_init) begin(); #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, HIGH); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH); #endif _isOn = true; } @@ -42,6 +49,8 @@ void GxEPDDisplay::turnOn() { void GxEPDDisplay::turnOff() { #if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN) digitalWrite(DISP_BACKLIGHT, LOW); +#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN) + expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); #endif _isOn = false; } diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index f178caad..916f4483 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -3,7 +3,7 @@ PCA9557 expander (0x18, &Wire1); void ThinknodeM5Board::begin() { - // Start expander + // Start expander and configure pins Wire1.begin(48, 47); expander.pinMode(EXP_PIN_POWER, OUTPUT); // eink expander.pinMode(EXP_PIN_BACKLIGHT, OUTPUT); // peripherals @@ -11,7 +11,6 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); - ESP32Board::begin(); } diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 11f56377..353a9c52 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -18,6 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MISO=7 -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 + -D PIN_BUTTON2=14 -D EXP_PIN_LED=1 # -D PIN_STATUS_LED=1 ; leds are on PCA !!! # -D LED_STATE_ON=HIGH @@ -29,6 +30,8 @@ build_flags = ${esp32_base.build_flags} -D EINK_SCALE_Y=1.5625f -D EINK_X_OFFSET=0 -D EINK_Y_OFFSET=10 + -D BACKLIGHT_BTN=PIN_BUTTON2 + -D AUTO_OFF_MILLIS=0 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fdc5ca7a..fa559610 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -24,8 +24,6 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); -// pinMode(21, INPUT); -// pinMode(48, OUTPUT); pinMode(P_LORA_EN, OUTPUT); digitalWrite(P_LORA_EN, HIGH); #if defined(P_LORA_SCLK) From 1c0017b634a68a12e232c46b2c7b4d4d47759858 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Fri, 28 Nov 2025 11:11:13 +0100 Subject: [PATCH 293/546] thinknode_m5: gps support --- examples/companion_radio/ui-new/UITask.cpp | 15 +++++++++++++-- .../sensors/EnvironmentSensorManager.cpp | 4 ++++ variants/thinknode_m5/ThinknodeM5Board.cpp | 5 +++++ variants/thinknode_m5/pins_arduino.h | 4 ++-- variants/thinknode_m5/platformio.ini | 19 +++++++++++-------- variants/thinknode_m5/target.cpp | 9 ++++++++- variants/thinknode_m5/target.h | 4 +++- variants/thinknode_m5/variant.h | 8 +++++++- 8 files changed, 53 insertions(+), 15 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index fe26277f..59a1b2de 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -260,13 +260,24 @@ public: #if ENV_INCLUDE_GPS == 1 } else if (_page == HomePage::GPS) { LocationProvider* nmea = sensors.getLocationProvider(); + char buf[50]; int y = 18; - display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off"); + bool gps_state = _task->getGPSState(); +#ifdef PIN_GPS_SWITCH + bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); + if (gps_state != hw_gps_state) { + strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); + } else { + strcpy(buf, gps_state ? "gps on" : "gps off"); + } +#else + strcpy(buf, gps_state ? "gps on" : "gps off"); +#endif + display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { y = y + 12; display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { - char buf[50]; strcpy(buf, nmea->isValid()?"fix":"no fix"); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 79dc87e5..bb675c27 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -548,7 +548,11 @@ void EnvironmentSensorManager::initBasicGPS() { delay(1000); // We'll consider GPS detected if we see any data on Serial1 +#ifdef ENV_SKIP_GPS_DETECT + gps_detected = true; +#else gps_detected = (Serial1.available() > 0); +#endif if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 916f4483..5adc8c00 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -11,6 +11,11 @@ void ThinknodeM5Board::begin() { expander.digitalWrite(EXP_PIN_POWER, HIGH); expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW); expander.digitalWrite(EXP_PIN_LED, LOW); + +#ifdef PIN_GPS_SWITCH + pinMode(PIN_GPS_SWITCH, INPUT); +#endif + ESP32Board::begin(); } diff --git a/variants/thinknode_m5/pins_arduino.h b/variants/thinknode_m5/pins_arduino.h index a5dee363..408ed236 100644 --- a/variants/thinknode_m5/pins_arduino.h +++ b/variants/thinknode_m5/pins_arduino.h @@ -12,8 +12,8 @@ #define USB_PID 0x1001 // Serial -static const uint8_t TX = GPS_TX; -static const uint8_t RX = GPS_RX; +static const uint8_t TX = PIN_GPS_TX; +static const uint8_t RX = PIN_GPS_RX; // Default SPI will be mapped to Radio static const uint8_t SS = P_LORA_NSS; diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 353a9c52..23db506a 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,9 +3,8 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 + -I src/helpres/sensors -D THINKNODE_M5 - -D GPS_RX=19 - -D GPS_TX=20 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 -D PIN_BOARD_SDA=2 @@ -19,10 +18,7 @@ build_flags = ${esp32_base.build_flags} -D P_LORA_MOSI=15 -D PIN_USER_BTN=21 -D PIN_BUTTON2=14 - -D EXP_PIN_LED=1 -# -D PIN_STATUS_LED=1 ; leds are on PCA !!! -# -D LED_STATE_ON=HIGH -# -D PIN_LED=3 + -D EXP_PIN_LED=1 ; led is on bus expander -D DISPLAY_ROTATION=4 -D DISPLAY_CLASS=GxEPDDisplay -D EINK_DISPLAY_MODEL=GxEPD2_154_D67 @@ -32,6 +28,7 @@ build_flags = ${esp32_base.build_flags} -D EINK_Y_OFFSET=10 -D BACKLIGHT_BTN=PIN_BUTTON2 -D AUTO_OFF_MILLIS=0 + -D DISABLE_DIAGNOSTIC_OUTPUT -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=3.3 -D SX126X_CURRENT_LIMIT=140 @@ -40,7 +37,11 @@ build_flags = ${esp32_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_RX_BOOSTED_GAIN=1 -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 build_src_filter = ${esp32_base.build_src_filter} + +<helpers/sensors/EnvironmentSensorManager.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/ui/GxEPDDisplay.cpp> +<helpers/ui/buzzer.cpp> @@ -49,6 +50,7 @@ lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 maxpromer/PCA9557-arduino + stevemarple/MicroNMEA @ ^2.0.6 [env:ThinkNode_M5_Repeater] extends = ThinkNode_M5 @@ -109,7 +111,7 @@ lib_deps = ${esp32_ota.lib_deps} [env:ThinkNode_M5_room_server] -extends = ThinkNode_M5 +extends = ThinkNonde_M5 build_src_filter = ${ThinkNode_M5.build_src_filter} +<../examples/simple_room_server> build_flags = @@ -148,9 +150,10 @@ build_flags = -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 + -D UI_RECENT_LIST_SIZE=9 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG build_src_filter = ${ThinkNode_M5.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index fa559610..8208d2c4 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -1,5 +1,6 @@ #include <Arduino.h> #include "target.h" +#include <helpers/sensors/MicroNMEALocationProvider.h> ThinknodeM5Board board; @@ -14,7 +15,13 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif #ifdef DISPLAY_CLASS DISPLAY_CLASS display; diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index c3584a70..2af42095 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -8,6 +8,8 @@ #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> #include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#include <helpers/sensors/LocationProvider.h> #ifdef DISPLAY_CLASS #include <helpers/ui/GxEPDDisplay.h> #include <helpers/ui/MomentaryButton.h> @@ -16,7 +18,7 @@ extern ThinknodeM5Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; extern PCA9557 expander; #ifdef DISPLAY_CLASS diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 7ee5f5cc..9b82416b 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -19,4 +19,10 @@ #define PIN_DISPLAY_RST (41) #define PIN_DISPLAY_BUSY (42) #define EXP_PIN_BACKLIGHT (5) -#define EXP_PIN_POWER (4) \ No newline at end of file +#define EXP_PIN_POWER (4) + +#define PIN_GPS_EN (11) +#define PIN_GPS_RESET (13) +#define PIN_GPS_RX (20) +#define PIN_GPS_TX (19) +#define PIN_GPS_SWITCH (10) \ No newline at end of file From c641beabd32f564f9064a520efd90afd1df524fa Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 16:37:10 +0800 Subject: [PATCH 294/546] https://github.com/meshcore-dev/MeshCore/issues/989 - persist GPS enabled state to preferences Add GPS configuration to NodePrefs structure and persist the GPS enabled state when toggled via UI. This ensures GPS settings are retained across device restarts. --- examples/companion_radio/DataStore.cpp | 4 ++++ examples/companion_radio/MyMesh.cpp | 7 +++++++ examples/companion_radio/NodePrefs.h | 2 ++ examples/companion_radio/ui-new/UITask.cpp | 3 +++ 4 files changed, 16 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 00e25cb2..7f5761f3 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -222,6 +222,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read(pad, 2); // 78 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 + file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.close(); } @@ -254,6 +256,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write(pad, 2); // 78 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 + file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 + file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 09d866c9..a94975b3 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -739,6 +739,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.gps_enabled = 0; // GPS disabled by default + _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed } @@ -776,6 +778,7 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -803,6 +806,10 @@ void MyMesh::begin(bool has_display) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); + +#if ENV_INCLUDE_GPS == 1 + sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); +#endif } const char *MyMesh::getNodeName() { diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 13c9f884..e9db5444 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -25,4 +25,6 @@ struct NodePrefs { // persisted to file uint32_t ble_pin; uint8_t advert_loc_policy; uint8_t buzzer_quiet; + uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) + uint32_t gps_interval; // GPS read interval in seconds }; \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 59a1b2de..4cc47e23 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -863,13 +863,16 @@ void UITask::toggleGPS() { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { _sensors->setSettingValue("gps", "0"); + _node_prefs->gps_enabled = 0; notify(UIEventType::ack); showAlert("GPS: Disabled", 800); } else { _sensors->setSettingValue("gps", "1"); + _node_prefs->gps_enabled = 1; notify(UIEventType::ack); showAlert("GPS: Enabled", 800); } + the_mesh.savePrefs(); _next_refresh = 0; break; } From 88fb173297d667894df4b21faffab84ddea14d7a Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 17:20:02 +0800 Subject: [PATCH 295/546] add configurable GPS update interval Make GPS update interval configurable via settings instead of using hardcoded 1 second value. The interval is persisted from preferences and can be adjusted at runtime through the sensor manager settings interface --- examples/companion_radio/MyMesh.cpp | 3 +++ src/helpers/sensors/EnvironmentSensorManager.cpp | 11 ++++++++++- src/helpers/sensors/EnvironmentSensorManager.h | 1 + 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a94975b3..be39b7fa 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -809,6 +809,9 @@ void MyMesh::begin(bool has_display) { #if ENV_INCLUDE_GPS == 1 sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval_str); #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index bb675c27..53c381f7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -521,6 +521,15 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val } return true; } + if (strcmp(name, "gps_interval") == 0) { + uint32_t interval_seconds = atoi(value); + if (interval_seconds > 0) { + gps_update_interval_ms = interval_seconds * 1000; + } else { + gps_update_interval_ms = 1000; // Default to 1 second if 0 + } + return true; + } #endif return false; // not supported } @@ -708,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + 1000; + next_gps_update = millis() + gps_update_interval_ms; } #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 5f1c08e2..6dd532e6 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -25,6 +25,7 @@ protected: bool gps_detected = false; bool gps_active = false; + uint32_t gps_update_interval_ms = 1000; // Default 1 second #if ENV_INCLUDE_GPS LocationProvider* _location; From 678915ef3b6ad3e9bb398bfa797b5c6f832f73f5 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 17:29:59 +0800 Subject: [PATCH 296/546] add GPS interval validation and bounds checking --- examples/companion_radio/MyMesh.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index be39b7fa..91bb7358 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -779,6 +779,7 @@ void MyMesh::begin(bool has_display) { _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 + _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours #ifdef BLE_PIN_CODE // 123456 by default if (_prefs.ble_pin == 0) { @@ -809,9 +810,11 @@ void MyMesh::begin(bool has_display) { #if ENV_INCLUDE_GPS == 1 sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); + if (_prefs.gps_interval > 0) { + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval_str); + } #endif } From 4aebc57add0c61a37e31bcf18b8a7c5620539785 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 18:02:08 +0800 Subject: [PATCH 297/546] fixed gps init value --- examples/companion_radio/MyMesh.cpp | 9 --------- examples/companion_radio/main.cpp | 10 ++++++++++ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 91bb7358..035e06a7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -807,15 +807,6 @@ void MyMesh::begin(bool has_display) { radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); - -#if ENV_INCLUDE_GPS == 1 - sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - if (_prefs.gps_interval > 0) { - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); - } -#endif } const char *MyMesh::getNodeName() { diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d..15b461f3 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -216,6 +216,16 @@ void setup() { sensors.begin(); +#if ENV_INCLUDE_GPS == 1 + // Apply GPS preferences after sensors.begin() so gps_detected is set + sensors.setSettingValue("gps", the_mesh.getNodePrefs()->gps_enabled ? "1" : "0"); + if (the_mesh.getNodePrefs()->gps_interval > 0) { + char interval_str[12]; + sprintf(interval_str, "%u", the_mesh.getNodePrefs()->gps_interval); + sensors.setSettingValue("gps_interval", interval_str); + } +#endif + #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif From 39503ad0b48515e527d38ff2bf477b207b5bd1b5 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 18:35:10 +0800 Subject: [PATCH 298/546] move GPS preference initialization to UITask --- examples/companion_radio/main.cpp | 10 ---------- examples/companion_radio/ui-new/UITask.cpp | 13 +++++++++++++ 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 15b461f3..82c8c21d 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -216,16 +216,6 @@ void setup() { sensors.begin(); -#if ENV_INCLUDE_GPS == 1 - // Apply GPS preferences after sensors.begin() so gps_detected is set - sensors.setSettingValue("gps", the_mesh.getNodePrefs()->gps_enabled ? "1" : "0"); - if (the_mesh.getNodePrefs()->gps_interval > 0) { - char interval_str[12]; - sprintf(interval_str, "%u", the_mesh.getNodePrefs()->gps_interval); - sensors.setSettingValue("gps_interval", interval_str); - } -#endif - #ifdef DISPLAY_CLASS ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved #endif diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 4cc47e23..ebdeb6d3 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -537,6 +537,19 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no #endif _node_prefs = node_prefs; + +#if ENV_INCLUDE_GPS == 1 + // Apply GPS preferences from stored prefs + if (_sensors != NULL && _node_prefs != NULL) { + _sensors->setSettingValue("gps", _node_prefs->gps_enabled ? "1" : "0"); + if (_node_prefs->gps_interval > 0) { + char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) + sprintf(interval_str, "%u", _node_prefs->gps_interval); + _sensors->setSettingValue("gps_interval", interval_str); + } + } +#endif + if (_display != NULL) { _display->turnOn(); } From 62e180dc0fa1b5af9e55308bc91d60ec1fc98ba8 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 19:02:00 +0800 Subject: [PATCH 299/546] changed ms to sec --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 +++--- src/helpers/sensors/EnvironmentSensorManager.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 53c381f7..2362eda7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -524,9 +524,9 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val if (strcmp(name, "gps_interval") == 0) { uint32_t interval_seconds = atoi(value); if (interval_seconds > 0) { - gps_update_interval_ms = interval_seconds * 1000; + gps_update_interval_sec = interval_seconds; } else { - gps_update_interval_ms = 1000; // Default to 1 second if 0 + gps_update_interval_sec = 1; // Default to 1 second if 0 } return true; } @@ -717,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + gps_update_interval_ms; + next_gps_update = millis() + gps_update_interval_sec; } #endif } diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 6dd532e6..f176a33f 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -25,7 +25,7 @@ protected: bool gps_detected = false; bool gps_active = false; - uint32_t gps_update_interval_ms = 1000; // Default 1 second + uint32_t gps_update_interval_sec = 1; // Default 1 second #if ENV_INCLUDE_GPS LocationProvider* _location; From df3cb3d192ab655b467ecb22e4fb41c3041c1673 Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sat, 29 Nov 2025 20:29:52 +0800 Subject: [PATCH 300/546] _location->loop() should be in the next tick --- src/helpers/sensors/EnvironmentSensorManager.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2362eda7..b072bcb0 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -695,9 +695,9 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS - _location->loop(); - if (millis() > next_gps_update) { + _location->loop(); + if(gps_active){ #ifdef RAK_WISBLOCK_GPS if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { @@ -717,7 +717,7 @@ void EnvironmentSensorManager::loop() { } #endif } - next_gps_update = millis() + gps_update_interval_sec; + next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif } From cfb7ed876ca7a424d7d1007ebb271aa08765e77b Mon Sep 17 00:00:00 2001 From: csrutil <keming.cao@gmail.com> Date: Sun, 30 Nov 2025 09:45:56 +0800 Subject: [PATCH 301/546] CMD_SET_CUSTOM_VAR will update gps and gps_interval --- examples/companion_radio/MyMesh.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 035e06a7..3aed2da7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1525,6 +1525,17 @@ void MyMesh::handleCmdFrame(size_t len) { *np++ = 0; // modify 'cmd_frame', replace ':' with null bool success = sensors.setSettingValue(sp, np); if (success) { + #if ENV_INCLUDE_GPS == 1 + // Update node preferences for GPS settings + if (strcmp(sp, "gps") == 0) { + _prefs.gps_enabled = (np[0] == '1') ? 1 : 0; + savePrefs(); + } else if (strcmp(sp, "gps_interval") == 0) { + uint32_t interval_seconds = atoi(np); + _prefs.gps_interval = constrain(interval_seconds, 0, 86400); + savePrefs(); + } + #endif writeOKFrame(); } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); From e054597a189cd7749c5327351d27e9dc92d0d125 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 30 Nov 2025 18:32:10 +1100 Subject: [PATCH 302/546] * ver 1.11.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 9c22532d..1fcc5697 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "13 Nov 2025" +#define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.10.0" +#define FIRMWARE_VERSION "v1.11.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 98bce787..ed9f0c5f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -68,11 +68,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 8641caaf..e7f1fee8 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 9259ad9c..c320eb44 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "13 Nov 2025" + #define FIRMWARE_BUILD_DATE "30 Nov 2025" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.10.0" + #define FIRMWARE_VERSION "v1.11.0" #endif #define FIRMWARE_ROLE "sensor" From 052f17738cd05e133265f3370f039497311bfe90 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Sun, 30 Nov 2025 10:52:33 +0100 Subject: [PATCH 303/546] add default LED_STATE_ON for boards that don't have it defined --- examples/companion_radio/ui-new/UITask.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 32d5f3a0..02c3cafb 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -8,6 +8,10 @@ #include <Arduino.h> #include <helpers/sensors/LPPDataHelpers.h> +#ifndef LED_STATE_ON + #define LED_STATE_ON 1 +#endif + #ifdef PIN_BUZZER #include <helpers/ui/buzzer.h> #endif @@ -50,7 +54,7 @@ class UITask : public AbstractUITask { UIScreen* curr; void userLedHandler(); - + // Button action handlers char checkDisplayOn(char c); char handleLongPress(char c); From 405f703bfebf5218355d0c07fd9f948ed4c40ef2 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Mon, 1 Dec 2025 09:40:02 +0100 Subject: [PATCH 304/546] thinknode_m5: fix repeater build --- variants/thinknode_m5/platformio.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 23db506a..fb2ba3ac 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,7 +3,7 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 - -I src/helpres/sensors + -I src/helpers/sensors -D THINKNODE_M5 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 @@ -44,7 +44,6 @@ build_src_filter = ${esp32_base.build_src_filter} +<helpers/sensors/EnvironmentSensorManager.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/ui/GxEPDDisplay.cpp> - +<helpers/ui/buzzer.cpp> +<../variants/thinknode_m5> lib_deps = ${esp32_base.lib_deps} zinggjm/GxEPD2 @ 1.6.2 @@ -159,6 +158,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/buzzer.cpp> lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -177,6 +177,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/buzzer.cpp> lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -198,6 +199,7 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/buzzer.cpp> lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -219,6 +221,8 @@ build_src_filter = ${ThinkNode_M5.build_src_filter} +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/buzzer.cpp> lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 From 07d6484b616ef86f340b5faf94bd43fcb415735a Mon Sep 17 00:00:00 2001 From: Florian Lippert <973586+flol@users.noreply.github.com> Date: Mon, 1 Dec 2025 21:29:03 +0100 Subject: [PATCH 305/546] Support for RAK11310 WisBlock --- variants/rak11310/RAK11310Board.cpp | 30 +++++++ variants/rak11310/RAK11310Board.h | 49 +++++++++++ variants/rak11310/platformio.ini | 132 ++++++++++++++++++++++++++++ variants/rak11310/target.cpp | 39 ++++++++ variants/rak11310/target.h | 20 +++++ 5 files changed, 270 insertions(+) create mode 100644 variants/rak11310/RAK11310Board.cpp create mode 100644 variants/rak11310/RAK11310Board.h create mode 100644 variants/rak11310/platformio.ini create mode 100644 variants/rak11310/target.cpp create mode 100644 variants/rak11310/target.h diff --git a/variants/rak11310/RAK11310Board.cpp b/variants/rak11310/RAK11310Board.cpp new file mode 100644 index 00000000..f45d8148 --- /dev/null +++ b/variants/rak11310/RAK11310Board.cpp @@ -0,0 +1,30 @@ +#include "RAK11310Board.h" + +#include <Arduino.h> +#include <Wire.h> + +void RAK11310Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); +#endif + +#ifdef PIN_VBAT_READ + pinMode(PIN_VBAT_READ, INPUT); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setSDA(PIN_BOARD_SDA); + Wire.setSCL(PIN_BOARD_SCL); +#endif + + Wire.begin(); + + delay(10); // give sx1262 some time to power up +} + +bool RAK11310Board::startOTAUpdate(const char *id, char reply[]) { + return false; +} diff --git a/variants/rak11310/RAK11310Board.h b/variants/rak11310/RAK11310Board.h new file mode 100644 index 00000000..ea0f15e2 --- /dev/null +++ b/variants/rak11310/RAK11310Board.h @@ -0,0 +1,49 @@ +#pragma once + +#include <Arduino.h> +#include <MeshCore.h> + +// from https://github.com/RAKWireless/RAK11300-AT-Command-Firmware/blob/9c48409a43620a828d653501d536473200aa33af/RAK11300-AT-Arduino/batt.cpp#L17-L19 +#define VBAT_MV_PER_LSB (0.806F) // 3.0V ADC range and 12 - bit ADC resolution = 3300mV / 4096 +#define VBAT_DIVIDER (0.6F) // 1.5M + 1M voltage divider on VBAT = (1.5M / (1M + 1.5M)) +#define VBAT_DIVIDER_COMP (1.846F) // // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ 26 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class RAK11310Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + void begin(); + uint8_t getStartupReason() const override { return startup_reason; } + +#ifdef P_LORA_TX_LED + void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); } + void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); } +#endif + + uint16_t getBattMilliVolts() override { +#if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw); +#else + return 0; +#endif + } + + const char *getManufacturerName() const override { return "RAK 11310"; } + + void reboot() override { rp2040.reboot(); } + + bool startOTAUpdate(const char *id, char reply[]) override; +}; diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini new file mode 100644 index 00000000..df99ea84 --- /dev/null +++ b/variants/rak11310/platformio.ini @@ -0,0 +1,132 @@ +; RAK11310 +; Pinout from https://github.com/beegee-tokyo/SX126x-Arduino/blob/6be1f87b84ad4d445a38ec53d65be4425f2383f3/src/boards/mcu/board.cpp#L259 + +[rak11310] +extends = rp2040_base +board = rakwireless_rak11300 +board_build.filesystem_size = 0.5m +build_flags = ${rp2040_base.build_flags} + -I variants/rak11310 + -D ARDUINO_RAKWIRELESS_RAK11300=1 + -D SX126X_CURRENT_LIMIT=140 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=29 + -D P_LORA_NSS=13 ; CS + -D P_LORA_RESET=14 + -D P_LORA_BUSY=15 + -D P_LORA_SCLK=10 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + -D P_LORA_TX_LED=24 ; green led = 23, blue led = 24 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 +; Debug options + ; -D DEBUG_RP2040_WIRE=1 + ; -D DEBUG_RP2040_SPI=1 + ; -D DEBUG_RP2040_CORE=1 + ; -D RADIOLIB_DEBUG_SPI=1 + ; -D DEBUG_RP2040_PORT=Serial +build_src_filter = ${rp2040_base.build_src_filter} + +<RAK11310Board.cpp> + +<../variants/rak11310> +lib_deps = ${rp2040_base.lib_deps} + +[env:rak11310_repeater] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RAK11310 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_repeater> + +[env:rak11310_repeater_bridge_rs232] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=8 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater> + +[env:rak11310_room_server] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D ADVERT_NAME='"RAK11310 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_room_server> + +[env:rak11310_companion_radio_usb] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=8 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/companion_radio/*.cpp> +lib_deps = ${rak11310.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; [env:rak11310_companion_radio_ble] +; extends = rak11310 +; build_flags = ${rak11310.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${rak11310.build_src_filter} +; +<../examples/companion_radio/*.cpp> +; lib_deps = ${rak11310.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +; [env:rak11310_companion_radio_wifi] +; extends = rak11310 +; build_flags = ${rak11310.build_flags} +; -D MAX_CONTACTS=100 +; -D MAX_GROUP_CHANNELS=8 +; -D WIFI_DEBUG_LOGGING=1 +; -D WIFI_SSID='"myssid"' +; -D WIFI_PWD='"mypwd"' +; ; -D MESH_PACKET_LOGGING=1 +; ; -D MESH_DEBUG=1 +; build_src_filter = ${rak11310.build_src_filter} +; +<../examples/companion_radio/*.cpp> +; lib_deps = ${rak11310.lib_deps} +; densaugeo/base64 @ ~1.4.0 + +[env:rak11310_terminal_chat] +extends = rak11310 +build_flags = ${rak11310.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${rak11310.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/rak11310/target.cpp b/variants/rak11310/target.cpp new file mode 100644 index 00000000..dba5bff2 --- /dev/null +++ b/variants/rak11310/target.cpp @@ -0,0 +1,39 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> + +RAK11310Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI1); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI1); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak11310/target.h b/variants/rak11310/target.h new file mode 100644 index 00000000..fe45c3f2 --- /dev/null +++ b/variants/rak11310/target.h @@ -0,0 +1,20 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/SensorManager.h> +#include <RAK11310Board.h> + +extern RAK11310Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From f56172738d3c24b82f27b2ae88dc4c8b58d64027 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Tue, 2 Dec 2025 10:30:45 +0000 Subject: [PATCH 306/546] Bridge: Fix RAK4631 serial2 GPS conflict --- variants/rak4631/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index b3357855..7293b4d4 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -81,6 +81,7 @@ build_flags = -D WITH_RS232_BRIDGE=Serial2 -D WITH_RS232_BRIDGE_RX=PIN_SERIAL2_RX -D WITH_RS232_BRIDGE_TX=PIN_SERIAL2_TX + -UENV_INCLUDE_GPS ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 From 69a9a0bce9db9896b155d5c13f0c2d2a3875bc8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Tue, 2 Dec 2025 10:31:24 +0000 Subject: [PATCH 307/546] Bridge: Add t114 rs232 targets --- src/helpers/bridges/RS232Bridge.cpp | 8 +++--- src/helpers/bridges/RS232Bridge.h | 2 +- variants/heltec_t114/platformio.ini | 38 +++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/src/helpers/bridges/RS232Bridge.cpp b/src/helpers/bridges/RS232Bridge.cpp index 77332855..0024f6f2 100644 --- a/src/helpers/bridges/RS232Bridge.cpp +++ b/src/helpers/bridges/RS232Bridge.cpp @@ -15,10 +15,9 @@ void RS232Bridge::begin() { #if defined(ESP32) ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); -#elif defined(RAK_4631) - ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(NRF52_PLATFORM) - ((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); + // Tested with RAK_4631 and T114 + ((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX); #elif defined(RP2040_PLATFORM) ((SerialUART *)_serial)->setRX(WITH_RS232_BRIDGE_RX); ((SerialUART *)_serial)->setTX(WITH_RS232_BRIDGE_TX); @@ -123,8 +122,7 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) { // Check if packet fits within our maximum payload size if (len > (MAX_TRANS_UNIT + 1)) { - BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, - MAX_TRANS_UNIT + 1); + BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, MAX_TRANS_UNIT + 1); return; } diff --git a/src/helpers/bridges/RS232Bridge.h b/src/helpers/bridges/RS232Bridge.h index 839c0ba0..8fc1c22c 100644 --- a/src/helpers/bridges/RS232Bridge.h +++ b/src/helpers/bridges/RS232Bridge.h @@ -40,7 +40,7 @@ * Platform Support: * Different platforms require different pin configuration methods: * - ESP32: Uses HardwareSerial::setPins(rx, tx) - * - NRF52: Uses HardwareSerial::setPins(rx, tx) + * - NRF52: Uses Uart::setPins(rx, tx) * - RP2040: Uses SerialUART::setRX(rx) and SerialUART::setTX(tx) * - STM32: Uses HardwareSerial::setRx(rx) and HardwareSerial::setTx(tx) */ diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 91ca78cd..7b6f5cee 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -59,6 +59,25 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +[env:Heltec_t114_without_display_repeater_bridge_rs232] +extends = Heltec_t114 +build_flags = + ${Heltec_t114.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=10 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_t114.build_src_filter} + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater> + [env:Heltec_t114_without_display_room_server] extends = Heltec_t114 build_src_filter = ${Heltec_t114.build_src_filter} @@ -151,6 +170,25 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +[env:Heltec_t114_repeater_bridge_rs232] +extends = Heltec_t114 +build_flags = + ${Heltec_t114.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=9 + -D WITH_RS232_BRIDGE_TX=10 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_t114_with_display.build_src_filter} + +<helpers/bridges/RS232Bridge.cpp> + +<../examples/simple_repeater> + [env:Heltec_t114_room_server] extends = Heltec_t114_with_display build_src_filter = ${Heltec_t114_with_display.build_src_filter} From dde9b7cc76ece24c753768c63fcdfa5bdbbd2cb3 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Wed, 3 Dec 2025 14:59:57 +1100 Subject: [PATCH 308/546] remove calls to sd_power_mode_set(NRF_POWER_MODE_LOWPWR); this is the default mode, there is no need to call it unless previously changing it. --- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp | 2 -- variants/wio_wm1110/WioWM1110Board.cpp | 1 - 2 files changed, 3 deletions(-) diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index c41a6bc0..561ed504 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -11,8 +11,6 @@ void MinewsemiME25LS01Board::begin() { pinMode(PIN_VBAT_READ, INPUT); - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - #ifdef BUTTON_PIN pinMode(BUTTON_PIN, INPUT); pinMode(LED_PIN, OUTPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index ca3638b3..98e0d616 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -23,7 +23,6 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void WioWM1110Board::begin() { startup_reason = BD_STARTUP_NORMAL; - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); NRF_POWER->DCDCEN = 1; pinMode(BATTERY_PIN, INPUT); From e1d3da942be1a4ec98ec37c1a1741084bcf1819c Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Wed, 3 Dec 2025 15:58:36 +1100 Subject: [PATCH 309/546] fix DC/DC enable for boards which currently have it. this fixes how the reg1 dc/dc converter is enabled on WisMesh Tag / T1000e / WM1110 and Xiao NRF52 --- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 10 +++++++++- variants/t1000-e/T1000eBoard.cpp | 10 +++++++--- variants/wio_wm1110/WioWM1110Board.cpp | 9 ++++++++- variants/xiao_nrf52/XiaoNrf52Board.cpp | 10 +++++++++- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 28f6f713..68638f0d 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -21,7 +21,15 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void RAKWismeshTagBoard::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 4bcdf98a..a41abd92 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -9,10 +9,14 @@ void T1000eBoard::begin() { startup_reason = BD_STARTUP_NORMAL; btn_prev_state = HIGH; - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - // Enable DC/DC converter for improved power efficiency - NRF_POWER->DCDCEN = 1; + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } #ifdef BUTTON_PIN pinMode(BATTERY_PIN, INPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index 98e0d616..153d476c 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -23,7 +23,14 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void WioWM1110Board::begin() { startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } pinMode(BATTERY_PIN, INPUT); pinMode(LED_GREEN, OUTPUT); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 396880ab..69218926 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -23,7 +23,15 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { void XiaoNrf52Board::begin() { // for future use, sub-classes SHOULD call this from their begin() startup_reason = BD_STARTUP_NORMAL; - NRF_POWER->DCDCEN = 1; + + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); From ec375fa24847c25965c5e0c3ac0a87eaf3bcb752 Mon Sep 17 00:00:00 2001 From: Luis Garcia <git@luigi311.com> Date: Tue, 2 Dec 2025 17:41:31 -0700 Subject: [PATCH 310/546] variants: lilygo_techo: variant: Turn off leds on poweroff Signed-off-by: Luis Garcia <git@luigi311.com> --- variants/lilygo_techo/TechoBoard.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 4792153a..08038797 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -32,13 +32,13 @@ public: void powerOff() override { #ifdef LED_RED - digitalWrite(LED_RED, LOW); + digitalWrite(LED_RED, HIGH); #endif #ifdef LED_GREEN - digitalWrite(LED_GREEN, LOW); + digitalWrite(LED_GREEN, HIGH); #endif #ifdef LED_BLUE - digitalWrite(LED_BLUE, LOW); + digitalWrite(LED_BLUE, HIGH); #endif #ifdef DISP_BACKLIGHT digitalWrite(DISP_BACKLIGHT, LOW); From 1a3f7a7ea985289ed7d2e430e131b1ea4f6739f9 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:47:41 +0100 Subject: [PATCH 311/546] Fix BLE semaphore leak in Bluefruit library Patches Bluefruit library to fix semaphore leak bug that causes device lockup when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). Co-authored-by: Liam Cottle <liamcottle@users.noreply.github.com> Co-authored-by: oltaco <oltaco@users.noreply.github.com> --- arch/nrf52/extra_scripts/patch_bluefruit.py | 198 ++++++++++++++++++++ platformio.ini | 4 +- 2 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 arch/nrf52/extra_scripts/patch_bluefruit.py diff --git a/arch/nrf52/extra_scripts/patch_bluefruit.py b/arch/nrf52/extra_scripts/patch_bluefruit.py new file mode 100644 index 00000000..b43bffb5 --- /dev/null +++ b/arch/nrf52/extra_scripts/patch_bluefruit.py @@ -0,0 +1,198 @@ +""" +Bluefruit BLE Patch Script + +Patches Bluefruit library to fix semaphore leak bug that causes device lockup +when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout). + +Patches applied: +1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size +2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect + +Bug description: +- When a BLE central disconnects unexpectedly (reason=8 supervision timeout), + the BLE_GATTS_EVT_HVN_TX_COMPLETE event may never fire +- This leaves the _hvn_sem counting semaphore in a decremented state +- Since BLEConnection objects are reused (destructor never called), the + semaphore count is never restored +- Eventually all semaphore counts are exhausted and notify() blocks/fails + +""" + +from pathlib import Path + +Import("env") # pylint: disable=undefined-variable + + +def _patch_ble_connection_header(source: Path) -> bool: + """ + Add _hvn_qsize member variable to BLEConnection class. + + This is needed to restore the semaphore to its correct count on disconnect. + + Returns True if patch was applied or already applied, False on error. + """ + try: + content = source.read_text() + + # Check if already patched + if "_hvn_qsize" in content: + return True # Already patched + + # Find the location to insert - after _phy declaration + original_pattern = ''' uint8_t _phy; + + uint8_t _role;''' + + patched_pattern = ''' uint8_t _phy; + uint8_t _hvn_qsize; + + uint8_t _role;''' + + if original_pattern not in content: + print("Bluefruit patch: WARNING - BLEConnection.h pattern not found") + return False + + content = content.replace(original_pattern, patched_pattern) + source.write_text(content) + + # Verify + if "_hvn_qsize" not in source.read_text(): + return False + + return True + except Exception as e: + print(f"Bluefruit patch: ERROR patching BLEConnection.h: {e}") + return False + + +def _patch_ble_connection_source(source: Path) -> bool: + """ + Patch BLEConnection.cpp to: + 1. Store hvn_qsize in constructor + 2. Restore _hvn_sem semaphore to full count on disconnect + + Returns True if patch was applied or already applied, False on error. + """ + try: + content = source.read_text() + + # Check if already patched (look for the restore loop) + if "uxSemaphoreGetCount(_hvn_sem)" in content: + return True # Already patched + + # Patch 1: Store queue size in constructor + constructor_original = ''' _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' + + constructor_patched = ''' _hvn_qsize = hvn_qsize; + _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);''' + + if constructor_original not in content: + print("Bluefruit patch: WARNING - BLEConnection.cpp constructor pattern not found") + return False + + content = content.replace(constructor_original, constructor_patched) + + # Patch 2: Restore semaphore on disconnect + disconnect_original = ''' case BLE_GAP_EVT_DISCONNECTED: + // mark as disconnected + _connected = false; + break;''' + + disconnect_patched = ''' case BLE_GAP_EVT_DISCONNECTED: + // Restore notification semaphore to full count + // This fixes lockup when disconnect occurs with notifications in flight + while (uxSemaphoreGetCount(_hvn_sem) < _hvn_qsize) { + xSemaphoreGive(_hvn_sem); + } + // Release indication semaphore if waiting + if (_hvc_sem) { + _hvc_received = false; + xSemaphoreGive(_hvc_sem); + } + // mark as disconnected + _connected = false; + break;''' + + if disconnect_original not in content: + print("Bluefruit patch: WARNING - BLEConnection.cpp disconnect pattern not found") + return False + + content = content.replace(disconnect_original, disconnect_patched) + source.write_text(content) + + # Verify + verify_content = source.read_text() + if "uxSemaphoreGetCount(_hvn_sem)" not in verify_content: + return False + if "_hvn_qsize = hvn_qsize" not in verify_content: + return False + + return True + except Exception as e: + print(f"Bluefruit patch: ERROR patching BLEConnection.cpp: {e}") + return False + + +def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument + framework_path = env.get("PLATFORMFW_DIR") + if not framework_path: + framework_path = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52") + + if not framework_path: + print("Bluefruit patch: ERROR - framework directory not found") + env.Exit(1) + return + + framework_dir = Path(framework_path) + bluefruit_lib = framework_dir / "libraries" / "Bluefruit52Lib" / "src" + patch_failed = False + + # Patch BLEConnection.h + conn_header = bluefruit_lib / "BLEConnection.h" + if conn_header.exists(): + before = conn_header.read_text() + success = _patch_ble_connection_header(conn_header) + after = conn_header.read_text() + + if success: + if before != after: + print("Bluefruit patch: OK - Applied BLEConnection.h fix (added _hvn_qsize member)") + else: + print("Bluefruit patch: OK - BLEConnection.h already patched") + else: + print("Bluefruit patch: FAILED - BLEConnection.h") + patch_failed = True + else: + print(f"Bluefruit patch: ERROR - BLEConnection.h not found at {conn_header}") + patch_failed = True + + # Patch BLEConnection.cpp + conn_source = bluefruit_lib / "BLEConnection.cpp" + if conn_source.exists(): + before = conn_source.read_text() + success = _patch_ble_connection_source(conn_source) + after = conn_source.read_text() + + if success: + if before != after: + print("Bluefruit patch: OK - Applied BLEConnection.cpp fix (restore semaphore on disconnect)") + else: + print("Bluefruit patch: OK - BLEConnection.cpp already patched") + else: + print("Bluefruit patch: FAILED - BLEConnection.cpp") + patch_failed = True + else: + print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}") + patch_failed = True + + if patch_failed: + print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.") + env.Exit(1) + + +# Register the patch to run before build +bluefruit_action = env.VerboseAction(_apply_bluefruit_patches, "Applying Bluefruit BLE patches...") +env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", bluefruit_action) + +# Also run immediately to patch before any compilation +_apply_bluefruit_patches(None, None, env) diff --git a/platformio.ini b/platformio.ini index 3907cf64..75d37e86 100644 --- a/platformio.ini +++ b/platformio.ini @@ -79,7 +79,9 @@ extends = arduino_base platform = nordicnrf52 platform_packages = framework-arduinoadafruitnrf52 @ 1.10700.0 -extra_scripts = create-uf2.py +extra_scripts = + create-uf2.py + arch/nrf52/extra_scripts/patch_bluefruit.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 From 6db57677f96e9f2ca0e7f43ada723adbfd030b3c Mon Sep 17 00:00:00 2001 From: Florent de Lamotte <florent@frizoncorrea.fr> Date: Thu, 4 Dec 2025 12:01:00 +0100 Subject: [PATCH 312/546] tracker_l1: enable dc/dc converter --- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index c5c9db65..34d9a874 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -23,6 +23,15 @@ void WioTrackerL1Board::begin() { startup_reason = BD_STARTUP_NORMAL; btn_prev_state = HIGH; + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } + pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input // Set all button pins to INPUT_PULLUP pinMode(PIN_BUTTON1, INPUT_PULLUP); From 73ab0d881361c215b01e34cae9d3ba8064bef576 Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 4 Dec 2025 11:48:01 +0100 Subject: [PATCH 313/546] Improve SerialBLEInterface --- src/helpers/nrf52/SerialBLEInterface.cpp | 376 ++++++++++++++++------- src/helpers/nrf52/SerialBLEInterface.h | 32 +- 2 files changed, 289 insertions(+), 119 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index dbe6f393..b4811a20 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -1,193 +1,353 @@ #include "SerialBLEInterface.h" +#include <stdio.h> +#include <string.h> +#include "ble_gap.h" +#include "ble_hci.h" -static SerialBLEInterface* instance; +#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds + +static SerialBLEInterface* instance = nullptr; void SerialBLEInterface::onConnect(uint16_t connection_handle) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: connected"); - // we now set _isDeviceConnected=true in onSecured callback instead + BLE_DEBUG_PRINTLN("SerialBLEInterface: connected handle=0x%04X", connection_handle); + if (instance) { + instance->_conn_handle = connection_handle; + instance->_isDeviceConnected = false; + instance->clearBuffers(); + } } void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason); - if(instance){ - instance->_isDeviceConnected = false; - instance->startAdv(); + BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected handle=0x%04X reason=%u", connection_handle, reason); + if (instance) { + if (instance->_conn_handle == connection_handle) { + instance->_conn_handle = BLE_CONN_HANDLE_INVALID; + instance->_isDeviceConnected = false; + instance->clearBuffers(); + } } } void SerialBLEInterface::onSecured(uint16_t connection_handle) { - BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured"); - if(instance){ - instance->_isDeviceConnected = true; - // no need to stop advertising on connect, as the ble stack does this automatically + BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured handle=0x%04X", connection_handle); + if (instance) { + if (instance->isValidConnection(connection_handle, true)) { + instance->_isDeviceConnected = true; + + // Connection interval units: 1.25ms, supervision timeout units: 10ms + // Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic." + // So we explicitly set it here to make Android & Apple match + ble_gap_conn_params_t conn_params; + conn_params.min_conn_interval = 12; // 15ms + conn_params.max_conn_interval = 24; // 30ms + conn_params.slave_latency = 0; + conn_params.conn_sup_timeout = 200; // 2000ms + + uint32_t err_code = sd_ble_gap_conn_param_update(connection_handle, &conn_params); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("Connection parameter update requested: 15-30ms interval, 2s timeout"); + } else { + BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code); + } + } else { + BLE_DEBUG_PRINTLN("onSecured: ignoring stale/duplicate callback"); + } + } +} + +bool SerialBLEInterface::onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request) { + (void)connection_handle; + (void)passkey; + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing passkey request match=%d", match_request); + return true; +} + +void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t auth_status) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing complete handle=0x%04X status=%u", connection_handle, auth_status); + if (instance) { + if (instance->isValidConnection(connection_handle)) { + if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing successful"); + } else { + BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, disconnecting"); + instance->disconnect(); + } + } else { + BLE_DEBUG_PRINTLN("onPairingComplete: ignoring stale callback"); + } + } +} + +void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) { + if (!instance) return; + + if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) { + uint16_t conn_handle = evt->evt.gap_evt.conn_handle; + if (instance->isValidConnection(conn_handle)) { + BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: handle=0x%04X, min_interval=%u, max_interval=%u, latency=%u, timeout=%u", + conn_handle, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency, + evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout); + + uint32_t err_code = sd_ble_gap_conn_param_update(conn_handle, NULL); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("Accepted CONN_PARAM_UPDATE_REQUEST (using PPCP)"); + } else { + BLE_DEBUG_PRINTLN("ERROR: Failed to accept CONN_PARAM_UPDATE_REQUEST: 0x%08X", err_code); + } + } else { + BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: ignoring stale callback for handle=0x%04X", conn_handle); + } } } void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { - instance = this; char charpin[20]; - sprintf(charpin, "%d", pin_code); - + snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code); + + // If we want to control BLE LED ourselves, uncomment this: + // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(250, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); // increase MTU - Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.begin(); + + // Connection interval units: 1.25ms, supervision timeout units: 10ms + ble_gap_conn_params_t ppcp_params; + ppcp_params.min_conn_interval = 12; // 15ms + ppcp_params.max_conn_interval = 24; // 30ms + ppcp_params.slave_latency = 0; + ppcp_params.conn_sup_timeout = 200; // 2000ms + + uint32_t err_code = sd_ble_gap_ppcp_set(&ppcp_params); + if (err_code == NRF_SUCCESS) { + BLE_DEBUG_PRINTLN("PPCP set: 15-30ms interval, 2s timeout"); + } else { + BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code); + } + + Bluefruit.setTxPower(BLE_TX_POWER); Bluefruit.setName(device_name); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); + Bluefruit.Security.setIOCaps(true, false, false); + Bluefruit.Security.setPairPasskeyCallback(onPairingPasskey); + Bluefruit.Security.setPairCompleteCallback(onPairingComplete); Bluefruit.Periph.setConnectCallback(onConnect); Bluefruit.Periph.setDisconnectCallback(onDisconnect); Bluefruit.Security.setSecuredCallback(onSecured); - // To be consistent OTA DFU should be added first if it exists - //bledfu.begin(); + Bluefruit.setEventCallback(onBLEEvent); - // Configure and start the BLE Uart service bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM); bleuart.begin(); - -} + bleuart.setRxCallback(onBleUartRX); -void SerialBLEInterface::startAdv() { - - BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising"); - - // clean restart if already advertising - if(Bluefruit.Advertising.isRunning()){ - BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart"); - Bluefruit.Advertising.stop(); - } - - Bluefruit.Advertising.clearData(); // clear advertising data - Bluefruit.ScanResponse.clearData(); // clear scan response data - - // Advertising packet Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); Bluefruit.Advertising.addTxPower(); - - // Include the BLE UART (AKA 'NUS') 128-bit UUID Bluefruit.Advertising.addService(bleuart); - // Secondary Scan Response packet (optional) - // Since there is no room for 'Name' in Advertising packet Bluefruit.ScanResponse.addName(); - /* Start Advertising - * - Enable auto advertising if disconnected - * - Interval: fast mode = 20 ms, slow mode = 152.5 ms - * - Timeout for fast mode is 30 seconds - * - Start(timeout) with timeout = 0 will advertise forever (until connected) - * - * For recommended advertising interval - * https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + Bluefruit.Advertising.setFastTimeout(30); + + Bluefruit.Advertising.restartOnDisconnect(true); } -void SerialBLEInterface::stopAdv() { +void SerialBLEInterface::clearBuffers() { + send_queue_len = 0; + recv_queue_len = 0; + bleuart.flush(); +} - BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising"); - - // we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack - if(!Bluefruit.Advertising.isRunning()){ - return; +void SerialBLEInterface::shiftSendQueueLeft() { + if (send_queue_len > 0) { + send_queue_len--; + for (uint8_t i = 0; i < send_queue_len; i++) { + send_queue[i] = send_queue[i + 1]; + } } - - // stop advertising - Bluefruit.Advertising.stop(); - } -// ---------- public methods +void SerialBLEInterface::shiftRecvQueueLeft() { + if (recv_queue_len > 0) { + recv_queue_len--; + for (uint8_t i = 0; i < recv_queue_len; i++) { + recv_queue[i] = recv_queue[i + 1]; + } + } +} -void SerialBLEInterface::enable() { +bool SerialBLEInterface::isValidConnection(uint16_t handle, bool requireWaitingForSecurity) const { + if (_conn_handle != handle) { + return false; + } + BLEConnection* conn = Bluefruit.Connection(handle); + if (conn == nullptr || !conn->connected()) { + return false; + } + if (requireWaitingForSecurity && _isDeviceConnected) { + return false; + } + return true; +} + +bool SerialBLEInterface::isAdvertising() const { + ble_gap_addr_t adv_addr; + uint32_t err_code = sd_ble_gap_adv_addr_get(0, &adv_addr); + return (err_code == NRF_SUCCESS); +} + +void SerialBLEInterface::enable() { if (_isEnabled) return; _isEnabled = true; clearBuffers(); + _last_health_check = millis(); - // Start advertising - startAdv(); + Bluefruit.Advertising.start(0); +} + +void SerialBLEInterface::disconnect() { + if (_conn_handle != BLE_CONN_HANDLE_INVALID) { + sd_ble_gap_disconnect(_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION); + } } void SerialBLEInterface::disable() { _isEnabled = false; - BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); + BLE_DEBUG_PRINTLN("SerialBLEInterface: disable"); -#ifdef RAK_BOARD - Bluefruit.disconnect(Bluefruit.connHandle()); -#else - uint16_t conn_id; - if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) { - Bluefruit.disconnect(conn_id); - } -#endif - - Bluefruit.Advertising.restartOnDisconnect(false); + disconnect(); Bluefruit.Advertising.stop(); - Bluefruit.Advertising.clearData(); - - stopAdv(); + _last_health_check = 0; } size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) { if (len > MAX_FRAME_SIZE) { - BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d", len); + BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%u", (unsigned)len); return 0; } - if (_isDeviceConnected && len > 0) { + bool connected = isConnected(); + if (connected && len > 0) { if (send_queue_len >= FRAME_QUEUE_SIZE) { BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!"); return 0; } - send_queue[send_queue_len].len = len; // add to send queue + send_queue[send_queue_len].len = len; memcpy(send_queue[send_queue_len].buf, src, len); send_queue_len++; - + return len; } return 0; } -#define BLE_WRITE_MIN_INTERVAL 60 - -bool SerialBLEInterface::isWriteBusy() const { - return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write? -} - size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { - if (send_queue_len > 0 // first, check send queue - && millis() >= _last_write + BLE_WRITE_MIN_INTERVAL // space the writes apart - ) { - _last_write = millis(); - bleuart.write(send_queue[0].buf, send_queue[0].len); - BLE_DEBUG_PRINTLN("writeBytes: sz=%d, hdr=%d", (uint32_t)send_queue[0].len, (uint32_t) send_queue[0].buf[0]); - - send_queue_len--; - for (int i = 0; i < send_queue_len; i++) { // delete top item from queue - send_queue[i] = send_queue[i + 1]; - } - } else { - int len = bleuart.available(); - if (len > 0) { - bleuart.readBytes(dest, len); - BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]); - return len; + if (send_queue_len > 0) { + if (!isConnected()) { + BLE_DEBUG_PRINTLN("writeBytes: connection invalid, clearing send queue"); + send_queue_len = 0; + } else { + Frame frame_to_send = send_queue[0]; + + size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len); + if (written == frame_to_send.len) { + BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]); + shiftSendQueueLeft(); + } else if (written > 0) { + BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len); + shiftSendQueueLeft(); + } else { + if (!isConnected()) { + BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame"); + shiftSendQueueLeft(); + } else { + BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + } + } } } + + if (recv_queue_len > 0) { + size_t len = recv_queue[0].len; + memcpy(dest, recv_queue[0].buf, len); + + BLE_DEBUG_PRINTLN("readBytes: sz=%u, hdr=%u", (unsigned)len, (unsigned)dest[0]); + + shiftRecvQueueLeft(); + return len; + } + + // Advertising watchdog: periodically check if advertising is running, restart if not + // Only run when truly disconnected (no connection handle), not during connection establishment + unsigned long now = millis(); + if (_isEnabled && !isConnected() && _conn_handle == BLE_CONN_HANDLE_INVALID) { + if (now - _last_health_check >= BLE_HEALTH_CHECK_INTERVAL) { + _last_health_check = now; + + if (!isAdvertising()) { + BLE_DEBUG_PRINTLN("SerialBLEInterface: advertising watchdog - advertising stopped, restarting"); + Bluefruit.Advertising.start(0); + } + } + } + return 0; } -bool SerialBLEInterface::isConnected() const { - return _isDeviceConnected; +void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) { + if (!instance) { + return; + } + + if (instance->_conn_handle != conn_handle || !instance->isConnected()) { + while (instance->bleuart.available() > 0) { + instance->bleuart.read(); + } + return; + } + + while (instance->bleuart.available() > 0) { + if (instance->recv_queue_len >= FRAME_QUEUE_SIZE) { + while (instance->bleuart.available() > 0) { + instance->bleuart.read(); + } + BLE_DEBUG_PRINTLN("onBleUartRX: recv queue full, dropping data"); + break; + } + + int avail = instance->bleuart.available(); + + if (avail > MAX_FRAME_SIZE) { + BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail); + uint8_t drain_buf[32]; + while (instance->bleuart.available() > 0) { + int chunk = instance->bleuart.available() > 32 ? 32 : instance->bleuart.available(); + instance->bleuart.readBytes(drain_buf, chunk); + } + continue; + } + + int read_len = avail; + instance->recv_queue[instance->recv_queue_len].len = read_len; + instance->bleuart.readBytes(instance->recv_queue[instance->recv_queue_len].buf, read_len); + instance->recv_queue_len++; + } +} + +bool SerialBLEInterface::isConnected() const { + return _isDeviceConnected && Bluefruit.connected() > 0; +} + +bool SerialBLEInterface::isWriteBusy() const { + return send_queue_len >= (FRAME_QUEUE_SIZE - 1); } diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index bf29892d..557b86c5 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -11,41 +11,51 @@ class SerialBLEInterface : public BaseSerialInterface { BLEUart bleuart; bool _isEnabled; bool _isDeviceConnected; - unsigned long _last_write; + uint16_t _conn_handle; + unsigned long _last_health_check; struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; }; - #define FRAME_QUEUE_SIZE 4 - int send_queue_len; + #define FRAME_QUEUE_SIZE 12 + + uint8_t send_queue_len; Frame send_queue[FRAME_QUEUE_SIZE]; + + uint8_t recv_queue_len; + Frame recv_queue[FRAME_QUEUE_SIZE]; - void clearBuffers() { send_queue_len = 0; } + void clearBuffers(); + void shiftSendQueueLeft(); + void shiftRecvQueueLeft(); + bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const; + bool isAdvertising() const; static void onConnect(uint16_t connection_handle); static void onDisconnect(uint16_t connection_handle, uint8_t reason); static void onSecured(uint16_t connection_handle); + static bool onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request); + static void onPairingComplete(uint16_t connection_handle, uint8_t auth_status); + static void onBLEEvent(ble_evt_t* evt); + static void onBleUartRX(uint16_t conn_handle); public: SerialBLEInterface() { _isEnabled = false; _isDeviceConnected = false; - _last_write = 0; + _conn_handle = BLE_CONN_HANDLE_INVALID; + _last_health_check = 0; send_queue_len = 0; + recv_queue_len = 0; } - void startAdv(); - void stopAdv(); void begin(const char* device_name, uint32_t pin_code); - - // BaseSerialInterface methods + void disconnect(); void enable() override; void disable() override; bool isEnabled() const override { return _isEnabled; } - bool isConnected() const override; - bool isWriteBusy() const override; size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; From 10b43a8f9fa2f1868b2149bdff2d45207b204500 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 5 Dec 2025 12:39:37 +0100 Subject: [PATCH 314/546] variants: XIAO NRF52: Enable button pullup Some versions of the Wio-SX1262 board don't have the button and the pullup resistor populated. Enable the internal pullup to prevent a floating pin and spurious button presses on those boards. This fixes #1173. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/xiao_nrf52/XiaoNrf52Board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 69218926..f847c654 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -38,7 +38,7 @@ void XiaoNrf52Board::begin() { digitalWrite(VBAT_ENABLE, HIGH); #ifdef PIN_USER_BTN - pinMode(PIN_USER_BTN, INPUT); + pinMode(PIN_USER_BTN, INPUT_PULLUP); #endif #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) From d834d66803cee513de9f381ecbe6014c67a93d65 Mon Sep 17 00:00:00 2001 From: Christophe Vanlancker <carroarmato0@gmail.com> Date: Fri, 5 Dec 2025 20:44:56 +0100 Subject: [PATCH 315/546] feat(tdeck): enable GPS support and configure pins --- variants/lilygo_tdeck/platformio.ini | 19 +++++++++++++++++++ variants/lilygo_tdeck/target.cpp | 4 +++- variants/lilygo_tdeck/target.h | 4 +++- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index adfe9d1e..807663f8 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -19,6 +19,21 @@ build_flags = -D SX126X_RX_BOOSTED_GAIN=1 -D SX126X_DIO3_TCXO_VOLTAGE=1.8f -D P_LORA_DIO_1=45 ; LORA IRQ pin + -D ENV_INCLUDE_GPS=1 + -D ENV_INCLUDE_AHTX0=0 + -D ENV_INCLUDE_BME280=0 + -D ENV_INCLUDE_BMP280=0 + -D ENV_INCLUDE_SHTC3=0 + -D ENV_INCLUDE_SHT4X=0 + -D ENV_INCLUDE_LPS22HB=0 + -D ENV_INCLUDE_INA3221=0 + -D ENV_INCLUDE_INA219=0 + -D ENV_INCLUDE_INA226=0 + -D ENV_INCLUDE_INA260=0 + -D ENV_INCLUDE_MLX90614=0 + -D ENV_INCLUDE_VL53L0X=0 + -D ENV_INCLUDE_BME680=0 + -D ENV_INCLUDE_BMP085=0 -D P_LORA_NSS=9 ; LORA SS pin -D P_LORA_RESET=17 ; LORA RST pin -D P_LORA_BUSY=13 ; LORA Busy pin @@ -35,8 +50,12 @@ build_flags = -D PIN_TFT_DC=11 -D PIN_TFT_SCL=40 -D PIN_TFT_SDA=41 + -D PIN_GPS_RX=43 + -D PIN_GPS_TX=44 + -D GPS_BAUD_RATE=38400 build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tdeck> + +<helpers/sensors/*.cpp> lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index 1120b3ad..50ffa735 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -14,7 +14,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -SensorManager sensors; +MicroNMEALocationProvider gps(Serial1, &rtc_clock); +EnvironmentSensorManager sensors(gps); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -24,6 +25,7 @@ SensorManager sensors; bool radio_init() { fallback_clock.begin(); rtc_clock.begin(Wire); + Wire.begin(18, 8); #if defined(P_LORA_SCLK) return radio.std_init(&spi); diff --git a/variants/lilygo_tdeck/target.h b/variants/lilygo_tdeck/target.h index c803bf2c..4640925f 100644 --- a/variants/lilygo_tdeck/target.h +++ b/variants/lilygo_tdeck/target.h @@ -11,11 +11,13 @@ #include <helpers/ui/ST7789LCDDisplay.h> #include <helpers/ui/MomentaryButton.h> #endif +#include "helpers/sensors/EnvironmentSensorManager.h" +#include "helpers/sensors/MicroNMEALocationProvider.h" extern TDeckBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; From 01eb8716aff657171719c3102e9ba0efdd3eba16 Mon Sep 17 00:00:00 2001 From: Christophe Vanlancker <carroarmato0@gmail.com> Date: Fri, 5 Dec 2025 20:45:10 +0100 Subject: [PATCH 316/546] fix(core): optimize GPS loop and add display GPIO safeguards --- .../sensors/EnvironmentSensorManager.cpp | 2 +- src/helpers/ui/ST7789LCDDisplay.cpp | 22 ++++++++++++++----- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b072bcb0..af29bb99 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -695,8 +695,8 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS + _location->loop(); if (millis() > next_gps_update) { - _location->loop(); if(gps_active){ #ifdef RAK_WISBLOCK_GPS diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 87f9b8ad..97d82f42 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -23,9 +23,13 @@ bool ST7789LCDDisplay::begin() { if (!_isOn) { if (_peripher_power) _peripher_power->claim(); - pinMode(PIN_TFT_LEDA_CTL, OUTPUT); - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, HIGH); + if (PIN_TFT_LEDA_CTL != -1) { + pinMode(PIN_TFT_LEDA_CTL, OUTPUT); + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + } + if (PIN_TFT_RST != -1) { + digitalWrite(PIN_TFT_RST, HIGH); + } // Im not sure if this is just a t-deck problem or not, if your display is slow try this. #ifdef LILYGO_TDECK @@ -54,9 +58,15 @@ void ST7789LCDDisplay::turnOn() { void ST7789LCDDisplay::turnOff() { if (_isOn) { - digitalWrite(PIN_TFT_LEDA_CTL, HIGH); - digitalWrite(PIN_TFT_RST, LOW); - digitalWrite(PIN_TFT_LEDA_CTL, LOW); + if (PIN_TFT_LEDA_CTL != -1) { + digitalWrite(PIN_TFT_LEDA_CTL, HIGH); + } + if (PIN_TFT_RST != -1) { + digitalWrite(PIN_TFT_RST, LOW); + } + if (PIN_TFT_LEDA_CTL != -1) { + digitalWrite(PIN_TFT_LEDA_CTL, LOW); + } _isOn = false; if (_peripher_power) _peripher_power->release(); From 638f41d14399ac772aaece7340775cc927175153 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sat, 6 Dec 2025 16:21:17 +1100 Subject: [PATCH 317/546] calculate shared_secret on demand --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/BaseChatMesh.cpp | 24 ++++++++++++++++++------ src/helpers/BaseChatMesh.h | 1 + src/helpers/ContactInfo.h | 3 ++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3aed2da7..9cbb2eba 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1238,7 +1238,7 @@ void MyMesh::handleCmdFrame(size_t len) { if (_store->saveMainIdentity(identity)) { self_id = identity; writeOKFrame(); - // re-load contacts, to recalc shared secrets + // re-load contacts, to invalidate ecdh shared_secrets resetContacts(); _store->loadContacts(this); } else { diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 4ab3e03b..2855c625 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -113,8 +113,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from->gps_lon = 0; from->sync_since = 0; - // only need to calculate the shared_secret once, for better performance - self_id.calcSharedSecret(from->shared_secret, id); + from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand } else { MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!"); return; @@ -147,7 +146,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; if (i >= 0 && i < num_contacts) { - // lookup pre-calculated shared_secret + ensureSharedSecretIsValid(contacts[i]); memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); @@ -293,6 +292,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy + ensureSharedSecretIsValid(contact); mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -342,6 +342,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } + ensureSharedSecretIsValid(recipient); return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); } @@ -373,6 +374,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); + ensureSharedSecretIsValid(recipient); auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); if (pkt == NULL) return MSG_SEND_FAILED; @@ -462,6 +464,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } + ensureSharedSecretIsValid(recipient); pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); } if (pkt) { @@ -489,6 +492,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); + ensureSharedSecretIsValid(recipient); pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len); } if (pkt) { @@ -516,6 +520,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique + ensureSharedSecretIsValid(recipient); pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); } if (pkt) { @@ -639,6 +644,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); + ensureSharedSecretIsValid(*contact); auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); @@ -703,14 +709,20 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { auto dest = &contacts[num_contacts++]; *dest = contact; - // calc the ECDH shared secret (just once for performance) - self_id.calcSharedSecret(dest->shared_secret, contact.id); - + dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } return false; } +void BaseChatMesh::ensureSharedSecretIsValid(const ContactInfo& contact) { + if (contact.shared_secret_valid) { + return; // already calculated + } + self_id.calcSharedSecret(contact.shared_secret, contact.id); + contact.shared_secret_valid = true; +} + bool BaseChatMesh::removeContact(ContactInfo& contact) { int idx = 0; while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 76b0dd1c..105d2a79 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -73,6 +73,7 @@ class BaseChatMesh : public mesh::Mesh { mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); + void ensureSharedSecretIsValid(const ContactInfo& contact); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index 4a8038d3..b0b54aef 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -9,9 +9,10 @@ struct ContactInfo { uint8_t type; // on of ADV_TYPE_* uint8_t flags; int8_t out_path_len; + mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock - uint8_t shared_secret[PUB_KEY_SIZE]; + mutable uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t lastmod; // by OUR clock int32_t gps_lat, gps_lon; // 6 dec places uint32_t sync_since; From d7adcc136b2ad9e4247955854d2451bb16863d47 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 6 Dec 2025 16:49:25 +1100 Subject: [PATCH 318/546] * LPPDataHelpers, readCurrent() signed value --- src/helpers/sensors/LPPDataHelpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/LPPDataHelpers.h b/src/helpers/sensors/LPPDataHelpers.h index b9025de4..37b50f3f 100644 --- a/src/helpers/sensors/LPPDataHelpers.h +++ b/src/helpers/sensors/LPPDataHelpers.h @@ -113,7 +113,7 @@ public: return _pos <= _len; } bool readCurrent(float& amps) { - amps = getFloat(&_buf[_pos], 2, 1000, false); _pos += 2; + amps = getFloat(&_buf[_pos], 2, 1000, true); _pos += 2; return _pos <= _len; } bool readPower(float& watts) { From 676c317f78df09f2f5fed9b499c25e0aa015e722 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 6 Dec 2025 19:17:45 +1100 Subject: [PATCH 319/546] * refactor: on-demand getSharedSecret() --- src/helpers/BaseChatMesh.cpp | 32 ++++++++------------------------ src/helpers/BaseChatMesh.h | 1 - src/helpers/ContactInfo.h | 12 +++++++++++- 3 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 2855c625..597444fa 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -146,8 +146,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) { void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { int i = matching_peer_indexes[peer_idx]; if (i >= 0 && i < num_contacts) { - ensureSharedSecretIsValid(contacts[i]); - memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE); + memcpy(dest_secret, contacts[i].getSharedSecret(self_id), PUB_KEY_SIZE); } else { MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i); } @@ -292,8 +291,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) { // NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY) // override this method in various firmwares, if there's a better strategy - ensureSharedSecretIsValid(contact); - mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0); + mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0); if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay } @@ -342,8 +340,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3 temp[len++] = attempt; // hide attempt number at tail end of payload } - ensureSharedSecretIsValid(recipient); - return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len); + return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len); } int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) { @@ -374,8 +371,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2); memcpy(&temp[5], text, text_len + 1); - ensureSharedSecretIsValid(recipient); - auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len); + auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len); if (pkt == NULL) return MSG_SEND_FAILED; uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -464,8 +460,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, tlen = 4 + len; } - ensureSharedSecretIsValid(recipient); - pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen); + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -492,8 +487,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique memcpy(&temp[4], req_data, data_len); - ensureSharedSecretIsValid(recipient); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -520,8 +514,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u memset(&temp[5], 0, 4); // reserved (possibly for 'since' param) getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique - ensureSharedSecretIsValid(recipient); - pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp)); + pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp)); } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); @@ -644,8 +637,7 @@ void BaseChatMesh::checkConnections() { // calc expected ACK reply mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE); - ensureSharedSecretIsValid(*contact); - auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9); + auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9); if (pkt) { sendDirect(pkt, contact->out_path, contact->out_path_len); } @@ -715,14 +707,6 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { return false; } -void BaseChatMesh::ensureSharedSecretIsValid(const ContactInfo& contact) { - if (contact.shared_secret_valid) { - return; // already calculated - } - self_id.calcSharedSecret(contact.shared_secret, contact.id); - contact.shared_secret_valid = true; -} - bool BaseChatMesh::removeContact(ContactInfo& contact) { int idx = 0; while (idx < num_contacts && !contacts[idx].id.matches(contact.id)) { diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 105d2a79..76b0dd1c 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -73,7 +73,6 @@ class BaseChatMesh : public mesh::Mesh { mesh::Packet* composeMsgPacket(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char *text, uint32_t& expected_ack); void sendAckTo(const ContactInfo& dest, uint32_t ack_hash); - void ensureSharedSecretIsValid(const ContactInfo& contact); protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index b0b54aef..eff07741 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -12,8 +12,18 @@ struct ContactInfo { mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock - mutable uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t lastmod; // by OUR clock int32_t gps_lat, gps_lon; // 6 dec places uint32_t sync_since; + + const uint8_t* getSharedSecret(const mesh::LocalIdentity& self_id) const { + if (!shared_secret_valid) { + self_id.calcSharedSecret(shared_secret, id.pub_key); + shared_secret_valid = true; + } + return shared_secret; + } + +private: + mutable uint8_t shared_secret[PUB_KEY_SIZE]; }; From b91b854a1dc5b24c772748bc701c9b08eb7e9e0c Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Mon, 8 Dec 2025 19:53:33 -0800 Subject: [PATCH 320/546] fix output from LPS22HB: convert barometric pressure from kPa to hPa in EnvironmentSensorManager --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index af29bb99..2692ec9c 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -399,7 +399,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_LPS22HB if (LPS22HB_initialized) { telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa } #endif From 4504ad4daf45dcccf825e4601d33e3a3727b924f Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Fri, 12 Dec 2025 19:01:15 +0700 Subject: [PATCH 321/546] Added default temperature from ESP32 MCU and NRF52 MCU Added NRF52Board.h and NRF52Board.cpp Modified NRF52 variants to extend from NRF52Board to share common feature --- examples/simple_repeater/MyMesh.cpp | 6 +++++ src/MeshCore.h | 2 ++ src/helpers/ESP32Board.h | 5 ++++ src/helpers/NRF52Board.cpp | 23 +++++++++++++++++++ src/helpers/NRF52Board.h | 12 ++++++++++ variants/heltec_mesh_solar/MeshSolarBoard.h | 3 ++- variants/heltec_t114/T114Board.h | 3 ++- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 3 ++- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 3 ++- variants/keepteen_lt1/KeepteenLT1Board.h | 3 ++- variants/lilygo_techo/TechoBoard.h | 3 ++- variants/lilygo_techo_lite/TechoBoard.h | 3 ++- variants/mesh_pocket/MeshPocket.h | 3 ++- .../MinewsemiME25LS01Board.h | 3 ++- variants/nano_g2_ultra/nano-g2.h | 3 ++- variants/promicro/PromicroBoard.h | 3 ++- variants/rak4631/RAK4631Board.h | 3 ++- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 3 ++- variants/sensecap_solar/SenseCapSolarBoard.h | 3 ++- variants/t1000-e/T1000eBoard.h | 3 ++- variants/thinknode_m1/ThinkNodeM1Board.h | 3 ++- variants/wio-tracker-l1/WioTrackerL1Board.h | 3 ++- variants/wio_wm1110/WioWM1110Board.h | 3 ++- variants/xiao_nrf52/XiaoNrf52Board.h | 3 ++- 24 files changed, 86 insertions(+), 19 deletions(-) create mode 100644 src/helpers/NRF52Board.cpp create mode 100644 src/helpers/NRF52Board.h diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 5fb1a729..39ecd105 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -173,6 +173,12 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + + float temperature = (float)board.getMCUTemperature(); + if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN + telemetry.addTemperature(TELEM_CHANNEL_SELF, (float)board.getMCUTemperature()); // Built-in MCU Temperature + } + // query other sensors -- target specific if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { perm_mask = 0x00; // just base telemetry allowed diff --git a/src/MeshCore.h b/src/MeshCore.h index 11a6a5b4..eb794058 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -1,6 +1,7 @@ #pragma once #include <stdint.h> +#include <math.h> #define MAX_HASH_SIZE 8 #define PUB_KEY_SIZE 32 @@ -42,6 +43,7 @@ namespace mesh { class MainBoard { public: virtual uint16_t getBattMilliVolts() = 0; + virtual float getMCUTemperature() { return NAN; } virtual bool setAdcMultiplier(float multiplier) { return false; }; virtual float getAdcMultiplier() const { return 0.0f; } virtual const char* getManufacturerName() const = 0; diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index e566f929..64c92c43 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -42,6 +42,11 @@ public: #endif } + // Temperature from ESP32 MCU + float getMCUTemperature() override { + return temperatureRead(); + } + uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp new file mode 100644 index 00000000..ee50fe4c --- /dev/null +++ b/src/helpers/NRF52Board.cpp @@ -0,0 +1,23 @@ +#if defined(NRF52_PLATFORM) +#include "NRF52Board.h" + +// Temperature from NRF52 MCU +float NRF52Board::getMCUTemperature() { + NRF_TEMP->TASKS_START = 1; // Start temperature measurement + + long startTime = millis(); + while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us + if(millis() - startTime > 5) { // To wait 5ms just in case + NRF_TEMP->TASKS_STOP = 1; + return NAN; + } + } + + NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag + + int32_t temp = NRF_TEMP->TEMP; // In 0.25 *C units + NRF_TEMP->TASKS_STOP = 1; + + return temp * 0.25f; // Convert to *C +} +#endif diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h new file mode 100644 index 00000000..1a6f879f --- /dev/null +++ b/src/helpers/NRF52Board.h @@ -0,0 +1,12 @@ +#pragma once + +#include <Arduino.h> +#include <MeshCore.h> + +#if defined(NRF52_PLATFORM) + +class NRF52Board : public mesh::MainBoard { +public: + float getMCUTemperature() override; +}; +#endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 3bec144f..688a8e88 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> #ifdef HELTEC_MESH_SOLAR #include "meshSolarApp.h" @@ -20,7 +21,7 @@ #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -class MeshSolarBoard : public mesh::MainBoard { +class MeshSolarBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 0f7fc47f..bd9287e2 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -2,13 +2,14 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // built-ins #define PIN_VBAT_READ 4 #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public mesh::MainBoard { +class T114Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index 8484085b..ac306977 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -2,10 +2,11 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public mesh::MainBoard { +class IkokaNanoNRFBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 4a061d42..8f817ff1 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -2,10 +2,11 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public mesh::MainBoard { +class IkokaStickNRFBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index 9892638b..e8c444ed 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -2,8 +2,9 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> -class KeepteenLT1Board : public mesh::MainBoard { +class KeepteenLT1Board : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 08038797..91dfb5ab 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -12,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public mesh::MainBoard { +class TechoBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 4792153a..79b5610c 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -12,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public mesh::MainBoard { +class TechoBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 8f5b09c9..876e3810 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -2,13 +2,14 @@ #include <Arduino.h> #include <MeshCore.h> +#include <helpers/NRF52Board.h> // built-ins #define PIN_VBAT_READ 29 #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public mesh::MainBoard { +class HeltecMeshPocket : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 777606a6..18aa9e8a 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // LoRa and SPI pins @@ -20,7 +21,7 @@ #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class MinewsemiME25LS01Board : public mesh::MainBoard { +class MinewsemiME25LS01Board : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 5cedb0f9..7ed1c74b 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -4,6 +4,7 @@ #include <Arduino.h> #include <MeshCore.h> +#include <helpers/NRF52Board.h> // LoRa radio module pins #define P_LORA_DIO_1 (32 + 10) @@ -34,7 +35,7 @@ #define PIN_VBAT_READ (0 + 2) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public mesh::MainBoard { +class NanoG2Ultra : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index dc20e550..916afefd 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> #define P_LORA_NSS 13 //P1.13 45 #define P_LORA_DIO_1 11 //P0.10 10 @@ -19,7 +20,7 @@ #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class PromicroBoard : public mesh::MainBoard { +class PromicroBoard : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 7f3a8fea..2040bf41 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // LoRa radio module pins for RAK4631 #define P_LORA_DIO_1 47 @@ -28,7 +29,7 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public mesh::MainBoard { +class RAK4631Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index e5104a58..fe554fe6 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -2,12 +2,13 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public mesh::MainBoard { +class RAKWismeshTagBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index b1e5f8f1..999c7783 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -2,8 +2,9 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> -class SenseCapSolarBoard : public mesh::MainBoard { +class SenseCapSolarBoard : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 359e5e9a..d04de5a3 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -2,8 +2,9 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> -class T1000eBoard : public mesh::MainBoard { +class T1000eBoard : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index cffa0aaa..5920b809 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -12,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM1Board : public mesh::MainBoard { +class ThinkNodeM1Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index f04b673f..6797f629 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -2,8 +2,9 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> -class WioTrackerL1Board : public mesh::MainBoard { +class WioTrackerL1Board : public NRF52Board { protected: uint8_t startup_reason; uint8_t btn_prev_state; diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 823acbc7..ffbc4e93 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -2,6 +2,7 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> #ifdef WIO_WM1110 @@ -10,7 +11,7 @@ #endif #define Serial Serial1 -class WioWM1110Board : public mesh::MainBoard { +class WioWM1110Board : public NRF52Board { protected: uint8_t startup_reason; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index f3766012..86be7aa4 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -2,10 +2,11 @@ #include <MeshCore.h> #include <Arduino.h> +#include <helpers/NRF52Board.h> #ifdef XIAO_NRF52 -class XiaoNrf52Board : public mesh::MainBoard { +class XiaoNrf52Board : public NRF52Board { protected: uint8_t startup_reason; From 14efaf6fd3a2ffd698134e37b4c983c87a50b683 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Sat, 29 Nov 2025 10:55:01 +0100 Subject: [PATCH 322/546] thinknode_m6: initial port --- boards/thinknode_m6.json | 72 +++++++++++++ variants/thinknode_m5/platformio.ini | 2 +- variants/thinknode_m6/ThinkNodeM6Board.cpp | 95 ++++++++++++++++ variants/thinknode_m6/ThinkNodeM6Board.h | 56 ++++++++++ variants/thinknode_m6/platformio.ini | 120 +++++++++++++++++++++ variants/thinknode_m6/target.cpp | 49 +++++++++ variants/thinknode_m6/target.h | 31 ++++++ variants/thinknode_m6/variant.cpp | 35 ++++++ variants/thinknode_m6/variant.h | 108 +++++++++++++++++++ 9 files changed, 567 insertions(+), 1 deletion(-) create mode 100644 boards/thinknode_m6.json create mode 100644 variants/thinknode_m6/ThinkNodeM6Board.cpp create mode 100644 variants/thinknode_m6/ThinkNodeM6Board.h create mode 100644 variants/thinknode_m6/platformio.ini create mode 100644 variants/thinknode_m6/target.cpp create mode 100644 variants/thinknode_m6/target.h create mode 100644 variants/thinknode_m6/variant.cpp create mode 100644 variants/thinknode_m6/variant.h diff --git a/boards/thinknode_m6.json b/boards/thinknode_m6.json new file mode 100644 index 00000000..1f91b9aa --- /dev/null +++ b/boards/thinknode_m6.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_solar", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M6", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "elecrow solar", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/Elecrow-RD", + "vendor": "ELECROW" +} diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 23db506a..cf9c9d41 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -3,7 +3,7 @@ extends = esp32_base board = ESP32-S3-WROOM-1-N4 build_flags = ${esp32_base.build_flags} -I variants/thinknode_m5 - -I src/helpres/sensors + -I src/helpers/sensors -D THINKNODE_M5 -D PIN_BUZZER=9 -D PIN_BOARD_SCL=1 diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp new file mode 100644 index 00000000..1ccc2026 --- /dev/null +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -0,0 +1,95 @@ +#include "ThinkNodeM6Board.h" +#include <Arduino.h> + +#ifdef THINKNODE_M6 + +#include <Wire.h> +#include <bluefruit.h> + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void ThinkNodeM6Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + + Wire.begin(); + +#ifdef P_LORA_TX_LED + pinMode(P_LORA_TX_LED, OUTPUT); + digitalWrite(P_LORA_TX_LED, LOW); +#endif + + delay(10); // give sx1262 some time to power up +} + +uint16_t ThinkNodeM6Board::getBattMilliVolts() { + int adcvalue = 0; + + digitalWrite(PIN_ADC_CTRL, HIGH); + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + delay(10); + + // ADC range is 0..3000mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + digitalWrite(PIN_ADC_CTRL, LOW); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); +} + +bool ThinkNodeM6Board::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("THINKNODE_M1_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h new file mode 100644 index 00000000..c3d7dad6 --- /dev/null +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -0,0 +1,56 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +// built-ins +#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 + +#define VBAT_DIVIDER_COMP ADC_MULTIPLIER // Compensation factor for the VBAT divider + +#define PIN_VBAT_READ BATTERY_PIN +#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) + +class ThinkNodeM6Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + +public: + + void begin(); + uint16_t getBattMilliVolts() override; + bool startOTAUpdate(const char* id, char reply[]) override; + + uint8_t getStartupReason() const override { + return startup_reason; + } + + #if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } + #endif + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode-M6"; + } + + void reboot() override { + NVIC_SystemReset(); + } + + void powerOff() override { + + // turn off all leds, sd_power_system_off will not do this for us + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, LOW); + #endif + + // power off board + sd_power_system_off(); + + } +}; diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini new file mode 100644 index 00000000..16394ced --- /dev/null +++ b/variants/thinknode_m6/platformio.ini @@ -0,0 +1,120 @@ +[ThinkNode_M6] +extends = nrf52_base +board = thinknode_m6 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/thinknode_m6 + -D THINKNODE_M6=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=38 + -D P_LORA_NSS=44 + -D P_LORA_RESET=42 + -D P_LORA_BUSY=43 + -D P_LORA_SCLK=45 + -D P_LORA_MISO=47 + -D P_LORA_MOSI=46 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=3.3 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=PIN_LED_BLUE +; -D PERSISTANT_GPS=1 +; -D ENV_SKIP_GPS_DETECT=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<helpers/*.cpp> + +<helpers/sensors> + +<ThinkNodeM6Board.cpp> + +<../variants/thinknode_m6> +lib_deps = + ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} +debug_tool = jlink +upload_protocol = nrfutil + +[env:ThinkNode_M6_repeater] +extends = ThinkNode_M6 +build_flags = + ${ThinkNode_M6.build_flags} + -D ADVERT_NAME='"ThinkNode Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 + -D GPS_NMEA_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/simple_repeater/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + +[env:ThinkNode_M6_room_server] +extends = ThinkNode_M6 +build_flags = + ${ThinkNode_M6.build_flags} + -D ADVERT_NAME='"ThinkNode Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/simple_room_server/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + +[env:ThinkNode_M6_companion_radio_ble] +extends = ThinkNode_M6 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${ThinkNode_M6.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D QSPIFLASH=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M6_companion_radio_usb] +extends = ThinkNode_M6 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${ThinkNode_M6.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D QSPIFLASH=1 + -D OFFLINE_QUEUE_SIZE=256 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<helpers/ui/buzzer.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${ThinkNode_M6.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 \ No newline at end of file diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp new file mode 100644 index 00000000..c14dd300 --- /dev/null +++ b/variants/thinknode_m6/target.cpp @@ -0,0 +1,49 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/MicroNMEALocationProvider.h> + +ThinkNodeM6Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/thinknode_m6/target.h b/variants/thinknode_m6/target.h new file mode 100644 index 00000000..38b1fed1 --- /dev/null +++ b/variants/thinknode_m6/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <ThinkNodeM6Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#include <helpers/sensors/LocationProvider.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/GxEPDDisplay.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern ThinkNodeM6Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m6/variant.cpp b/variants/thinknode_m6/variant.cpp new file mode 100644 index 00000000..c88f387d --- /dev/null +++ b/variants/thinknode_m6/variant.cpp @@ -0,0 +1,35 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(QSPI_FLASH_EN, OUTPUT); + digitalWrite(QSPI_FLASH_EN, HIGH); + + // For now stick adc_ctrl to fixed value + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, LOW); + + pinMode(PIN_LED_RED, OUTPUT); + pinMode(PIN_LED_BLUE, OUTPUT); + digitalWrite(PIN_LED_BLUE, LOW); + digitalWrite(PIN_LED_RED, LOW); + + // gps + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, HIGH); + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); + pinMode(PIN_GPS_RESET, OUTPUT); + digitalWrite(PIN_GPS_RESET, HIGH); +} diff --git a/variants/thinknode_m6/variant.h b/variants/thinknode_m6/variant.h new file mode 100644 index 00000000..70fd6506 --- /dev/null +++ b/variants/thinknode_m6/variant.h @@ -0,0 +1,108 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +#define WIRE_INTERFACES_COUNT (1) +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_PWR_EN (27) + +#define BATTERY_PIN (28) +#define ADC_MULTIPLIER (1.75F) +#define PIN_ADC_CTRL (11) + +#define ADC_RESOLUTION (12) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +#define PIN_SERIAL2_RX (22) +#define PIN_SERIAL2_TX (24) + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define PIN_WIRE_SDA (41) // P1.9 +#define PIN_WIRE_SCL (8) // P0.8 + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) +#define PIN_SPI_MOSI (46) +#define PIN_SPI_SCK (45) +//#define PIN_SPI_NSS (24) + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define PIN_LED_RED (12) +#define PIN_LED_BLUE (7) +#define LED_BLUE (-1) + +#define LED_BUILTIN PIN_LED_BLUE +#define PIN_LED LED_BUILTIN +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON HIGH + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (17) +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +//////////////////////////////////////////////////////////////////////////////// +// QSPI + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +#define PIN_QSPI_SCK (35) +#define PIN_QSPI_CS (23) +#define PIN_QSPI_IO0 (33) // MOSI if using two bit interface +#define PIN_QSPI_IO1 (34) // MISO if using two bit interface +#define PIN_QSPI_IO2 (36) // WP if using two bit interface (i.e. not used) +#define PIN_QSPI_IO3 (37) // HOLD if using two bit interface (i.e. not used) +#define QSPI_FLASH_EN (21) + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define GPS_L76K +#define PIN_GPS_RX (2) +#define PIN_GPS_TX (3) +#define PIN_GPS_EN (6) // EN +#define PIN_GPS_RESET (29) +#define PIN_GPS_STANDBY (30) // STANDBY +#define PIN_GPS_PPS (31) +#define GPS_BAUD_RATE 9600 From bde4fc3a231f53a4ba95ca2a34ff23509ab60ac0 Mon Sep 17 00:00:00 2001 From: Florent <florent@frizoncorrea.fr> Date: Fri, 28 Nov 2025 22:04:24 +0100 Subject: [PATCH 323/546] thinknode_m3: initial commit --- boards/thinknode_m3.json | 72 ++++++++++++ variants/thinknode_m3/ThinknodeM3Board.cpp | 80 ++++++++++++++ variants/thinknode_m3/ThinknodeM3Board.h | 68 ++++++++++++ variants/thinknode_m3/platformio.ini | 122 +++++++++++++++++++++ variants/thinknode_m3/target.cpp | 99 +++++++++++++++++ variants/thinknode_m3/target.h | 29 +++++ variants/thinknode_m3/variant.cpp | 95 ++++++++++++++++ variants/thinknode_m3/variant.h | 109 ++++++++++++++++++ 8 files changed, 674 insertions(+) create mode 100644 boards/thinknode_m3.json create mode 100644 variants/thinknode_m3/ThinknodeM3Board.cpp create mode 100644 variants/thinknode_m3/ThinknodeM3Board.h create mode 100644 variants/thinknode_m3/platformio.ini create mode 100644 variants/thinknode_m3/target.cpp create mode 100644 variants/thinknode_m3/target.h create mode 100644 variants/thinknode_m3/variant.cpp create mode 100644 variants/thinknode_m3/variant.h diff --git a/boards/thinknode_m3.json b/boards/thinknode_m3.json new file mode 100644 index 00000000..617740b6 --- /dev/null +++ b/boards/thinknode_m3.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x4405" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ] + ], + "usb_product": "elecrow_eink", + "mcu": "nrf52840", + "variant": "ELECROW-ThinkNode-M3", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "onboard_tools": [ + "jlink" + ], + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "elecrow nrf", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ] + }, + "url": "https://github.com/Elecrow-RD", + "vendor": "ELECROW" +} \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp new file mode 100644 index 00000000..74019fcb --- /dev/null +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -0,0 +1,80 @@ +#include <Arduino.h> +#include "ThinknodeM3Board.h" +#include <Wire.h> + +#include <bluefruit.h> + +void ThinknodeM3Board::begin() { + // for future use, sub-classes SHOULD call this from their begin() + startup_reason = BD_STARTUP_NORMAL; + btn_prev_state = HIGH; + + sd_power_mode_set(NRF_POWER_MODE_LOWPWR); + + // Enable DC/DC converter for improved power efficiency + NRF_POWER->DCDCEN = 1; + + Wire.begin(); + + delay(10); // give sx1262 some time to power up +} + +#if 0 +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + + +bool TrackerThinknodeM3Board::startOTAUpdate(const char* id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName("T1000E_OTA"); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + strcpy(reply, "OK - started"); + return true; +} +#endif \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h new file mode 100644 index 00000000..c9b96273 --- /dev/null +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -0,0 +1,68 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> + +#define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) + +class ThinknodeM3Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + uint8_t btn_prev_state; + +public: + void begin(); + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + + analogReference(AR_INTERNAL_2_4); + analogReadResolution(ADC_RESOLUTION); + delay(10); + + // ADC range is 0..2400mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * ADC_FACTOR); + } + + uint8_t getStartupReason() const override { return startup_reason; } + + #if defined(P_LORA_TX_LED) + #if !defined(P_LORA_TX_LED_ON) + #define P_LORA_TX_LED_ON HIGH + #endif + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, P_LORA_TX_LED_ON); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, !P_LORA_TX_LED_ON); // turn TX LED off + } + #endif + + const char* getManufacturerName() const override { + return "Elecrow ThinkNode M3"; + } + + int buttonStateChanged() { + #ifdef BUTTON_PIN + uint8_t v = digitalRead(BUTTON_PIN); + if (v != btn_prev_state) { + btn_prev_state = v; + return (v == LOW) ? 1 : -1; + } + #endif + return 0; + } + + void powerOff() override { + sd_power_system_off(); + } + + void reboot() override { + NVIC_SystemReset(); + } + +// bool startOTAUpdate(const char* id, char reply[]) override; +}; \ No newline at end of file diff --git a/variants/thinknode_m3/platformio.ini b/variants/thinknode_m3/platformio.ini new file mode 100644 index 00000000..8ef2ba54 --- /dev/null +++ b/variants/thinknode_m3/platformio.ini @@ -0,0 +1,122 @@ +[ThinkNode_M3] +extends = nrf52_base +board = thinknode_m3 +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/thinknode_m3 + -I src/helpers/ui + -D THINKNODE_M3 + -D PIN_USER_BTN=12 + -D USER_BTN_PRESSED=LOW + -D PIN_STATUS_LED=35 + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LORA_TX_POWER=22 + -D RF_SWITCH_TABLE + -D RX_BOOSTED_GAIN=true + -D P_LORA_BUSY=43 + -D P_LORA_SCLK=45 + -D P_LORA_NSS=44 + -D P_LORA_DIO_1=40 + -D P_LORA_MISO=47 + -D P_LORA_MOSI=46 + -D P_LORA_RESET=42 + -D P_LORA_TX_LED=PIN_LED_BLUE + -D P_LORA_TX_LED_ON=LOW + -D LR11X0_DIO_AS_RF_SWITCH=true + -D LR11X0_DIO3_TCXO_VOLTAGE=3.3 + -D MESH_DEBUG=1 + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<helpers/*.cpp> + +<../variants/thinknode_m3> + +<helpers/sensors> +debug_tool = stlink +upload_protocol = nrfutil +lib_deps= ${nrf52_base.lib_deps} + +[env:ThinkNode_M3_repeater] +extends = ThinkNode_M3 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"ThinkNode_M3 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M3_room_server] +extends = ThinkNode_M3 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D ADVERT_NAME='"ThinkNode_M3 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D RF_SWITCH_TABLE +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + +[env:ThinkNode_M3_companion_radio_usb] +extends = ThinkNode_M3 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=23 + -D PIN_BUZZER_EN=36 +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<helpers/ui/buzzer.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M3.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M3_companion_radio_ble] +extends = ThinkNode_M3 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 708608 +build_flags = ${ThinkNode_M3.build_flags} + -I examples/companion_radio/ui-orig + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_TX_POWER=0 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 + -D GPS_NMEA_DEBUG + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=NullDisplayDriver + -D PIN_BUZZER=23 + -D PIN_BUZZER_EN=36 +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<helpers/ui/buzzer.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${ThinkNode_M3.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp new file mode 100644 index 00000000..c6708e4d --- /dev/null +++ b/variants/thinknode_m3/target.cpp @@ -0,0 +1,99 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/sensors/MicroNMEALocationProvider.h> + +ThinknodeM3Board board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + NullDisplayDriver display; +#endif + +#ifndef LORA_CR + #define LORA_CR 5 +#endif + +#ifdef RF_SWITCH_TABLE +static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { + RADIOLIB_LR11X0_DIO5, + RADIOLIB_LR11X0_DIO6, + RADIOLIB_NC, + RADIOLIB_NC, + RADIOLIB_NC +}; + +static const Module::RfSwitchMode_t rfswitch_table[] = { + // mode DIO5 DIO6 + { LR11x0::MODE_STBY, {LOW , LOW }}, + { LR11x0::MODE_RX, {HIGH, LOW }}, + { LR11x0::MODE_TX, {HIGH, HIGH }}, + { LR11x0::MODE_TX_HP, {LOW , HIGH }}, + { LR11x0::MODE_TX_HF, {LOW , LOW }}, + { LR11x0::MODE_GNSS, {LOW , LOW }}, + { LR11x0::MODE_WIFI, {LOW , LOW }}, + END_OF_MODE_TABLE, +}; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + +#ifdef LR11X0_DIO3_TCXO_VOLTAGE + float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; +#else + float tcxo = 1.6f; +#endif + + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR, RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); + if (status != RADIOLIB_ERR_NONE) { + Serial.print("ERROR: radio init failed: "); + Serial.println(status); + return false; // fail + } + + radio.setCRC(2); + radio.explicitHeader(); + +#ifdef RF_SWITCH_TABLE + radio.setRfSwitchTable(rfswitch_dios, rfswitch_table); +#endif +#ifdef RX_BOOSTED_GAIN + radio.setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif + + return true; // success +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} \ No newline at end of file diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h new file mode 100644 index 00000000..f60a85b0 --- /dev/null +++ b/variants/thinknode_m3/target.h @@ -0,0 +1,29 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include "ThinknodeM3Board.h" +#include <helpers/radiolib/CustomLR1110Wrapper.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#include <helpers/sensors/LocationProvider.h> +#include <helpers/AutoDiscoverRTCClock.h> +#ifdef DISPLAY_CLASS + #include "NullDisplayDriver.h" +#endif + +#ifdef DISPLAY_CLASS + extern NullDisplayDriver display; +#endif + +extern ThinknodeM3Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m3/variant.cpp b/variants/thinknode_m3/variant.cpp new file mode 100644 index 00000000..dad0f3f5 --- /dev/null +++ b/variants/thinknode_m3/variant.cpp @@ -0,0 +1,95 @@ +/* + * variant.cpp + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = +{ + 0, // P0.00 + 1, // P0.01 + 2, // P0.02 + 3, // P0.03 + 4, // P0.04 + 5, // P0.05 + 6, // P0.06 + 7, // P0.07 + 8, // P0.08 + 9, // P0.09 + 10, // P0.10 + 11, // P0.11 + 12, // P0.12 + 13, // P0.13 + 14, // P0.14 + 15, // P0.15 + 16, // P0.16 + 17, // P0.17 + 18, // P0.18 + 19, // P0.19 + 20, // P0.20 + 21, // P0.21 + 22, // P0.22 + 23, // P0.23 + 24, // P0.24 + 25, // P0.25 + 26, // P0.26 + 27, // P0.27 + 28, // P0.28 + 29, // P0.29 + 30, // P0.30 + 31, // P0.31 + 32, // P1.00 + 33, // P1.01 + 34, // P1.02 + 35, // P1.03 + 36, // P1.04 + 37, // P1.05 + 38, // P1.06 + 39, // P1.07 + 40, // P1.08 + 41, // P1.09 + 42, // P1.10 + 43, // P1.11 + 44, // P1.12 + 45, // P1.13 + 46, // P1.14 + 47, // P1.15 +}; + +void initVariant() +{ +/* TODO */ + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(BAT_POWER, OUTPUT); + digitalWrite(BAT_POWER, HIGH); + pinMode(EEPROM_POWER, OUTPUT); + digitalWrite(EEPROM_POWER, HIGH); + + pinMode(36, OUTPUT); + digitalWrite(36, HIGH); + pinMode(34, OUTPUT); + digitalWrite(34, HIGH); + + pinMode(LED_POWER, OUTPUT); + digitalWrite(LED_POWER, HIGH); + + pinMode(PIN_LED_BLUE, OUTPUT); + pinMode(PIN_LED_GREEN, OUTPUT); + pinMode(PIN_LED_RED, OUTPUT); + + pinMode(BUTTON_PIN, INPUT_PULLUP); + + pinMode(PIN_GPS_POWER, OUTPUT); + pinMode(PIN_GPS_EN, OUTPUT); + pinMode(PIN_GPS_RESET, OUTPUT); + + // Power on gps but in standby + digitalWrite(PIN_GPS_EN, LOW); + digitalWrite(PIN_GPS_POWER, HIGH); +} diff --git a/variants/thinknode_m3/variant.h b/variants/thinknode_m3/variant.h new file mode 100644 index 00000000..02ed78a8 --- /dev/null +++ b/variants/thinknode_m3/variant.h @@ -0,0 +1,109 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) +// #define USE_LFRC // 32.768 kHz RC oscillator + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define NRF_APM // detect usb power + + +#define EXT_CHRG_DETECT (32) // P1.3 +#define EXT_PWR_DETECT (31) // P0.5 + +#define PIN_VBAT_READ (5) +#define AREF_VOLTAGE (2.4f) +#define ADC_MULTIPLIER (2.0) //(1.75f) +// 2.0 gives more coherent value, 4.2V when charged, needs tweaking +#define ADC_RESOLUTION (12) +#define ADC_MAX (4096) + +#define EEPROM_POWER (7) +#define BAT_POWER (17) +#define PIN_PWR_EN (16) + + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX + +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define HAS_WIRE (1) +#define WIRE_INTERFACES_COUNT (1) + +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 +#define I2C_NO_RESCAN + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (1) + +#define PIN_SPI_MISO (47) // P1.15 +#define PIN_SPI_MOSI (46) // P1.14 +#define PIN_SPI_SCK (45) // P1.13 +#define PIN_SPI_NSS (44) // P1.12 + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_POWER (29) +#define LED_BLUE (-1) // No blue led +#define PIN_LED_BLUE (37) +#define PIN_LED_GREEN (35) // P0.24 +#define PIN_LED_RED (33) +#define LED_PIN PIN_LED_GREEN +#define LED_BUILTIN PIN_LED_BLUE +#define LED_STATE_ON LOW + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 (12) // P0.12 +#define BUTTON_PIN PIN_BUTTON1 + +//////////////////////////////////////////////////////////////////////////////// +// GPS + +#define HAS_GPS 1 +#define PIN_GPS_RX (22) +#define PIN_GPS_TX (20) + +#define PIN_GPS_POWER (14) +#define PIN_GPS_EN (21) // STANDBY +#define PIN_GPS_RESET (25) // REINIT +#define GPS_RESET_ACTIVE LOW +#define GPS_EN_ACTIVE HIGH +#define GPS_BAUDRATE 9600 + +//////////////////////////////////////////////////////////////////////////////// +// Buzzer + +#define BUZZER_EN (37) // P1.5 +#define BUZZER_PIN (25) // P0.25 \ No newline at end of file From 0df8c86b98119eb667e22290897ceb14a3023786 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Fri, 12 Dec 2025 17:24:28 +0000 Subject: [PATCH 324/546] Refactor devcontainer runArgs --- .devcontainer/devcontainer.json | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index b734fe6b..fcde5048 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -9,10 +9,12 @@ } }, "runArgs": [ - "--network=host", "--privileged", - "--volume", - "/dev/bus/usb:/dev/bus/usb" + // arch tty* is owned by uucp (986) + // debian tty* is owned by uucp (20) - no change needed + "--group-add=986", + "--network=host", + "--volume=/dev/bus/usb:/dev/bus/usb:ro" ], "postCreateCommand": { "platformio": "pipx install platformio" From 2deb9cf1440a658c0b51cb824c6582eed7fb87f1 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Sat, 13 Dec 2025 07:32:26 +0700 Subject: [PATCH 325/546] Fixed to call getMCUTemperature once. --- examples/simple_repeater/MyMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 39ecd105..6ae6ac0a 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -174,9 +174,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - float temperature = (float)board.getMCUTemperature(); + float temperature = board.getMCUTemperature(); if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN - telemetry.addTemperature(TELEM_CHANNEL_SELF, (float)board.getMCUTemperature()); // Built-in MCU Temperature + telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature } // query other sensors -- target specific From 6486192477f519dc2e93b86bfde7c4bbeab133d5 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Wed, 17 Dec 2025 10:22:15 +0100 Subject: [PATCH 326/546] variants: IkokaNrf52Board: Use NRF52Board base class Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 9dfc3833..58f9e399 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -1,11 +1,12 @@ #pragma once -#include <MeshCore.h> #include <Arduino.h> +#include <MeshCore.h> +#include <helpers/NRF52Board.h> #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public mesh::MainBoard { +class IkokaNrf52Board : public NRF52Board { protected: uint8_t startup_reason; From 87b0e432bbd1796aff43e506969151c07b245052 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 9 Dec 2025 14:31:55 +0100 Subject: [PATCH 327/546] Deduplicate reboot() for NRF52 boards The reboot() method is the same for all NRF52 boards. Use a shared implementation. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/NRF52Board.h | 1 + variants/heltec_mesh_solar/MeshSolarBoard.h | 4 ---- variants/heltec_t114/T114Board.h | 4 ---- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 4 ---- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 4 ---- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 4 ---- variants/keepteen_lt1/KeepteenLT1Board.h | 4 ---- variants/lilygo_techo/TechoBoard.h | 4 ---- variants/lilygo_techo_lite/TechoBoard.h | 4 ---- variants/mesh_pocket/MeshPocket.h | 4 ---- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h | 5 ----- variants/nano_g2_ultra/nano-g2.h | 2 -- variants/promicro/PromicroBoard.h | 4 ---- variants/rak4631/RAK4631Board.h | 4 ---- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 4 ---- variants/sensecap_solar/SenseCapSolarBoard.h | 4 ---- variants/t1000-e/T1000eBoard.h | 4 ---- variants/thinknode_m1/ThinkNodeM1Board.h | 4 ---- variants/wio-tracker-l1/WioTrackerL1Board.h | 4 ---- variants/wio_wm1110/WioWM1110Board.h | 4 ---- variants/xiao_nrf52/XiaoNrf52Board.h | 4 ---- 21 files changed, 1 insertion(+), 79 deletions(-) diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 1a6f879f..5158229d 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -8,5 +8,6 @@ class NRF52Board : public mesh::MainBoard { public: float getMCUTemperature() override; + virtual void reboot() override { NVIC_SystemReset(); } }; #endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 688a8e88..24c5a812 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -37,9 +37,5 @@ public: return "Heltec Mesh Solar"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index bd9287e2..e35afeb1 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -44,10 +44,6 @@ public: return "Heltec T114"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { #ifdef LED_PIN digitalWrite(LED_PIN, HIGH); diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 58f9e399..87f85980 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -43,10 +43,6 @@ public: return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index ac306977..2d185365 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -51,10 +51,6 @@ public: return MANUFACTURER_STRING; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char *id, char reply[]) override; }; diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 8f817ff1..1a9b00c4 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -51,10 +51,6 @@ public: return MANUFACTURER_STRING; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char *id, char reply[]) override; }; diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index e8c444ed..2e720b72 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -40,10 +40,6 @@ public: } #endif - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 91dfb5ab..316536ae 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -49,8 +49,4 @@ public: #endif sd_power_system_off(); } - - void reboot() override { - NVIC_SystemReset(); - } }; diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 79b5610c..db800951 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -49,8 +49,4 @@ public: #endif sd_power_system_off(); } - - void reboot() override { - NVIC_SystemReset(); - } }; diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 876e3810..fd9c819e 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -38,10 +38,6 @@ public: return "Heltec MeshPocket"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 18aa9e8a..ba496491 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -80,10 +80,5 @@ public: } #endif - - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; \ No newline at end of file diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 7ed1c74b..1e8dbce4 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -48,8 +48,6 @@ public: const char *getManufacturerName() const override { return "Nano G2 Ultra"; } - void reboot() override { NVIC_SystemReset(); } - void powerOff() override { // put GPS chip to sleep digitalWrite(PIN_GPS_STANDBY, LOW); diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 916afefd..55137010 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -75,10 +75,6 @@ public: return 0; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 2040bf41..8922cc31 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -55,9 +55,5 @@ public: return "RAK 4631"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index fe554fe6..732edc69 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -43,10 +43,6 @@ public: return "RAK WisMesh Tag"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; void powerOff() override { diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index 999c7783..f14f2f1a 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -35,9 +35,5 @@ public: return "Seeed SenseCap Solar"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index d04de5a3..02aa0733 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -93,9 +93,5 @@ public: sd_power_system_off(); } - void reboot() override { - NVIC_SystemReset(); - } - // bool startOTAUpdate(const char* id, char reply[]) override; }; \ No newline at end of file diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index 5920b809..b6eff743 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -40,10 +40,6 @@ public: return "Elecrow ThinkNode-M1"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { // turn off all leds, sd_power_system_off will not do this for us diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 6797f629..b51f56bd 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -35,10 +35,6 @@ public: return "Seeed Wio Tracker L1"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { sd_power_system_off(); } diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index ffbc4e93..5d36834b 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -41,10 +41,6 @@ public: return "Seeed Wio WM1110"; } - void reboot() override { - NVIC_SystemReset(); - } - bool startOTAUpdate(const char* id, char reply[]) override; void enableSensorPower(bool enable) { diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 86be7aa4..32dcc576 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -43,10 +43,6 @@ public: return "Seeed Xiao-nrf52"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { // set led on and wait for button release before poweroff digitalWrite(PIN_LED, LOW); From 93d1560d1471d765be4b94e65623db38e69d80a3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 9 Dec 2025 15:00:08 +0100 Subject: [PATCH 328/546] Use common NRF52 begin() and deduplicate() startup reason init Use a common begin() method that can be called from derived classes to contain the shared initialization code. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/NRF52Board.h | 5 +++++ variants/heltec_mesh_solar/MeshSolarBoard.cpp | 3 +-- variants/heltec_mesh_solar/MeshSolarBoard.h | 4 ---- variants/heltec_t114/T114Board.cpp | 3 +-- variants/heltec_t114/T114Board.h | 4 ---- variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp | 3 +-- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 4 ---- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 3 +-- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 4 ---- variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp | 3 +-- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 4 ---- variants/keepteen_lt1/KeepteenLT1Board.cpp | 3 +-- variants/keepteen_lt1/KeepteenLT1Board.h | 3 --- variants/lilygo_techo/TechoBoard.cpp | 3 +-- variants/lilygo_techo/TechoBoard.h | 8 -------- variants/lilygo_techo_lite/TechoBoard.cpp | 3 +-- variants/lilygo_techo_lite/TechoBoard.h | 8 -------- variants/mesh_pocket/MeshPocket.cpp | 3 +-- variants/mesh_pocket/MeshPocket.h | 6 ------ variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp | 3 +-- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h | 3 --- variants/nano_g2_ultra/nano-g2.cpp | 3 +-- variants/nano_g2_ultra/nano-g2.h | 5 ----- variants/promicro/PromicroBoard.cpp | 3 +-- variants/promicro/PromicroBoard.h | 3 --- variants/rak4631/RAK4631Board.cpp | 3 +-- variants/rak4631/RAK4631Board.h | 4 ---- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 5 ++--- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 4 ---- variants/sensecap_solar/SenseCapSolarBoard.cpp | 3 +-- variants/sensecap_solar/SenseCapSolarBoard.h | 4 ---- variants/t1000-e/T1000eBoard.cpp | 3 +-- variants/t1000-e/T1000eBoard.h | 3 --- variants/thinknode_m1/ThinkNodeM1Board.cpp | 3 +-- variants/thinknode_m1/ThinkNodeM1Board.h | 7 ------- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 3 +-- variants/wio-tracker-l1/WioTrackerL1Board.h | 2 -- variants/wio_wm1110/WioWM1110Board.cpp | 2 +- variants/wio_wm1110/WioWM1110Board.h | 4 ---- variants/xiao_nrf52/XiaoNrf52Board.cpp | 3 +-- variants/xiao_nrf52/XiaoNrf52Board.h | 4 ---- 41 files changed, 26 insertions(+), 128 deletions(-) diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 5158229d..c349a8e7 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -6,7 +6,12 @@ #if defined(NRF52_PLATFORM) class NRF52Board : public mesh::MainBoard { +protected: + uint8_t startup_reason; + public: + virtual void begin() { startup_reason = BD_STARTUP_NORMAL; } + virtual uint8_t getStartupReason() const override { return startup_reason; } float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } }; diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp index 54929cd1..ad5f1333 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.cpp +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) } void MeshSolarBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); meshSolarStart(); diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 24c5a812..0ccbb127 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -22,12 +22,8 @@ class MeshSolarBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } uint16_t getBattMilliVolts() override { return meshSolarGetBattVoltage(); diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index f46a1a84..4730a49b 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void T114Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index e35afeb1..a040e37f 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -10,12 +10,8 @@ #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range class T114Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp index 44940b8f..bf3f4a9e 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void IkokaNrf52Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // ensure we have pull ups on the screen i2c, this isn't always available // in hardware and it should only be 20k ohms. Disable the pullups if we diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 87f85980..25cf643c 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -7,12 +7,8 @@ #ifdef IKOKA_NRF52 class IkokaNrf52Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp index ee799692..15507ad3 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void IkokaNanoNRFBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index 2d185365..e6da3d11 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -7,12 +7,8 @@ #ifdef XIAO_NRF52 class IkokaNanoNRFBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 6b660383..1adc14bd 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void IkokaStickNRFBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 1a9b00c4..78cb8a7f 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -7,12 +7,8 @@ #ifdef XIAO_NRF52 class IkokaStickNRFBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp index 46bff1fc..a61909fe 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.cpp +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -7,8 +7,7 @@ static BLEDfu bledfu; void KeepteenLT1Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index 2e720b72..e98972ae 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -6,14 +6,11 @@ class KeepteenLT1Board : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } - #define BATTERY_SAMPLES 8 uint16_t getBattMilliVolts() override { diff --git a/variants/lilygo_techo/TechoBoard.cpp b/variants/lilygo_techo/TechoBoard.cpp index dee14688..e1259644 100644 --- a/variants/lilygo_techo/TechoBoard.cpp +++ b/variants/lilygo_techo/TechoBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void TechoBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 316536ae..b7650d00 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -14,19 +14,11 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class TechoBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: - void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - const char* getManufacturerName() const override { return "LilyGo T-Echo"; } diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp index dee14688..e1259644 100644 --- a/variants/lilygo_techo_lite/TechoBoard.cpp +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void TechoBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index db800951..c92bcfca 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -14,19 +14,11 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class TechoBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: - void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - const char* getManufacturerName() const override { return "LilyGo T-Echo"; } diff --git a/variants/mesh_pocket/MeshPocket.cpp b/variants/mesh_pocket/MeshPocket.cpp index 0d0e8993..0fa7d5f9 100644 --- a/variants/mesh_pocket/MeshPocket.cpp +++ b/variants/mesh_pocket/MeshPocket.cpp @@ -20,8 +20,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) } void HeltecMeshPocket::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Serial.begin(115200); pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index fd9c819e..0f4d2c8b 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -10,14 +10,8 @@ #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range class HeltecMeshPocket : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } - - uint16_t getBattMilliVolts() override { int adcvalue = 0; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index 561ed504..c38f71e4 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -5,8 +5,7 @@ #include <bluefruit.h> void MinewsemiME25LS01Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index ba496491..34612e93 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -23,7 +23,6 @@ class MinewsemiME25LS01Board : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: @@ -42,8 +41,6 @@ public: return (ADC_MULTIPLIER * raw); } - uint8_t getStartupReason() const override { return startup_reason; } - const char* getManufacturerName() const override { return "Minewsemi"; } diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 9a278287..6a749aab 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -24,8 +24,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) void NanoG2Ultra::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // set user button pinMode(PIN_BUTTON1, INPUT); diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 1e8dbce4..6961fc86 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -36,16 +36,11 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class NanoG2Ultra : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char *id, char reply[]) override; - uint8_t getStartupReason() const override { return startup_reason; } - const char *getManufacturerName() const override { return "Nano G2 Ultra"; } void powerOff() override { diff --git a/variants/promicro/PromicroBoard.cpp b/variants/promicro/PromicroBoard.cpp index b923e16e..4ac72072 100644 --- a/variants/promicro/PromicroBoard.cpp +++ b/variants/promicro/PromicroBoard.cpp @@ -7,8 +7,7 @@ static BLEDfu bledfu; void PromicroBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; pinMode(PIN_VBAT_READ, INPUT); diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 55137010..ba002134 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -22,15 +22,12 @@ class PromicroBoard : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } - #define BATTERY_SAMPLES 8 uint16_t getBattMilliVolts() override { diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 97a96602..1a37db15 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void RAK4631Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); pinMode(PIN_VBAT_READ, INPUT); #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 8922cc31..394b05fb 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -30,12 +30,8 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) class RAK4631Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #define BATTERY_SAMPLES 8 diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 68638f0d..11b9f0a5 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -19,9 +19,8 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void RAKWismeshTagBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; - + NRF52Board::begin(); + // Enable DC/DC converter for improved power efficiency uint8_t sd_enabled = 0; sd_softdevice_is_enabled(&sd_enabled); diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 732edc69..032b00af 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -9,12 +9,8 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) class RAKWismeshTagBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) && defined(LED_STATE_ON) void onBeforeTransmit() override { diff --git a/variants/sensecap_solar/SenseCapSolarBoard.cpp b/variants/sensecap_solar/SenseCapSolarBoard.cpp index d6c044d1..1493d22a 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.cpp +++ b/variants/sensecap_solar/SenseCapSolarBoard.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void SenseCapSolarBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index f14f2f1a..eab08163 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -5,12 +5,8 @@ #include <helpers/NRF52Board.h> class SenseCapSolarBoard : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index a41abd92..27432b1b 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -5,8 +5,7 @@ #include <bluefruit.h> void T1000eBoard::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; // Enable DC/DC converter for improved power efficiency diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 02aa0733..c16d8ccd 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -6,7 +6,6 @@ class T1000eBoard : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: @@ -34,8 +33,6 @@ public: #endif } - uint8_t getStartupReason() const override { return startup_reason; } - const char* getManufacturerName() const override { return "Seeed Tracker T1000-e"; } diff --git a/variants/thinknode_m1/ThinkNodeM1Board.cpp b/variants/thinknode_m1/ThinkNodeM1Board.cpp index 12dd7362..bfec5f3f 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.cpp +++ b/variants/thinknode_m1/ThinkNodeM1Board.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void ThinkNodeM1Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index b6eff743..7e961f00 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -14,19 +14,12 @@ #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) class ThinkNodeM1Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); uint16_t getBattMilliVolts() override; bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index 34d9a874..d07df83f 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -19,8 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioTrackerL1Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); btn_prev_state = HIGH; // Enable DC/DC converter for improved power efficiency diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index b51f56bd..e1930ff7 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -6,12 +6,10 @@ class WioTrackerL1Board : public NRF52Board { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index 153d476c..cb54609d 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -21,7 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioWM1110Board::begin() { - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // Enable DC/DC converter for improved power efficiency uint8_t sd_enabled = 0; diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 5d36834b..9b4e153b 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -12,12 +12,8 @@ #define Serial Serial1 class WioWM1110Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(LED_GREEN) void onBeforeTransmit() override { diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index f847c654..34c15767 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -21,8 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void XiaoNrf52Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); // Enable DC/DC converter for improved power efficiency uint8_t sd_enabled = 0; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 32dcc576..1f1490cf 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -7,12 +7,8 @@ #ifdef XIAO_NRF52 class XiaoNrf52Board : public NRF52Board { -protected: - uint8_t startup_reason; - public: void begin(); - uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { From e3bb225efb158cb0d757edb14bd7c56205e347db Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 9 Dec 2025 17:05:33 +0100 Subject: [PATCH 329/546] Deduplicate DC/DC regulator enable for NRF52 boards Some NRF52 boards are able to use the internal power-efficient DC/DC regulator. Add a new class that can be inherited by board classes to enable this feature and reduce the power consumption. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/NRF52Board.cpp | 17 +++++++++++++++++ src/helpers/NRF52Board.h | 14 +++++++++++++- variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp | 11 +---------- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 2 +- variants/t1000-e/T1000eBoard.cpp | 11 +---------- variants/t1000-e/T1000eBoard.h | 2 +- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 11 +---------- variants/wio-tracker-l1/WioTrackerL1Board.h | 2 +- variants/wio_wm1110/WioWM1110Board.cpp | 11 +---------- variants/wio_wm1110/WioWM1110Board.h | 2 +- variants/xiao_nrf52/XiaoNrf52Board.cpp | 11 +---------- variants/xiao_nrf52/XiaoNrf52Board.h | 2 +- 12 files changed, 40 insertions(+), 56 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index ee50fe4c..93dfb288 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,6 +1,23 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" +void NRF52Board::begin() { + startup_reason = BD_STARTUP_NORMAL; +} + +void NRF52BoardDCDC::begin() { + NRF52Board::begin(); + + // Enable DC/DC converter for improved power efficiency + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); + } else { + NRF_POWER->DCDCEN = 1; + } +} + // Temperature from NRF52 MCU float NRF52Board::getMCUTemperature() { NRF_TEMP->TASKS_START = 1; // Start temperature measurement diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c349a8e7..588c190a 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -10,9 +10,21 @@ protected: uint8_t startup_reason; public: - virtual void begin() { startup_reason = BD_STARTUP_NORMAL; } + virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } }; + +/* + * The NRF52 has an internal DC/DC regulator that allows increased efficiency + * compared to the LDO regulator. For being able to use it, the module/board + * needs to have the required inductors and and capacitors populated. If the + * hardware requirements are met, this subclass can be used to enable the DC/DC + * regulator. + */ +class NRF52BoardDCDC : virtual public NRF52Board { +public: + virtual void begin() override; +}; #endif \ No newline at end of file diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 11b9f0a5..88d09249 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -19,16 +19,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void RAKWismeshTagBoard::begin() { - NRF52Board::begin(); - - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 032b00af..d62dee85 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -8,7 +8,7 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public NRF52Board { +class RAKWismeshTagBoard : public NRF52BoardDCDC { public: void begin(); diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 27432b1b..9211b4c6 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -5,18 +5,9 @@ #include <bluefruit.h> void T1000eBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); btn_prev_state = HIGH; - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } - #ifdef BUTTON_PIN pinMode(BATTERY_PIN, INPUT); pinMode(BUTTON_PIN, INPUT); diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index c16d8ccd..32f54bb3 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -4,7 +4,7 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class T1000eBoard : public NRF52Board { +class T1000eBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index d07df83f..fbf64f77 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -19,18 +19,9 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioTrackerL1Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); btn_prev_state = HIGH; - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } - pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input // Set all button pins to INPUT_PULLUP pinMode(PIN_BUTTON1, INPUT_PULLUP); diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index e1930ff7..7e82e447 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -4,7 +4,7 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class WioTrackerL1Board : public NRF52Board { +class WioTrackerL1Board : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index cb54609d..d0ab9785 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -21,16 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void WioWM1110Board::begin() { - NRF52Board::begin(); - - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } + NRF52BoardDCDC::begin(); pinMode(BATTERY_PIN, INPUT); pinMode(LED_GREEN, OUTPUT); diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 9b4e153b..697028b0 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -11,7 +11,7 @@ #endif #define Serial Serial1 -class WioWM1110Board : public NRF52Board { +class WioWM1110Board : public NRF52BoardDCDC { public: void begin(); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index 34c15767..e61f3806 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -21,16 +21,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { } void XiaoNrf52Board::begin() { - NRF52Board::begin(); - - // Enable DC/DC converter for improved power efficiency - uint8_t sd_enabled = 0; - sd_softdevice_is_enabled(&sd_enabled); - if (sd_enabled) { - sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE); - } else { - NRF_POWER->DCDCEN = 1; - } + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1f1490cf..07325493 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,7 +6,7 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52Board { +class XiaoNrf52Board : public NRF52BoardDCDC { public: void begin(); From b024b9e1a14db4309692972f6d2cfca53c4020dd Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 9 Dec 2025 19:56:00 +0100 Subject: [PATCH 330/546] Deduplicate NRF52 startOTAUpdate() The startOTAUpdate() is the same for all NRF52 boards. Use a common implementation for all boards that currently have a specific implementation. The following boards currently have an empty startOTAUpdate() for whatever reasons and therefore are not inheriting NRF52BoardOTA to keep the same state: Nano G2 Ultra, Seeed SenseCAP T1000-E, Wio WM1110. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/NRF52Board.cpp | 64 ++++++++++++++++++ src/helpers/NRF52Board.h | 9 +++ variants/heltec_mesh_solar/MeshSolarBoard.cpp | 62 +---------------- variants/heltec_mesh_solar/MeshSolarBoard.h | 6 +- variants/heltec_t114/T114Board.cpp | 60 +---------------- variants/heltec_t114/T114Board.h | 5 +- .../ikoka_handheld_nrf/IkokaNrf52Board.cpp | 59 ---------------- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 5 +- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp | 60 +---------------- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 5 +- .../ikoka_stick_nrf/IkokaStickNRFBoard.cpp | 60 +---------------- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 5 +- variants/keepteen_lt1/KeepteenLT1Board.cpp | 61 +---------------- variants/keepteen_lt1/KeepteenLT1Board.h | 5 +- variants/lilygo_techo/TechoBoard.cpp | 62 +---------------- variants/lilygo_techo/TechoBoard.h | 4 +- variants/lilygo_techo_lite/TechoBoard.cpp | 62 +---------------- variants/lilygo_techo_lite/TechoBoard.h | 4 +- variants/mesh_pocket/MeshPocket.cpp | 61 +---------------- variants/mesh_pocket/MeshPocket.h | 5 +- .../MinewsemiME25LS01Board.cpp | 61 +---------------- .../MinewsemiME25LS01Board.h | 6 +- variants/promicro/PromicroBoard.cpp | 61 +---------------- variants/promicro/PromicroBoard.h | 5 +- variants/rak4631/RAK4631Board.cpp | 67 +------------------ variants/rak4631/RAK4631Board.h | 5 +- .../rak_wismesh_tag/RAKWismeshTagBoard.cpp | 67 +------------------ variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 5 +- .../sensecap_solar/SenseCapSolarBoard.cpp | 63 +---------------- variants/sensecap_solar/SenseCapSolarBoard.h | 5 +- variants/t1000-e/T1000eBoard.cpp | 65 +----------------- variants/thinknode_m1/ThinkNodeM1Board.cpp | 64 +----------------- variants/thinknode_m1/ThinkNodeM1Board.h | 5 +- variants/wio-tracker-l1/WioTrackerL1Board.cpp | 65 +----------------- variants/wio-tracker-l1/WioTrackerL1Board.h | 5 +- variants/xiao_nrf52/XiaoNrf52Board.cpp | 59 ---------------- variants/xiao_nrf52/XiaoNrf52Board.h | 5 +- 37 files changed, 133 insertions(+), 1144 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 93dfb288..c0d58314 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -1,6 +1,22 @@ #if defined(NRF52_PLATFORM) #include "NRF52Board.h" +#include <bluefruit.h> + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + void NRF52Board::begin() { startup_reason = BD_STARTUP_NORMAL; } @@ -37,4 +53,52 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } + +bool NRF52BoardOTA::startOTAUpdate(const char *id, char reply[]) { + // Config the peripheral connection with maximum bandwidth + // more SRAM required by SoftDevice + // Note: All config***() function must be called before begin() + Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); + Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); + + Bluefruit.begin(1, 0); + // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 + Bluefruit.setTxPower(4); + // Set the BLE device name + Bluefruit.setName(ota_name); + + Bluefruit.Periph.setConnectCallback(connect_callback); + Bluefruit.Periph.setDisconnectCallback(disconnect_callback); + + // To be consistent OTA DFU should be added first if it exists + bledfu.begin(); + + // Set up and start advertising + // Advertising packet + Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); + Bluefruit.Advertising.addTxPower(); + Bluefruit.Advertising.addName(); + + /* Start Advertising + - Enable auto advertising if disconnected + - Interval: fast mode = 20 ms, slow mode = 152.5 ms + - Timeout for fast mode is 30 seconds + - Start(timeout) with timeout = 0 will advertise forever (until connected) + + For recommended advertising interval + https://developer.apple.com/library/content/qa/qa1931/_index.html + */ + Bluefruit.Advertising.restartOnDisconnect(true); + Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms + Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode + Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds + + uint8_t mac_addr[6]; + memset(mac_addr, 0, sizeof(mac_addr)); + Bluefruit.getAddr(mac_addr); + sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[5], mac_addr[4], mac_addr[3], + mac_addr[2], mac_addr[1], mac_addr[0]); + + return true; +} #endif diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 588c190a..c5f32a20 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -27,4 +27,13 @@ class NRF52BoardDCDC : virtual public NRF52Board { public: virtual void begin() override; }; + +class NRF52BoardOTA : virtual public NRF52Board { +private: + char *ota_name; + +public: + NRF52BoardOTA(char *name) : ota_name(name) {} + virtual bool startOTAUpdate(const char *id, char reply[]) override; +}; #endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.cpp b/variants/heltec_mesh_solar/MeshSolarBoard.cpp index ad5f1333..bc955fb5 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.cpp +++ b/variants/heltec_mesh_solar/MeshSolarBoard.cpp @@ -1,24 +1,7 @@ #include <Arduino.h> -#include "MeshSolarBoard.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) -{ - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) -{ - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "MeshSolarBoard.h" void MeshSolarBoard::begin() { NRF52Board::begin(); @@ -31,46 +14,3 @@ void MeshSolarBoard::begin() { Wire.begin(); } - -bool MeshSolarBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("MESH_SOLAR_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 0ccbb127..69437c87 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -20,9 +20,9 @@ #define SX126X_DIO2_AS_RF_SWITCH true #define SX126X_DIO3_TCXO_VOLTAGE 1.8 - -class MeshSolarBoard : public NRF52Board { +class MeshSolarBoard : public NRF52BoardOTA { public: + MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {} void begin(); uint16_t getBattMilliVolts() override { @@ -32,6 +32,4 @@ public: const char* getManufacturerName() const override { return "Heltec Mesh Solar"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 4730a49b..4995e7de 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -2,21 +2,6 @@ #include <Arduino.h> #include <Wire.h> -#include <bluefruit.h> - -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} void T114Board::begin() { NRF52Board::begin(); @@ -38,47 +23,4 @@ void T114Board::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -bool T114Board::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("T114_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} +} \ No newline at end of file diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index a040e37f..74e26455 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -9,8 +9,9 @@ #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public NRF52Board { +class T114Board : public NRF52BoardOTA { public: + T114Board() : NRF52BoardOTA("T114_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -50,6 +51,4 @@ public: #endif sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp index bf3f4a9e..f1d9f17d 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.cpp @@ -2,24 +2,9 @@ #include <Arduino.h> #include <Wire.h> -#include <bluefruit.h> #include "IkokaNrf52Board.h" -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - void IkokaNrf52Board::begin() { NRF52Board::begin(); @@ -52,48 +37,4 @@ void IkokaNrf52Board::begin() { delay(10); // give sx1262 some time to power up } -bool IkokaNrf52Board::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("XIAO_NRF52_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - - return true; -} - #endif diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 25cf643c..acb58b3c 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -6,8 +6,9 @@ #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public NRF52Board { +class IkokaNrf52Board : public NRF52BoardOTA { public: + IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -38,8 +39,6 @@ public: const char* getManufacturerName() const override { return "Ikoka Handheld E22 30dBm (Xiao_nrf52)"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; #endif diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp index 15507ad3..b963b2f4 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.cpp @@ -1,24 +1,9 @@ #ifdef XIAO_NRF52 #include <Arduino.h> -#include "IkokaNanoNRFBoard.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "IkokaNanoNRFBoard.h" void IkokaNanoNRFBoard::begin() { NRF52Board::begin(); @@ -47,47 +32,4 @@ void IkokaNanoNRFBoard::begin() { delay(10); // give sx1262 some time to power up } -bool IkokaNanoNRFBoard::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("XIAO_NRF52_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} - #endif diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index e6da3d11..c7e96921 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -6,8 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public NRF52Board { +class IkokaNanoNRFBoard : public NRF52BoardOTA { public: + IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -46,8 +47,6 @@ public: const char *getManufacturerName() const override { return MANUFACTURER_STRING; } - - bool startOTAUpdate(const char *id, char reply[]) override; }; #endif diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp index 1adc14bd..1c416039 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.cpp @@ -1,24 +1,9 @@ #ifdef XIAO_NRF52 #include <Arduino.h> -#include "IkokaStickNRFBoard.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "IkokaStickNRFBoard.h" void IkokaStickNRFBoard::begin() { NRF52Board::begin(); @@ -47,47 +32,4 @@ void IkokaStickNRFBoard::begin() { delay(10); // give sx1262 some time to power up } -bool IkokaStickNRFBoard::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("XIAO_NRF52_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} - #endif diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 78cb8a7f..00a26029 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -6,8 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public NRF52Board { +class IkokaStickNRFBoard : public NRF52BoardOTA { public: + IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -46,8 +47,6 @@ public: const char *getManufacturerName() const override { return MANUFACTURER_STRING; } - - bool startOTAUpdate(const char *id, char reply[]) override; }; #endif diff --git a/variants/keepteen_lt1/KeepteenLT1Board.cpp b/variants/keepteen_lt1/KeepteenLT1Board.cpp index a61909fe..2f1fa5f9 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.cpp +++ b/variants/keepteen_lt1/KeepteenLT1Board.cpp @@ -1,10 +1,7 @@ #include <Arduino.h> -#include "KeepteenLT1Board.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; +#include "KeepteenLT1Board.h" void KeepteenLT1Board::begin() { NRF52Board::begin(); @@ -17,58 +14,4 @@ void KeepteenLT1Board::begin() { #endif Wire.begin(); -} - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - -bool KeepteenLT1Board::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("KeepteenLT1_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} +} \ No newline at end of file diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index e98972ae..a9844c90 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -4,11 +4,12 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class KeepteenLT1Board : public NRF52Board { +class KeepteenLT1Board : public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + KeepteenLT1Board() : NRF52BoardOTA("KeepteenLT1_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -40,6 +41,4 @@ public: void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/lilygo_techo/TechoBoard.cpp b/variants/lilygo_techo/TechoBoard.cpp index e1259644..81d3d0c9 100644 --- a/variants/lilygo_techo/TechoBoard.cpp +++ b/variants/lilygo_techo/TechoBoard.cpp @@ -1,25 +1,10 @@ #include <Arduino.h> +#include <Wire.h> + #include "TechoBoard.h" #ifdef LILYGO_TECHO -#include <bluefruit.h> -#include <Wire.h> - -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - void TechoBoard::begin() { NRF52Board::begin(); @@ -43,47 +28,4 @@ uint16_t TechoBoard::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool TechoBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("TECHO_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} #endif diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index b7650d00..c9bdc229 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -13,11 +13,11 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52Board { +class TechoBoard : public NRF52BoardOTA { public: + TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; const char* getManufacturerName() const override { return "LilyGo T-Echo"; diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp index e1259644..81d3d0c9 100644 --- a/variants/lilygo_techo_lite/TechoBoard.cpp +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -1,25 +1,10 @@ #include <Arduino.h> +#include <Wire.h> + #include "TechoBoard.h" #ifdef LILYGO_TECHO -#include <bluefruit.h> -#include <Wire.h> - -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - void TechoBoard::begin() { NRF52Board::begin(); @@ -43,47 +28,4 @@ uint16_t TechoBoard::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool TechoBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("TECHO_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} #endif diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index c92bcfca..8e6974bd 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -13,11 +13,11 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52Board { +class TechoBoard : public NRF52BoardOTA { public: + TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; const char* getManufacturerName() const override { return "LilyGo T-Echo"; diff --git a/variants/mesh_pocket/MeshPocket.cpp b/variants/mesh_pocket/MeshPocket.cpp index 0fa7d5f9..0c53121f 100644 --- a/variants/mesh_pocket/MeshPocket.cpp +++ b/variants/mesh_pocket/MeshPocket.cpp @@ -1,23 +1,7 @@ #include <Arduino.h> -#include "MeshPocket.h" -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) -{ - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) -{ - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "MeshPocket.h" void HeltecMeshPocket::begin() { NRF52Board::begin(); @@ -26,46 +10,3 @@ void HeltecMeshPocket::begin() { pinMode(PIN_USER_BTN, INPUT); } - -bool HeltecMeshPocket::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("MESH_POCKET_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 0f4d2c8b..db65c99b 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -9,8 +9,9 @@ #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public NRF52Board { +class HeltecMeshPocket : public NRF52BoardOTA { public: + HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {} void begin(); uint16_t getBattMilliVolts() override { @@ -35,6 +36,4 @@ public: void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp index c38f71e4..0267185c 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.cpp @@ -1,8 +1,7 @@ #include <Arduino.h> -#include "MinewsemiME25LS01Board.h" #include <Wire.h> -#include <bluefruit.h> +#include "MinewsemiME25LS01Board.h" void MinewsemiME25LS01Board::begin() { NRF52Board::begin(); @@ -27,62 +26,4 @@ void MinewsemiME25LS01Board::begin() { #endif delay(10); // give sx1262 some time to power up -} - -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - - -bool MinewsemiME25LS01Board::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("Minewsemi_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; } \ No newline at end of file diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 34612e93..805f9dfa 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -20,12 +20,12 @@ #define PIN_VBAT_READ BATTERY_PIN #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking - -class MinewsemiME25LS01Board : public NRF52Board { +class MinewsemiME25LS01Board : public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + MinewsemiME25LS01Board() : NRF52BoardOTA("Minewsemi_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -76,6 +76,4 @@ public: digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } #endif - - bool startOTAUpdate(const char* id, char reply[]) override; }; \ No newline at end of file diff --git a/variants/promicro/PromicroBoard.cpp b/variants/promicro/PromicroBoard.cpp index 4ac72072..7011521b 100644 --- a/variants/promicro/PromicroBoard.cpp +++ b/variants/promicro/PromicroBoard.cpp @@ -1,10 +1,7 @@ #include <Arduino.h> -#include "PromicroBoard.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; +#include "PromicroBoard.h" void PromicroBoard::begin() { NRF52Board::begin(); @@ -25,58 +22,4 @@ void PromicroBoard::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - -bool PromicroBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("ProMicro_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} +} \ No newline at end of file diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index ba002134..c23ed1c9 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -20,12 +20,13 @@ #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class PromicroBoard : public NRF52Board { +class PromicroBoard : public NRF52BoardOTA { protected: uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; public: + PromicroBoard() : NRF52BoardOTA("ProMicro_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -75,6 +76,4 @@ public: void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 1a37db15..cb7d1930 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -1,22 +1,7 @@ #include <Arduino.h> -#include "RAK4631Board.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "RAK4631Board.h" void RAK4631Board::begin() { NRF52Board::begin(); @@ -38,52 +23,4 @@ void RAK4631Board::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -bool RAK4631Board::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("RAK4631_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - Bluefruit.getAddr(mac_addr); - sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", - mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); - - return true; -} +} \ No newline at end of file diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index 394b05fb..b329cdeb 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -29,8 +29,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public NRF52Board { +class RAK4631Board : public NRF52BoardOTA { public: + RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} void begin(); #define BATTERY_SAMPLES 8 @@ -50,6 +51,4 @@ public: const char* getManufacturerName() const override { return "RAK 4631"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp index 88d09249..7eab4bc9 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.cpp @@ -1,22 +1,7 @@ #include <Arduino.h> -#include "RAKWismeshTagBoard.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "RAKWismeshTagBoard.h" void RAKWismeshTagBoard::begin() { NRF52BoardDCDC::begin(); @@ -30,52 +15,4 @@ void RAKWismeshTagBoard::begin() { pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up -} - -bool RAKWismeshTagBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("WISMESHTAG_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - Bluefruit.getAddr(mac_addr); - sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", - mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); - - return true; -} +} \ No newline at end of file diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index d62dee85..9aa457d2 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -8,8 +8,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public NRF52BoardDCDC { +class RAKWismeshTagBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: + RAKWismeshTagBoard() : NRF52BoardOTA("WISMESHTAG_OTA") {} void begin(); #if defined(P_LORA_TX_LED) && defined(LED_STATE_ON) @@ -39,8 +40,6 @@ public: return "RAK WisMesh Tag"; } - bool startOTAUpdate(const char* id, char reply[]) override; - void powerOff() override { #ifdef BUZZER_EN digitalWrite(BUZZER_EN, LOW); diff --git a/variants/sensecap_solar/SenseCapSolarBoard.cpp b/variants/sensecap_solar/SenseCapSolarBoard.cpp index 1493d22a..c0883035 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.cpp +++ b/variants/sensecap_solar/SenseCapSolarBoard.cpp @@ -1,22 +1,7 @@ #include <Arduino.h> -#include "SenseCapSolarBoard.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "SenseCapSolarBoard.h" void SenseCapSolarBoard::begin() { NRF52Board::begin(); @@ -33,48 +18,4 @@ void SenseCapSolarBoard::begin() { #endif delay(10); // give sx1262 some time to power up -} - -bool SenseCapSolarBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("SENSECAP_SOLAR_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - - return true; -} +} \ No newline at end of file diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index eab08163..dfe007bf 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -4,8 +4,9 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class SenseCapSolarBoard : public NRF52Board { +class SenseCapSolarBoard : public NRF52BoardOTA { public: + SenseCapSolarBoard() : NRF52BoardOTA("SENSECAP_SOLAR_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -30,6 +31,4 @@ public: const char* getManufacturerName() const override { return "Seeed SenseCap Solar"; } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/t1000-e/T1000eBoard.cpp b/variants/t1000-e/T1000eBoard.cpp index 9211b4c6..0d390f79 100644 --- a/variants/t1000-e/T1000eBoard.cpp +++ b/variants/t1000-e/T1000eBoard.cpp @@ -1,8 +1,7 @@ #include <Arduino.h> -#include "T1000eBoard.h" #include <Wire.h> -#include <bluefruit.h> +#include "T1000eBoard.h" void T1000eBoard::begin() { NRF52BoardDCDC::begin(); @@ -21,64 +20,4 @@ void T1000eBoard::begin() { Wire.begin(); delay(10); // give sx1262 some time to power up -} - -#if 0 -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - - -bool TrackerT1000eBoard::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("T1000E_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/variants/thinknode_m1/ThinkNodeM1Board.cpp b/variants/thinknode_m1/ThinkNodeM1Board.cpp index bfec5f3f..45449baf 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.cpp +++ b/variants/thinknode_m1/ThinkNodeM1Board.cpp @@ -1,25 +1,10 @@ -#include "ThinkNodeM1Board.h" #include <Arduino.h> +#include <Wire.h> + +#include "ThinkNodeM1Board.h" #ifdef THINKNODE_M1 -#include <Wire.h> -#include <bluefruit.h> - -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - void ThinkNodeM1Board::begin() { NRF52Board::begin(); @@ -48,47 +33,4 @@ uint16_t ThinkNodeM1Board::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool ThinkNodeM1Board::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("THINKNODE_M1_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} #endif diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index 7e961f00..500a0265 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -13,12 +13,11 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM1Board : public NRF52Board { +class ThinkNodeM1Board : public NRF52BoardOTA { public: - + ThinkNodeM1Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; #if defined(P_LORA_TX_LED) void onBeforeTransmit() override { diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.cpp b/variants/wio-tracker-l1/WioTrackerL1Board.cpp index fbf64f77..4153714e 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.cpp +++ b/variants/wio-tracker-l1/WioTrackerL1Board.cpp @@ -1,22 +1,7 @@ #include <Arduino.h> -#include "WioTrackerL1Board.h" - -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include "WioTrackerL1Board.h" void WioTrackerL1Board::begin() { NRF52BoardDCDC::begin(); @@ -45,51 +30,3 @@ void WioTrackerL1Board::begin() { delay(10); // give sx1262 some time to power up } - -bool WioTrackerL1Board::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("WioTrackerL1 OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - uint8_t mac_addr[6]; - memset(mac_addr, 0, sizeof(mac_addr)); - Bluefruit.getAddr(mac_addr); - sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", - mac_addr[5], mac_addr[4], mac_addr[3], mac_addr[2], mac_addr[1], mac_addr[0]); - - return true; -} diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index 7e82e447..bfd87d39 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -4,11 +4,12 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class WioTrackerL1Board : public NRF52BoardDCDC { +class WioTrackerL1Board : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + WioTrackerL1Board() : NRF52BoardOTA("WioTrackerL1 OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -36,6 +37,4 @@ public: void powerOff() override { sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index e61f3806..b7b60dc6 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -2,24 +2,9 @@ #include <Arduino.h> #include <Wire.h> -#include <bluefruit.h> #include "XiaoNrf52Board.h" -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - void XiaoNrf52Board::begin() { NRF52BoardDCDC::begin(); @@ -47,48 +32,4 @@ void XiaoNrf52Board::begin() { delay(10); // give sx1262 some time to power up } -bool XiaoNrf52Board::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("XIAO_NRF52_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - - return true; -} - #endif \ No newline at end of file diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 07325493..1c46dfee 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,8 +6,9 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52BoardDCDC { +class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: + XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) @@ -56,8 +57,6 @@ public: sd_power_system_off(); } - - bool startOTAUpdate(const char* id, char reply[]) override; }; #endif \ No newline at end of file From 22b1585959c1dfd265f157ac2d146cca1753b07b Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Wed, 17 Dec 2025 10:35:47 +0100 Subject: [PATCH 331/546] NRF52Board.h: Mark getMCUTemperature() as virtual The function in the derived class is virtual per definition. Mark it to make this clearer to the reader. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/NRF52Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index c5f32a20..0d6c0a43 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -12,7 +12,7 @@ protected: public: virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } - float getMCUTemperature() override; + virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } }; From 8eb229bcf8aa665f1b7f6aa1668aeb616b6b0843 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 9 Dec 2025 20:58:11 +0100 Subject: [PATCH 332/546] variants: RAK4631: Enable DC/DC regulator to reduce power consumption The RAK4631/RAK4630 module are able to use the DC/DC converter. Enable it to reduce power consumption. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/rak4631/RAK4631Board.cpp | 2 +- variants/rak4631/RAK4631Board.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index cb7d1930..65c54711 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -4,7 +4,7 @@ #include "RAK4631Board.h" void RAK4631Board::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index b329cdeb..a181256b 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -29,7 +29,7 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public NRF52BoardOTA { +class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} void begin(); From cba29ea50c6cdbb79f31f5f65902f10bee035ccf Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Tue, 16 Dec 2025 09:01:38 +0100 Subject: [PATCH 333/546] queue throttling + slave latency and minor refactor --- src/helpers/nrf52/SerialBLEInterface.cpp | 90 ++++++++++++++++-------- src/helpers/nrf52/SerialBLEInterface.h | 2 + 2 files changed, 64 insertions(+), 28 deletions(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index b4811a20..eb1e90bb 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -4,7 +4,23 @@ #include "ble_gap.h" #include "ble_hci.h" +// Magic numbers came from actual testing #define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds +#define BLE_RETRY_THROTTLE_MS 250 // Throttle retries to 250ms when queue buildup detected + +// Connection parameters (units: interval=1.25ms, timeout=10ms) +#define BLE_MIN_CONN_INTERVAL 12 // 15ms +#define BLE_MAX_CONN_INTERVAL 24 // 30ms +#define BLE_SLAVE_LATENCY 4 +#define BLE_CONN_SUP_TIMEOUT 200 // 2000ms + +// Advertising parameters +#define BLE_ADV_INTERVAL_MIN 32 // 20ms (units: 0.625ms) +#define BLE_ADV_INTERVAL_MAX 244 // 152.5ms (units: 0.625ms) +#define BLE_ADV_FAST_TIMEOUT 30 // seconds + +// RX drain buffer size for overflow protection +#define BLE_RX_DRAIN_BUF_SIZE 32 static SerialBLEInterface* instance = nullptr; @@ -38,14 +54,18 @@ void SerialBLEInterface::onSecured(uint16_t connection_handle) { // Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic." // So we explicitly set it here to make Android & Apple match ble_gap_conn_params_t conn_params; - conn_params.min_conn_interval = 12; // 15ms - conn_params.max_conn_interval = 24; // 30ms - conn_params.slave_latency = 0; - conn_params.conn_sup_timeout = 200; // 2000ms + conn_params.min_conn_interval = BLE_MIN_CONN_INTERVAL; + conn_params.max_conn_interval = BLE_MAX_CONN_INTERVAL; + conn_params.slave_latency = BLE_SLAVE_LATENCY; + conn_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT; uint32_t err_code = sd_ble_gap_conn_param_update(connection_handle, &conn_params); if (err_code == NRF_SUCCESS) { - BLE_DEBUG_PRINTLN("Connection parameter update requested: 15-30ms interval, 2s timeout"); + BLE_DEBUG_PRINTLN("Connection parameter update requested: %u-%ums interval, latency=%u, %ums timeout", + conn_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units) + conn_params.max_conn_interval * 5 / 4, + conn_params.slave_latency, + conn_params.conn_sup_timeout * 10); // convert to ms (10ms units) } else { BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code); } @@ -116,14 +136,18 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { // Connection interval units: 1.25ms, supervision timeout units: 10ms ble_gap_conn_params_t ppcp_params; - ppcp_params.min_conn_interval = 12; // 15ms - ppcp_params.max_conn_interval = 24; // 30ms - ppcp_params.slave_latency = 0; - ppcp_params.conn_sup_timeout = 200; // 2000ms + ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL; + ppcp_params.max_conn_interval = BLE_MAX_CONN_INTERVAL; + ppcp_params.slave_latency = BLE_SLAVE_LATENCY; + ppcp_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT; uint32_t err_code = sd_ble_gap_ppcp_set(&ppcp_params); if (err_code == NRF_SUCCESS) { - BLE_DEBUG_PRINTLN("PPCP set: 15-30ms interval, 2s timeout"); + BLE_DEBUG_PRINTLN("PPCP set: %u-%ums interval, latency=%u, %ums timeout", + ppcp_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units) + ppcp_params.max_conn_interval * 5 / 4, + ppcp_params.slave_latency, + ppcp_params.conn_sup_timeout * 10); // convert to ms (10ms units) } else { BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code); } @@ -153,8 +177,8 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { Bluefruit.ScanResponse.addName(); - Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); + Bluefruit.Advertising.setInterval(BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX); + Bluefruit.Advertising.setFastTimeout(BLE_ADV_FAST_TIMEOUT); Bluefruit.Advertising.restartOnDisconnect(true); @@ -163,6 +187,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { void SerialBLEInterface::clearBuffers() { send_queue_len = 0; recv_queue_len = 0; + _last_retry_attempt = 0; bleuart.flush(); } @@ -257,21 +282,30 @@ size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) { BLE_DEBUG_PRINTLN("writeBytes: connection invalid, clearing send queue"); send_queue_len = 0; } else { - Frame frame_to_send = send_queue[0]; - - size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len); - if (written == frame_to_send.len) { - BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]); - shiftSendQueueLeft(); - } else if (written > 0) { - BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len); - shiftSendQueueLeft(); - } else { - if (!isConnected()) { - BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame"); + unsigned long now = millis(); + bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < BLE_RETRY_THROTTLE_MS); + + if (!throttle_active) { + Frame frame_to_send = send_queue[0]; + + size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len); + if (written == frame_to_send.len) { + BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]); + _last_retry_attempt = 0; + shiftSendQueueLeft(); + } else if (written > 0) { + BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len); + _last_retry_attempt = 0; shiftSendQueueLeft(); } else { - BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + if (!isConnected()) { + BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame"); + _last_retry_attempt = 0; + shiftSendQueueLeft(); + } else { + BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry"); + _last_retry_attempt = now; + } } } } @@ -329,9 +363,9 @@ void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) { if (avail > MAX_FRAME_SIZE) { BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail); - uint8_t drain_buf[32]; + uint8_t drain_buf[BLE_RX_DRAIN_BUF_SIZE]; while (instance->bleuart.available() > 0) { - int chunk = instance->bleuart.available() > 32 ? 32 : instance->bleuart.available(); + int chunk = instance->bleuart.available() > BLE_RX_DRAIN_BUF_SIZE ? BLE_RX_DRAIN_BUF_SIZE : instance->bleuart.available(); instance->bleuart.readBytes(drain_buf, chunk); } continue; @@ -349,5 +383,5 @@ bool SerialBLEInterface::isConnected() const { } bool SerialBLEInterface::isWriteBusy() const { - return send_queue_len >= (FRAME_QUEUE_SIZE - 1); + return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3); } diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index 557b86c5..25968d78 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -13,6 +13,7 @@ class SerialBLEInterface : public BaseSerialInterface { bool _isDeviceConnected; uint16_t _conn_handle; unsigned long _last_health_check; + unsigned long _last_retry_attempt; struct Frame { uint8_t len; @@ -46,6 +47,7 @@ public: _isDeviceConnected = false; _conn_handle = BLE_CONN_HANDLE_INVALID; _last_health_check = 0; + _last_retry_attempt = 0; send_queue_len = 0; recv_queue_len = 0; } From e855706abb633a1cd7ee791cd49d92481576d2de Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:26:09 +0100 Subject: [PATCH 334/546] move showalert after saveprefs --- examples/companion_radio/ui-new/UITask.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 29cc59d3..8077627f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -892,14 +892,13 @@ void UITask::toggleGPS() { _sensors->setSettingValue("gps", "0"); _node_prefs->gps_enabled = 0; notify(UIEventType::ack); - showAlert("GPS: Disabled", 800); } else { _sensors->setSettingValue("gps", "1"); _node_prefs->gps_enabled = 1; notify(UIEventType::ack); - showAlert("GPS: Enabled", 800); } the_mesh.savePrefs(); + showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800); _next_refresh = 0; break; } @@ -913,13 +912,12 @@ void UITask::toggleBuzzer() { if (buzzer.isQuiet()) { buzzer.quiet(false); notify(UIEventType::ack); - showAlert("Buzzer: ON", 800); } else { buzzer.quiet(true); - showAlert("Buzzer: OFF", 800); } _node_prefs->buzzer_quiet = buzzer.isQuiet(); the_mesh.savePrefs(); + showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800); _next_refresh = 0; // trigger refresh #endif } From 6c993827de266a9c81c3f4f6f3dc92737bde5aff Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Fri, 19 Dec 2025 23:51:36 +1100 Subject: [PATCH 335/546] Fixed T1000-E temperature and lux sensors --- variants/t1000-e/t1000e_sensors.cpp | 10 +++++++--- variants/t1000-e/target.cpp | 1 + variants/t1000-e/variant.cpp | 2 ++ 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/variants/t1000-e/t1000e_sensors.cpp b/variants/t1000-e/t1000e_sensors.cpp index a5b443cf..f0254138 100644 --- a/variants/t1000-e/t1000e_sensors.cpp +++ b/variants/t1000-e/t1000e_sensors.cpp @@ -5,7 +5,7 @@ #define HEATER_NTC_BX 4250 // thermistor coefficient B #define HEATER_NTC_RP 8250 // ohm, series resistance to thermistor #define HEATER_NTC_KA 273.15 // 25 Celsius at Kelvin -#define NTC_REF_VCC 3000 // mV, output voltage of LDO +#define NTC_REF_VCC 3300 // mV, max voltage of 3V3 sensor rail #define LIGHT_REF_VCC 2400 // static unsigned int ntc_res2[136] = { @@ -54,6 +54,7 @@ static int get_light_lv(unsigned int light_volt) { float Vout = 0, Vin = 0, Rt = 0, temp = 0; unsigned int light_level = 0; + // Seeed's firmware maps the photocell reading to a 0-100 % range rather than lux. if (light_volt <= 80) { light_level = 0; return light_level; @@ -75,7 +76,8 @@ float t1000e_get_temperature(void) { analogReference(AR_INTERNAL_3_0); analogReadResolution(12); delay(10); - vcc_v = (1000.0 * (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE)) / 4096; + unsigned int rail_v = (1000.0 * (analogRead(BATTERY_PIN) * ADC_MULTIPLIER * AREF_VOLTAGE)) / 4096; + vcc_v = (rail_v > NTC_REF_VCC) ? NTC_REF_VCC : rail_v; ntc_v = (1000.0 * AREF_VOLTAGE * analogRead(TEMP_SENSOR)) / 4096; digitalWrite(PIN_3V3_EN, LOW); digitalWrite(SENSOR_EN, LOW); @@ -87,6 +89,7 @@ uint32_t t1000e_get_light(void) { int lux = 0; unsigned int lux_v = 0; + digitalWrite(PIN_3V3_EN, HIGH); digitalWrite(SENSOR_EN, HIGH); analogReference(AR_INTERNAL_3_0); analogReadResolution(12); @@ -94,6 +97,7 @@ uint32_t t1000e_get_light(void) { lux_v = 1000 * analogRead(LUX_SENSOR) * AREF_VOLTAGE / 4096; lux = get_light_lv(lux_v); digitalWrite(SENSOR_EN, LOW); + digitalWrite(PIN_3V3_EN, LOW); return lux; -} \ No newline at end of file +} diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 2a6380d5..82d958b5 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -154,6 +154,7 @@ bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } if (requester_permissions & TELEM_PERM_ENVIRONMENT) { + // Firmware reports light as a 0-100 % scale, but expose it via Luminosity so app labels it "Luminosity". telemetry.addLuminosity(TELEM_CHANNEL_SELF, t1000e_get_light()); telemetry.addTemperature(TELEM_CHANNEL_SELF, t1000e_get_temperature()); } diff --git a/variants/t1000-e/variant.cpp b/variants/t1000-e/variant.cpp index f17b3a8d..a598e3ca 100644 --- a/variants/t1000-e/variant.cpp +++ b/variants/t1000-e/variant.cpp @@ -67,6 +67,8 @@ void initVariant() // https://github.com/Seeed-Studio/Adafruit_nRF52_Arduino/blob/fab7d30a997a1dfeef9d1d59bfb549adda73815a/cores/nRF5/wiring.c#L65-L69 pinMode(BATTERY_PIN, INPUT); + pinMode(TEMP_SENSOR, INPUT); + pinMode(LUX_SENSOR, INPUT); pinMode(EXT_CHRG_DETECT, INPUT); pinMode(EXT_PWR_DETECT, INPUT); pinMode(GPS_RESETB, INPUT); From cc28b1a34d19d5f9254b7e443ca7ba5baa239b9b Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Sat, 20 Dec 2025 21:51:51 +1100 Subject: [PATCH 336/546] EnvironmentSensorManager.cpp: Mitigate BME280 self-heating causing inaccurate readings. --- .../sensors/EnvironmentSensorManager.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2692ec9c..77a791bd 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -192,6 +192,13 @@ bool EnvironmentSensorManager::begin() { if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID()); + // Reduce self-heating: single-shot conversions, light oversampling, long standby. + BME280.setSampling(Adafruit_BME280::MODE_FORCED, + Adafruit_BME280::SAMPLING_X1, // temperature + Adafruit_BME280::SAMPLING_X1, // pressure + Adafruit_BME280::SAMPLING_X1, // humidity + Adafruit_BME280::FILTER_OFF, + Adafruit_BME280::STANDBY_MS_1000); BME280_initialized = true; } else { BME280_initialized = false; @@ -359,10 +366,12 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_BME280 if (BME280_initialized) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); - telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100); - telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); + if (BME280.takeForcedMeasurement()) { // trigger a fresh reading in forced mode + telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); + telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100); + telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); + } } #endif From 245a818085c02167698149a456078f4795cf21fb Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Sat, 20 Dec 2025 23:06:17 +1100 Subject: [PATCH 337/546] Fix TX LED stuck on when StartTransmit() fails --- src/helpers/radiolib/RadioLibWrappers.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 9014743a..e3407821 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -137,6 +137,7 @@ bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { } MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err); idle(); // trigger another startRecv() + _board->onAfterTransmit(); return false; } From 5c6c15942bda590f02193509c496b8e3c71f3e94 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Tue, 23 Dec 2025 12:48:08 +0700 Subject: [PATCH 338/546] Added powersaving to all ESP32 boards with RTC-supported DIO1 Added CLI to enable/disable powersaving --- examples/simple_repeater/MyMesh.cpp | 5 +++++ examples/simple_repeater/MyMesh.h | 3 +++ examples/simple_repeater/main.cpp | 18 ++++++++++++++++++ src/MeshCore.h | 1 + src/helpers/CommonCLI.cpp | 18 ++++++++++++++++-- src/helpers/CommonCLI.h | 2 ++ src/helpers/ESP32Board.h | 23 +++++++++++++++++++++++ 7 files changed, 68 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6ae6ac0a..326417a4 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1115,3 +1115,8 @@ void MyMesh::loop() { uptime_millis += now - last_millis; last_millis = now; } + +// To get the current pending work +int MyMesh::hasPendingWork() const { + return _mgr->getOutboundCount(0xFFFFFFFF); +} diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index ed9f0c5f..6dc4792e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -225,4 +225,7 @@ public: bridge.begin(); } #endif + + // To get the current pending work + int hasPendingWork() const; }; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 7387e77e..d67a7029 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -19,12 +19,19 @@ void halt() { static char command[160]; +// For power saving +unsigned long lastActive = 0; // mark last active time +unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot + void setup() { Serial.begin(115200); delay(1000); board.begin(); + // For power saving + lastActive = millis(); // mark last active time since boot + #ifdef DISPLAY_CLASS if (display.begin()) { display.startFrame(); @@ -117,4 +124,15 @@ void loop() { ui_task.loop(); #endif rtc_clock.tick(); + + if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled + the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep + if (the_mesh.hasPendingWork() == 0) { // No pending work. Safe to sleep + board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet + lastActive = millis(); + nextSleepinSecs = 5; // Default: To work for 5s and sleep again + } else { + nextSleepinSecs += 5; // When there is pending work, to work another 5s + } + } } diff --git a/src/MeshCore.h b/src/MeshCore.h index eb794058..718660d3 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -51,6 +51,7 @@ public: virtual void onAfterTransmit() { } virtual void reboot() = 0; virtual void powerOff() { /* no op */ } + virtual void sleep(uint32_t secs) { /* no op */ } virtual uint32_t getGpio() { return 0; } virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a3de990a..af27a902 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -65,7 +65,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 - file.read(pad, 4); // 152 + file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 @@ -145,7 +145,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131 file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 - file.write(pad, 4); // 152 + file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 @@ -676,6 +676,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Can't find GPS"); } #endif + } else if (memcmp(command, "powersaving on", 14) == 0) { + _prefs->powersaving_enabled = 1; + savePrefs(); + strcpy(reply, "ok"); // TODO: to return Not supported if required + } else if (memcmp(command, "powersaving off", 15) == 0) { + _prefs->powersaving_enabled = 0; + savePrefs(); + strcpy(reply, "ok"); + } else if (memcmp(command, "powersaving", 11) == 0) { + if (_prefs->powersaving_enabled) { + strcpy(reply, "on"); + } else { + strcpy(reply, "off"); + } } else if (memcmp(command, "log start", 9) == 0) { _callbacks->setLoggingOn(true); strcpy(reply, " logging on"); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 068783ab..ca1a5612 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -48,6 +48,8 @@ struct NodePrefs { // persisted to file uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; + // Power setting + uint8_t powersaving_enabled; // boolean }; class CommonCLICallbacks { diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 64c92c43..d49feddf 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -8,6 +8,8 @@ #include <rom/rtc.h> #include <sys/time.h> #include <Wire.h> +#include <WiFi.h> +#include "driver/rtc_io.h" class ESP32Board : public mesh::MainBoard { protected: @@ -47,6 +49,27 @@ public: return temperatureRead(); } + void enterLightSleep(uint32_t secs) { +#if defined(CONFIG_IDF_TARGET_ESP32S3) // Supported ESP32 variants + if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs + } + + esp_light_sleep_start(); // CPU enters light sleep + } +#endif + } + + void sleep(uint32_t secs) override { + if (WiFi.getMode() == WIFI_MODE_NULL) { // WiFi is off ~ No active OTA, safe to go to sleep + enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet + } + } + uint8_t getStartupReason() const override { return startup_reason; } #if defined(P_LORA_TX_LED) From 1706f759b74ff3adb6c514d120e5b2d0c06951c5 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Wed, 24 Dec 2025 11:00:34 +0700 Subject: [PATCH 339/546] Modified hasPendingWork to return bool --- examples/simple_repeater/MyMesh.cpp | 6 +++--- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_repeater/main.cpp | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 326417a4..e86d1ef0 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1116,7 +1116,7 @@ void MyMesh::loop() { last_millis = now; } -// To get the current pending work -int MyMesh::hasPendingWork() const { - return _mgr->getOutboundCount(0xFFFFFFFF); +// To check if there is pending work +bool MyMesh::hasPendingWork() const { + return _mgr->getOutboundCount(0xFFFFFFFF) > 0; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 6dc4792e..343aa44f 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -226,6 +226,6 @@ public: } #endif - // To get the current pending work - int hasPendingWork() const; + // To check if there is pending work + bool hasPendingWork() const; }; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d67a7029..8c745613 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -127,7 +127,7 @@ void loop() { if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep - if (the_mesh.hasPendingWork() == 0) { // No pending work. Safe to sleep + if (!the_mesh.hasPendingWork()) { // No pending work. Safe to sleep board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet lastActive = millis(); nextSleepinSecs = 5; // Default: To work for 5s and sleep again From 89a289eb222e106cde4434d0f48fd57e602feac2 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Wed, 24 Dec 2025 11:23:19 +0700 Subject: [PATCH 340/546] Added powersaving_enabled sanitization Moved powersaving_enabled to match serialization order --- src/helpers/CommonCLI.cpp | 2 ++ src/helpers/CommonCLI.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index af27a902..43a49ac4 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -93,6 +93,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200); _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); + _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1); + _prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1); _prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index ca1a5612..642a4cce 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -42,14 +42,14 @@ struct NodePrefs { // persisted to file uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200) uint8_t bridge_channel; // 1-14 (ESP-NOW only) char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only) + // Power setting + uint8_t powersaving_enabled; // boolean // Gps settings uint8_t gps_enabled; uint32_t gps_interval; // in seconds uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; - // Power setting - uint8_t powersaving_enabled; // boolean }; class CommonCLICallbacks { From 0d11a02e71588e5bda9690b0fc93869e22785876 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Wed, 24 Dec 2025 11:47:19 +0700 Subject: [PATCH 341/546] Added extra check for P_LORA_DIO_1 before going to sleep --- src/helpers/ESP32Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index d49feddf..cfc403cd 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -50,7 +50,7 @@ public: } void enterLightSleep(uint32_t secs) { -#if defined(CONFIG_IDF_TARGET_ESP32S3) // Supported ESP32 variants +#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet From def1902688d27a3bf2f1c1dcac914aa8b845bd19 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Wed, 24 Dec 2025 12:04:39 +0700 Subject: [PATCH 342/546] Fixed T-Beam board to work with sleep --- src/helpers/esp32/TBeamBoard.h | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/helpers/esp32/TBeamBoard.h b/src/helpers/esp32/TBeamBoard.h index 74baebc3..4ff95551 100644 --- a/src/helpers/esp32/TBeamBoard.h +++ b/src/helpers/esp32/TBeamBoard.h @@ -2,16 +2,7 @@ #if defined(TBEAM_SUPREME_SX1262) || defined(TBEAM_SX1262) || defined(TBEAM_SX1276) -#include <Wire.h> -#include <Arduino.h> -#include "XPowersLib.h" -#include "helpers/ESP32Board.h" -#include <driver/rtc_io.h> -//#include <RadioLib.h> -//#include <helpers/RadioLibWrappers.h> -//#include <helpers/CustomSX1262Wrapper.h> -//#include <helpers/CustomSX1276Wrapper.h> - +// Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_1 #ifdef TBEAM_SUPREME_SX1262 // LoRa radio module pins for TBeam S3 Supreme SX1262 #define P_LORA_DIO_0 -1 //NC @@ -90,6 +81,13 @@ // SX1276 // }; +// Include headers AFTER pin definitions so ESP32Board::sleep() can use P_LORA_DIO_1 +#include <Wire.h> +#include <Arduino.h> +#include "XPowersLib.h" +#include "helpers/ESP32Board.h" +#include <driver/rtc_io.h> + class TBeamBoard : public ESP32Board { XPowersLibInterface *PMU = NULL; //PhysicalLayer * pl; From 26321162eeaa3a158285a4175254ea9530210614 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Sat, 27 Dec 2025 15:23:23 +0700 Subject: [PATCH 343/546] To fix the default temperature to be overridden by external sensors (if any) --- examples/simple_repeater/MyMesh.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6ae6ac0a..993b2779 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -174,17 +174,18 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); - float temperature = board.getMCUTemperature(); - if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN - telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature - } - // query other sensors -- target specific if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) { perm_mask = 0x00; // just base telemetry allowed } sensors.querySensors(perm_mask, telemetry); + // This default temperature will be overridden by external sensors (if any) + float temperature = board.getMCUTemperature(); + if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN + telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature + } + uint8_t tlen = telemetry.getSize(); memcpy(&reply_data[4], telemetry.getBuffer(), tlen); return 4 + tlen; // reply_len From 0b30d2433f014fec50037b5d49ec4cbfa6b3cc22 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Sat, 27 Dec 2025 15:25:21 +0700 Subject: [PATCH 344/546] To get and average the temperature so it is more accurate, especially in low temperature --- src/helpers/ESP32Board.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 64c92c43..61f814fd 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -44,7 +44,14 @@ public: // Temperature from ESP32 MCU float getMCUTemperature() override { - return temperatureRead(); + uint32_t raw = 0; + + // To get and average the temperature so it is more accurate, especially in low temperature + for (int i = 0; i < 4; i++) { + raw += temperatureRead(); + } + + return raw / 4; } uint8_t getStartupReason() const override { return startup_reason; } From 90d1e87ba1043663283ea6eb8a55c6392aaba637 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 27 Dec 2025 20:46:28 +1100 Subject: [PATCH 345/546] * check for 'early receive' ACK --- src/Mesh.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 1bda9e96..0548c907 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -78,6 +78,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { + // check for 'early received' ACK + if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { + int i = 0; + uint32_t ack_crc; + memcpy(&ack_crc, &pkt->payload[i], 4); i += 4; + if (i <= pkt->payload_len) { + onAckRecv(pkt, ack_crc); + } + } + if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt); From 992d971f07e319c95397e98330636bc0682c61fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Sun, 28 Dec 2025 20:04:57 +0000 Subject: [PATCH 346/546] Add RS232 bridge environment configuration for ProMicro --- variants/promicro/platformio.ini | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 78ea5fa1..15bb5ce6 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -53,6 +53,31 @@ build_flags = lib_deps = ${Promicro.lib_deps} adafruit/RTClib @ ^2.1.3 +[env:ProMicro_repeater_bridge_rs232_serial1] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} + +<../examples/simple_repeater> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/bridges/RS232Bridge.cpp> +build_flags = + ${Promicro.build_flags} + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=SSD1306Display + -D WITH_RS232_BRIDGE=Serial1 + -D WITH_RS232_BRIDGE_RX=PIN_SERIAL1_RX + -D WITH_RS232_BRIDGE_TX=PIN_SERIAL1_TX + -UENV_INCLUDE_GPS +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = ${Promicro.lib_deps} + adafruit/RTClib @ ^2.1.3 + [env:ProMicro_room_server] extends = Promicro build_src_filter = ${Promicro.build_src_filter} From 33b1e7edb9635544ce3761fb7d1079824b9acc79 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Mon, 29 Dec 2025 21:49:13 +0700 Subject: [PATCH 347/546] Added pad after powersaving_enabled --- src/helpers/CommonCLI.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 43a49ac4..78e1b5e0 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -66,6 +66,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 + file.read(pad, 3); // 153 file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 @@ -148,6 +149,7 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135 file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136 file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152 + file.write(pad, 3); // 153 file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156 file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157 file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 From d911a34eeb959c566d7d1c4751eb361db5b7b24f Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Mon, 29 Dec 2025 22:38:05 +0700 Subject: [PATCH 348/546] Used esp_wifi_get_mode instead of WiFi.getMode() to reduce the code size --- src/helpers/ESP32Board.h | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index cfc403cd..5d28b6da 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -8,7 +8,7 @@ #include <rom/rtc.h> #include <sys/time.h> #include <Wire.h> -#include <WiFi.h> +#include "esp_wifi.h" #include "driver/rtc_io.h" class ESP32Board : public mesh::MainBoard { @@ -65,8 +65,12 @@ public: } void sleep(uint32_t secs) override { - if (WiFi.getMode() == WIFI_MODE_NULL) { // WiFi is off ~ No active OTA, safe to go to sleep - enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet + // To check for WiFi status to see if there is active OTA + wifi_mode_t mode; + esp_err_t err = esp_wifi_get_mode(&mode); + + if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep + enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet } } From 4a869163b27b013cf46ff807143b1e1c4ebdf158 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Tue, 30 Dec 2025 21:58:59 +1100 Subject: [PATCH 349/546] BUGFIX: replay protection on repeaters tripped by timestamp sent from companion node mobile app. Send the node's RTC timestamp for TXT_TYPE_CLI_DATA messages instead of the timestamp from the app (matches the sendRequest() code logic). --- examples/companion_radio/MyMesh.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9cbb2eba..56894dde 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -903,6 +903,7 @@ void MyMesh::handleCmdFrame(size_t len) { int result; uint32_t expected_ack; if (txt_type == TXT_TYPE_CLI_DATA) { + msg_timestamp = getRTCClock()->getCurrentTimeUnique(); // Use node's RTC instead of app timestamp to avoid tripping replay protection result = sendCommandData(*recipient, msg_timestamp, attempt, text, est_timeout); expected_ack = 0; // no Ack expected } else { @@ -1880,4 +1881,4 @@ bool MyMesh::advert() { } else { return false; } -} \ No newline at end of file +} From 7ea751d3a0c3307bb5afeb337a05b4d687f08e56 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel.nieboer@onior.com> Date: Wed, 31 Dec 2025 13:01:56 +0100 Subject: [PATCH 350/546] Add venv dirs to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index db044b5a..50631d89 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ cmake-* .cache .ccls compile_commands.json +.venv/ +venv/ From e79ee118722fc0050c1de0378bb5ac93fc7084e6 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 16 Dec 2025 20:27:48 +0100 Subject: [PATCH 351/546] EnvironmentSensorManager.cpp: Fix RAK4631 serial GPS detection Serial1 is always true. If we want to check for the presence of a GPS receiver, we need to check if any data was received. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/sensors/EnvironmentSensorManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 77a791bd..f0bb5654 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -653,8 +653,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ _location = &RAK12500_provider; return true; - } - else if(Serial1){ + } else if (Serial1.available()) { MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on"); if(PIN_GPS_EN){ gpsResetPin = PIN_GPS_EN; From ab7935142cd257c860f58671dc849dfb047338b8 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Tue, 16 Dec 2025 20:28:20 +0100 Subject: [PATCH 352/546] EnvironmentSensorManager.cpp: Cleanup after failed RAK4631 GPS detection If no GPS was detected, revert the hardware to the initial state, otherwise we may see conflicts or increased power consumption on some boards. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/sensors/EnvironmentSensorManager.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index f0bb5654..b7238def 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -615,6 +615,7 @@ void EnvironmentSensorManager::rakGPSInit(){ MESH_DEBUG_PRINTLN("No GPS found"); gps_active = false; gps_detected = false; + Serial1.end(); return; } @@ -663,6 +664,8 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ gps_detected = true; return true; } + + pinMode(ioPin, INPUT); MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next"); return false; } From 813e50297051a12d7ef525bfcf1b24ea057352a5 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 2 Jan 2026 12:54:57 +1100 Subject: [PATCH 353/546] * added protocol_guide doc --- docs/protocol_guide.md | 1201 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1201 insertions(+) create mode 100644 docs/protocol_guide.md diff --git a/docs/protocol_guide.md b/docs/protocol_guide.md new file mode 100644 index 00000000..ceedbbf0 --- /dev/null +++ b/docs/protocol_guide.md @@ -0,0 +1,1201 @@ +# MeshCore Device Communication Protocol Guide + +This document provides a comprehensive guide for communicating with MeshCore devices over Bluetooth Low Energy (BLE). It is platform-agnostic and can be used for Android, iOS, Python, JavaScript, or any other platform that supports BLE. + +## ⚠️ Important Security Note + +**All secrets, hashes, and cryptographic values shown in this guide are EXAMPLE VALUES ONLY and are NOT real secrets.** + +- The secret `9b647d242d6e1c5883fde0c5cf5c4c5e` used in examples is a made-up example value +- All hex values, public keys, and hashes in examples are for demonstration purposes only +- **Never use example secrets in production** - always generate new cryptographically secure random secrets +- This guide is for protocol documentation only - implement proper security practices in your actual implementation + +## Table of Contents + +1. [BLE Connection](#ble-connection) +2. [Protocol Overview](#protocol-overview) +3. [Commands](#commands) +4. [Channel Management](#channel-management) +5. [Secret Generation and QR Codes](#secret-generation-and-qr-codes) +6. [Message Handling](#message-handling) +7. [Response Parsing](#response-parsing) +8. [Example Implementation Flow](#example-implementation-flow) + +--- + +## BLE Connection + +### Service and Characteristics + +MeshCore devices expose a BLE service with the following UUIDs: + +- **Service UUID**: `0000ff00-0000-1000-8000-00805f9b34fb` +- **RX Characteristic** (Device → Client): `0000ff01-0000-1000-8000-00805f9b34fb` +- **TX Characteristic** (Client → Device): `0000ff02-0000-1000-8000-00805f9b34fb` + +### Connection Steps + +1. **Scan for Devices** + - Scan for BLE devices advertising the MeshCore service UUID + - Filter by device name (typically contains "MeshCore" or similar) + - Note the device MAC address for reconnection + +2. **Connect to GATT** + - Connect to the device using the discovered MAC address + - Wait for connection to be established + +3. **Discover Services and Characteristics** + - Discover the service with UUID `0000ff00-0000-1000-8000-00805f9b34fb` + - Discover RX characteristic (`0000ff01-...`) for receiving data + - Discover TX characteristic (`0000ff02-...`) for sending commands + +4. **Enable Notifications** + - Subscribe to notifications on the RX characteristic + - Enable notifications/indications to receive data from the device + - On some platforms, you may need to write to a descriptor (e.g., `0x2902`) with value `0x01` or `0x02` + +5. **Send AppStart Command** + - Send the app start command (see [Commands](#commands)) to initialize communication + - Wait for OK response before sending other commands + +### Connection State Management + +- **Disconnected**: No connection established +- **Connecting**: Connection attempt in progress +- **Connected**: GATT connection established, ready for commands +- **Error**: Connection failed or lost + +**Note**: MeshCore devices may disconnect after periods of inactivity. Implement auto-reconnect logic with exponential backoff. + +### BLE Write Type + +When writing commands to the TX characteristic, specify the write type: + +- **Write with Response** (default): Waits for acknowledgment from device +- **Write without Response**: Faster but no acknowledgment + +**Platform-specific**: +- **Android**: Use `BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT` or `WRITE_TYPE_NO_RESPONSE` +- **iOS**: Use `CBCharacteristicWriteType.withResponse` or `.withoutResponse` +- **Python (bleak)**: Use `write_gatt_char()` with `response=True` or `False` + +**Recommendation**: Use write with response for reliability, especially for critical commands like `SET_CHANNEL`. + +### MTU (Maximum Transmission Unit) + +The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (66 bytes), you may need to: + +1. **Request Larger MTU**: Request MTU of 512 bytes if supported + - Android: `gatt.requestMtu(512)` + - iOS: `peripheral.maximumWriteValueLength(for:)` + - Python (bleak): MTU is negotiated automatically + +2. **Handle Chunking**: If MTU is small, commands may be split automatically by the BLE stack + - Ensure all chunks are sent before waiting for response + - Responses may also arrive in chunks - buffer until complete + +### Command Sequencing and Timing + +**Critical**: Commands must be sent in the correct sequence: + +1. **After Connection**: + - Wait for GATT connection established + - Wait for services/characteristics discovered + - Wait for notifications enabled (descriptor write complete) + - **Wait 200-1000ms** for device to be ready (some devices need initialization time) + - Send `APP_START` command + - **Wait for `PACKET_OK` response** before sending any other commands + +2. **Command-Response Matching**: + - Send one command at a time + - Wait for response before sending next command + - Use timeout (typically 5 seconds) + - Match response to command by: + - Command type (e.g., `GET_CHANNEL` → `PACKET_CHANNEL_INFO`) + - Sequence number (if implemented) + - First-in-first-out queue + +3. **Timing Considerations**: + - Minimum delay between commands: 50-100ms + - After `APP_START`: Wait 200-500ms before next command + - After `SET_CHANNEL`: Wait 500-1000ms for channel to be created + - After enabling notifications: Wait 200ms before sending commands + +**Example Flow**: +```python +# 1. Connect and discover +await connect_to_device(device) +await discover_services() +await enable_notifications() +await asyncio.sleep(0.2) # Wait for device ready + +# 2. Send AppStart +send_command(build_app_start()) +response = await wait_for_response(PACKET_OK, timeout=5.0) +if response.type != PACKET_OK: + raise Exception("AppStart failed") + +# 3. Now safe to send other commands +await asyncio.sleep(0.1) # Small delay between commands +send_command(build_device_query()) +response = await wait_for_response(PACKET_DEVICE_INFO, timeout=5.0) +``` + +### Command Queue Management + +For reliable operation, implement a command queue: + +1. **Queue Structure**: + - Maintain a queue of pending commands + - Track which command is currently waiting for response + - Only send next command after receiving response or timeout + +2. **Implementation**: +```python +class CommandQueue: + def __init__(self): + self.queue = [] + self.waiting_for_response = False + self.current_command = None + + async def send_command(self, command, expected_response_type, timeout=5.0): + if self.waiting_for_response: + # Queue the command + self.queue.append((command, expected_response_type, timeout)) + return + + self.waiting_for_response = True + self.current_command = (command, expected_response_type, timeout) + + # Send command + await write_to_tx_characteristic(command) + + # Wait for response + response = await wait_for_response(expected_response_type, timeout) + + self.waiting_for_response = False + self.current_command = None + + # Process next queued command + if self.queue: + next_cmd, next_type, next_timeout = self.queue.pop(0) + await self.send_command(next_cmd, next_type, next_timeout) + + return response +``` + +3. **Error Handling**: + - On timeout: Clear current command, process next in queue + - On error: Log error, clear current command, process next + - Don't block queue on single command failure + +--- + +## Protocol Overview + +The MeshCore protocol uses a binary format with the following structure: + +- **Commands**: Sent from client to device via TX characteristic +- **Responses**: Received from device via RX characteristic (notifications) +- **All multi-byte integers**: Little-endian byte order +- **All strings**: UTF-8 encoding + +### Packet Structure + +Most packets follow this format: +``` +[Packet Type (1 byte)] [Data (variable length)] +``` + +The first byte indicates the packet type (see [Response Parsing](#response-parsing)). + +--- + +## Commands + +### 1. App Start + +**Purpose**: Initialize communication with the device. Must be sent first after connection. + +**Command Format**: +``` +Byte 0: 0x01 +Byte 1: 0x03 +Bytes 2-10: "mccli" (ASCII, null-padded to 9 bytes) +``` + +**Example** (hex): +``` +01 03 6d 63 63 6c 69 00 00 00 00 +``` + +**Response**: `PACKET_OK` (0x00) + +--- + +### 2. Device Query + +**Purpose**: Query device information. + +**Command Format**: +``` +Byte 0: 0x16 +Byte 1: 0x03 +``` + +**Example** (hex): +``` +16 03 +``` + +**Response**: `PACKET_DEVICE_INFO` (0x0D) with device information + +--- + +### 3. Get Channel Info + +**Purpose**: Retrieve information about a specific channel. + +**Command Format**: +``` +Byte 0: 0x1F +Byte 1: Channel Index (0-7) +``` + +**Example** (get channel 1): +``` +1F 01 +``` + +**Response**: `PACKET_CHANNEL_INFO` (0x12) with channel details + +**Note**: The device does not return channel secrets for security reasons. Store secrets locally when creating channels. + +--- + +### 4. Set Channel + +**Purpose**: Create or update a channel on the device. + +**Command Format**: +``` +Byte 0: 0x20 +Byte 1: Channel Index (0-7) +Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded) +Bytes 34-65: Secret (32 bytes, see [Secret Generation](#secret-generation)) +``` + +**Total Length**: 66 bytes + +**Channel Index**: +- Index 0: Reserved for public channels (no secret) +- Indices 1-7: Available for private channels + +**Channel Name**: +- UTF-8 encoded +- Maximum 32 bytes +- Padded with null bytes (0x00) if shorter + +**Secret Field** (32 bytes): +- For **private channels**: 32-byte secret (see [Secret Generation](#secret-generation)) +- For **public channels**: All zeros (0x00) + +**Example** (create channel "YourChannelName" at index 1 with secret): +``` +20 01 53 4D 53 00 00 ... (name padded to 32 bytes) + [32 bytes of secret] +``` + +**Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure + +--- + +### 5. Send Channel Message + +**Purpose**: Send a text message to a channel. + +**Command Format**: +``` +Byte 0: 0x03 +Byte 1: 0x00 +Byte 2: Channel Index (0-7) +Bytes 3-6: Timestamp (32-bit little-endian Unix timestamp, seconds) +Bytes 7+: Message Text (UTF-8, variable length) +``` + +**Timestamp**: Unix timestamp in seconds (32-bit unsigned integer, little-endian) + +**Example** (send "Hello" to channel 1 at timestamp 1234567890): +``` +03 00 01 D2 02 96 49 48 65 6C 6C 6F +``` + +**Response**: `PACKET_MSG_SENT` (0x06) on success + +--- + +### 6. Get Message + +**Purpose**: Request the next queued message from the device. + +**Command Format**: +``` +Byte 0: 0x0A +``` + +**Example** (hex): +``` +0A +``` + +**Response**: +- `PACKET_CHANNEL_MSG_RECV` (0x08) or `PACKET_CHANNEL_MSG_RECV_V3` (0x11) for channel messages +- `PACKET_CONTACT_MSG_RECV` (0x07) or `PACKET_CONTACT_MSG_RECV_V3` (0x10) for contact messages +- `PACKET_NO_MORE_MSGS` (0x0A) if no messages available + +**Note**: Poll this command periodically to retrieve queued messages. The device may also send `PACKET_MESSAGES_WAITING` (0x83) as a notification when messages are available. + +--- + +### 7. Get Battery + +**Purpose**: Query device battery level. + +**Command Format**: +``` +Byte 0: 0x14 +``` + +**Example** (hex): +``` +14 +``` + +**Response**: `PACKET_BATTERY` (0x0C) with battery percentage + +--- + +## Channel Management + +### Channel Types + +1. **Public Channels** (Index 0) + - No secret required + - Anyone with the channel name can join + - Use for open communication + +2. **Private Channels** (Indices 1-7) + - Require a 16-byte secret + - Secret is expanded to 32 bytes using SHA-512 (see [Secret Generation](#secret-generation)) + - Only devices with the secret can access the channel + +### Channel Lifecycle + +1. **Create Channel**: + - Choose an available index (1-7 for private channels) + - Generate or provide a 16-byte secret + - Send `SET_CHANNEL` command with name and secret + - **Store the secret locally** (device does not return it) + +2. **Query Channel**: + - Send `GET_CHANNEL` command with channel index + - Parse `PACKET_CHANNEL_INFO` response + - Note: Secret will be null in response (security feature) + +3. **Delete Channel**: + - Send `SET_CHANNEL` command with empty name and all-zero secret + - Or overwrite with a new channel + +### Channel Index Management + +- **Index 0**: Reserved for public channels +- **Indices 1-7**: Available for private channels +- If a channel exists at index 0 but should be private, migrate it to index 1-7 + +--- + +## Secret Generation and QR Codes + +### Secret Generation + +For private channels, generate a cryptographically secure 16-byte secret: + +**Pseudocode**: +```python +import secrets + +# Generate 16 random bytes +secret_bytes = secrets.token_bytes(16) + +# Convert to hex string for storage/sharing +secret_hex = secret_bytes.hex() # 32 hex characters +``` + +**Important**: Use a cryptographically secure random number generator (CSPRNG). Do not use predictable values. + +### Secret Expansion + +When sending the secret to the device via `SET_CHANNEL`, the 16-byte secret must be expanded to 32 bytes: + +**Process**: +1. Take the 16-byte secret +2. Compute SHA-512 hash: `hash = SHA-512(secret)` +3. Use the first 32 bytes of the hash as the secret field in the command + +**Pseudocode**: +```python +import hashlib + +secret_16_bytes = ... # Your 16-byte secret +sha512_hash = hashlib.sha512(secret_16_bytes).digest() # 64 bytes +secret_32_bytes = sha512_hash[:32] # First 32 bytes +``` + +This matches MeshCore's ED25519 key expansion method. + +### QR Code Format + +QR codes for sharing channel secrets use the following format: + +**URL Scheme**: +``` +meshcore://channel/add?name=<ChannelName>&secret=<32HexChars> +``` + +**Parameters**: +- `name`: Channel name (URL-encoded if needed) +- `secret`: 32-character hexadecimal representation of the 16-byte secret + +**Example** (using example secret - NOT a real secret): +``` +meshcore://channel/add?name=YourChannelName&secret=9b647d242d6e1c5883fde0c5cf5c4c5e +``` + +**Alternative Formats** (for backward compatibility): + +1. **JSON Format**: +```json +{ + "name": "YourChannelName", + "secret": "9b647d242d6e1c5883fde0c5cf5c4c5e" +} +``` +*Note: The secret value above is an example only - generate your own secure random secret.* + +2. **Plain Hex** (32 hex characters): +``` +9b647d242d6e1c5883fde0c5cf5c4c5e +``` +*Note: This is an example hex value - always generate your own cryptographically secure random secret.* + +### QR Code Generation + +**Steps**: +1. Generate or use existing 16-byte secret +2. Convert to 32-character hex string (lowercase) +3. URL-encode the channel name +4. Construct the `meshcore://` URL +5. Generate QR code from the URL string + +**Example** (Python with `qrcode` library): +```python +import qrcode +from urllib.parse import quote +import secrets + +channel_name = "YourChannelName" +# Generate a real cryptographically secure secret (NOT the example value) +secret_bytes = secrets.token_bytes(16) +secret_hex = secret_bytes.hex() # This will be a different value each time + +# Example value shown in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e" +# DO NOT use the example value - always generate your own! + +url = f"meshcore://channel/add?name={quote(channel_name)}&secret={secret_hex}" +qr = qrcode.QRCode(version=1, box_size=10, border=5) +qr.add_data(url) +qr.make(fit=True) +img = qr.make_image(fill_color="black", back_color="white") +img.save("channel_qr.png") +``` + +### QR Code Scanning + +When scanning a QR code: + +1. **Parse URL Format**: + - Extract `name` and `secret` query parameters + - Validate secret is 32 hex characters + +2. **Parse JSON Format**: + - Parse JSON object + - Extract `name` and `secret` fields + +3. **Parse Plain Hex**: + - Extract only hex characters (0-9, a-f, A-F) + - Validate length is 32 characters + - Convert to lowercase + +4. **Validate Secret**: + - Must be exactly 32 hex characters (16 bytes) + - Convert hex string to bytes + +5. **Create Channel**: + - Use extracted name and secret + - Send `SET_CHANNEL` command + +--- + +## Message Handling + +### Receiving Messages + +Messages are received via the RX characteristic (notifications). The device sends: + +1. **Channel Messages**: + - `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format + - `PACKET_CHANNEL_MSG_RECV_V3` (0x11) - Version 3 with SNR + +2. **Contact Messages**: + - `PACKET_CONTACT_MSG_RECV` (0x07) - Standard format + - `PACKET_CONTACT_MSG_RECV_V3` (0x10) - Version 3 with SNR + +3. **Notifications**: + - `PACKET_MESSAGES_WAITING` (0x83) - Indicates messages are queued + +### Contact Message Format + +**Standard Format** (`PACKET_CONTACT_MSG_RECV`, 0x07): +``` +Byte 0: 0x07 (packet type) +Bytes 1-6: Public Key Prefix (6 bytes, hex) +Byte 7: Path Length +Byte 8: Text Type +Bytes 9-12: Timestamp (32-bit little-endian) +Bytes 13-16: Signature (4 bytes, only if txt_type == 2) +Bytes 17+: Message Text (UTF-8) +``` + +**V3 Format** (`PACKET_CONTACT_MSG_RECV_V3`, 0x10): +``` +Byte 0: 0x10 (packet type) +Byte 1: SNR (signed byte, multiplied by 4) +Bytes 2-3: Reserved +Bytes 4-9: Public Key Prefix (6 bytes, hex) +Byte 10: Path Length +Byte 11: Text Type +Bytes 12-15: Timestamp (32-bit little-endian) +Bytes 16-19: Signature (4 bytes, only if txt_type == 2) +Bytes 20+: Message Text (UTF-8) +``` + +**Parsing Pseudocode**: +```python +def parse_contact_message(data): + packet_type = data[0] + offset = 1 + + # Check for V3 format + if packet_type == 0x10: # V3 + snr_byte = data[offset] + snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0) + offset += 3 # Skip SNR + reserved + + pubkey_prefix = data[offset:offset+6].hex() + offset += 6 + + path_len = data[offset] + txt_type = data[offset + 1] + offset += 2 + + timestamp = int.from_bytes(data[offset:offset+4], 'little') + offset += 4 + + # If txt_type == 2, skip 4-byte signature + if txt_type == 2: + offset += 4 + + message = data[offset:].decode('utf-8') + + return { + 'pubkey_prefix': pubkey_prefix, + 'path_len': path_len, + 'txt_type': txt_type, + 'timestamp': timestamp, + 'message': message, + 'snr': snr if packet_type == 0x10 else None + } +``` + +### Channel Message Format + +**Standard Format** (`PACKET_CHANNEL_MSG_RECV`, 0x08): +``` +Byte 0: 0x08 (packet type) +Byte 1: Channel Index (0-7) +Byte 2: Path Length +Byte 3: Text Type +Bytes 4-7: Timestamp (32-bit little-endian) +Bytes 8+: Message Text (UTF-8) +``` + +**V3 Format** (`PACKET_CHANNEL_MSG_RECV_V3`, 0x11): +``` +Byte 0: 0x11 (packet type) +Byte 1: SNR (signed byte, multiplied by 4) +Bytes 2-3: Reserved +Byte 4: Channel Index (0-7) +Byte 5: Path Length +Byte 6: Text Type +Bytes 7-10: Timestamp (32-bit little-endian) +Bytes 11+: Message Text (UTF-8) +``` + +**Parsing Pseudocode**: +```python +def parse_channel_message(data): + packet_type = data[0] + offset = 1 + + # Check for V3 format + if packet_type == 0x11: # V3 + snr_byte = data[offset] + snr = ((snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0) + offset += 3 # Skip SNR + reserved + + channel_idx = data[offset] + path_len = data[offset + 1] + txt_type = data[offset + 2] + timestamp = int.from_bytes(data[offset+3:offset+7], 'little') + message = data[offset+7:].decode('utf-8') + + return { + 'channel_idx': channel_idx, + 'timestamp': timestamp, + 'message': message, + 'snr': snr if packet_type == 0x11 else None + } +``` + +### Sending Messages + +Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)). + +**Important**: +- Messages are limited to 133 characters per MeshCore specification +- Long messages should be split into chunks +- Include a chunk indicator (e.g., "[1/3] message text") + +--- + +## Response Parsing + +### Packet Types + +| Value | Name | Description | +|-------|------|-------------| +| 0x00 | PACKET_OK | Command succeeded | +| 0x01 | PACKET_ERROR | Command failed | +| 0x02 | PACKET_CONTACT_START | Start of contact list | +| 0x03 | PACKET_CONTACT | Contact information | +| 0x04 | PACKET_CONTACT_END | End of contact list | +| 0x05 | PACKET_SELF_INFO | Device self-information | +| 0x06 | PACKET_MSG_SENT | Message sent confirmation | +| 0x07 | PACKET_CONTACT_MSG_RECV | Contact message (standard) | +| 0x08 | PACKET_CHANNEL_MSG_RECV | Channel message (standard) | +| 0x09 | PACKET_CURRENT_TIME | Current time response | +| 0x0A | PACKET_NO_MORE_MSGS | No more messages available | +| 0x0C | PACKET_BATTERY | Battery level | +| 0x0D | PACKET_DEVICE_INFO | Device information | +| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) | +| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) | +| 0x12 | PACKET_CHANNEL_INFO | Channel information | +| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet | +| 0x82 | PACKET_ACK | Acknowledgment | +| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification | +| 0x88 | PACKET_LOG_DATA | RF log data (can be ignored) | + +### Parsing Responses + +**PACKET_OK** (0x00): +``` +Byte 0: 0x00 +Bytes 1-4: Optional value (32-bit little-endian integer) +``` + +**PACKET_ERROR** (0x01): +``` +Byte 0: 0x01 +Byte 1: Error code (optional) +``` + +**PACKET_CHANNEL_INFO** (0x12): +``` +Byte 0: 0x12 +Byte 1: Channel Index +Bytes 2-33: Channel Name (32 bytes, null-terminated) +Bytes 34-65: Secret (32 bytes, but device typically only returns 20 bytes total) +``` + +**Note**: The device may not return the full 66-byte packet. Parse what is available. The secret field is typically not returned for security reasons. + +**PACKET_DEVICE_INFO** (0x0D): +``` +Byte 0: 0x0D +Byte 1: Firmware Version (uint8) +Bytes 2+: Variable length based on firmware version + +For firmware version >= 3: +Byte 2: Max Contacts Raw (uint8, actual = value * 2) +Byte 3: Max Channels (uint8) +Bytes 4-7: BLE PIN (32-bit little-endian) +Bytes 8-19: Firmware Build (12 bytes, UTF-8, null-padded) +Bytes 20-59: Model (40 bytes, UTF-8, null-padded) +Bytes 60-79: Version (20 bytes, UTF-8, null-padded) +``` + +**Parsing Pseudocode**: +```python +def parse_device_info(data): + if len(data) < 2: + return None + + fw_ver = data[1] + info = {'fw_ver': fw_ver} + + if fw_ver >= 3 and len(data) >= 80: + info['max_contacts'] = data[2] * 2 + info['max_channels'] = data[3] + info['ble_pin'] = int.from_bytes(data[4:8], 'little') + info['fw_build'] = data[8:20].decode('utf-8').rstrip('\x00').strip() + info['model'] = data[20:60].decode('utf-8').rstrip('\x00').strip() + info['ver'] = data[60:80].decode('utf-8').rstrip('\x00').strip() + + return info +``` + +**PACKET_BATTERY** (0x0C): +``` +Byte 0: 0x0C +Bytes 1-2: Battery Level (16-bit little-endian, percentage 0-100) + +Optional (if data size > 3): +Bytes 3-6: Used Storage (32-bit little-endian, KB) +Bytes 7-10: Total Storage (32-bit little-endian, KB) +``` + +**Parsing Pseudocode**: +```python +def parse_battery(data): + if len(data) < 3: + return None + + level = int.from_bytes(data[1:3], 'little') + info = {'level': level} + + if len(data) > 3: + used_kb = int.from_bytes(data[3:7], 'little') + total_kb = int.from_bytes(data[7:11], 'little') + info['used_kb'] = used_kb + info['total_kb'] = total_kb + + return info +``` + +**PACKET_SELF_INFO** (0x05): +``` +Byte 0: 0x05 +Byte 1: Advertisement Type +Byte 2: TX Power +Byte 3: Max TX Power +Bytes 4-35: Public Key (32 bytes, hex) +Bytes 36-39: Advertisement Latitude (32-bit little-endian, divided by 1e6) +Bytes 40-43: Advertisement Longitude (32-bit little-endian, divided by 1e6) +Byte 44: Multi ACKs +Byte 45: Advertisement Location Policy +Byte 46: Telemetry Mode (bitfield) +Byte 47: Manual Add Contacts (bool) +Bytes 48-51: Radio Frequency (32-bit little-endian, divided by 1000.0) +Bytes 52-55: Radio Bandwidth (32-bit little-endian, divided by 1000.0) +Byte 56: Radio Spreading Factor +Byte 57: Radio Coding Rate +Bytes 58+: Device Name (UTF-8, variable length, null-terminated) +``` + +**Parsing Pseudocode**: +```python +def parse_self_info(data): + if len(data) < 36: + return None + + offset = 1 + info = { + 'adv_type': data[offset], + 'tx_power': data[offset + 1], + 'max_tx_power': data[offset + 2], + 'public_key': data[offset + 3:offset + 35].hex() + } + offset += 35 + + lat = int.from_bytes(data[offset:offset+4], 'little') / 1e6 + lon = int.from_bytes(data[offset+4:offset+8], 'little') / 1e6 + info['adv_lat'] = lat + info['adv_lon'] = lon + offset += 8 + + info['multi_acks'] = data[offset] + info['adv_loc_policy'] = data[offset + 1] + telemetry_mode = data[offset + 2] + info['telemetry_mode_env'] = (telemetry_mode >> 4) & 0b11 + info['telemetry_mode_loc'] = (telemetry_mode >> 2) & 0b11 + info['telemetry_mode_base'] = telemetry_mode & 0b11 + info['manual_add_contacts'] = data[offset + 3] > 0 + offset += 4 + + freq = int.from_bytes(data[offset:offset+4], 'little') / 1000.0 + bw = int.from_bytes(data[offset+4:offset+8], 'little') / 1000.0 + info['radio_freq'] = freq + info['radio_bw'] = bw + info['radio_sf'] = data[offset + 8] + info['radio_cr'] = data[offset + 9] + offset += 10 + + if offset < len(data): + name_bytes = data[offset:] + info['name'] = name_bytes.decode('utf-8').rstrip('\x00').strip() + + return info +``` + +**PACKET_MSG_SENT** (0x06): +``` +Byte 0: 0x06 +Byte 1: Message Type +Bytes 2-5: Expected ACK (4 bytes, hex) +Bytes 6-9: Suggested Timeout (32-bit little-endian, seconds) +``` + +**PACKET_ACK** (0x82): +``` +Byte 0: 0x82 +Bytes 1-6: ACK Code (6 bytes, hex) +``` + +### Error Codes + +**PACKET_ERROR** (0x01) may include an error code in byte 1: + +| Error Code | Description | +|------------|-------------| +| 0x00 | Generic error (no specific code) | +| 0x01 | Invalid command | +| 0x02 | Invalid parameter | +| 0x03 | Channel not found | +| 0x04 | Channel already exists | +| 0x05 | Channel index out of range | +| 0x06 | Secret mismatch | +| 0x07 | Message too long | +| 0x08 | Device busy | +| 0x09 | Not enough storage | + +**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response. + +### Partial Packet Handling + +BLE notifications may arrive in chunks, especially for larger packets. Implement buffering: + +**Implementation**: +```python +class PacketBuffer: + def __init__(self): + self.buffer = bytearray() + self.expected_length = None + + def add_data(self, data): + self.buffer.extend(data) + + # Check if we have a complete packet + if len(self.buffer) >= 1: + packet_type = self.buffer[0] + + # Determine expected length based on packet type + expected = self.get_expected_length(packet_type) + + if expected is not None and len(self.buffer) >= expected: + # Complete packet + packet = bytes(self.buffer[:expected]) + self.buffer = self.buffer[expected:] + return packet + elif expected is None: + # Variable length packet - try to parse what we have + # Some packets have minimum length requirements + if self.can_parse_partial(packet_type): + return self.try_parse_partial() + + return None # Incomplete packet + + def get_expected_length(self, packet_type): + # Fixed-length packets + fixed_lengths = { + 0x00: 5, # PACKET_OK (minimum) + 0x01: 2, # PACKET_ERROR (minimum) + 0x0A: 1, # PACKET_NO_MORE_MSGS + 0x14: 3, # PACKET_BATTERY (minimum) + } + return fixed_lengths.get(packet_type) + + def can_parse_partial(self, packet_type): + # Some packets can be parsed partially + return packet_type in [0x12, 0x08, 0x11, 0x07, 0x10, 0x05, 0x0D] + + def try_parse_partial(self): + # Try to parse with available data + # Return packet if successfully parsed, None otherwise + # This is packet-type specific + pass +``` + +**Usage**: +```python +buffer = PacketBuffer() + +def on_notification_received(data): + packet = buffer.add_data(data) + if packet: + parse_and_handle_packet(packet) +``` + +### Response Handling + +1. **Command-Response Pattern**: + - Send command via TX characteristic + - Wait for response via RX characteristic (notification) + - Match response to command using sequence numbers or command type + - Handle timeout (typically 5 seconds) + - Use command queue to prevent concurrent commands + +2. **Asynchronous Messages**: + - Device may send messages at any time via RX characteristic + - Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command + - Parse incoming messages and route to appropriate handlers + - Buffer partial packets until complete + +3. **Response Matching**: + - Match responses to commands by expected packet type: + - `APP_START` → `PACKET_OK` + - `DEVICE_QUERY` → `PACKET_DEVICE_INFO` + - `GET_CHANNEL` → `PACKET_CHANNEL_INFO` + - `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR` + - `SEND_CHANNEL_MESSAGE` → `PACKET_MSG_SENT` + - `GET_MESSAGE` → `PACKET_CHANNEL_MSG_RECV`, `PACKET_CONTACT_MSG_RECV`, or `PACKET_NO_MORE_MSGS` + - `GET_BATTERY` → `PACKET_BATTERY` + +4. **Timeout Handling**: + - Default timeout: 5 seconds per command + - On timeout: Log error, clear current command, proceed to next in queue + - Some commands may take longer (e.g., `SET_CHANNEL` may need 1-2 seconds) + - Consider longer timeout for channel operations + +5. **Error Recovery**: + - On `PACKET_ERROR`: Log error code, clear current command + - On connection loss: Clear command queue, attempt reconnection + - On invalid response: Log warning, clear current command, proceed + +--- + +## Example Implementation Flow + +### Initialization + +```python +# 1. Scan for MeshCore device +device = scan_for_device("MeshCore") + +# 2. Connect to BLE GATT +gatt = connect_to_device(device) + +# 3. Discover services and characteristics +service = discover_service(gatt, "0000ff00-0000-1000-8000-00805f9b34fb") +rx_char = discover_characteristic(service, "0000ff01-0000-1000-8000-00805f9b34fb") +tx_char = discover_characteristic(service, "0000ff02-0000-1000-8000-00805f9b34fb") + +# 4. Enable notifications on RX characteristic +enable_notifications(rx_char, on_notification_received) + +# 5. Send AppStart command +send_command(tx_char, build_app_start()) +wait_for_response(PACKET_OK) +``` + +### Creating a Private Channel + +```python +# 1. Generate 16-byte secret +secret_16_bytes = generate_secret(16) # Use CSPRNG +secret_hex = secret_16_bytes.hex() + +# 2. Expand secret to 32 bytes using SHA-512 +import hashlib +sha512_hash = hashlib.sha512(secret_16_bytes).digest() +secret_32_bytes = sha512_hash[:32] + +# 3. Build SET_CHANNEL command +channel_name = "YourChannelName" +channel_index = 1 # Use 1-7 for private channels +command = build_set_channel(channel_index, channel_name, secret_32_bytes) + +# 4. Send command +send_command(tx_char, command) +response = wait_for_response(PACKET_OK) + +# 5. Store secret locally (device won't return it) +store_channel_secret(channel_index, secret_hex) +``` + +### Sending a Message + +```python +# 1. Build channel message command +channel_index = 1 +message = "Hello, MeshCore!" +timestamp = int(time.time()) +command = build_channel_message(channel_index, message, timestamp) + +# 2. Send command +send_command(tx_char, command) +response = wait_for_response(PACKET_MSG_SENT) +``` + +### Receiving Messages + +```python +def on_notification_received(data): + packet_type = data[0] + + if packet_type == PACKET_CHANNEL_MSG_RECV or packet_type == PACKET_CHANNEL_MSG_RECV_V3: + message = parse_channel_message(data) + handle_channel_message(message) + elif packet_type == PACKET_MESSAGES_WAITING: + # Poll for messages + send_command(tx_char, build_get_message()) +``` + +### QR Code Sharing + +```python +import secrets +from urllib.parse import quote + +# 1. Generate QR code data +channel_name = "YourChannelName" +# Generate a real secret (NOT the example value from documentation) +secret_bytes = secrets.token_bytes(16) +secret_hex = secret_bytes.hex() + +# Example value in documentation: "9b647d242d6e1c5883fde0c5cf5c4c5e" +# DO NOT use example values - always generate your own secure random secrets! + +url = f"meshcore://channel/add?name={quote(channel_name)}&secret={secret_hex}" + +# 2. Generate QR code image +qr = qrcode.QRCode(version=1, box_size=10, border=5) +qr.add_data(url) +qr.make(fit=True) +img = qr.make_image(fill_color="black", back_color="white") + +# 3. Display or save QR code +img.save("channel_qr.png") +``` + +--- + +## Best Practices + +1. **Connection Management**: + - Implement auto-reconnect with exponential backoff + - Handle disconnections gracefully + - Store last connected device address for quick reconnection + +2. **Secret Management**: + - Always use cryptographically secure random number generators + - Store secrets securely (encrypted storage) + - Never log or transmit secrets in plain text + - Device does not return secrets - you must store them locally + +3. **Message Handling**: + - Poll `GET_MESSAGE` periodically or when `PACKET_MESSAGES_WAITING` is received + - Handle message chunking for long messages (>133 characters) + - Implement message deduplication to avoid processing the same message twice + +4. **Error Handling**: + - Implement timeouts for all commands (typically 5 seconds) + - Handle `PACKET_ERROR` responses appropriately + - Log errors for debugging but don't expose sensitive information + +5. **Channel Management**: + - Avoid using channel index 0 for private channels + - Migrate channels from index 0 to 1-7 if needed + - Query channels after connection to discover existing channels + +--- + +## Platform-Specific Notes + +### Android +- Use `BluetoothGatt` API +- Request `BLUETOOTH_CONNECT` and `BLUETOOTH_SCAN` permissions (Android 12+) +- Enable notifications by writing to descriptor `0x2902` with value `0x01` or `0x02` + +### iOS +- Use `CoreBluetooth` framework +- Implement `CBPeripheralDelegate` for notifications +- Request Bluetooth permissions in Info.plist + +### Python +- Use `bleak` library for cross-platform BLE support +- Handle async/await for BLE operations +- Use `asyncio` for command-response patterns + +### JavaScript/Node.js +- Use `noble` or `@abandonware/noble` for BLE +- Handle callbacks or promises for async operations +- Use `Buffer` for binary data manipulation + +--- + +## Troubleshooting + +### Connection Issues +- **Device not found**: Ensure device is powered on and advertising +- **Connection timeout**: Check Bluetooth permissions and device proximity +- **GATT errors**: Ensure proper service/characteristic discovery + +### Command Issues +- **No response**: Verify notifications are enabled, check connection state +- **Error responses**: Verify command format, check channel index validity +- **Timeout**: Increase timeout value or check device responsiveness + +### Message Issues +- **Messages not received**: Poll `GET_MESSAGE` command periodically +- **Duplicate messages**: Implement message deduplication using timestamps/hashes +- **Message truncation**: Split long messages into chunks + +### Secret/Channel Issues +- **Secret not working**: Verify secret expansion (SHA-512) is correct +- **Channel not found**: Query channels after connection to discover existing channels +- **Channel index 0**: Migrate to index 1-7 for private channels + +--- + +## References + +- MeshCore Python implementation: `meshcore_py-main/src/meshcore/` +- BLE GATT Specification: Bluetooth SIG Core Specification +- ED25519 Key Expansion: RFC 8032 + +--- + +**Last Updated**: 2025-01-01 +**Protocol Version**: Based on MeshCore v1.36.0+ + From faf177de467d124485ff5e7710cc2c46e216a4aa Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Wed, 31 Dec 2025 13:40:05 +0100 Subject: [PATCH 354/546] ESP factory reset clear NVS too --- examples/companion_radio/DataStore.cpp | 5 ++++- examples/companion_radio/MyMesh.cpp | 4 ++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 7f5761f3..4faac975 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -65,6 +65,7 @@ void DataStore::begin() { #if defined(ESP32) #include <SPIFFS.h> + #include <nvs_flash.h> #elif defined(RP2040_PLATFORM) #include <LittleFS.h> #elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) @@ -172,7 +173,9 @@ bool DataStore::formatFileSystem() { #elif defined(RP2040_PLATFORM) return LittleFS.format(); #elif defined(ESP32) - return ((fs::SPIFFSFS *)_fs)->format(); + bool fs_success = ((fs::SPIFFSFS *)_fs)->format(); + esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot + return fs_success && (nvs_err == ESP_OK); #else #error "need to implement format()" #endif diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9cbb2eba..19a92f77 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1613,6 +1613,10 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type } } else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) { + if (_serial) { + MESH_DEBUG_PRINTLN("Factory reset: disabling serial interface to prevent reconnects (BLE/WiFi)"); + _serial->disable(); // Phone app disconnects before we can send OK frame so it's safe here + } bool success = _store->formatFileSystem(); if (success) { writeOKFrame(); From 3af25495bb4e2eaf17de4a916ce9355e62e71f31 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 3 Jan 2026 12:02:15 +1100 Subject: [PATCH 355/546] * Repeater: new anon request sub-type: ANON_REQ_TYPE_REGIONS (rate limited to max 4 every 3 mins) * Companion: new CMD_SEND_ANON_REQ command (reply with existing RESP_CODE_SENT frame) --- examples/companion_radio/MyMesh.cpp | 22 ++++++++++++++++++ examples/simple_repeater/MyMesh.cpp | 22 +++++++++++++++--- examples/simple_repeater/MyMesh.h | 3 ++- src/helpers/BaseChatMesh.cpp | 25 ++++++++++++++++++++ src/helpers/BaseChatMesh.h | 1 + src/helpers/RegionMap.cpp | 36 ++++++++++++++++++++++++----- src/helpers/RegionMap.h | 7 ++++-- 7 files changed, 104 insertions(+), 12 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7689708c..59a0078f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -53,6 +53,7 @@ #define CMD_SET_FLOOD_SCOPE 54 // v8+ #define CMD_SEND_CONTROL_DATA 55 // v8+ #define CMD_GET_STATS 56 // v8+, second byte is stats type +#define CMD_SEND_ANON_REQ 57 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -1286,6 +1287,27 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found } + } else if (cmd_frame[0] == CMD_SEND_ANON_REQ && len > 1 + PUB_KEY_SIZE) { + uint8_t *pub_key = &cmd_frame[1]; + ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); + uint8_t *data = &cmd_frame[1 + PUB_KEY_SIZE]; + if (recipient) { + uint32_t tag, est_timeout; + int result = sendAnonReq(*recipient, data, len - (1 + PUB_KEY_SIZE), tag, est_timeout); + if (result == MSG_SEND_FAILED) { + writeErrFrame(ERR_CODE_TABLE_FULL); + } else { + clearPendingReqs(); + pending_req = tag; // match this to onContactResponse() + out_frame[0] = RESP_CODE_SENT; + out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0; + memcpy(&out_frame[2], &tag, 4); + memcpy(&out_frame[6], &est_timeout, 4); + _serial->writeFrame(out_frame, 10); + } + } else { + writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found + } } else if (cmd_frame[0] == CMD_SEND_STATUS_REQ && len >= 1 + PUB_KEY_SIZE) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 33e32a68..253fa701 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -51,6 +51,8 @@ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ +#define ANON_REQ_TYPE_REGIONS 0x01 + #define CLI_REPLY_DELAY_MILLIS 600 #define LAZY_CONTACTS_WRITE_DELAY 5000 @@ -139,6 +141,19 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr return 13; // reply length } +uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + // REVISIT: should there be params like 'since' in request data[] ? + if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + + return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -450,8 +465,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m uint8_t reply_len; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); - //} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes - // TODO + } else if (data[4] == ANON_REQ_TYPE_REGIONS) { + reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown request type } @@ -668,7 +683,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), - discover_limiter(4, 120) // max 4 every 2 minutes + discover_limiter(4, 120), // max 4 every 2 minutes + regions_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 343aa44f..172c756b 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -93,7 +93,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; - RateLimiter discover_limiter; + RateLimiter discover_limiter, regions_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS @@ -114,6 +114,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); + uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 597444fa..08185628 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -477,6 +477,31 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, return MSG_SEND_FAILED; } +int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout) { + mesh::Packet* pkt; + { + uint8_t temp[MAX_PACKET_PAYLOAD]; + tag = getRTCClock()->getCurrentTimeUnique(); + memcpy(temp, &tag, 4); // tag to match later (also extra blob to help make packet_hash unique) + memcpy(&temp[4], data, len); + + pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len); + } + if (pkt) { + uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); + if (recipient.out_path_len < 0) { + sendFloodScoped(recipient, pkt); + est_timeout = calcFloodTimeoutMillisFor(t); + return MSG_SEND_SENT_FLOOD; + } else { + sendDirect(pkt, recipient.out_path, recipient.out_path_len); + est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len); + return MSG_SEND_SENT_DIRECT; + } + } + return MSG_SEND_FAILED; +} + int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout) { if (data_len > MAX_PACKET_PAYLOAD - 16) return MSG_SEND_FAILED; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 76b0dd1c..40818fed 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -141,6 +141,7 @@ public: int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout); bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len); int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout); + int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout); int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout); int sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout); bool shareContactZeroHop(const ContactInfo& contact); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 36844615..e227532a 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -24,12 +24,12 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #endif } -bool RegionMap::load(FILESYSTEM* _fs) { - if (_fs->exists("/regions2")) { +bool RegionMap::load(FILESYSTEM* _fs, const char* path) { + if (_fs->exists(path ? path : "/regions2")) { #if defined(RP2040_PLATFORM) - File file = _fs->open("/regions2", "r"); + File file = _fs->open(path ? path : "/regions2", "r"); #else - File file = _fs->open("/regions2"); + File file = _fs->open(path ? path : "/regions2"); #endif if (file) { @@ -67,8 +67,8 @@ bool RegionMap::load(FILESYSTEM* _fs) { return false; // failed } -bool RegionMap::save(FILESYSTEM* _fs) { - File file = openWrite(_fs, "/regions2"); +bool RegionMap::save(FILESYSTEM* _fs, const char* path) { + File file = openWrite(_fs, path ? path : "/regions2"); if (file) { uint8_t pad[128]; memset(pad, 0, sizeof(pad)); @@ -235,3 +235,27 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& void RegionMap::exportTo(Stream& out) const { printChildRegions(0, &wildcard, out); // recursive } + +int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { + char *dp = dest; + if ((wildcard.flags & mask) == 0) { + *dp++ = '*'; + *dp++ = ','; + } + + for (int i = 0; i < num_regions; i++) { + auto region = ®ions[i]; + if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) + int len = strlen(region->name); + if ((dp - dest) + len + 2 < max_len) { // only append if name will fit + memcpy(dp, region->name, len); + dp += len; + *dp++ = ','; + } + } + } + if (dp > dest) { dp--; } // don't include trailing comma + + *dp = 0; // set null terminator + return dp - dest; // return length +} diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 50513be1..dcd9a774 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -32,8 +32,8 @@ public: static bool is_name_char(char c); - bool load(FILESYSTEM* _fs); - bool save(FILESYSTEM* _fs); + bool load(FILESYSTEM* _fs, const char* path=NULL); + bool save(FILESYSTEM* _fs, const char* path=NULL); RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0); RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask); @@ -47,6 +47,9 @@ public: bool clear(); void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; } int getCount() const { return num_regions; } + const RegionEntry* getByIdx(int i) const { return ®ions[i]; } + const RegionEntry* getRoot() const { return &wildcard; } + int exportNamesTo(char *dest, int max_len, uint8_t mask); void exportTo(Stream& out) const; }; From ed263b07270096ebf8062d155052c3a9d727beb3 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sat, 3 Jan 2026 15:39:57 +1300 Subject: [PATCH 356/546] implement frame header parising for wifi interface --- src/helpers/esp32/SerialWifiInterface.cpp | 85 +++++++++++++++++++++-- src/helpers/esp32/SerialWifiInterface.h | 7 ++ 2 files changed, 86 insertions(+), 6 deletions(-) diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index 2df9980a..9470c827 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -54,6 +54,9 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { // switch active connection to new client client = newClient; + + // forget received frame header + received_frame_header = NULL; } @@ -86,13 +89,83 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { send_queue[i] = send_queue[i + 1]; } } else { - int len = client.available(); - if (len > 0) { - uint8_t buf[MAX_FRAME_SIZE + 4]; - client.readBytes(buf, len); - memcpy(dest, buf+3, len-3); // remove header (don't even check ... problems are on the other dir) - return len-3; + + // check if we are waiting for a frame header + if(received_frame_header == NULL){ + + // make sure we have received enough bytes for a frame header + // 3 bytes frame header = (1 byte frame type) + (2 bytes frame length as unsigned 16-bit little endian) + int frame_header_length = 3; + if(client.available() >= frame_header_length){ + + // read frame header + uint8_t frame_header[3]; + client.readBytes(frame_header, frame_header_length); + + // parse frame header + uint8_t frame_type = frame_header[0]; + uint16_t frame_length; + memcpy(&frame_length, &frame_header[1], 2); + + // we have received a frame header + received_frame_header = new FrameHeader(); + received_frame_header->type = frame_type; + received_frame_header->length = frame_length; + + } + } + + // check if we have received a frame header + if(received_frame_header){ + + // make sure we have received enough bytes for the required frame length + int available = client.available(); + int frame_type = received_frame_header->type; + int frame_length = received_frame_header->length; + if(frame_length > available){ + WIFI_DEBUG_PRINTLN("Waiting for %d more bytes", frame_length - available); + return 0; + } + + // skip frames that are larger than MAX_FRAME_SIZE + if(frame_length > MAX_FRAME_SIZE){ + WIFI_DEBUG_PRINTLN("Skipping frame: length=%d is larger than MAX_FRAME_SIZE=%d", frame_length, MAX_FRAME_SIZE); + while(frame_length > 0){ + uint8_t skip[1]; + int skipped = client.read(skip, 1); + frame_length -= skipped; + } + received_frame_header = NULL; + return 0; + } + + // skip frames that are not expected type + // '<' is 0x3c which indicates a frame sent from app to radio + if(frame_type != '<'){ + WIFI_DEBUG_PRINTLN("Skipping frame: type=0x%x is unexpected", frame_type); + while(frame_length > 0){ + uint8_t skip[1]; + int skipped = client.read(skip, 1); + frame_length -= skipped; + } + received_frame_header = NULL; + return 0; + } + + // read frame data + uint8_t frame_data[MAX_FRAME_SIZE]; + client.readBytes(frame_data, frame_length); + + // copy frame to provided buffer + memcpy(dest, frame_data, frame_length); + + // ready for next frame + received_frame_header = NULL; + return frame_length; + + } + } } diff --git a/src/helpers/esp32/SerialWifiInterface.h b/src/helpers/esp32/SerialWifiInterface.h index 2b6c6edd..c7139b40 100644 --- a/src/helpers/esp32/SerialWifiInterface.h +++ b/src/helpers/esp32/SerialWifiInterface.h @@ -12,11 +12,18 @@ class SerialWifiInterface : public BaseSerialInterface { WiFiServer server; WiFiClient client; + struct FrameHeader { + uint8_t type; + uint16_t length; + }; + struct Frame { uint8_t len; uint8_t buf[MAX_FRAME_SIZE]; }; + FrameHeader* received_frame_header = NULL; + #define FRAME_QUEUE_SIZE 4 int recv_queue_len; Frame recv_queue[FRAME_QUEUE_SIZE]; From 71bb49e556d11e2fb4276c4346d901f125ff1850 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sat, 3 Jan 2026 16:36:19 +1300 Subject: [PATCH 357/546] remove use of dynamic allocation --- src/helpers/esp32/SerialWifiInterface.cpp | 30 ++++++++++++++--------- src/helpers/esp32/SerialWifiInterface.h | 7 +++++- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index 9470c827..de5cce78 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -43,6 +43,15 @@ bool SerialWifiInterface::isWriteBusy() const { return false; } +bool SerialWifiInterface::hasReceivedFrameHeader() { + return received_frame_header.type != 0 && received_frame_header.length != 0; +} + +void SerialWifiInterface::resetReceivedFrameHeader() { + received_frame_header.type = 0; + received_frame_header.length = 0; +} + size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { // check if new client connected auto newClient = server.available(); @@ -56,7 +65,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { client = newClient; // forget received frame header - received_frame_header = NULL; + resetReceivedFrameHeader(); } @@ -91,7 +100,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { } else { // check if we are waiting for a frame header - if(received_frame_header == NULL){ + if(!hasReceivedFrameHeader()){ // make sure we have received enough bytes for a frame header // 3 bytes frame header = (1 byte frame type) + (2 bytes frame length as unsigned 16-bit little endian) @@ -108,21 +117,20 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { memcpy(&frame_length, &frame_header[1], 2); // we have received a frame header - received_frame_header = new FrameHeader(); - received_frame_header->type = frame_type; - received_frame_header->length = frame_length; + received_frame_header.type = frame_type; + received_frame_header.length = frame_length; } } // check if we have received a frame header - if(received_frame_header){ + if(hasReceivedFrameHeader()){ // make sure we have received enough bytes for the required frame length int available = client.available(); - int frame_type = received_frame_header->type; - int frame_length = received_frame_header->length; + int frame_type = received_frame_header.type; + int frame_length = received_frame_header.length; if(frame_length > available){ WIFI_DEBUG_PRINTLN("Waiting for %d more bytes", frame_length - available); return 0; @@ -136,7 +144,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { int skipped = client.read(skip, 1); frame_length -= skipped; } - received_frame_header = NULL; + resetReceivedFrameHeader(); return 0; } @@ -149,7 +157,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { int skipped = client.read(skip, 1); frame_length -= skipped; } - received_frame_header = NULL; + resetReceivedFrameHeader(); return 0; } @@ -161,7 +169,7 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { memcpy(dest, frame_data, frame_length); // ready for next frame - received_frame_header = NULL; + resetReceivedFrameHeader(); return frame_length; } diff --git a/src/helpers/esp32/SerialWifiInterface.h b/src/helpers/esp32/SerialWifiInterface.h index c7139b40..19291497 100644 --- a/src/helpers/esp32/SerialWifiInterface.h +++ b/src/helpers/esp32/SerialWifiInterface.h @@ -22,7 +22,7 @@ class SerialWifiInterface : public BaseSerialInterface { uint8_t buf[MAX_FRAME_SIZE]; }; - FrameHeader* received_frame_header = NULL; + FrameHeader received_frame_header; #define FRAME_QUEUE_SIZE 4 int recv_queue_len; @@ -40,6 +40,8 @@ public: _isEnabled = false; _last_write = 0; send_queue_len = recv_queue_len = 0; + received_frame_header.type = 0; + received_frame_header.length = 0; } void begin(int port); @@ -54,6 +56,9 @@ public: size_t writeFrame(const uint8_t src[], size_t len) override; size_t checkRecvFrame(uint8_t dest[]) override; + + bool hasReceivedFrameHeader(); + void resetReceivedFrameHeader(); }; #if WIFI_DEBUG_LOGGING && ARDUINO From 63ae92aa0973d312b802ef938eb44f1414731c82 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Sat, 3 Jan 2026 16:32:36 +0100 Subject: [PATCH 358/546] fix compilation errors for m6 companion roles --- variants/thinknode_m6/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 16394ced..db22073c 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -90,7 +90,6 @@ build_src_filter = ${ThinkNode_M6.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -113,7 +112,6 @@ build_src_filter = ${ThinkNode_M6.build_src_filter} +<helpers/ui/buzzer.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 From 63767cdb7de6dcdec7eadadf3cf16716b3d3f88e Mon Sep 17 00:00:00 2001 From: cj-vana <cj@depth23.online> Date: Sat, 3 Jan 2026 18:54:48 -0700 Subject: [PATCH 359/546] Fix typos in README and source comments --- README.md | 4 ++-- src/helpers/radiolib/CustomLR1110.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 73fa960c..d3bcbbef 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,7 @@ Please submit PR's using 'dev' as the base branch! For minor changes just submit your PR and I'll try to review it, but for anything more 'impactful' please open an Issue first and start a discussion. Is better to sound out what it is you want to achieve first, and try to come to a consensus on what the best approach is, especially when it impacts the structure or architecture of this codebase. Here are some general principals you should try to adhere to: -* Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unecessary layers. +* Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unnecessary layers. * No dynamic memory allocation, except during setup/begin functions. * Use the same brace and indenting style that's in the core source modules. (A .clang-format is prob going to be added soon, but please do NOT retroactively re-format existing code. This just creates unnecessary diffs that make finding problems harder) @@ -106,7 +106,7 @@ There are a number of fairly major features in the pipeline, with no particular - [ ] Core + Apps: support for LZW message compression - [ ] Core: dynamic CR (Coding Rate) for weak vs strong hops - [ ] Core: new framework for hosting multiple virtual nodes on one physical device -- [ ] V2 protocol spec: discussion and concensus around V2 packet protocol, including path hashes, new encryption specs, etc +- [ ] V2 protocol spec: discussion and consensus around V2 packet protocol, including path hashes, new encryption specs, etc ## 📞 Get Support diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index 2e536de5..e4332013 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -10,7 +10,7 @@ class CustomLR1110 : public LR1110 { size_t getPacketLength(bool update) override { size_t len = LR1110::getPacketLength(update); if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) { - // we've just recieved a corrupted packet + // we've just received a corrupted packet // this may have triggered a bug causing subsequent packets to be shifted // call standby() to return radio to known-good state // recvRaw will call startReceive() to restart rx From 8708fa012ad9f64f1b7bc1fa93ef466378f4f496 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Sun, 4 Jan 2026 17:43:25 +1300 Subject: [PATCH 360/546] simplify reading frame header --- src/helpers/esp32/SerialWifiInterface.cpp | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/helpers/esp32/SerialWifiInterface.cpp b/src/helpers/esp32/SerialWifiInterface.cpp index de5cce78..462e3ecc 100644 --- a/src/helpers/esp32/SerialWifiInterface.cpp +++ b/src/helpers/esp32/SerialWifiInterface.cpp @@ -108,17 +108,8 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { if(client.available() >= frame_header_length){ // read frame header - uint8_t frame_header[3]; - client.readBytes(frame_header, frame_header_length); - - // parse frame header - uint8_t frame_type = frame_header[0]; - uint16_t frame_length; - memcpy(&frame_length, &frame_header[1], 2); - - // we have received a frame header - received_frame_header.type = frame_type; - received_frame_header.length = frame_length; + client.readBytes(&received_frame_header.type, 1); + client.readBytes((uint8_t*)&received_frame_header.length, 2); } @@ -161,12 +152,8 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) { return 0; } - // read frame data - uint8_t frame_data[MAX_FRAME_SIZE]; - client.readBytes(frame_data, frame_length); - - // copy frame to provided buffer - memcpy(dest, frame_data, frame_length); + // read frame data to provided buffer + client.readBytes(dest, frame_length); // ready for next frame resetReceivedFrameHeader(); From 818f5e9da507840be3753eb7bffb08a23546f286 Mon Sep 17 00:00:00 2001 From: alex-vg <22611767+alex-vg@users.noreply.github.com> Date: Sat, 20 Dec 2025 00:12:39 +0100 Subject: [PATCH 361/546] variants: Xiao_S3_WIO: Add WiFi companion env --- variants/xiao_s3_wio/platformio.ini | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 8979edc2..22bb4090 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -195,6 +195,29 @@ lib_deps = densaugeo/base64 @ ~1.4.0 adafruit/Adafruit SSD1306 @ ^2.5.13 +[env:Xiao_S3_WIO_companion_radio_wifi] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + +<helpers/ui/NullDisplayDriver.cpp> + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> +build_flags = + ${Xiao_S3_WIO.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"password"' + ; -D BLE_DEBUG_LOGGING=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 +lib_deps = + ${Xiao_S3_WIO.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Xiao_S3_WIO_sensor] extends = Xiao_S3_WIO build_src_filter = ${Xiao_S3_WIO.build_src_filter} From d4a2e5789f0de675a84589fb0cdeaee451929bb1 Mon Sep 17 00:00:00 2001 From: Luke <luke@sheldrick.co.uk> Date: Tue, 6 Jan 2026 14:49:15 +0000 Subject: [PATCH 362/546] OFFLINE_QUEUE_SIZE for Heltec Wifi companions OFFLINE_QUEUE_SIZE missing from h3/h4 wifi companions, causing them only store 16 messages. --- variants/heltec_v3/platformio.ini | 2 ++ variants/heltec_v4/platformio.ini | 1 + 2 files changed, 3 insertions(+) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 36c6386f..dcb2873c 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -189,6 +189,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} @@ -341,6 +342,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v3.build_src_filter} diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc6..ecfd7889 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -176,6 +176,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v4.build_src_filter} From a7a6bb51ce867da45851583f8f50bf0a64abfeaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Oliver=20Weyhm=C3=BCller?= <o.weyhm@gmx.de> Date: Wed, 7 Jan 2026 03:40:00 +0100 Subject: [PATCH 363/546] Apply #1331 to other WiFi companions --- variants/heltec_tracker_v2/platformio.ini | 1 + variants/heltec_v2/platformio.ini | 1 + variants/lilygo_tlora_v2_1/platformio.ini | 1 + variants/nibble_screen_connect/platformio.ini | 1 + variants/station_g2/platformio.ini | 1 + 5 files changed, 5 insertions(+) diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 61ccd4f4..36de671e 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -185,6 +185,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_v2.build_src_filter} diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 539cc52e..f8cc9360 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -183,6 +183,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_lora32_v2.build_src_filter} diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 9ef9734e..f27f57fd 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -136,6 +136,7 @@ build_flags = -D WIFI_SSID='"ssid"' -D WIFI_PWD='"password"' -D WIFI_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} +<helpers/esp32/*.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini index e18bcde1..0d3d4652 100644 --- a/variants/nibble_screen_connect/platformio.ini +++ b/variants/nibble_screen_connect/platformio.ini @@ -147,6 +147,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 build_src_filter = ${nibble_screen_connect_base.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index b3ba2a86..1428221d 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -226,6 +226,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Station_G2.build_src_filter} From 55fc03b1090251ae4dac5070ee09ed1a1cfd663b Mon Sep 17 00:00:00 2001 From: Ferdia McKeogh <frieze_tench_0k@icloud.com> Date: Tue, 6 Jan 2026 20:11:19 +0100 Subject: [PATCH 364/546] Fix capitalization in T1000-E manufacturer string --- variants/t1000-e/T1000eBoard.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index 32f54bb3..f26bdf02 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -34,7 +34,7 @@ public: } const char* getManufacturerName() const override { - return "Seeed Tracker T1000-e"; + return "Seeed Tracker T1000-E"; } int buttonStateChanged() { @@ -91,4 +91,4 @@ public: } // bool startOTAUpdate(const char* id, char reply[]) override; -}; \ No newline at end of file +}; From 5cc44dd802b07a4f4180e8ba694fabcb7de96174 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 8 Jan 2026 13:20:52 +1100 Subject: [PATCH 365/546] * ANON_REQ_TYPE_REGIONS now direct only, with reply_path encoded in request --- examples/simple_repeater/MyMesh.cpp | 18 ++++++++++++++---- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 253fa701..261184c5 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -142,8 +142,13 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { - // REVISIT: should there be params like 'since' in request data[] ? if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + // other params?? + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -463,12 +468,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m data[len] = 0; // ensure null terminator uint8_t reply_len; + + reply_path_len = -1; if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); - } else if (data[4] == ANON_REQ_TYPE_REGIONS) { + } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else { - reply_len = 0; // unknown request type + reply_len = 0; // unknown/invalid request type } if (reply_len == 0) return; // invalid request @@ -478,9 +485,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFlood(path, SERVER_RESPONSE_DELAY); - } else { + } else if (reply_path_len < 0) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + } else { + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); } } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 172c756b..49952770 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -88,6 +88,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { NodePrefs _prefs; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; + uint8_t reply_path[MAX_PATH_SIZE]; + int8_t reply_path_len; ClientACL acl; TransportKeyStore key_store; RegionMap region_map, temp_map; From fa48d4fe815bbbebbdffa2804dfca450ffe11eec Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Mon, 22 Dec 2025 16:21:44 +0100 Subject: [PATCH 366/546] variants: Nano G2 Ultra: Use common implementation of startOTAUpdate() Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/nano_g2_ultra/nano-g2.cpp | 61 ------------------------------ variants/nano_g2_ultra/nano-g2.h | 4 +- 2 files changed, 2 insertions(+), 63 deletions(-) diff --git a/variants/nano_g2_ultra/nano-g2.cpp b/variants/nano_g2_ultra/nano-g2.cpp index 6a749aab..23695845 100644 --- a/variants/nano_g2_ultra/nano-g2.cpp +++ b/variants/nano_g2_ultra/nano-g2.cpp @@ -3,25 +3,8 @@ #ifdef NANO_G2_ULTRA -#include <bluefruit.h> #include <Wire.h> -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) -{ - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) -{ - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - void NanoG2Ultra::begin() { NRF52Board::begin(); @@ -56,48 +39,4 @@ uint16_t NanoG2Ultra::getBattMilliVolts() // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool NanoG2Ultra::startOTAUpdate(const char *id, char reply[]) -{ - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("NANO_G2_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} #endif diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 6961fc86..4f7eb074 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -35,11 +35,11 @@ #define PIN_VBAT_READ (0 + 2) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public NRF52Board { +class NanoG2Ultra : public NRF52BoardOTA { public: + NanoG2Ultra() : NRF52BoardOTA("NANO_G2_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char *id, char reply[]) override; const char *getManufacturerName() const override { return "Nano G2 Ultra"; } From 57fa1ba8542829747c4d3e752c043033ed26e51e Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Mon, 22 Dec 2025 16:22:54 +0100 Subject: [PATCH 367/546] variants: Wio WM1110: Use common implementation of startOTAUpdate() Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/wio_wm1110/WioWM1110Board.cpp | 45 ++------------------------ variants/wio_wm1110/WioWM1110Board.h | 5 ++- 2 files changed, 4 insertions(+), 46 deletions(-) diff --git a/variants/wio_wm1110/WioWM1110Board.cpp b/variants/wio_wm1110/WioWM1110Board.cpp index d0ab9785..2825e554 100644 --- a/variants/wio_wm1110/WioWM1110Board.cpp +++ b/variants/wio_wm1110/WioWM1110Board.cpp @@ -1,24 +1,9 @@ #ifdef WIO_WM1110 -#include <Arduino.h> -#include <Wire.h> -#include <bluefruit.h> - #include "WioWM1110Board.h" -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} +#include <Arduino.h> +#include <Wire.h> void WioWM1110Board::begin() { NRF52BoardDCDC::begin(); @@ -42,31 +27,5 @@ void WioWM1110Board::begin() { delay(10); } - -bool WioWM1110Board::startOTAUpdate(const char *id, char reply[]) { - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - Bluefruit.setTxPower(4); - Bluefruit.setName("WM1110_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - bledfu.begin(); - - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); - Bluefruit.Advertising.setFastTimeout(30); - Bluefruit.Advertising.start(0); - - strcpy(reply, "OK - started"); - return true; -} - #endif diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index 697028b0..adcea877 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -11,8 +11,9 @@ #endif #define Serial Serial1 -class WioWM1110Board : public NRF52BoardDCDC { +class WioWM1110Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: + WioWM1110Board() : NRF52BoardOTA("WM1110_OTA") {} void begin(); #if defined(LED_GREEN) @@ -37,8 +38,6 @@ public: return "Seeed Wio WM1110"; } - bool startOTAUpdate(const char* id, char reply[]) override; - void enableSensorPower(bool enable) { digitalWrite(SENSOR_POWER_PIN, enable ? HIGH : LOW); if (enable) { From 578d55b28ab48a06b2fb0bc348e3558b0a9f54f3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Mon, 22 Dec 2025 16:25:35 +0100 Subject: [PATCH 368/546] variants: Thinknode M3/M6: Use common Nrf52Board class Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/thinknode_m3/ThinknodeM3Board.cpp | 70 +--------------------- variants/thinknode_m3/ThinknodeM3Board.h | 26 +++----- variants/thinknode_m6/ThinkNodeM6Board.cpp | 61 +------------------ variants/thinknode_m6/ThinkNodeM6Board.h | 22 ++----- 4 files changed, 16 insertions(+), 163 deletions(-) diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp index 74019fcb..d7ecd62d 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.cpp +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -5,76 +5,10 @@ #include <bluefruit.h> void ThinknodeM3Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + Nrf52BoardDCDC::begin(); btn_prev_state = HIGH; - sd_power_mode_set(NRF_POWER_MODE_LOWPWR); - - // Enable DC/DC converter for improved power efficiency - NRF_POWER->DCDCEN = 1; - Wire.begin(); delay(10); // give sx1262 some time to power up -} - -#if 0 -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} - - -bool TrackerThinknodeM3Board::startOTAUpdate(const char* id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("T1000E_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h index c9b96273..62694087 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.h +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -1,13 +1,13 @@ #pragma once -#include <MeshCore.h> #include <Arduino.h> +#include <MeshCore.h> +#include <helpers/NRF52Board.h> #define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) -class ThinknodeM3Board : public mesh::MainBoard { +class ThinknodeM3Board : public Nrf52BoardDCDC { protected: - uint8_t startup_reason; uint8_t btn_prev_state; public: @@ -27,12 +27,10 @@ public: return (uint16_t)((float)adcvalue * ADC_FACTOR); } - uint8_t getStartupReason() const override { return startup_reason; } - - #if defined(P_LORA_TX_LED) - #if !defined(P_LORA_TX_LED_ON) - #define P_LORA_TX_LED_ON HIGH - #endif +#if defined(P_LORA_TX_LED) +#if !defined(P_LORA_TX_LED_ON) +#define P_LORA_TX_LED_ON HIGH +#endif void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, P_LORA_TX_LED_ON); // turn TX LED on } @@ -56,13 +54,5 @@ public: return 0; } - void powerOff() override { - sd_power_system_off(); - } - - void reboot() override { - NVIC_SystemReset(); - } - -// bool startOTAUpdate(const char* id, char reply[]) override; + void powerOff() override { sd_power_system_off(); } }; \ No newline at end of file diff --git a/variants/thinknode_m6/ThinkNodeM6Board.cpp b/variants/thinknode_m6/ThinkNodeM6Board.cpp index 1ccc2026..8ebae64c 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.cpp +++ b/variants/thinknode_m6/ThinkNodeM6Board.cpp @@ -4,25 +4,9 @@ #ifdef THINKNODE_M6 #include <Wire.h> -#include <bluefruit.h> - -static BLEDfu bledfu; - -static void connect_callback(uint16_t conn_handle) { - (void)conn_handle; - MESH_DEBUG_PRINTLN("BLE client connected"); -} - -static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { - (void)conn_handle; - (void)reason; - - MESH_DEBUG_PRINTLN("BLE client disconnected"); -} void ThinkNodeM6Board::begin() { - // for future use, sub-classes SHOULD call this from their begin() - startup_reason = BD_STARTUP_NORMAL; + NRF52Board::begin(); Wire.begin(); @@ -49,47 +33,4 @@ uint16_t ThinkNodeM6Board::getBattMilliVolts() { // divider into account (providing the actual LIPO voltage) return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB); } - -bool ThinkNodeM6Board::startOTAUpdate(const char *id, char reply[]) { - // Config the peripheral connection with maximum bandwidth - // more SRAM required by SoftDevice - // Note: All config***() function must be called before begin() - Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); - Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); - - Bluefruit.begin(1, 0); - // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4 - Bluefruit.setTxPower(4); - // Set the BLE device name - Bluefruit.setName("THINKNODE_M1_OTA"); - - Bluefruit.Periph.setConnectCallback(connect_callback); - Bluefruit.Periph.setDisconnectCallback(disconnect_callback); - - // To be consistent OTA DFU should be added first if it exists - bledfu.begin(); - - // Set up and start advertising - // Advertising packet - Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE); - Bluefruit.Advertising.addTxPower(); - Bluefruit.Advertising.addName(); - - /* Start Advertising - - Enable auto advertising if disconnected - - Interval: fast mode = 20 ms, slow mode = 152.5 ms - - Timeout for fast mode is 30 seconds - - Start(timeout) with timeout = 0 will advertise forever (until connected) - - For recommended advertising interval - https://developer.apple.com/library/content/qa/qa1931/_index.html - */ - Bluefruit.Advertising.restartOnDisconnect(true); - Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms - Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode - Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds - - strcpy(reply, "OK - started"); - return true; -} #endif diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index c3d7dad6..c03e1fbc 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -1,7 +1,8 @@ #pragma once -#include <MeshCore.h> #include <Arduino.h> +#include <MeshCore.h> +#include <helpers/NRF52Board.h> // built-ins #define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096 @@ -11,21 +12,13 @@ #define PIN_VBAT_READ BATTERY_PIN #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM6Board : public mesh::MainBoard { -protected: - uint8_t startup_reason; - +class ThinkNodeM6Board : public Nrf52BoardOTA { public: - + ThinkNodeM6Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} void begin(); uint16_t getBattMilliVolts() override; - bool startOTAUpdate(const char* id, char reply[]) override; - uint8_t getStartupReason() const override { - return startup_reason; - } - - #if defined(P_LORA_TX_LED) +#if defined(P_LORA_TX_LED) void onBeforeTransmit() override { digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on } @@ -38,10 +31,6 @@ public: return "Elecrow ThinkNode-M6"; } - void reboot() override { - NVIC_SystemReset(); - } - void powerOff() override { // turn off all leds, sd_power_system_off will not do this for us @@ -51,6 +40,5 @@ public: // power off board sd_power_system_off(); - } }; From 24a4b99e314595a20fa160b3d40d9552c4138a12 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:35:27 +0100 Subject: [PATCH 369/546] variants: Heltec Mesh Solar: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/heltec_mesh_solar/MeshSolarBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 69437c87..92d69f3a 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -20,7 +20,7 @@ #define SX126X_DIO2_AS_RF_SWITCH true #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -class MeshSolarBoard : public NRF52BoardOTA { +class MeshSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {} void begin(); From 3b0870e2c13582e7a6a5e24cc3088fe2da0ebc0f Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:37:04 +0100 Subject: [PATCH 370/546] variants: Heltec T114: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/heltec_t114/T114Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 74e26455..b1fe1f53 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -9,7 +9,7 @@ #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public NRF52BoardOTA { +class T114Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: T114Board() : NRF52BoardOTA("T114_OTA") {} void begin(); From 041f67ab7106e3033d90513bfa0d2ba5c7f65417 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:37:34 +0100 Subject: [PATCH 371/546] variants: Ikoka NRF: Use DC/DC regulator The Ikoka boards are based on the Xioa NRF52840 module which is known to have the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 2 +- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 2 +- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index acb58b3c..7912868a 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -6,7 +6,7 @@ #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public NRF52BoardOTA { +class IkokaNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { public: IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index c7e96921..3062de49 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -6,7 +6,7 @@ #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public NRF52BoardOTA { +class IkokaNanoNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 00a26029..7edd85d1 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -6,7 +6,7 @@ #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public NRF52BoardOTA { +class IkokaStickNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); From bf93d6cf7a673b000981f66f650a5e8d05b03fa3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:39:29 +0100 Subject: [PATCH 372/546] variants: Lilygo T-Echo (Lite): Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/lilygo_techo/TechoBoard.h | 2 +- variants/lilygo_techo_lite/TechoBoard.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index c9bdc229..66038827 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -13,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 8e6974bd..6e816b4e 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -13,7 +13,7 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} void begin(); From 465b481a2ed112836b9f818a11784763ba04d437 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:40:16 +0100 Subject: [PATCH 373/546] variants: Mesh Pocket: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/mesh_pocket/MeshPocket.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index db65c99b..8d22106f 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -9,7 +9,7 @@ #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public NRF52BoardOTA { +class HeltecMeshPocket : public NRF52BoardDCDC, public NRF52BoardOTA { public: HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {} void begin(); From 137eed3ede7eae919cb8b07cb5e8193d542c97d3 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:40:39 +0100 Subject: [PATCH 374/546] variants: Minewsemi ME25LS01: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 805f9dfa..131148f8 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -20,7 +20,7 @@ #define PIN_VBAT_READ BATTERY_PIN #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class MinewsemiME25LS01Board : public NRF52BoardOTA { +class MinewsemiME25LS01Board : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; From 80ca720002109c93ad629224515ce4d19d6f5bf7 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:41:53 +0100 Subject: [PATCH 375/546] variants: ProMicro: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/promicro/PromicroBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index c23ed1c9..6719fc46 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -20,7 +20,7 @@ #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class PromicroBoard : public NRF52BoardOTA { +class PromicroBoard : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; From 1651db81f9bf8c5ba93b1ce98da7ce369247b5c4 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Fri, 19 Dec 2025 11:42:34 +0100 Subject: [PATCH 376/546] variants: Sensecap Solar: Use DC/DC regulator The schematic shows the LC circuit for the internal DC/DC regulator to be available. Enable it to save power. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/sensecap_solar/SenseCapSolarBoard.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index dfe007bf..bb67f1e3 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -4,7 +4,7 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class SenseCapSolarBoard : public NRF52BoardOTA { +class SenseCapSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { public: SenseCapSolarBoard() : NRF52BoardOTA("SENSECAP_SOLAR_OTA") {} void begin(); From 686d887f723a1db57491ebe68e1277292433ff52 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Sat, 20 Dec 2025 10:39:57 +0100 Subject: [PATCH 377/546] variants: T1000E: Add OTA support To reduce the number of different code paths, add OTA support for the remaining NRF52 boards. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- variants/t1000-e/T1000eBoard.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index f26bdf02..bc6e9c03 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -4,11 +4,12 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class T1000eBoard : public NRF52BoardDCDC { +class T1000eBoard : public NRF52BoardDCDC, public NRF52BoardOTA { protected: uint8_t btn_prev_state; public: + T1000eBoard() : NRF52BoardOTA("T1000E_OTA") {} void begin(); uint16_t getBattMilliVolts() override { @@ -89,6 +90,4 @@ public: sd_power_system_off(); } - -// bool startOTAUpdate(const char* id, char reply[]) override; }; From 4f46ec75dda7a4ed850f3dc42b426432d8256b33 Mon Sep 17 00:00:00 2001 From: Frieder Schrempf <frieder@fris.de> Date: Sat, 20 Dec 2025 10:44:13 +0100 Subject: [PATCH 378/546] Remove NRF52BoardOTA class and integrate it into NRF52Board As all NRF52 boards now have OTA support, let's remove the subclass and integrate it into the base class. Signed-off-by: Frieder Schrempf <frieder@fris.de> --- src/helpers/NRF52Board.cpp | 2 +- src/helpers/NRF52Board.h | 13 ++++--------- variants/heltec_mesh_solar/MeshSolarBoard.h | 4 ++-- variants/heltec_t114/T114Board.h | 4 ++-- variants/ikoka_handheld_nrf/IkokaNrf52Board.h | 4 ++-- variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h | 4 ++-- variants/ikoka_stick_nrf/IkokaStickNRFBoard.h | 4 ++-- variants/keepteen_lt1/KeepteenLT1Board.h | 4 ++-- variants/lilygo_techo/TechoBoard.h | 4 ++-- variants/lilygo_techo_lite/TechoBoard.h | 4 ++-- variants/mesh_pocket/MeshPocket.h | 4 ++-- .../minewsemi_me25ls01/MinewsemiME25LS01Board.h | 4 ++-- variants/nano_g2_ultra/nano-g2.h | 4 ++-- variants/promicro/PromicroBoard.h | 4 ++-- variants/rak4631/RAK4631Board.h | 4 ++-- variants/rak_wismesh_tag/RAKWismeshTagBoard.h | 4 ++-- variants/sensecap_solar/SenseCapSolarBoard.h | 4 ++-- variants/t1000-e/T1000eBoard.h | 4 ++-- variants/thinknode_m1/ThinkNodeM1Board.h | 4 ++-- variants/wio-tracker-l1/WioTrackerL1Board.h | 4 ++-- variants/wio_wm1110/WioWM1110Board.h | 4 ++-- variants/xiao_nrf52/XiaoNrf52Board.h | 4 ++-- 22 files changed, 45 insertions(+), 50 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index c0d58314..8f60823c 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -54,7 +54,7 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } -bool NRF52BoardOTA::startOTAUpdate(const char *id, char reply[]) { +bool NRF52Board::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice // Note: All config***() function must be called before begin() diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0d6c0a43..f464b79a 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -8,12 +8,15 @@ class NRF52Board : public mesh::MainBoard { protected: uint8_t startup_reason; + char *ota_name; public: + NRF52Board(char *otaname) : ota_name(otaname) {} virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual bool startOTAUpdate(const char *id, char reply[]) override; }; /* @@ -25,15 +28,7 @@ public: */ class NRF52BoardDCDC : virtual public NRF52Board { public: + NRF52BoardDCDC() {} virtual void begin() override; }; - -class NRF52BoardOTA : virtual public NRF52Board { -private: - char *ota_name; - -public: - NRF52BoardOTA(char *name) : ota_name(name) {} - virtual bool startOTAUpdate(const char *id, char reply[]) override; -}; #endif \ No newline at end of file diff --git a/variants/heltec_mesh_solar/MeshSolarBoard.h b/variants/heltec_mesh_solar/MeshSolarBoard.h index 92d69f3a..81633625 100644 --- a/variants/heltec_mesh_solar/MeshSolarBoard.h +++ b/variants/heltec_mesh_solar/MeshSolarBoard.h @@ -20,9 +20,9 @@ #define SX126X_DIO2_AS_RF_SWITCH true #define SX126X_DIO3_TCXO_VOLTAGE 1.8 -class MeshSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class MeshSolarBoard : public NRF52BoardDCDC { public: - MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {} + MeshSolarBoard() : NRF52Board("MESH_SOLAR_OTA") {} void begin(); uint16_t getBattMilliVolts() override { diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index b1fe1f53..340cfa97 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -9,9 +9,9 @@ #define PIN_BAT_CTL 6 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class T114Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class T114Board : public NRF52BoardDCDC { public: - T114Board() : NRF52BoardOTA("T114_OTA") {} + T114Board() : NRF52Board("T114_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h index 7912868a..372dac56 100644 --- a/variants/ikoka_handheld_nrf/IkokaNrf52Board.h +++ b/variants/ikoka_handheld_nrf/IkokaNrf52Board.h @@ -6,9 +6,9 @@ #ifdef IKOKA_NRF52 -class IkokaNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class IkokaNrf52Board : public NRF52BoardDCDC { public: - IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + IkokaNrf52Board() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h index 3062de49..eb05092e 100644 --- a/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h +++ b/variants/ikoka_nano_nrf/IkokaNanoNRFBoard.h @@ -6,9 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaNanoNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class IkokaNanoNRFBoard : public NRF52BoardDCDC { public: - IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + IkokaNanoNRFBoard() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h index 7edd85d1..3c04930f 100644 --- a/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h +++ b/variants/ikoka_stick_nrf/IkokaStickNRFBoard.h @@ -6,9 +6,9 @@ #ifdef XIAO_NRF52 -class IkokaStickNRFBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class IkokaStickNRFBoard : public NRF52BoardDCDC { public: - IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + IkokaStickNRFBoard() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/keepteen_lt1/KeepteenLT1Board.h b/variants/keepteen_lt1/KeepteenLT1Board.h index a9844c90..752b27e7 100644 --- a/variants/keepteen_lt1/KeepteenLT1Board.h +++ b/variants/keepteen_lt1/KeepteenLT1Board.h @@ -4,12 +4,12 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class KeepteenLT1Board : public NRF52BoardOTA { +class KeepteenLT1Board : public NRF52Board { protected: uint8_t btn_prev_state; public: - KeepteenLT1Board() : NRF52BoardOTA("KeepteenLT1_OTA") {} + KeepteenLT1Board() : NRF52Board("KeepteenLT1_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index 66038827..e560cd14 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -13,9 +13,9 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC { public: - TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} + TechoBoard() : NRF52Board("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 6e816b4e..fda393e7 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -13,9 +13,9 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class TechoBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class TechoBoard : public NRF52BoardDCDC { public: - TechoBoard() : NRF52BoardOTA("TECHO_OTA") {} + TechoBoard() : NRF52Board("TECHO_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/mesh_pocket/MeshPocket.h b/variants/mesh_pocket/MeshPocket.h index 8d22106f..478bd56d 100644 --- a/variants/mesh_pocket/MeshPocket.h +++ b/variants/mesh_pocket/MeshPocket.h @@ -9,9 +9,9 @@ #define PIN_BAT_CTL 34 #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range -class HeltecMeshPocket : public NRF52BoardDCDC, public NRF52BoardOTA { +class HeltecMeshPocket : public NRF52BoardDCDC { public: - HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {} + HeltecMeshPocket() : NRF52Board("MESH_POCKET_OTA") {} void begin(); uint16_t getBattMilliVolts() override { diff --git a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h index 131148f8..6858a106 100644 --- a/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h +++ b/variants/minewsemi_me25ls01/MinewsemiME25LS01Board.h @@ -20,12 +20,12 @@ #define PIN_VBAT_READ BATTERY_PIN #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class MinewsemiME25LS01Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class MinewsemiME25LS01Board : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - MinewsemiME25LS01Board() : NRF52BoardOTA("Minewsemi_OTA") {} + MinewsemiME25LS01Board() : NRF52Board("Minewsemi_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/nano_g2_ultra/nano-g2.h b/variants/nano_g2_ultra/nano-g2.h index 4f7eb074..cf771efe 100644 --- a/variants/nano_g2_ultra/nano-g2.h +++ b/variants/nano_g2_ultra/nano-g2.h @@ -35,9 +35,9 @@ #define PIN_VBAT_READ (0 + 2) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class NanoG2Ultra : public NRF52BoardOTA { +class NanoG2Ultra : public NRF52Board { public: - NanoG2Ultra() : NRF52BoardOTA("NANO_G2_OTA") {} + NanoG2Ultra() : NRF52Board("NANO_G2_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/promicro/PromicroBoard.h b/variants/promicro/PromicroBoard.h index 6719fc46..7b6afb1b 100644 --- a/variants/promicro/PromicroBoard.h +++ b/variants/promicro/PromicroBoard.h @@ -20,13 +20,13 @@ #define PIN_VBAT_READ 17 #define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking -class PromicroBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class PromicroBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; float adc_mult = ADC_MULTIPLIER; public: - PromicroBoard() : NRF52BoardOTA("ProMicro_OTA") {} + PromicroBoard() : NRF52Board("ProMicro_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index a181256b..a253e142 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -29,9 +29,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class RAK4631Board : public NRF52BoardDCDC { public: - RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} + RAK4631Board() : NRF52Board("RAK4631_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h index 9aa457d2..cc5aa06f 100644 --- a/variants/rak_wismesh_tag/RAKWismeshTagBoard.h +++ b/variants/rak_wismesh_tag/RAKWismeshTagBoard.h @@ -8,9 +8,9 @@ #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -class RAKWismeshTagBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class RAKWismeshTagBoard : public NRF52BoardDCDC { public: - RAKWismeshTagBoard() : NRF52BoardOTA("WISMESHTAG_OTA") {} + RAKWismeshTagBoard() : NRF52Board("WISMESHTAG_OTA") {} void begin(); #if defined(P_LORA_TX_LED) && defined(LED_STATE_ON) diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index bb67f1e3..67215b8e 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -4,9 +4,9 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class SenseCapSolarBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class SenseCapSolarBoard : public NRF52BoardDCDC { public: - SenseCapSolarBoard() : NRF52BoardOTA("SENSECAP_SOLAR_OTA") {} + SenseCapSolarBoard() : NRF52Board("SENSECAP_SOLAR_OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/t1000-e/T1000eBoard.h b/variants/t1000-e/T1000eBoard.h index bc6e9c03..49223607 100644 --- a/variants/t1000-e/T1000eBoard.h +++ b/variants/t1000-e/T1000eBoard.h @@ -4,12 +4,12 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class T1000eBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class T1000eBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - T1000eBoard() : NRF52BoardOTA("T1000E_OTA") {} + T1000eBoard() : NRF52Board("T1000E_OTA") {} void begin(); uint16_t getBattMilliVolts() override { diff --git a/variants/thinknode_m1/ThinkNodeM1Board.h b/variants/thinknode_m1/ThinkNodeM1Board.h index 500a0265..ebc46e6e 100644 --- a/variants/thinknode_m1/ThinkNodeM1Board.h +++ b/variants/thinknode_m1/ThinkNodeM1Board.h @@ -13,9 +13,9 @@ #define PIN_VBAT_READ (4) #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM1Board : public NRF52BoardOTA { +class ThinkNodeM1Board : public NRF52Board { public: - ThinkNodeM1Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} + ThinkNodeM1Board() : NRF52Board("THINKNODE_M1_OTA") {} void begin(); uint16_t getBattMilliVolts() override; diff --git a/variants/wio-tracker-l1/WioTrackerL1Board.h b/variants/wio-tracker-l1/WioTrackerL1Board.h index bfd87d39..052238e6 100644 --- a/variants/wio-tracker-l1/WioTrackerL1Board.h +++ b/variants/wio-tracker-l1/WioTrackerL1Board.h @@ -4,12 +4,12 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -class WioTrackerL1Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class WioTrackerL1Board : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - WioTrackerL1Board() : NRF52BoardOTA("WioTrackerL1 OTA") {} + WioTrackerL1Board() : NRF52Board("WioTrackerL1 OTA") {} void begin(); #if defined(P_LORA_TX_LED) diff --git a/variants/wio_wm1110/WioWM1110Board.h b/variants/wio_wm1110/WioWM1110Board.h index adcea877..26f95c86 100644 --- a/variants/wio_wm1110/WioWM1110Board.h +++ b/variants/wio_wm1110/WioWM1110Board.h @@ -11,9 +11,9 @@ #endif #define Serial Serial1 -class WioWM1110Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class WioWM1110Board : public NRF52BoardDCDC { public: - WioWM1110Board() : NRF52BoardOTA("WM1110_OTA") {} + WioWM1110Board() : NRF52Board("WM1110_OTA") {} void begin(); #if defined(LED_GREEN) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1c46dfee..bf3115d3 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,9 +6,9 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class XiaoNrf52Board : public NRF52BoardDCDC { public: - XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} + XiaoNrf52Board() : NRF52Board("XIAO_NRF52_OTA") {} void begin(); #if defined(P_LORA_TX_LED) From 5475043083756b4bed07da6e6dec683f5d5f99a8 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 9 Jan 2026 11:07:31 +1100 Subject: [PATCH 379/546] * new ANON_REQ_TYPE_VER_OWNER * CommonCLI: new "get/set owner.info ..." --- examples/simple_repeater/MyMesh.cpp | 27 +++++++++++++++++++++++++-- examples/simple_repeater/MyMesh.h | 3 ++- src/helpers/CommonCLI.cpp | 25 +++++++++++++++++++++++-- src/helpers/CommonCLI.h | 1 + 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 261184c5..eb561939 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -52,6 +52,7 @@ #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define ANON_REQ_TYPE_REGIONS 0x01 +#define ANON_REQ_TYPE_VER_OWNER 0x02 #define CLI_REPLY_DELAY_MILLIS 600 @@ -142,7 +143,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { - if (regions_limiter.allow(rtc_clock.getCurrentTime())) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); @@ -159,6 +160,26 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } +uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + // other params?? + + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + + sprintf((char *) &reply_data[8], "%s,%s", FIRMWARE_VERSION, _prefs.owner_info); + + return 8 + strlen((char *) &reply_data[8]); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -474,6 +495,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { + reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } @@ -694,7 +717,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), discover_limiter(4, 120), // max 4 every 2 minutes - regions_limiter(4, 180) // max 4 every 3 minutes + anon_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) , bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc) #endif diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 49952770..f63aee24 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -95,7 +95,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionMap region_map, temp_map; RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; - RateLimiter discover_limiter, regions_limiter; + RateLimiter discover_limiter, anon_limiter; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS @@ -117,6 +117,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 78e1b5e0..14b67b39 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -72,7 +72,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161 file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 + // 290 // sanitise bad pref values _prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f); @@ -155,7 +156,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161 file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162 file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166 - // 170 + file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170 + // 290 file.close(); } @@ -301,6 +303,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sprintf(reply, "> %d", (uint32_t)_prefs->flood_max); } else if (memcmp(config, "direct.txdelay", 14) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor)); + } else if (memcmp(config, "owner.info", 10) == 0) { + *reply++ = '>'; + *reply++ = ' '; + const char* sp = _prefs->owner_info; + while (*sp) { + *reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|' + sp++; + } + *reply = 0; // set null terminator } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -479,6 +490,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { strcpy(reply, "Error, cannot be negative"); } + } else if (memcmp(config, "owner.info ", 11) == 0) { + config += 11; + char *dp = _prefs->owner_info; + while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) { + *dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars + config++; + } + *dp = 0; + savePrefs(); + strcpy(reply, "OK"); } else if (memcmp(config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi(&config[3]); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 642a4cce..3b1d05f9 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -50,6 +50,7 @@ struct NodePrefs { // persisted to file uint8_t advert_loc_policy; uint32_t discovery_mod_timestamp; float adc_multiplier; + char owner_info[120]; }; class CommonCLICallbacks { From 2a035ad8167f03901ff064d637b7ae714c57bcd2 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 9 Jan 2026 13:20:20 +1100 Subject: [PATCH 380/546] * ANON_REQ_TYPE_VER_OWNER, now includes node_name --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index eb561939..d86e97bc 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -173,7 +173,7 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) - sprintf((char *) &reply_data[8], "%s,%s", FIRMWARE_VERSION, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } From fd69acb4212fef2ad955860de4a9955e309ce4d2 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 9 Jan 2026 13:54:18 +1100 Subject: [PATCH 381/546] * new ANON_REQ_TYPE_VER (for just simple clock + ver info) --- examples/simple_repeater/MyMesh.cpp | 29 ++++++++++++++++++++++------- examples/simple_repeater/MyMesh.h | 1 + 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d86e97bc..38f81ff9 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -53,6 +53,7 @@ #define ANON_REQ_TYPE_REGIONS 0x01 #define ANON_REQ_TYPE_VER_OWNER 0x02 +#define ANON_REQ_TYPE_VER 0x03 #define CLI_REPLY_DELAY_MILLIS 600 @@ -148,12 +149,10 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - // other params?? memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length } @@ -166,13 +165,10 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen reply_path_len = *data++ & 0x3F; memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - // other params?? memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag - uint32_t now = getRTCClock()->getCurrentTime(); - memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, if this is a trusted node) - + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length @@ -180,6 +176,23 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen return 0; } +uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { + if (anon_limiter.allow(rtc_clock.getCurrentTime())) { + // request data has: {reply-path-len}{reply-path} + reply_path_len = *data++ & 0x3F; + memcpy(reply_path, data, reply_path_len); + // data += reply_path_len; + + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag + uint32_t now = getRTCClock()->getCurrentTime(); + memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) + strcpy((char *) &reply_data[8], FIRMWARE_VERSION); + + return 8 + strlen((char *) &reply_data[8]); // reply length + } + return 0; +} + int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) { // uint32_t now = getRTCClock()->getCurrentTimeUnique(); // memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp @@ -497,6 +510,8 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_VER && packet->isRouteDirect()) { + reply_len = handleAnonVerReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f63aee24..96a51da8 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -118,6 +118,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); From 65796c8f2043dd2a3bc4327dfe091304ccbe6945 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 9 Jan 2026 16:28:08 +1100 Subject: [PATCH 382/546] * CommonCLI: added "set name ..." validation * ANON_REQ_TYPE_VER_OWNER, now removes commas from node_name --- examples/simple_repeater/MyMesh.cpp | 13 ++++++++++++- src/helpers/CommonCLI.cpp | 18 +++++++++++++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 38f81ff9..c50d860f 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -159,6 +159,14 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } +static void sanitiseName(char* dest, const char* src) { + while (*src) { + *dest++ = (*src == ',') ? ' ' : *src; + src++; + } + *dest = 0; +} + uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} @@ -166,10 +174,13 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_path, data, reply_path_len); // data += reply_path_len; + char tmp[sizeof(_prefs.node_name)]; + sanitiseName(tmp, _prefs.node_name); + memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, tmp, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 14b67b39..2fc93006 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -14,6 +14,14 @@ static uint32_t _atoi(const char* sp) { return n; } +static bool isValidName(const char *n) { + while (*n) { + if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; + n++; + } + return true; +} + void CommonCLI::loadPrefs(FILESYSTEM* fs) { if (fs->exists("/com_prefs")) { loadPrefsInt(fs, "/com_prefs"); // new filename @@ -421,9 +429,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "Error, invalid key"); } } else if (memcmp(config, "name ", 5) == 0) { - StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); - savePrefs(); - strcpy(reply, "OK"); + if (isValidName(&config[5])) { + StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name)); + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, bad chars"); + } } else if (memcmp(config, "repeat ", 7) == 0) { _prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0; savePrefs(); From 4e4f6d92a0fced540cded8fd7866a4fe4e1b2557 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 9 Jan 2026 16:32:08 +1100 Subject: [PATCH 383/546] * ANON_REQ_TYPE_VER_OWNER now delimited by newline chars --- examples/simple_repeater/MyMesh.cpp | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index c50d860f..d504fec9 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -159,14 +159,6 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } -static void sanitiseName(char* dest, const char* src) { - while (*src) { - *dest++ = (*src == ',') ? ' ' : *src; - src++; - } - *dest = 0; -} - uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} @@ -174,13 +166,10 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_path, data, reply_path_len); // data += reply_path_len; - char tmp[sizeof(_prefs.node_name)]; - sanitiseName(tmp, _prefs.node_name); - memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s,%s,%s", FIRMWARE_VERSION, tmp, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } From b6110eee38b2c7b80b36f54186095430e975bc83 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 12 Jan 2026 16:58:35 +1100 Subject: [PATCH 384/546] * new req/resp (after login): REQ_TYPE_GET_OWNER_INFO (includes firmware-ver) * ANON_REQ_TYPE_OWNER, firmware-ver removed (security exploit) * ANON_REQ_TYPE_BASIC, formware-ver removed, just remote clock + some 'feature' bits * CTL_TYPE_NODE_DISCOVER_REQ now ingored if 'repeat off' has been set --- examples/simple_repeater/MyMesh.cpp | 39 +++++++++++++++++++---------- examples/simple_repeater/MyMesh.h | 4 +-- src/helpers/RegionMap.cpp | 5 ++-- src/helpers/RegionMap.h | 2 +- 4 files changed, 32 insertions(+), 18 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d504fec9..54f3ef79 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -48,12 +48,13 @@ #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 #define REQ_TYPE_GET_NEIGHBOURS 0x06 +#define REQ_TYPE_GET_OWNER_INFO 0x07 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ #define ANON_REQ_TYPE_REGIONS 0x01 -#define ANON_REQ_TYPE_VER_OWNER 0x02 -#define ANON_REQ_TYPE_VER 0x03 +#define ANON_REQ_TYPE_OWNER 0x02 +#define ANON_REQ_TYPE_BASIC 0x03 // just remote clock #define CLI_REPLY_DELAY_MILLIS 600 @@ -159,7 +160,7 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send return 0; } -uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; @@ -169,14 +170,14 @@ uint8_t MyMesh::handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sen memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - sprintf((char *) &reply_data[8], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); + sprintf((char *) &reply_data[8], "%s\n%s", _prefs.node_name, _prefs.owner_info); return 8 + strlen((char *) &reply_data[8]); // reply length } return 0; } -uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { +uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} reply_path_len = *data++ & 0x3F; @@ -186,9 +187,16 @@ uint8_t MyMesh::handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_t memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness) - strcpy((char *) &reply_data[8], FIRMWARE_VERSION); - - return 8 + strlen((char *) &reply_data[8]); // reply length + reply_data[8] = 0; // features +#ifdef WITH_RS232_BRIDGE + reply_data[8] |= 0x01; // is bridge, type UART +#elif WITH_ESPNOW_BRIDGE + reply_data[8] |= 0x03; // is bridge, type ESP-NOW +#endif + if (_prefs.disable_fwd) { // is this repeater currently disabled + reply_data[8] |= 0x80; // is disabled + } + return 9; // reply length } return 0; } @@ -350,6 +358,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return reply_offset; } + } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) { + sprintf((char *) &reply_data[4], "%s\n%s", FIRMWARE_VERSION, _prefs.owner_info); + return 4 + strlen((char *) &reply_data[4]); } return 0; // unknown command } @@ -508,10 +519,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood()); } else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) { reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]); - } else if (data[4] == ANON_REQ_TYPE_VER_OWNER && packet->isRouteDirect()) { - reply_len = handleAnonVerOwnerReq(sender, timestamp, &data[5]); - } else if (data[4] == ANON_REQ_TYPE_VER && packet->isRouteDirect()) { - reply_len = handleAnonVerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_OWNER && packet->isRouteDirect()) { + reply_len = handleAnonOwnerReq(sender, timestamp, &data[5]); + } else if (data[4] == ANON_REQ_TYPE_BASIC && packet->isRouteDirect()) { + reply_len = handleAnonClockReq(sender, timestamp, &data[5]); } else { reply_len = 0; // unknown/invalid request type } @@ -700,7 +711,9 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t void MyMesh::onControlDataRecv(mesh::Packet* packet) { uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits - if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) { + if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 + && !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime()) + ) { int i = 1; uint8_t filter = packet->payload[i++]; uint32_t tag; diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 96a51da8..f930ee7e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -117,8 +117,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); - uint8_t handleAnonVerOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); - uint8_t handleAnonVerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); + uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len); mesh::Packet* createSelfAdvert(); diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index e227532a..fbc5f017 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -9,8 +9,9 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { strcpy(wildcard.name, "*"); } -bool RegionMap::is_name_char(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#'; +bool RegionMap::is_name_char(uint8_t c) { + // accept all alpha-num or accented characters, but exclude most punctuation chars + return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; } static File openWrite(FILESYSTEM* _fs, const char* filename) { diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index dcd9a774..01174d09 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -30,7 +30,7 @@ class RegionMap { public: RegionMap(TransportKeyStore& store); - static bool is_name_char(char c); + static bool is_name_char(uint8_t c); bool load(FILESYSTEM* _fs, const char* path=NULL); bool save(FILESYSTEM* _fs, const char* path=NULL); From 69a71d0e25b956e70d33ada889ff0580d6dbb060 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 12 Jan 2026 17:47:51 +1100 Subject: [PATCH 385/546] * repeater login response, FIRMWARE_VER_LEVEL now bumped to 2 --- docs/payloads.md | 53 ++++++++++++++++++++++++++++- examples/simple_repeater/MyMesh.cpp | 7 ++-- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/docs/payloads.md b/docs/payloads.md index 5a41e69c..4742bfbb 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -103,7 +103,9 @@ Request type | `0x02` | keepalive | (deprecated) | | `0x03` | get telemetry data | TODO | | `0x04` | get min,max,avg data | sensor nodes - get min, max, average for given time span | -| `0x05` | get access list | get node's approved access list | +| `0x05` | get access list | get node's approved access list | +| `0x06` | get neighbors | get repeater node's neighbors | +| `0x07` | get owner info | get repeater firmware-ver/name/owner info | ### Get stats @@ -132,6 +134,27 @@ Gets information about the node, possibly including the following: Request data about sensors on the node, including battery level. +### Get Telemetry + +TODO + +### Get Min/Max/Ave (Sensor nodes) + +TODO + +### Get Access List + +TODO + +### Get Neighors + +TODO + +### Get Owner Info + +TODO + + ## Response | Field | Size (bytes) | Description | @@ -179,6 +202,34 @@ txt_type | timestamp | 4 | sender time (unix timestamp) | | password | rest of message | password for repeater/sensor | +## Repeater - Regions request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x01 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + +## Repeater - Owner info request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x02 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + +## Repeater - Clock and status request + +| Field | Size (bytes) | Description | +|----------------|-----------------|-------------------------------------------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| req type | 1 | 0x03 (request sub type) | +| reply path len | 1 | path len for reply | +| reply path | (variable) | reply path | + + # Group text message / datagram | Field | Size (bytes) | Description | diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 54f3ef79..d926148d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -41,14 +41,14 @@ #define TXT_ACK_DELAY 200 #endif -#define FIRMWARE_VER_LEVEL 1 +#define FIRMWARE_VER_LEVEL 2 #define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS #define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_ACCESS_LIST 0x05 #define REQ_TYPE_GET_NEIGHBOURS 0x06 -#define REQ_TYPE_GET_OWNER_INFO 0x07 +#define REQ_TYPE_GET_OWNER_INFO 0x07 // FIRMWARE_VER_LEVEL >= 2 #define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ @@ -196,6 +196,7 @@ uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender if (_prefs.disable_fwd) { // is this repeater currently disabled reply_data[8] |= 0x80; // is disabled } + // TODO: add some kind of moving-window utilisation metric, so can query 'how busy' is this repeater return 9; // reply length } return 0; @@ -359,7 +360,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return reply_offset; } } else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) { - sprintf((char *) &reply_data[4], "%s\n%s", FIRMWARE_VERSION, _prefs.owner_info); + sprintf((char *) &reply_data[4], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info); return 4 + strlen((char *) &reply_data[4]); } return 0; // unknown command From 266e4893fda2b42e8e7e5f08239a827a13ef26dd Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Mon, 12 Jan 2026 19:19:23 +0100 Subject: [PATCH 386/546] remove serial debug logging from t3s3 sx1276 companion usb --- variants/lilygo_t3s3_sx1276/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index f544be11..5a7ece2c 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -138,8 +138,6 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> From 324eab93948868dd4d5c86bb23117d243e58f3e3 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Mon, 12 Jan 2026 19:29:32 +0100 Subject: [PATCH 387/546] cleanup ikoka variants and add all supported sensors --- variants/ikoka_handheld_nrf/platformio.ini | 48 +++++----- variants/ikoka_nano_nrf/platformio.ini | 104 +++++++-------------- variants/ikoka_stick_nrf/platformio.ini | 84 ++++++----------- 3 files changed, 90 insertions(+), 146 deletions(-) diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini index e4643c38..d2bbeffe 100644 --- a/variants/ikoka_handheld_nrf/platformio.ini +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -1,8 +1,5 @@ -[ikoka_nrf52] -extends = Xiao_nrf52 -lib_deps = ${nrf52_base.lib_deps} - ${sensor_base.lib_deps} - densaugeo/base64 @ ~1.4.0 +[ikoka_handheld_nrf] +extends = nrf52_base build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_7.3.0_API/include @@ -26,12 +23,15 @@ build_flags = ${nrf52_base.build_flags} build_src_filter = ${nrf52_base.build_src_filter} +<../variants/ikoka_handheld_nrf> +<helpers/sensors> +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + densaugeo/base64 @ ~1.4.0 # larger screen has a different driver, this is for the 0.96 inch -[ikoka_nrf52_ssd1306_companion] -lib_deps = ${ikoka_nrf52.lib_deps} +[ikoka_handheld_nrf_ssd1306_companion] +lib_deps = ${ikoka_handheld_nrf.lib_deps} adafruit/Adafruit SSD1306 @ ^2.5.13 -build_flags = ${ikoka_nrf52.build_flags} +build_flags = ${ikoka_handheld_nrf.build_flags} -D DISPLAY_CLASS=SSD1306Display -D DISPLAY_ROTATION=0 -D PIN_WIRE_SCL=D6 @@ -42,62 +42,62 @@ build_flags = ${ikoka_nrf52.build_flags} -D OFFLINE_QUEUE_SIZE=256 -D QSPIFLASH=1 -I examples/companion_radio/ui-new -build_src_filter = ${ikoka_nrf52.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/companion_radio/ui-new/UITask.cpp> +<../examples/companion_radio/*.cpp> [env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D BLE_PIN_CODE=123456 -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D BLE_PIN_CODE=123456 -D LORA_TX_POWER=20 - -D DISPLAY_ROTATION=2 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} + -D DISPLAY_ROTATION=2 +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> [env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb] extends = ikoka_nrf52 -build_flags = ${ikoka_nrf52_ssd1306_companion.build_flags} +build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags} -D LORA_TX_POWER=20 -D DISPLAY_ROTATION=2 -build_src_filter = ${ikoka_nrf52_ssd1306_companion.build_src_filter} +build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_repeater] extends = ikoka_nrf52 build_flags = - ${ikoka_nrf52.build_flags} + ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' -D MAX_NEIGHBOURS=50 - -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52.build_src_filter} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/simple_repeater/*.cpp> [env:ikoka_handheld_nrf_e22_30dbm_room_server] extends = ikoka_nrf52 build_flags = - ${ikoka_nrf52.build_flags} + ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' - -D LORA_TX_POWER=20 -build_src_filter = ${ikoka_nrf52.build_src_filter} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> diff --git a/variants/ikoka_nano_nrf/platformio.ini b/variants/ikoka_nano_nrf/platformio.ini index abfbcf67..08b1101b 100644 --- a/variants/ikoka_nano_nrf/platformio.ini +++ b/variants/ikoka_nano_nrf/platformio.ini @@ -1,151 +1,124 @@ -[nrf52840_xiao] +[ikoka_nano_nrf] extends = nrf52_base -platform_packages = - toolchain-gccarmnoneeabi@~1.100301.0 - framework-arduinoadafruitnrf52 board = seeed-xiao-afruitnrf52-nrf52840 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -D NRF52_PLATFORM -D XIAO_NRF52 -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -lib_ignore = - BluetoothOTA - lvgl - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit SSD1306 @ ^2.5.13 - -[ikoka_nano_nrf_baseboard] -extends = nrf52840_xiao -;board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_xiao.build_flags} - -D P_LORA_TX_LED=11 -I variants/ikoka_nano_nrf -I src/helpers/nrf52 + -D P_LORA_TX_LED=11 -D DISPLAY_CLASS=NullDisplayDriver -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D P_LORA_DIO_1=D1 -; -D P_LORA_BUSY=D3 - -D P_LORA_BUSY=D2 ; specific to ikoka nano variant. -; -D P_LORA_RESET=D2 - -D P_LORA_RESET=D3 ; specific to ikoka nano variant. -; -D P_LORA_NSS=D4 - -D P_LORA_NSS=D0 ; specific to ikoka nano variant. -; -D SX126X_RXEN=D5 + -D P_LORA_BUSY=D2 + -D P_LORA_RESET=D3 + -D P_LORA_NSS=D0 -D SX126X_RXEN=D7 -D SX126X_TXEN=RADIOLIB_NC -D SX126X_DIO2_AS_RF_SWITCH=1 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D PIN_WIRE_SCL=5 ; specific to ikoka nano variant. - -D PIN_WIRE_SDA=4 ; specific to ikoka nano variant. - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 + -D PIN_WIRE_SCL=5 + -D PIN_WIRE_SDA=4 + -UENV_INCLUDE_GPS debug_tool = jlink upload_protocol = nrfutil - - -;;; abstracted hardware variants +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} [ikoka_nano_nrf_e22_22dbm] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf ; No PA in this model, full 22dBm build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"' -D LORA_TX_POWER=22 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<helpers/ui/NullDisplayDriver.cpp> +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_e22_30dbm] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf ; limit txpower to 20dBm on E22-900M30S. Anything higher will ; cause distortion in the PA output. 20dBm in -> 30dBm out build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"' -D LORA_TX_POWER=20 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<helpers/ui/NullDisplayDriver.cpp> +<../variants/ikoka_nano_nrf> [ikoka_nano_nrf_e22_33dbm] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf ; limit txpower to 9dBm on E22-900M33S to avoid hardware damage ; to the rf amplifier frontend. 9dBm in -> 33dBm out build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"' -D LORA_TX_POWER=9 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<helpers/ui/NullDisplayDriver.cpp> +<../variants/ikoka_nano_nrf> -;;; abstracted firmware roles - [ikoka_nano_nrf_companion_radio_ble] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_nano_nrf_baseboard.lib_deps} + ${ikoka_nano_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_nano_nrf_companion_radio_usb] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_nano_nrf_baseboard.lib_deps} + ${ikoka_nano_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_nano_nrf_repeater] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D ADVERT_NAME='"Ikoka Nano Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -153,26 +126,23 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<../examples/simple_repeater/*.cpp> [ikoka_nano_nrf_room_server] -extends = ikoka_nano_nrf_baseboard +extends = ikoka_nano_nrf build_flags = - ${ikoka_nano_nrf_baseboard.build_flags} + ${ikoka_nano_nrf.build_flags} -D ADVERT_NAME='"Ikoka Nano Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_nano_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> -;;; hardware + firmware variants - ;;; 22dBm EBYTE E22-900M22 variants - [env:ikoka_nano_nrf_22dbm_companion_radio_usb] extends = ikoka_nano_nrf_e22_22dbm @@ -219,7 +189,6 @@ build_src_filter = ;;; 30dBm EBYTE E22-900M30 variants - [env:ikoka_nano_nrf_30dbm_companion_radio_usb] extends = ikoka_nano_nrf_e22_30dbm @@ -266,7 +235,6 @@ build_src_filter = ;;; 33dBm EBYTE E22-900M33 variants - [env:ikoka_nano_nrf_33dbm_companion_radio_usb] extends = ikoka_nano_nrf_e22_33dbm diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 9ced2bbb..4f664054 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -1,34 +1,15 @@ -[nrf52840_xiao] +[ikoka_stick_nrf] extends = nrf52_base -platform_packages = - toolchain-gccarmnoneeabi@~1.100301.0 - framework-arduinoadafruitnrf52 board = seeed-xiao-afruitnrf52-nrf52840 board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -D NRF52_PLATFORM -D XIAO_NRF52 -I lib/nrf52/s140_nrf52_7.3.0_API/include -I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52 -lib_ignore = - BluetoothOTA - lvgl - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - adafruit/Adafruit INA3221 Library @ ^1.0.1 - adafruit/Adafruit INA219 @ ^1.2.3 - adafruit/Adafruit AHTX0 @ ^2.0.5 - adafruit/Adafruit BME280 Library @ ^2.3.0 - adafruit/Adafruit SSD1306 @ ^2.5.13 - -[ikoka_stick_nrf_baseboard] -extends = nrf52840_xiao -;board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_xiao.build_flags} - -D P_LORA_TX_LED=11 -I variants/ikoka_stick_nrf -I src/helpers/nrf52 + -D P_LORA_TX_LED=11 -D DISPLAY_CLASS=SSD1306Display -D DISPLAY_ROTATION=2 -D RADIO_CLASS=CustomSX1262 @@ -46,24 +27,17 @@ build_flags = ${nrf52840_xiao.build_flags} -D PIN_USER_BTN=0 -D PIN_WIRE_SCL=7 -D PIN_WIRE_SDA=6 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 -debug_tool = jlink -upload_protocol = nrfutil - - -;;; abstracted hardware variants +lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} [ikoka_stick_nrf_e22_22dbm] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf ; No PA in this model, full 22dBm build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Stick-E22-22dBm (Xiao_nrf52)"' -D LORA_TX_POWER=22 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<helpers/ui/MomentaryButton.cpp> @@ -71,14 +45,14 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} +<../variants/ikoka_stick_nrf> [ikoka_stick_nrf_e22_30dbm] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf ; limit txpower to 20dBm on E22-900M30S. Anything higher will ; cause distortion in the PA output. 20dBm in -> 30dBm out build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Stick-E22-30dBm (Xiao_nrf52)"' -D LORA_TX_POWER=20 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<helpers/ui/MomentaryButton.cpp> @@ -86,14 +60,14 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} +<../variants/ikoka_stick_nrf> [ikoka_stick_nrf_e22_33dbm] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf ; limit txpower to 9dBm on E22-900M33S to avoid hardware damage ; to the rf amplifier frontend. 9dBm in -> 33dBm out build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MANUFACTURER_STRING='"Ikoka Stick-E22-33dBm (Xiao_nrf52)"' -D LORA_TX_POWER=9 -build_src_filter = ${nrf52840_xiao.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> +<helpers/ui/MomentaryButton.cpp> @@ -103,50 +77,52 @@ build_src_filter = ${nrf52840_xiao.build_src_filter} ;;; abstracted firmware roles [ikoka_stick_nrf_companion_radio_ble] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_stick_nrf_baseboard.lib_deps} + ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_stick_nrf_companion_radio_usb] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld board_upload.maximum_size = 708608 build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -I examples/companion_radio/ui-new + -D QSPIFLASH=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${ikoka_stick_nrf_baseboard.lib_deps} + ${ikoka_stick_nrf.lib_deps} densaugeo/base64 @ ~1.4.0 [ikoka_stick_nrf_repeater] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D ADVERT_NAME='"Ikoka Stick Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -154,21 +130,21 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater/*.cpp> [ikoka_stick_nrf_room_server] -extends = ikoka_stick_nrf_baseboard +extends = ikoka_stick_nrf build_flags = - ${ikoka_stick_nrf_baseboard.build_flags} + ${ikoka_stick_nrf.build_flags} -D ADVERT_NAME='"Ikoka Stick Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter} +build_src_filter = ${ikoka_stick_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> ;;; hardware + firmware variants From a48b18518932791494d9838d277f0649491c4c1d Mon Sep 17 00:00:00 2001 From: liquidraver <504870+liquidraver@users.noreply.github.com> Date: Sun, 11 Jan 2026 22:03:48 +0100 Subject: [PATCH 388/546] DISABLE_DEBUG=1 env variable to build.sh --- build.sh | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/build.sh b/build.sh index f2127941..b7f95dd7 100755 --- a/build.sh +++ b/build.sh @@ -29,6 +29,20 @@ $ sh build.sh build-repeater-firmwares Build all chat room server firmwares $ sh build.sh build-room-server-firmwares + +Environment Variables: + DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.) + If not set, debug flags from variant platformio.ini files are used. + +Examples: +Build without debug logging: +$ export FIRMWARE_VERSION=v1.0.0 +$ export DISABLE_DEBUG=1 +$ sh build.sh build-firmware RAK_4631_repeater + +Build with debug logging (default, uses flags from variant files): +$ export FIRMWARE_VERSION=v1.0.0 +$ sh build.sh build-firmware RAK_4631_repeater EOF } @@ -68,6 +82,13 @@ get_pio_envs_ending_with_string() { done } +# disable all debug logging flags if DISABLE_DEBUG=1 is set +disable_debug_flags() { + if [ "$DISABLE_DEBUG" == "1" ]; then + export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -UCFG_DEBUG -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL" + fi +} + # build firmware for the provided pio env in $1 build_firmware() { @@ -94,6 +115,9 @@ build_firmware() { # add firmware version info to end of existing platformio build flags in environment vars export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'" + # disable debug flags if requested + disable_debug_flags + # build firmware target pio run -e $1 From 06c4ca19ab064e359b6708c565be8c3e9bbc532a Mon Sep 17 00:00:00 2001 From: chrisdavis2110 <chrisdavis2110@gmail.com> Date: Tue, 13 Jan 2026 10:06:50 -0800 Subject: [PATCH 389/546] added variant rak3401 --- variants/rak3401/RAK3401Board.cpp | 37 ++++++ variants/rak3401/RAK3401Board.h | 81 ++++++++++++ variants/rak3401/platformio.ini | 135 +++++++++++++++++++ variants/rak3401/target.cpp | 66 ++++++++++ variants/rak3401/target.h | 30 +++++ variants/rak3401/variant.cpp | 43 +++++++ variants/rak3401/variant.h | 207 ++++++++++++++++++++++++++++++ 7 files changed, 599 insertions(+) create mode 100644 variants/rak3401/RAK3401Board.cpp create mode 100644 variants/rak3401/RAK3401Board.h create mode 100644 variants/rak3401/platformio.ini create mode 100644 variants/rak3401/target.cpp create mode 100644 variants/rak3401/target.h create mode 100644 variants/rak3401/variant.cpp create mode 100644 variants/rak3401/variant.h diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp new file mode 100644 index 00000000..50499dff --- /dev/null +++ b/variants/rak3401/RAK3401Board.cpp @@ -0,0 +1,37 @@ +#include <Arduino.h> +#include <Wire.h> + +#include "RAK3401Board.h" + +void RAK3401Board::begin() { + NRF52BoardDCDC::begin(); + pinMode(PIN_VBAT_READ, INPUT); +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#endif + +#ifdef PIN_USER_BTN_ANA + pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP); +#endif + +#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL) + Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL); +#endif + + Wire.begin(); + + // Enable 3.3V periphery power rail (GPS, IO Module, etc.) + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up + +#ifdef P_LORA_PA_EN + // Initialize RAK13302 1W LoRa transceiver module PA control pin + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled + delay(10); // Allow PA module to initialize +#endif +} diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h new file mode 100644 index 00000000..5188f274 --- /dev/null +++ b/variants/rak3401/RAK3401Board.h @@ -0,0 +1,81 @@ +#pragma once + +#include <MeshCore.h> +#include <Arduino.h> +#include <helpers/NRF52Board.h> + +// LoRa radio module pins for RAK3401 with RAK13302 (uses SPI1) +#define P_LORA_DIO_1 10 +#define P_LORA_NSS 26 +#define P_LORA_RESET 4 +#define P_LORA_BUSY 9 +#define P_LORA_SCLK 3 // SPI1_SCK +#define P_LORA_MISO 29 // SPI1_MISO +#define P_LORA_MOSI 30 // SPI1_MOSI +#define SX126X_POWER_EN 21 + +//#define PIN_GPS_SDA 13 //GPS SDA pin (output option) +//#define PIN_GPS_SCL 14 //GPS SCL pin (output option) +//#define PIN_GPS_TX 16 //GPS TX pin +//#define PIN_GPS_RX 15 //GPS RX pin +#define PIN_GPS_1PPS 17 //GPS PPS pin +#define GPS_BAUD_RATE 9600 +#define GPS_ADDRESS 0x42 //i2c address for GPS + +// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) +// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA +// This pin must be controlled during transmission to enable the 1W power amplifier +// +// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot +// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) +// +// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet +// Override by defining P_LORA_PA_EN in platformio.ini if needed +#ifndef P_LORA_PA_EN + #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) +#endif + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// built-ins +#define PIN_VBAT_READ 5 +#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) + +// 3.3V periphery enable (GPS, IO Module, etc.) +#define PIN_3V3_EN (34) +#define WB_IO2 PIN_3V3_EN + +class RAK3401Board : public NRF52BoardDCDC, public NRF52BoardOTA { +public: + RAK3401Board() : NRF52BoardOTA("RAK3401_OTA") {} + void begin(); + + #define BATTERY_SAMPLES 8 + + uint16_t getBattMilliVolts() override { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / BATTERY_SAMPLES; + + return (ADC_MULTIPLIER * raw) / 4096; + } + + const char* getManufacturerName() const override { + return "RAK 3401"; + } + +#ifdef P_LORA_PA_EN + void onBeforeTransmit() override { + digitalWrite(P_LORA_PA_EN, HIGH); // Enable PA before transmission + } + + void onAfterTransmit() override { + digitalWrite(P_LORA_PA_EN, LOW); // Disable PA after transmission to save power + } +#endif +}; diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini new file mode 100644 index 00000000..f1c4bd2d --- /dev/null +++ b/variants/rak3401/platformio.ini @@ -0,0 +1,135 @@ +[rak3401] +extends = nrf52_base +board = rak4631 +board_check = true +build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} + -I variants/rak3401 + -D RAK_4631 + -D RAK_3401 + -D RAK13302 + -D RAK_BOARD + -D PIN_BOARD_SCL=14 + -D PIN_BOARD_SDA=13 + -D PIN_GPS_TX=PIN_SERIAL1_RX + -D PIN_GPS_RX=PIN_SERIAL1_TX + -D PIN_GPS_EN=-1 + -D PIN_OLED_RESET=-1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/rak3401> + +<helpers/sensors> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> +lib_deps = + ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27 + +[env:RAK_3401_repeater] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3401 1W Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> + +[env:RAK_3401_room_server] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Test Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_room_server> + +[env:RAK_3401_companion_radio_usb] +extends = rak3401 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${rak3401.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3401.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK_3401_companion_radio_ble] +extends = rak3401 +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${rak3401.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3401.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK_3401_terminal_chat] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D MAX_CONTACTS=100 + -D MAX_GROUP_CHANNELS=1 + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${rak3401.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK_3401_sensor] +extends = rak3401 +build_flags = + ${rak3401.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3401 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + ;-D MESH_PACKET_LOGGING=1 + ;-D MESH_DEBUG=1 +build_src_filter = ${rak3401.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_sensor> \ No newline at end of file diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp new file mode 100644 index 00000000..ba827ad1 --- /dev/null +++ b/variants/rak3401/target.cpp @@ -0,0 +1,66 @@ +#include <Arduino.h> +#include "target.h" +#include <helpers/ArduinoHelpers.h> + +RAK3401Board board; + +#ifndef PIN_USER_BTN + #define PIN_USER_BTN (-1) +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true); + + #if defined(PIN_USER_BTN_ANA) + MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, 20); + #endif +#endif + +// RAK3401 uses SPI1 for the RAK13302 LoRa module +// Note: nRF52 doesn't have a separate SPI1 object, so we use SPI but configure it with SPI1 pins +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + + // Configure SPI with SPI1 pins for RAK13302 + // nRF52 uses the same SPI peripheral but with different pin assignments + SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); + SPI.begin(); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak3401/target.h b/variants/rak3401/target.h new file mode 100644 index 00000000..32f17cd1 --- /dev/null +++ b/variants/rak3401/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <RAK3401Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/sensors/EnvironmentSensorManager.h> + +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + extern DISPLAY_CLASS display; + #include <helpers/ui/MomentaryButton.h> + extern MomentaryButton user_btn; + #if defined(PIN_USER_BTN_ANA) + extern MomentaryButton analog_btn; + #endif +#endif + +extern RAK3401Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3401/variant.cpp b/variants/rak3401/variant.cpp new file mode 100644 index 00000000..db55920c --- /dev/null +++ b/variants/rak3401/variant.cpp @@ -0,0 +1,43 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; + +void initVariant() +{ + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); + + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); + + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); +} diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h new file mode 100644 index 00000000..03e9c2a8 --- /dev/null +++ b/variants/rak3401/variant.h @@ -0,0 +1,207 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef _VARIANT_RAK3401_ +#define _VARIANT_RAK3401_ + +#define RAK4630 + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF +// define USE_LFRC // Board uses RC for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +// Number of pins defined in PinDescription array +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) +#define PIN_LED2 (36) + +#define LED_BUILTIN PIN_LED1 +#define LED_CONN PIN_LED2 + +#define LED_GREEN PIN_LED1 +#define LED_BLUE PIN_LED2 + +#define LED_STATE_ON 1 // State when LED is litted + +/* + * Analog pins + */ +#define PIN_A0 (5) +#define PIN_A1 (31) +#define PIN_A2 (28) +#define PIN_A3 (29) +#define PIN_A4 (30) +#define PIN_A5 (31) +#define PIN_A6 (0xff) +#define PIN_A7 (0xff) + +static const uint8_t A0 = PIN_A0; +static const uint8_t A1 = PIN_A1; +static const uint8_t A2 = PIN_A2; +static const uint8_t A3 = PIN_A3; +static const uint8_t A4 = PIN_A4; +static const uint8_t A5 = PIN_A5; +static const uint8_t A6 = PIN_A6; +static const uint8_t A7 = PIN_A7; +#define ADC_RESOLUTION 14 + +// Other pins +#define WB_I2C1_SDA (13) // SENSOR_SLOT IO_SLOT +#define WB_I2C1_SCL (14) // SENSOR_SLOT IO_SLOT + +#define PIN_AREF (2) +#define PIN_NFC1 (9) +#define WB_IO5 PIN_NFC1 +#define WB_IO4 (4) +#define PIN_NFC2 (10) + +static const uint8_t AREF = PIN_AREF; + +/* + * Serial interfaces + */ +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) + +// Connected to Jlink CDC +#define PIN_SERIAL2_RX (8) +#define PIN_SERIAL2_TX (6) + +/* + * SPI Interfaces + */ +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +// SPI1 pins for RAK13302 1W LoRa module +#define PIN_SPI1_MISO (29) // (0 + 29) +#define PIN_SPI1_MOSI (30) // (0 + 30) +#define PIN_SPI1_SCK (3) // (0 + 3) + +static const uint8_t SS = 42; +static const uint8_t MOSI = PIN_SPI_MOSI; +static const uint8_t MISO = PIN_SPI_MISO; +static const uint8_t SCK = PIN_SPI_SCK; + +/* + * Wire Interfaces + */ +#define WIRE_INTERFACES_COUNT 1 + +#define PIN_WIRE_SDA (WB_I2C1_SDA) +#define PIN_WIRE_SCL (WB_I2C1_SCL) + +// QSPI Pins +#define PIN_QSPI_SCK 3 +#define PIN_QSPI_CS 26 +#define PIN_QSPI_IO0 30 +#define PIN_QSPI_IO1 29 +#define PIN_QSPI_IO2 28 +#define PIN_QSPI_IO3 2 + +// On-board QSPI Flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +// RAK13302 1W LoRa transceiver module (uses SPI1) +// LoRa radio module pins for RAK3401 with RAK13302 +#define P_LORA_DIO_1 10 +#define P_LORA_NSS 26 +#define P_LORA_RESET 4 +#define P_LORA_BUSY 9 +#define P_LORA_SCLK PIN_SPI1_SCK // 3 +#define P_LORA_MISO PIN_SPI1_MISO // 29 +#define P_LORA_MOSI PIN_SPI1_MOSI // 30 +#define SX126X_POWER_EN 21 + +// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) +// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA +// This pin must be controlled during transmission to enable the 1W power amplifier +// +// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot +// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) +// +// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet +// Override by defining P_LORA_PA_EN in platformio.ini if needed +#ifndef P_LORA_PA_EN + #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) +#endif + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// enables 3.3V periphery like GPS or IO Module +// Do not toggle this for GPS power savings +#define PIN_3V3_EN (34) +#define WB_IO2 PIN_3V3_EN + +// RAK1910 GPS module +// If using the wisblock GPS module and pluged into Port A on WisBlock base +// IO1 is hooked to PPS (pin 12 on header) = gpio 17 +// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on). +// Therefore must be 1 to keep peripherals powered +// Power is on the controllable 3V3_S rail +#define PIN_GPS_PPS (17) // Pulse per second input from the GPS + +#define GPS_RX_PIN PIN_SERIAL1_RX +#define GPS_TX_PIN PIN_SERIAL1_TX + +// Battery +// The battery sense is hooked to pin A0 (5) +#define BATTERY_PIN PIN_A0 +// and has 12 bit resolution +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#undef AREF_VOLTAGE +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 + +#define HAS_RTC 1 + +#define RAK_4631 1 + +#ifdef __cplusplus +} +#endif + +/*---------------------------------------------------------------------------- + * Arduino objects - C++ only + *----------------------------------------------------------------------------*/ + +#endif From 4575800e4069004cf8fb024517b2b33387bd2e5a Mon Sep 17 00:00:00 2001 From: Socalix <48040807+Socalix@users.noreply.github.com> Date: Wed, 14 Jan 2026 17:52:15 -0600 Subject: [PATCH 390/546] Turn on register 0x8B5 LSB for improved RX, turn off boosted gain --- src/helpers/radiolib/CustomSX1262.h | 8 ++++++++ variants/heltec_v4/platformio.ini | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index bfaea7c7..be6812c6 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -76,6 +76,14 @@ class CustomSX1262 : public SX1262 { setRfSwitchPins(SX126X_RXEN, SX126X_TXEN); #endif + // for improved RX with Heltec v4 + #ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + writeRegister(0x8B5, &r_data, 1); + #endif + return true; // success } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ecfd7889..9ab3e162 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -17,9 +17,9 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=2 - -D P_LORA_PA_TX_EN=46 ;enable tx + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - Power on GC1109 + -D P_LORA_PA_EN=2 ; PA CSD - Enable GC1109 + -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 @@ -27,10 +27,11 @@ build_flags = -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output - -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX + -D SX126X_DIO2_AS_RF_SWITCH=true ; GC1109 CTX is controlled by SX1262 DIO2 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 - -D SX126X_RX_BOOSTED_GAIN=1 +; -D SX126X_RX_BOOSTED_GAIN=1 ; Turned off for improved RX -D PIN_GPS_RX=38 -D PIN_GPS_TX=39 -D PIN_GPS_RESET=42 From 31f98bdd43c2a404ceea304a2d8dbfe2618cf498 Mon Sep 17 00:00:00 2001 From: Dustin Brewer <mannkind@thenullpointer.net> Date: Wed, 14 Jan 2026 17:53:42 -0800 Subject: [PATCH 391/546] Fix Ikoka Stick builds --- variants/ikoka_stick_nrf/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index 4f664054..2e43b700 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -27,6 +27,7 @@ build_flags = ${nrf52_base.build_flags} -D PIN_USER_BTN=0 -D PIN_WIRE_SCL=7 -D PIN_WIRE_SDA=6 + -UENV_INCLUDE_GPS lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} From 403ce1db08249b51dc637bb1545d45894f1a03c7 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 13 Jan 2026 00:38:20 +1100 Subject: [PATCH 392/546] contacts: granular autoadd and overwrite-oldest --- examples/companion_radio/DataStore.cpp | 2 + examples/companion_radio/MyMesh.cpp | 70 +++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 4 ++ examples/companion_radio/NodePrefs.h | 1 + src/helpers/BaseChatMesh.cpp | 69 +++++++++++++++++++------ src/helpers/BaseChatMesh.h | 5 ++ 6 files changed, 134 insertions(+), 17 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 4faac975..f61f53ae 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -227,6 +227,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 + file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 file.close(); } @@ -261,6 +262,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 + file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 59a0078f..803e3b03 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -54,6 +54,8 @@ #define CMD_SEND_CONTROL_DATA 55 // v8+ #define CMD_GET_STATS 56 // v8+, second byte is stats type #define CMD_SEND_ANON_REQ 57 +#define CMD_SET_AUTOADD_CONFIG 58 +#define CMD_GET_AUTOADD_CONFIG 59 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -85,6 +87,7 @@ #define RESP_CODE_ADVERT_PATH 22 #define RESP_CODE_TUNING_PARAMS 23 #define RESP_CODE_STATS 24 // v8+, second byte is stats type +#define RESP_CODE_AUTOADD_CONFIG 25 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -110,6 +113,8 @@ #define PUSH_CODE_BINARY_RESPONSE 0x8C #define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D #define PUSH_CODE_CONTROL_DATA 0x8E // v8+ +#define PUSH_CODE_CONTACT_DELETED 0x8F // used to notify client app of deleted contact when overwriting oldest +#define PUSH_CODE_CONTACTS_FULL 0x90 // used to notify client app that contacts storage is full #define ERR_CODE_UNSUPPORTED_CMD 1 #define ERR_CODE_NOT_FOUND 2 @@ -120,6 +125,15 @@ #define MAX_SIGN_DATA_LEN (8 * 1024) // 8K +// Auto-add config bitmask +// Bit 0: If set, overwrite oldest non-favourite contact when contacts file is full +// Bits 1-4: these indicate which contact types to auto-add when manual_contact_mode = 0x01 +#define AUTO_ADD_OVERWRITE_OLDEST (1 << 0) // 0x01 - overwrite oldest non-favourite when full +#define AUTO_ADD_CHAT (1 << 1) // 0x02 - auto-add Chat (Companion) (ADV_TYPE_CHAT) +#define AUTO_ADD_REPEATER (1 << 2) // 0x04 - auto-add Repeater (ADV_TYPE_REPEATER) +#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) + void MyMesh::writeOKFrame() { uint8_t buf[1]; buf[0] = RESP_CODE_OK; @@ -262,9 +276,54 @@ bool MyMesh::isAutoAddEnabled() const { return (_prefs.manual_add_contacts & 1) == 0; } +bool MyMesh::shouldAutoAddContactType(uint8_t contact_type) const { + if ((_prefs.manual_add_contacts & 1) == 0) { + return true; + } + + uint8_t type_bit = 0; + switch (contact_type) { + case ADV_TYPE_CHAT: + type_bit = AUTO_ADD_CHAT; + break; + case ADV_TYPE_REPEATER: + type_bit = AUTO_ADD_REPEATER; + break; + case ADV_TYPE_ROOM: + type_bit = AUTO_ADD_ROOM_SERVER; + break; + case ADV_TYPE_SENSOR: + type_bit = AUTO_ADD_SENSOR; + break; + default: + return false; // Unknown type, don't auto-add + } + + return (_prefs.autoadd_config & type_bit) != 0; +} + +bool MyMesh::shouldOverwriteWhenFull() const { + return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0; +} + +void MyMesh::onContactOverwrite(const uint8_t* pub_key) { + if (_serial->isConnected()) { + out_frame[0] = PUSH_CODE_CONTACT_DELETED; + memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE); + _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); + } +} + +void MyMesh::onContactsFull() { + if (_serial->isConnected()) { + out_frame[0] = PUSH_CODE_CONTACTS_FULL; + _serial->writeFrame(out_frame, 1); + } +} + void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) { if (_serial->isConnected()) { - if (!isAutoAddEnabled() && is_new) { + if (!shouldAutoAddContactType(contact.type) && is_new) { writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact); } else { out_frame[0] = PUSH_CODE_ADVERT; @@ -1663,6 +1722,15 @@ void MyMesh::handleCmdFrame(size_t len) { } else { writeErrFrame(ERR_CODE_TABLE_FULL); } + } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { + _prefs.autoadd_config = cmd_frame[1]; + savePrefs(); + writeOKFrame(); + } else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) { + int i = 0; + out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; + out_frame[i++] = _prefs.autoadd_config; + _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 1fcc5697..a2b0033f 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -114,6 +114,10 @@ protected: void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override; bool isAutoAddEnabled() const override; + bool shouldAutoAddContactType(uint8_t type) const override; + bool shouldOverwriteWhenFull() const override; + void onContactsFull() override; + void onContactOverwrite(const uint8_t* pub_key) override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override; void onContactPathUpdated(const ContactInfo &contact) override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index e9db5444..62cd4164 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -27,4 +27,5 @@ struct NodePrefs { // persisted to file uint8_t buzzer_quiet; uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval; // GPS read interval in seconds + uint8_t autoadd_config; // bitmask for auto-add contacts config }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 08185628..241c5d32 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -55,6 +55,28 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { } } +ContactInfo* BaseChatMesh::allocateContactSlot() { + if (num_contacts < MAX_CONTACTS) { + return &contacts[num_contacts++]; + } else if (shouldOverwriteWhenFull()) { + // Find oldest non-favourite contact by last_advert_timestamp + int oldest_idx = -1; + uint32_t oldest_timestamp = 0xFFFFFFFF; + for (int i = 0; i < num_contacts; i++) { + bool is_favourite = (contacts[i].flags & 0x01) != 0; + if (!is_favourite && contacts[i].last_advert_timestamp < oldest_timestamp) { + oldest_timestamp = contacts[i].last_advert_timestamp; + oldest_idx = i; + } + } + if (oldest_idx >= 0) { + onContactOverwrite(contacts[oldest_idx].id.pub_key); + return &contacts[oldest_idx]; + } + } + return NULL; // no space, no overwrite or all contacts are all favourites +} + void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { AdvertDataParser parser(app_data, app_data_len); if (!(parser.isValid() && parser.hasName())) { @@ -87,7 +109,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, bool is_new = false; if (from == NULL) { - if (!isAutoAddEnabled()) { + if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; memset(&ci, 0, sizeof(ci)); ci.id = id; @@ -105,20 +127,33 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } is_new = true; - if (num_contacts < MAX_CONTACTS) { - from = &contacts[num_contacts++]; - from->id = id; - from->out_path_len = -1; // initially out_path is unknown - from->gps_lat = 0; // initially unknown GPS loc - from->gps_lon = 0; - from->sync_since = 0; - - from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand - } else { - MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!"); + from = allocateContactSlot(); + if (from == NULL) { + ContactInfo ci; + memset(&ci, 0, sizeof(ci)); + ci.id = id; + ci.out_path_len = -1; // initially out_path is unknown + StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); + ci.type = parser.getType(); + if (parser.hasLatLon()) { + ci.gps_lat = parser.getIntLat(); + ci.gps_lon = parser.getIntLon(); + } + ci.last_advert_timestamp = timestamp; + ci.lastmod = getRTCClock()->getCurrentTime(); + onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + onContactsFull(); + MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact"); return; } - } + + from->id = id; + from->out_path_len = -1; // initially out_path is unknown + from->gps_lat = 0; // initially unknown GPS loc + from->gps_lon = 0; + from->sync_since = 0; + from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand + } // update StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); @@ -722,10 +757,12 @@ ContactInfo* BaseChatMesh::lookupContactByPubKey(const uint8_t* pub_key, int pre } bool BaseChatMesh::addContact(const ContactInfo& contact) { - if (num_contacts < MAX_CONTACTS) { - auto dest = &contacts[num_contacts++]; + ContactInfo* dest = allocateContactSlot(); + if (dest) { *dest = contact; - + if (dest->last_advert_timestamp == 0) { // ensure non-zero timestamp to prevent contacts added from discover list being considered 'oldest' + dest->last_advert_timestamp = getRTCClock()->getCurrentTimeUnique(); + } dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 40818fed..bfbf254e 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -89,9 +89,14 @@ protected: } void resetContacts() { num_contacts = 0; } + ContactInfo* allocateContactSlot(); // helper to find slot for new contact // 'UI' concepts, for sub-classes to implement virtual bool isAutoAddEnabled() const { return true; } + virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } + virtual void onContactsFull() {}; + virtual bool shouldOverwriteWhenFull() const { return false; } + virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; virtual void onContactPathUpdated(const ContactInfo& contact) = 0; From 741564dd48d29ea328b1fd041bf4891ee28aa6e7 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 13 Jan 2026 01:05:42 +1100 Subject: [PATCH 393/546] refactor: add populateContactFromAdvert() --- src/helpers/BaseChatMesh.cpp | 66 +++++++++++++++--------------------- src/helpers/BaseChatMesh.h | 1 + 2 files changed, 29 insertions(+), 38 deletions(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 241c5d32..b68f4805 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -77,6 +77,20 @@ ContactInfo* BaseChatMesh::allocateContactSlot() { return NULL; // no space, no overwrite or all contacts are all favourites } +void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) { + memset(&ci, 0, sizeof(ci)); + ci.id = id; + ci.out_path_len = -1; // initially out_path is unknown + StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); + ci.type = parser.getType(); + if (parser.hasLatLon()) { + ci.gps_lat = parser.getIntLat(); + ci.gps_lon = parser.getIntLon(); + } + ci.last_advert_timestamp = timestamp; + ci.lastmod = getRTCClock()->getCurrentTime(); +} + void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) { AdvertDataParser parser(app_data, app_data_len); if (!(parser.isValid() && parser.hasName())) { @@ -111,17 +125,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, if (from == NULL) { if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; - memset(&ci, 0, sizeof(ci)); - ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown - StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); - ci.type = parser.getType(); - if (parser.hasLatLon()) { - ci.gps_lat = parser.getIntLat(); - ci.gps_lon = parser.getIntLon(); - } - ci.last_advert_timestamp = timestamp; - ci.lastmod = getRTCClock()->getCurrentTime(); + populateContactFromAdvert(ci, id, parser, timestamp); onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know return; } @@ -130,40 +134,26 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; - memset(&ci, 0, sizeof(ci)); - ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown - StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); - ci.type = parser.getType(); - if (parser.hasLatLon()) { - ci.gps_lat = parser.getIntLat(); - ci.gps_lon = parser.getIntLon(); - } - ci.last_advert_timestamp = timestamp; - ci.lastmod = getRTCClock()->getCurrentTime(); - onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + populateContactFromAdvert(ci, id, parser, timestamp); + onDiscoveredContact(ci, true, packet->path_len, packet->path); onContactsFull(); MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact"); return; } - from->id = id; - from->out_path_len = -1; // initially out_path is unknown - from->gps_lat = 0; // initially unknown GPS loc - from->gps_lon = 0; + populateContactFromAdvert(*from, id, parser, timestamp); from->sync_since = 0; - from->shared_secret_valid = false; // ecdh shared_secret will be calculated later on demand - } - - // update - StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); - from->type = parser.getType(); - if (parser.hasLatLon()) { - from->gps_lat = parser.getIntLat(); - from->gps_lon = parser.getIntLon(); + from->shared_secret_valid = false; } - from->last_advert_timestamp = timestamp; - from->lastmod = getRTCClock()->getCurrentTime(); + // update + StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); + from->type = parser.getType(); + if (parser.hasLatLon()) { + from->gps_lat = parser.getIntLat(); + from->gps_lon = parser.getIntLon(); + } + from->last_advert_timestamp = timestamp; + from->lastmod = getRTCClock()->getCurrentTime(); onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index bfbf254e..5387d3dd 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -89,6 +89,7 @@ protected: } void resetContacts() { num_contacts = 0; } + void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp); ContactInfo* allocateContactSlot(); // helper to find slot for new contact // 'UI' concepts, for sub-classes to implement From df6687034a12793db019788575a24e8d0b530269 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 13 Jan 2026 04:27:27 +1100 Subject: [PATCH 394/546] bootstrap RTC from contact.lastmod and improve slot overwrite logic slot overwrite logic can now safely use contact.lastmod to find oldest contact for overwrite --- examples/companion_radio/MyMesh.cpp | 1 + src/helpers/BaseChatMesh.cpp | 23 ++++++++++++++++------- src/helpers/BaseChatMesh.h | 1 + 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 803e3b03..28d60a0f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -862,6 +862,7 @@ void MyMesh::begin(bool has_display) { resetContacts(); _store->loadContacts(this); + bootstrapRTCfromContacts(); addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel _store->loadChannels(this); diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index b68f4805..98b40962 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -55,17 +55,29 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { } } +void BaseChatMesh::bootstrapRTCfromContacts() { + uint32_t latest = 0; + for (int i = 0; i < num_contacts; i++) { + if (contacts[i].lastmod > latest) { + latest = contacts[i].lastmod; + } + } + if (latest != 0) { + getRTCClock()->setCurrentTime(latest + 1); + } +} + ContactInfo* BaseChatMesh::allocateContactSlot() { if (num_contacts < MAX_CONTACTS) { return &contacts[num_contacts++]; } else if (shouldOverwriteWhenFull()) { - // Find oldest non-favourite contact by last_advert_timestamp + // Find oldest non-favourite contact by oldest lastmod timestamp int oldest_idx = -1; - uint32_t oldest_timestamp = 0xFFFFFFFF; + uint32_t oldest_lastmod = 0xFFFFFFFF; for (int i = 0; i < num_contacts; i++) { bool is_favourite = (contacts[i].flags & 0x01) != 0; - if (!is_favourite && contacts[i].last_advert_timestamp < oldest_timestamp) { - oldest_timestamp = contacts[i].last_advert_timestamp; + if (!is_favourite && contacts[i].lastmod < oldest_lastmod) { + oldest_lastmod = contacts[i].lastmod; oldest_idx = i; } } @@ -750,9 +762,6 @@ bool BaseChatMesh::addContact(const ContactInfo& contact) { ContactInfo* dest = allocateContactSlot(); if (dest) { *dest = contact; - if (dest->last_advert_timestamp == 0) { // ensure non-zero timestamp to prevent contacts added from discover list being considered 'oldest' - dest->last_advert_timestamp = getRTCClock()->getCurrentTimeUnique(); - } dest->shared_secret_valid = false; // mark shared_secret as needing calculation return true; // success } diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 5387d3dd..fd391b98 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -88,6 +88,7 @@ protected: memset(connections, 0, sizeof(connections)); } + void bootstrapRTCfromContacts(); void resetContacts() { num_contacts = 0; } void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp); ContactInfo* allocateContactSlot(); // helper to find slot for new contact From 11565673c37d0b89aea3c5e5d39f4edb9ede8a33 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Thu, 15 Jan 2026 15:39:44 +0100 Subject: [PATCH 395/546] fix: bump max contacts for v3 companion usb --- variants/heltec_v3/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index dcb2873c..6b61eff5 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -323,7 +323,7 @@ lib_deps = extends = Heltec_lora32_v3 build_flags = ${Heltec_lora32_v3.build_flags} - -D MAX_CONTACTS=140 + -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 From c61fde9328503c2e34677fc7e091c716bdaec2e5 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 16 Jan 2026 12:17:22 +1100 Subject: [PATCH 396/546] always send PUSH_CODE_NEW_ADVERT when advert was not added to contacts[] --- examples/companion_radio/MyMesh.cpp | 2 +- src/helpers/BaseChatMesh.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 28d60a0f..3d8798b2 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -323,7 +323,7 @@ void MyMesh::onContactsFull() { void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) { if (_serial->isConnected()) { - if (!shouldAutoAddContactType(contact.type) && is_new) { + if (is_new) { writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact); } else { out_frame[0] = PUSH_CODE_ADVERT; diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 98b40962..aebfc1b6 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -133,7 +133,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, } putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); - bool is_new = false; + bool is_new = false; // true = not in contacts[], false = exists in contacts[] if (from == NULL) { if (!shouldAutoAddContactType(parser.getType())) { ContactInfo ci; @@ -142,7 +142,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } - is_new = true; from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; From b919119fafaa0ac46810fc1f4f0954eb1c4ec57f Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 16 Jan 2026 13:14:51 +1100 Subject: [PATCH 397/546] only write contacts when changed --- examples/companion_radio/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 3d8798b2..c80e2869 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -358,7 +358,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(p->path, path, p->path_len); } - dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); + if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[] } static int sort_by_recent(const void *a, const void *b) { From 5c7b28f1104b2168b2fb6fd97c4bed3aac0389e3 Mon Sep 17 00:00:00 2001 From: WattleFoxxo <wattle@wattlefoxxo.com> Date: Sun, 18 Jan 2026 14:29:50 +1100 Subject: [PATCH 398/546] Change the Station G2 default tx power set the default TX power to 7dBm to avoid illegal power output by default. --- variants/station_g2/platformio.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 1428221d..91ef5f7a 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -16,7 +16,8 @@ build_flags = -D P_LORA_SCLK=12 -D P_LORA_MISO=14 -D P_LORA_MOSI=13 - -D LORA_TX_POWER=19 + -D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled. + -D MAX_LORA_TX_POWER=19 ; max output without burning out the PA ; -D P_LORA_TX_LED=35 -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 From ed5d2909fc91c3f1769e87a9b0f3350acd9e2bcf Mon Sep 17 00:00:00 2001 From: chrisdavis2110 <chrisdavis2110@gmail.com> Date: Sat, 17 Jan 2026 22:54:20 -0800 Subject: [PATCH 399/546] updated variant rak3401 --- boards/rak3401.json | 72 +++++++++++++++++++++++++++ variants/rak3401/RAK3401Board.cpp | 7 +-- variants/rak3401/RAK3401Board.h | 37 +++++--------- variants/rak3401/platformio.ini | 10 +--- variants/rak3401/target.cpp | 8 --- variants/rak3401/variant.cpp | 37 ++++++++------ variants/rak3401/variant.h | 82 ++++++++++++++----------------- 7 files changed, 147 insertions(+), 106 deletions(-) create mode 100644 boards/rak3401.json diff --git a/boards/rak3401.json b/boards/rak3401.json new file mode 100644 index 00000000..a2816a63 --- /dev/null +++ b/boards/rak3401.json @@ -0,0 +1,72 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "WisCore RAK3401 Board", + "mcu": "nrf52840", + "variant": "WisCore_RAK3401_Board", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd" + }, + "frameworks": [ + "arduino" + ], + "name": "WisCore RAK3401 Board", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://www.rakwireless.com", + "vendor": "RAKwireless" +} diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index 50499dff..b9431c92 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -20,18 +20,13 @@ void RAK3401Board::begin() { Wire.begin(); - // Enable 3.3V periphery power rail (GPS, IO Module, etc.) pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); - pinMode(SX126X_POWER_EN, OUTPUT); - digitalWrite(SX126X_POWER_EN, HIGH); - delay(10); // give sx1262 some time to power up - #ifdef P_LORA_PA_EN // Initialize RAK13302 1W LoRa transceiver module PA control pin pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled delay(10); // Allow PA module to initialize #endif -} +} \ No newline at end of file diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 5188f274..609393c3 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -4,45 +4,34 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -// LoRa radio module pins for RAK3401 with RAK13302 (uses SPI1) -#define P_LORA_DIO_1 10 +// LoRa radio module pins for RAK13302 +#define P_LORA_SCLK 3 +#define P_LORA_MISO 29 +#define P_LORA_MOSI 30 #define P_LORA_NSS 26 -#define P_LORA_RESET 4 +#define P_LORA_DIO_1 10 #define P_LORA_BUSY 9 -#define P_LORA_SCLK 3 // SPI1_SCK -#define P_LORA_MISO 29 // SPI1_MISO -#define P_LORA_MOSI 30 // SPI1_MOSI -#define SX126X_POWER_EN 21 +#define P_LORA_RESET 4 +#ifndef P_LORA_PA_EN + #define P_LORA_PA_EN 31 +#endif //#define PIN_GPS_SDA 13 //GPS SDA pin (output option) //#define PIN_GPS_SCL 14 //GPS SCL pin (output option) -//#define PIN_GPS_TX 16 //GPS TX pin -//#define PIN_GPS_RX 15 //GPS RX pin +// #define PIN_GPS_TX 16 //GPS TX pin +// #define PIN_GPS_RX 15 //GPS RX pin #define PIN_GPS_1PPS 17 //GPS PPS pin #define GPS_BAUD_RATE 9600 #define GPS_ADDRESS 0x42 //i2c address for GPS -// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) -// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA -// This pin must be controlled during transmission to enable the 1W power amplifier -// -// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot -// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) -// -// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet -// Override by defining P_LORA_PA_EN in platformio.ini if needed -#ifndef P_LORA_PA_EN - #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) -#endif - -#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 + // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) -// 3.3V periphery enable (GPS, IO Module, etc.) #define PIN_3V3_EN (34) #define WB_IO2 PIN_3V3_EN diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index f1c4bd2d..30d35d0b 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -1,20 +1,12 @@ [rak3401] extends = nrf52_base -board = rak4631 +board = rak3401 board_check = true build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak3401 - -D RAK_4631 -D RAK_3401 -D RAK13302 - -D RAK_BOARD - -D PIN_BOARD_SCL=14 - -D PIN_BOARD_SDA=13 - -D PIN_GPS_TX=PIN_SERIAL1_RX - -D PIN_GPS_RX=PIN_SERIAL1_TX - -D PIN_GPS_EN=-1 - -D PIN_OLED_RESET=-1 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index ba827ad1..52f3a3d5 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -17,8 +17,6 @@ RAK3401Board board; #endif #endif -// RAK3401 uses SPI1 for the RAK13302 LoRa module -// Note: nRF52 doesn't have a separate SPI1 object, so we use SPI but configure it with SPI1 pins RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); WRAPPER_CLASS radio_driver(radio, board); @@ -36,12 +34,6 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); bool radio_init() { rtc_clock.begin(Wire); - - // Configure SPI with SPI1 pins for RAK13302 - // nRF52 uses the same SPI peripheral but with different pin assignments - SPI.setPins(P_LORA_MISO, P_LORA_SCLK, P_LORA_MOSI); - SPI.begin(); - return radio.std_init(&SPI); } diff --git a/variants/rak3401/variant.cpp b/variants/rak3401/variant.cpp index db55920c..d562189f 100644 --- a/variants/rak3401/variant.cpp +++ b/variants/rak3401/variant.cpp @@ -7,37 +7,46 @@ modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "variant.h" -#include "nrf.h" #include "wiring_constants.h" #include "wiring_digital.h" +#include "nrf.h" -const uint32_t g_ADigitalPinMap[] = { - // P0 - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, +const uint32_t g_ADigitalPinMap[] = +{ + // P0 + 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , + 8 , 9 , 10, 11, 12, 13, 14, 15, + 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; - // P1 - 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}; void initVariant() { - // LED1 & LED2 - pinMode(PIN_LED1, OUTPUT); - ledOff(PIN_LED1); + // LED1 & LED2 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); - pinMode(PIN_LED2, OUTPUT); - ledOff(PIN_LED2); + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); - // 3V3 Power Rail - pinMode(PIN_3V3_EN, OUTPUT); - digitalWrite(PIN_3V3_EN, HIGH); + // 3V3 Power Rail + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, HIGH); } diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 03e9c2a8..9c182247 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -34,7 +34,8 @@ #include "WVariant.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif // __cplusplus // Number of pins defined in PinDescription array @@ -58,8 +59,8 @@ extern "C" { /* * Analog pins */ -#define PIN_A0 (5) -#define PIN_A1 (31) +#define PIN_A0 (5) //(3) +#define PIN_A1 (31) //(4) #define PIN_A2 (28) #define PIN_A3 (29) #define PIN_A4 (30) @@ -67,14 +68,14 @@ extern "C" { #define PIN_A6 (0xff) #define PIN_A7 (0xff) -static const uint8_t A0 = PIN_A0; -static const uint8_t A1 = PIN_A1; -static const uint8_t A2 = PIN_A2; -static const uint8_t A3 = PIN_A3; -static const uint8_t A4 = PIN_A4; -static const uint8_t A5 = PIN_A5; -static const uint8_t A6 = PIN_A6; -static const uint8_t A7 = PIN_A7; + static const uint8_t A0 = PIN_A0; + static const uint8_t A1 = PIN_A1; + static const uint8_t A2 = PIN_A2; + static const uint8_t A3 = PIN_A3; + static const uint8_t A4 = PIN_A4; + static const uint8_t A5 = PIN_A5; + static const uint8_t A6 = PIN_A6; + static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 // Other pins @@ -92,6 +93,7 @@ static const uint8_t AREF = PIN_AREF; /* * Serial interfaces */ +// TXD1 RXD1 on Base Board #define PIN_SERIAL1_RX (15) #define PIN_SERIAL1_TX (16) @@ -108,15 +110,14 @@ static const uint8_t AREF = PIN_AREF; #define PIN_SPI_MOSI (44) #define PIN_SPI_SCK (43) -// SPI1 pins for RAK13302 1W LoRa module -#define PIN_SPI1_MISO (29) // (0 + 29) -#define PIN_SPI1_MOSI (30) // (0 + 30) -#define PIN_SPI1_SCK (3) // (0 + 3) +#define PIN_SPI1_MISO (29) +#define PIN_SPI1_MOSI (30) +#define PIN_SPI1_SCK (3) -static const uint8_t SS = 42; -static const uint8_t MOSI = PIN_SPI_MOSI; -static const uint8_t MISO = PIN_SPI_MISO; -static const uint8_t SCK = PIN_SPI_SCK; + static const uint8_t SS = 42; + static const uint8_t MOSI = PIN_SPI_MOSI; + static const uint8_t MISO = PIN_SPI_MISO; + static const uint8_t SCK = PIN_SPI_SCK; /* * Wire Interfaces @@ -127,6 +128,7 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_WIRE_SCL (WB_I2C1_SCL) // QSPI Pins +// QSPI occupied by GPIO's #define PIN_QSPI_SCK 3 #define PIN_QSPI_CS 26 #define PIN_QSPI_IO0 30 @@ -135,35 +137,25 @@ static const uint8_t SCK = PIN_SPI_SCK; #define PIN_QSPI_IO3 2 // On-board QSPI Flash +// No onboard flash #define EXTERNAL_FLASH_DEVICES IS25LP080D #define EXTERNAL_FLASH_USE_QSPI -// RAK13302 1W LoRa transceiver module (uses SPI1) -// LoRa radio module pins for RAK3401 with RAK13302 -#define P_LORA_DIO_1 10 -#define P_LORA_NSS 26 -#define P_LORA_RESET 4 -#define P_LORA_BUSY 9 -#define P_LORA_SCLK PIN_SPI1_SCK // 3 -#define P_LORA_MISO PIN_SPI1_MISO // 29 -#define P_LORA_MOSI PIN_SPI1_MOSI // 30 -#define SX126X_POWER_EN 21 +#define P_LORA_SCK PIN_SPI1_SCK +#define P_LORA_MISO PIN_SPI1_MISO +#define P_LORA_MOSI PIN_SPI1_MOSI +#define P_LORA_CS 26 -// RAK13302 1W LoRa transceiver module PA control (WisBlock IO slot) -// The RAK13302 mounts to the IO slot and has an ANT_SW (antenna switch) pin that controls the PA -// This pin must be controlled during transmission to enable the 1W power amplifier -// -// According to RAK13302 datasheet: ANT_SW connects to IO3 on the IO slot -// RAK19007 base board pin mapping: IO3 = pin 31 (also available as AIN1/A1 for analog input) -// -// Default: Pin 31 (IO3) - ANT_SW pin from RAK13302 datasheet -// Override by defining P_LORA_PA_EN in platformio.ini if needed -#ifndef P_LORA_PA_EN - #define P_LORA_PA_EN 31 // ANT_SW pin from RAK13302 datasheet (IO3, pin 31 on RAK19007) -#endif +#define USE_SX1262 +#define SX126X_CS (26) +#define SX126X_DIO1 (10) +#define SX126X_BUSY (9) +#define SX126X_RESET (4) -#define SX126X_DIO2_AS_RF_SWITCH true -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_POWER_EN (21) +// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings @@ -178,8 +170,8 @@ static const uint8_t SCK = PIN_SPI_SCK; // Power is on the controllable 3V3_S rail #define PIN_GPS_PPS (17) // Pulse per second input from the GPS -#define GPS_RX_PIN PIN_SERIAL1_RX -#define GPS_TX_PIN PIN_SERIAL1_TX +#define PIN_GPS_RX PIN_SERIAL1_RX +#define PIN_GPS_TX PIN_SERIAL1_TX // Battery // The battery sense is hooked to pin A0 (5) From e51a2d1ba0b57df25893a50bc6c8f7d649fdf96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89tienne=20Fesser?= <etiennf01@gmail.com> Date: Mon, 19 Jan 2026 21:39:01 +0100 Subject: [PATCH 400/546] Update T114 I2C pins --- variants/heltec_t114/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index b3f760bb..7b18585d 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -50,8 +50,8 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (26) // P0.26 -#define PIN_WIRE_SCL (27) // P0.27 +#define PIN_WIRE_SDA (16) // P0.16 +#define PIN_WIRE_SCL (13) // P0.13 //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From a7cadc8e4461ad709f62166096d4b00f12f1c2be Mon Sep 17 00:00:00 2001 From: Miguel de Matos <11491485+Snayler@users.noreply.github.com> Date: Tue, 20 Jan 2026 01:52:45 +0000 Subject: [PATCH 401/546] Fix Serial and TX LED not working on Heltec Wireless Paper V1.2 As described on #1276, tested and working on my heltec wireless paper v1.2 --- variants/heltec_wireless_paper/platformio.ini | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index 9cf76153..f0bca860 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -5,7 +5,7 @@ build_flags = ${esp32_base.build_flags} -I variants/heltec_wireless_paper -D HELTEC_WIRELESS_PAPER - -D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial + ;-D ARDUINO_USB_CDC_ON_BOOT=1 ; this breaks Serial -D P_LORA_DIO_1=14 -D P_LORA_NSS=8 -D P_LORA_RESET=RADIOLIB_NC @@ -17,8 +17,8 @@ build_flags = -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D P_LORA_TX_LED=18 - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 + ;-D PIN_BOARD_SDA=17 + ;-D PIN_BOARD_SCL=18 ; same GPIO as P_LORA_TX_LED -D PIN_USER_BTN=0 -D PIN_VEXT_EN=45 -D PIN_VBAT_READ=20 @@ -139,4 +139,4 @@ build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} +<../examples/simple_room_server> lib_deps = ${Heltec_Wireless_Paper_base.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} From d68bc74514ec9a965c18f3c13b5fd79b05eb667e Mon Sep 17 00:00:00 2001 From: nakoeppen <nicholaskoeppen@gmail.com> Date: Tue, 20 Jan 2026 20:19:10 -0600 Subject: [PATCH 402/546] Remove _serial->isConnected() logic from buzzer notifications --- examples/companion_radio/MyMesh.cpp | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c80e2869..c11c70d7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -330,11 +330,10 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); } - } else { + } #ifdef DISPLAY_CLASS - if (_ui) _ui->notify(UIEventType::newContactMessage); + if (_ui && !_prefs.buzzer_quiet) _ui->notify(UIEventType::newContactMessage); //buzz if enabled #endif - } // add inbound-path to mem cache if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid @@ -441,9 +440,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; if (should_display && _ui) { _ui->newMsg(path_len, from.name, text, offline_queue_len); - if (!_serial->isConnected()) { - _ui->notify(UIEventType::contactMessage); - } + if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::contactMessage); //buzz if enabled } #endif } @@ -528,11 +525,8 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe uint8_t frame[1]; frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); - } else { -#ifdef DISPLAY_CLASS - if (_ui) _ui->notify(UIEventType::channelMessage); -#endif } + #ifdef DISPLAY_CLASS // Get the channel name from the channel index const char *channel_name = "Unknown"; @@ -540,7 +534,10 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe if (getChannel(channel_idx, channel_details)) { channel_name = channel_details.name; } - if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); + if (_ui) { + _ui->newMsg(path_len, channel_name, text, offline_queue_len); + if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled + } #endif } @@ -799,6 +796,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; + _prefs.buzzer_quiet = 0; _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed @@ -838,6 +836,7 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours From 46e4cc06e39043c74cd4d164cc2aedb0f5bbeccf Mon Sep 17 00:00:00 2001 From: Socalix <48040807+Socalix@users.noreply.github.com> Date: Wed, 21 Jan 2026 21:12:54 -0600 Subject: [PATCH 403/546] Revert boosted gain flag to original --- variants/heltec_v4/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 9ab3e162..258a9967 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -31,7 +31,7 @@ build_flags = -D SX126X_DIO2_AS_RF_SWITCH=true ; GC1109 CTX is controlled by SX1262 DIO2 -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -; -D SX126X_RX_BOOSTED_GAIN=1 ; Turned off for improved RX + -D SX126X_RX_BOOSTED_GAIN=1 ; In some cases, commenting this out will improve RX -D PIN_GPS_RX=38 -D PIN_GPS_TX=39 -D PIN_GPS_RESET=42 From b09ddfc5e18f0e73fdd14f603f6c90cfeb813ec2 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Thu, 22 Jan 2026 14:41:07 +1100 Subject: [PATCH 404/546] thinknode m1: add missing getLocationProvider() override --- variants/thinknode_m1/target.h | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index 1e4e1381..8425369d 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -22,6 +22,7 @@ class ThinkNodeM1SensorManager : public SensorManager { void stop_gps(); public: ThinkNodeM1SensorManager(LocationProvider &location): _location(&location) { } + LocationProvider* getLocationProvider() override { return _location; } bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; void loop() override; From ea85486dca3bd912a974109ec01b05c753464492 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Thu, 22 Jan 2026 14:42:08 +1100 Subject: [PATCH 405/546] thinknode m1: add missing GPS page to new UI --- variants/thinknode_m1/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index ade487e9..397bf8e3 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -83,6 +83,7 @@ build_flags = -D PIN_BUZZER=6 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 -D QSPIFLASH=1 + -D ENV_INCLUDE_GPS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${ThinkNode_M1.build_src_filter} From 36f230d074f6de62049f59b3804278f05dd1017d Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Thu, 22 Jan 2026 14:42:43 +1100 Subject: [PATCH 406/546] thinknode m1: allow GPS to sync clock --- variants/thinknode_m1/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 2b04d7c6..c3b1abc2 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea); #ifdef DISPLAY_CLASS From fc61018d4daabed08ccf5e220bcc29549f4313cd Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Fri, 23 Jan 2026 10:45:13 +0800 Subject: [PATCH 407/546] Fix the issue of inconsistent I2C usage in the environmental sensor. --- src/helpers/sensors/EnvironmentSensorManager.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index b7238def..8471d80d 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -42,7 +42,7 @@ static Adafruit_BME280 BME280; #endif #define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level #include <Adafruit_BMP280.h> -static Adafruit_BMP280 BMP280; +static Adafruit_BMP280 BMP280(TELEM_WIRE); #endif #if ENV_INCLUDE_SHTC3 @@ -58,6 +58,7 @@ static SensirionI2cSht4x SHT4X; #if ENV_INCLUDE_LPS22HB #include <Arduino_LPS22HB.h> +LPS22HBClass LPS22HB(*TELEM_WIRE); #endif #if ENV_INCLUDE_INA3221 @@ -218,7 +219,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_SHTC3 - if (SHTC3.begin()) { + if (SHTC3.begin(TELEM_WIRE)) { MESH_DEBUG_PRINTLN("Found sensor: SHTC3"); SHTC3_initialized = true; } else { @@ -243,7 +244,7 @@ bool EnvironmentSensorManager::begin() { #endif #if ENV_INCLUDE_LPS22HB - if (BARO.begin()) { + if (LPS22HB.begin()) { MESH_DEBUG_PRINTLN("Found sensor: LPS22HB"); LPS22HB_initialized = true; } else { @@ -407,8 +408,8 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen #if ENV_INCLUDE_LPS22HB if (LPS22HB_initialized) { - telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature()); - telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa + telemetry.addTemperature(TELEM_CHANNEL_SELF, LPS22HB.readTemperature()); + telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, LPS22HB.readPressure() * 10); // convert kPa to hPa } #endif From 3c27132914eb9b92ad39310e12106143619134b3 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Fri, 23 Jan 2026 15:53:58 +1100 Subject: [PATCH 408/546] * T1000e BLE - default node name is now the MAC address --- examples/companion_radio/MyMesh.cpp | 8 ++++---- examples/companion_radio/main.cpp | 8 ++------ src/helpers/esp32/SerialBLEInterface.cpp | 14 ++++++++++++-- src/helpers/esp32/SerialBLEInterface.h | 8 +++++++- src/helpers/nrf52/SerialBLEInterface.cpp | 16 +++++++++++++--- src/helpers/nrf52/SerialBLEInterface.h | 9 ++++++++- variants/t1000-e/platformio.ini | 1 + 7 files changed, 47 insertions(+), 17 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c11c70d7..9de91e45 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -815,14 +815,14 @@ void MyMesh::begin(bool has_display) { _store->saveMainIdentity(self_id); } +// if name is provided as a build flag, use that as default node name instead +#ifdef ADVERT_NAME + strcpy(_prefs.node_name, ADVERT_NAME); +#else // use hex of first 4 bytes of identity public key as default node name char pub_key_hex[10]; mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4); strcpy(_prefs.node_name, pub_key_hex); - -// if name is provided as a build flag, use that as default node name instead -#ifdef ADVERT_NAME - strcpy(_prefs.node_name, ADVERT_NAME); #endif // load persisted prefs diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 82c8c21d..7e636ace 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -151,9 +151,7 @@ void setup() { ); #ifdef BLE_PIN_CODE - char dev_name[32+16]; - sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); - serial_interface.begin(dev_name, the_mesh.getBLEPin()); + serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); #else serial_interface.begin(Serial); #endif @@ -199,9 +197,7 @@ void setup() { WiFi.begin(WIFI_SSID, WIFI_PWD); serial_interface.begin(TCP_PORT); #elif defined(BLE_PIN_CODE) - char dev_name[32+16]; - sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName()); - serial_interface.begin(dev_name, the_mesh.getBLEPin()); + serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin()); #elif defined(SERIAL_RX) companion_serial.setPins(SERIAL_RX, SERIAL_TX); companion_serial.begin(115200); diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index 7ec93723..eccfeca6 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -9,11 +9,21 @@ #define ADVERT_RESTART_DELAY 1000 // millis -void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { +void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { _pin_code = pin_code; + if (strcmp(name, "@@MAC") == 0) { + uint8_t addr[8]; + memset(addr, 0, sizeof(addr)); + esp_efuse_mac_get_default(addr); + sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param) + addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]); + } + char dev_name[32+16]; + sprintf(dev_name, "%s%s", prefix, name); + // Create the BLE Device - BLEDevice::init(device_name); + BLEDevice::init(dev_name); BLEDevice::setSecurityCallbacks(this); BLEDevice::setMTU(MAX_FRAME_SIZE); diff --git a/src/helpers/esp32/SerialBLEInterface.h b/src/helpers/esp32/SerialBLEInterface.h index 29ad897a..965e90fd 100644 --- a/src/helpers/esp32/SerialBLEInterface.h +++ b/src/helpers/esp32/SerialBLEInterface.h @@ -61,7 +61,13 @@ public: send_queue_len = recv_queue_len = 0; } - void begin(const char* device_name, uint32_t pin_code); + /** + * init the BLE interface. + * @param prefix a prefix for the device name + * @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned + * @param pin_code the BLE security pin + */ + void begin(const char* prefix, char* name, uint32_t pin_code); // BaseSerialInterface methods void enable() override; diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index eb1e90bb..5648707e 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -123,7 +123,7 @@ void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) { } } -void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { +void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) { instance = this; char charpin[20]; @@ -133,7 +133,17 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { // Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.begin(); - + + char dev_name[32+16]; + if (strcmp(name, "@@MAC") == 0) { + ble_gap_addr_t addr; + if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) { + sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param) + addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], addr.addr[1], addr.addr[0]); + } + } + sprintf(dev_name, "%s%s", prefix, name); + // Connection interval units: 1.25ms, supervision timeout units: 10ms ble_gap_conn_params_t ppcp_params; ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL; @@ -153,7 +163,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) { } Bluefruit.setTxPower(BLE_TX_POWER); - Bluefruit.setName(device_name); + Bluefruit.setName(dev_name); Bluefruit.Security.setMITM(true); Bluefruit.Security.setPIN(charpin); diff --git a/src/helpers/nrf52/SerialBLEInterface.h b/src/helpers/nrf52/SerialBLEInterface.h index 25968d78..e2fc6cb9 100644 --- a/src/helpers/nrf52/SerialBLEInterface.h +++ b/src/helpers/nrf52/SerialBLEInterface.h @@ -52,7 +52,14 @@ public: recv_queue_len = 0; } - void begin(const char* device_name, uint32_t pin_code); + /** + * init the BLE interface. + * @param prefix a prefix for the device name + * @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned + * @param pin_code the BLE security pin + */ + void begin(const char* prefix, char* name, uint32_t pin_code); + void disconnect(); void enable() override; void disable() override; diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 555b182f..ac929308 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -107,6 +107,7 @@ build_flags = ${t1000-e.build_flags} -D DISPLAY_CLASS=NullDisplayDriver -D PIN_BUZZER=25 -D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E + -D ADVERT_NAME='"@@MAC"' build_src_filter = ${t1000-e.build_src_filter} +<helpers/nrf52/SerialBLEInterface.cpp> +<helpers/ui/buzzer.cpp> From 1f59e5288049cc9f6601bfc7728ab3205c0c25c3 Mon Sep 17 00:00:00 2001 From: entr0p1 <1475255+entr0p1@users.noreply.github.com> Date: Fri, 23 Jan 2026 17:18:41 +1100 Subject: [PATCH 409/546] nRF52840 Power Management - Phase 1 - Boot Low VBAT Voltage Lockout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added NRF52840 power management core functionality: - Boot‑voltage lockout - Initial support for shutdown/reset reason storage and capture (via RESETREAS/GPREGRET2) - LPCOMP wake (for voltage-driven shutdowns) - VBUS wake (for voltage-driven shutdowns) - Per-board shutdown handler for board-specific tasks - Exposed CLI queries for power‑management status in CommonCLI.cpp - Added documentation in docs/nrf52_power_management.md. - Enabled power management support in Xiao nRF52840, RAK4631, Heltec T114 boards --- docs/nrf52_power_management.md | 213 ++++++++++++++++++++++++ src/MeshCore.h | 8 + src/helpers/CommonCLI.cpp | 27 +++ src/helpers/NRF52Board.cpp | 217 +++++++++++++++++++++++++ src/helpers/NRF52Board.h | 43 +++++ variants/heltec_t114/T114Board.cpp | 34 ++++ variants/heltec_t114/T114Board.h | 13 +- variants/heltec_t114/platformio.ini | 1 + variants/heltec_t114/variant.h | 8 + variants/rak4631/RAK4631Board.cpp | 27 +++ variants/rak4631/RAK4631Board.h | 5 + variants/rak4631/platformio.ini | 1 + variants/rak4631/variant.h | 8 + variants/xiao_nrf52/XiaoNrf52Board.cpp | 47 +++++- variants/xiao_nrf52/XiaoNrf52Board.h | 23 +-- variants/xiao_nrf52/platformio.ini | 1 + variants/xiao_nrf52/variant.h | 15 ++ 17 files changed, 667 insertions(+), 24 deletions(-) create mode 100644 docs/nrf52_power_management.md diff --git a/docs/nrf52_power_management.md b/docs/nrf52_power_management.md new file mode 100644 index 00000000..ebe9bbbe --- /dev/null +++ b/docs/nrf52_power_management.md @@ -0,0 +1,213 @@ +# nRF52 Power Management + +## Overview + +The nRF52 Power Management module provides battery protection features to prevent over-discharge, minimise likelihood of brownout and flash corruption conditions existing, and enable safe voltage-based recovery. + +## Features + +### Boot Voltage Protection +- Checks battery voltage immediately after boot and before mesh operations commence +- If voltage is below a configurable threshold (e.g., 3300mV), the device configures voltage wake (LPCOMP + VBUS) and enters protective shutdown (SYSTEMOFF) +- Prevents boot loops when battery is critically low +- Skipped when external power (USB VBUS) is detected + +### Voltage Wake (LPCOMP + VBUS) +- Configures the nRF52's Low Power Comparator (LPCOMP) before entering SYSTEMOFF +- Enables USB VBUS detection so external power can wake the device +- Device automatically wakes when battery voltage rises above recovery threshold or when VBUS is detected + +### Early Boot Register Capture +- Captures RESETREAS (reset reason) and GPREGRET2 (shutdown reason) before SystemInit() clears them +- Allows firmware to determine why it booted (cold boot, watchdog, LPCOMP wake, etc.) +- Allows firmware to determine why it last shut down (user request, low voltage, boot protection) + +### Shutdown Reason Tracking +Shutdown reason codes (stored in GPREGRET2): +| Code | Name | Description | +|------|------|-------------| +| 0x00 | NONE | Normal boot / no previous shutdown | +| 0x4C | LOW_VOLTAGE | Runtime low voltage threshold reached | +| 0x55 | USER | User requested powerOff() | +| 0x42 | BOOT_PROTECT | Boot voltage protection triggered | + +## Supported Boards + +| Board | Implemented | LPCOMP wake | VBUS wake | +|-------|-------------|-------------|-----------| +| Seeed Studio XIAO nRF52840 (`xiao_nrf52`) | Yes | Yes | Yes | +| RAK4631 (`rak4631`) | Yes | Yes | Yes | +| Heltec T114 (`heltec_t114`) | Yes | Yes | Yes | +| Promicro nRF52840 | No | No | No | +| RAK WisMesh Tag | No | No | No | +| Heltec Mesh Solar | No | No | No | +| LilyGo T-Echo / T-Echo Lite | No | No | No | +| SenseCAP Solar | No | No | No | +| WIO Tracker L1 / L1 E-Ink | No | No | No | +| WIO WM1110 | No | No | No | +| Mesh Pocket | No | No | No | +| Nano G2 Ultra | No | No | No | +| ThinkNode M1/M3/M6 | No | No | No | +| T1000-E | No | No | No | +| Ikoka Nano/Stick/Handheld (nRF) | No | No | No | +| Keepteen LT1 | No | No | No | +| Minewsemi ME25LS01 | No | No | No | + +Notes: +- "Implemented" reflects Phase 1 (boot lockout + shutdown reason capture). +- User power-off on Heltec T114 does not enable LPCOMP wake. +- VBUS detection is used to skip boot lockout on external power, and VBUS wake is configured alongside LPCOMP when supported hardware exposes VBUS to the nRF52. + +## Technical Details + +### Architecture + +The power management functionality is integrated into the `NRF52Board` base class in `src/helpers/NRF52Board.cpp`. Board variants provide hardware-specific configuration via a `PowerMgtConfig` struct and override `initiateShutdown(uint8_t reason)` to perform board-specific power-down work and conditionally enable voltage wake (LPCOMP + VBUS). + +### Early Boot Capture + +A static constructor with priority 101 in `NRF52Board.cpp` captures the RESETREAS and GPREGRET2 registers before: +- SystemInit() (priority 102) - which clears RESETREAS +- Static C++ constructors (default priority 65535) + +This ensures we capture the true reset reason before any initialisation code runs. + +### Board Implementation + +To enable power management on a board variant: + +1. **Enable in platformio.ini**: + ```ini + -D NRF52_POWER_MANAGEMENT + ``` + +2. **Define configuration in variant.h**: + ```c + #define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) + #define PWRMGT_LPCOMP_AIN 7 // AIN channel for voltage sensing + #define PWRMGT_LPCOMP_REFSEL 2 // REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) + ``` + +3. **Implement in board .cpp file**: + ```cpp + #ifdef NRF52_POWER_MANAGEMENT + const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK + }; + + void MyBoard::initiateShutdown(uint8_t reason) { + // Board-specific shutdown preparation (e.g., disable peripherals) + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); + } + #endif + + void MyBoard::begin() { + NRF52Board::begin(); // or NRF52BoardDCDC::begin() + // ... board setup ... + + #ifdef NRF52_POWER_MANAGEMENT + checkBootVoltage(&power_config); + #endif + } + ``` + + For user-initiated shutdowns, `powerOff()` remains board-specific. Power management only arms LPCOMP for automated shutdown reasons (boot protection/low voltage). + +4. **Declare override in board .h file**: + ```cpp + #ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; + #endif + ``` + +### Voltage Wake Configuration + +The LPCOMP (Low Power Comparator) is configured to: +- Monitor the specified AIN channel (0-7 corresponding to P0.02-P0.05, P0.28-P0.31) +- Compare against VDD fraction reference (REFSEL: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) +- Detect UP events (voltage rising above threshold) +- Use 50mV hysteresis for noise immunity +- Wake the device from SYSTEMOFF when triggered + +VBUS wake is enabled via the POWER peripheral USBDETECTED event whenever `configureVoltageWake()` is used. This requires USB VBUS to be routed to the nRF52 (typical on nRF52840 boards with native USB). + +**LPCOMP Reference Selection (PWRMGT_LPCOMP_REFSEL)**: +| REFSEL | Fraction | VBAT @ 1M/1M divider (VDD=3.0-3.3) | VBAT @ 1.5M/1M divider (VDD=3.0-3.3) | +|--------|----------|------------------------------------|--------------------------------------| +| 0 | 1/8 | 0.75-0.82 V | 0.94-1.03 V | +| 1 | 2/8 | 1.50-1.65 V | 1.88-2.06 V | +| 2 | 3/8 | 2.25-2.47 V | 2.81-3.09 V | +| 3 | 4/8 | 3.00-3.30 V | 3.75-4.12 V | +| 4 | 5/8 | 3.75-4.12 V | 4.69-5.16 V | +| 5 | 6/8 | 4.50-4.95 V | 5.62-6.19 V | +| 6 | 7/8 | 5.25-5.77 V | 6.56-7.22 V | +| 7 | ARef | - | - | +| 8 | 1/16 | 0.38-0.41 V | 0.47-0.52 V | +| 9 | 3/16 | 1.12-1.24 V | 1.41-1.55 V | +| 10 | 5/16 | 1.88-2.06 V | 2.34-2.58 V | +| 11 | 7/16 | 2.62-2.89 V | 3.28-3.61 V | +| 12 | 9/16 | 3.38-3.71 V | 4.22-4.64 V | +| 13 | 11/16 | 4.12-4.54 V | 5.16-5.67 V | +| 14 | 13/16 | 4.88-5.36 V | 6.09-6.70 V | +| 15 | 15/16 | 5.62-6.19 V | 7.03-7.73 V | + +**Important**: For boards with a voltage divider on the battery sense pin, LPCOMP measures the divided voltage. Use: +`VBAT_threshold ≈ (VDD * fraction) * divider_scale`, where `divider_scale = (Rtop + Rbottom) / Rbottom` (e.g., 2.0 for 1M/1M, 2.5 for 1.5M/1M, 3.0 for XIAO). + +### SoftDevice Compatibility + +The power management code checks whether SoftDevice is enabled and uses the appropriate API: +- When SD enabled: `sd_power_*` functions +- When SD disabled: Direct register access (NRF_POWER->*) + +This ensures compatibility regardless of BLE stack state. + +## CLI Commands + +Power management status can be queried via the CLI: + +| Command | Description | +|---------|-------------| +| `get pwrmgt.support` | Returns "supported" or "unsupported" | +| `get pwrmgt.source` | Returns current power source - "battery" or "external" (5V/USB power) | +| `get pwrmgt.bootreason` | Returns reset and shutdown reason strings | +| `get pwrmgt.bootmv` | Returns boot voltage in millivolts | + +On boards without power management enabled, all commands except `get pwrmgt.support` return: +``` +ERROR: Power management not supported +``` + +## Debug Output + +When `MESH_DEBUG=1` is enabled, the power management module outputs: +``` +DEBUG: PWRMGT: Reset = Wake from LPCOMP (0x20000); Shutdown = Low Voltage (0x4C) +DEBUG: PWRMGT: Boot voltage = 3450 mV (threshold = 3300 mV) +DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD) +``` + +## Phase 2 (Planned) + +- Runtime voltage monitoring +- Voltage state machine (Normal -> Warning -> Critical -> Shutdown) +- Configurable thresholds +- Load shedding callbacks for power reduction +- Deep sleep integration +- Scheduled wake-up +- Extended sleep with periodic monitoring + +## References + +- [nRF52840 Product Specification - POWER](https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html) +- [nRF52840 Product Specification - LPCOMP](https://infocenter.nordicsemi.com/topic/ps_nrf52840/lpcomp.html) +- [SoftDevice S140 API - Power Management](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.1.0/group__nrf__sdm__api.html) diff --git a/src/MeshCore.h b/src/MeshCore.h index 718660d3..f194cdeb 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -56,6 +56,14 @@ public: virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported + + // Power management interface (boards with power management override these) + virtual bool isExternalPowered() { return false; } + virtual uint16_t getBootVoltage() { return 0; } + virtual uint32_t getResetReason() const { return 0; } + virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; } + virtual uint8_t getShutdownReason() const { return 0; } + virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; } }; /** diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2fc93006..6dac9fff 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -364,6 +364,33 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else { sprintf(reply, "> %.3f", adc_mult); } + // Power management commands + } else if (memcmp(config, "pwrmgt.support", 14) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + strcpy(reply, "> supported"); +#else + strcpy(reply, "> unsupported"); +#endif + } else if (memcmp(config, "pwrmgt.source", 13) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery"); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + sprintf(reply, "> Reset: %s; Shutdown: %s", + _board->getResetReasonString(_board->getResetReason()), + _board->getShutdownReasonString(_board->getShutdownReason())); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif + } else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) { +#ifdef NRF52_POWER_MANAGEMENT + sprintf(reply, "> %u mV", _board->getBootVoltage()); +#else + strcpy(reply, "ERROR: Power management not supported"); +#endif } else { sprintf(reply, "??: %s", config); } diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index c0d58314..1303d5be 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -2,6 +2,7 @@ #include "NRF52Board.h" #include <bluefruit.h> +#include <nrf_soc.h> static BLEDfu bledfu; @@ -21,6 +22,222 @@ void NRF52Board::begin() { startup_reason = BD_STARTUP_NORMAL; } +#ifdef NRF52_POWER_MANAGEMENT +#include "nrf.h" + +// Power Management global variables +uint32_t g_nrf52_reset_reason = 0; // Reset/Startup reason +uint8_t g_nrf52_shutdown_reason = 0; // Shutdown reason + +// Early constructor - runs before SystemInit() clears the registers +// Priority 101 ensures this runs before SystemInit (102) and before +// any C++ static constructors (default 65535) +static void __attribute__((constructor(101))) nrf52_early_reset_capture() { + g_nrf52_reset_reason = NRF_POWER->RESETREAS; + g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2; +} + +void NRF52Board::initPowerMgr() { + // Copy early-captured register values + reset_reason = g_nrf52_reset_reason; + shutdown_reason = g_nrf52_shutdown_reason; + boot_voltage_mv = 0; // Will be set by checkBootVoltage() + + // Clear registers for next boot + // Note: At this point SoftDevice may or may not be enabled + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_reset_reason_clr(0xFFFFFFFF); + sd_power_gpregret_clr(1, 0xFF); + } else { + NRF_POWER->RESETREAS = 0xFFFFFFFF; // Write 1s to clear + NRF_POWER->GPREGRET2 = 0; + } + + // Log reset/shutdown info + if (shutdown_reason != SHUTDOWN_REASON_NONE) { + MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX); Shutdown = %s (0x%02X)", + getResetReasonString(reset_reason), (unsigned long)reset_reason, + getShutdownReasonString(shutdown_reason), shutdown_reason); + } else { + MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX)", + getResetReasonString(reset_reason), (unsigned long)reset_reason); + } +} + +bool NRF52Board::isExternalPowered() { + // Check if SoftDevice is enabled before using its API + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + + if (sd_enabled) { + uint32_t usb_status; + sd_power_usbregstatus_get(&usb_status); + return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0; + } else { + return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0; + } +} + +const char* NRF52Board::getResetReasonString(uint32_t reason) { + if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin"; + if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog"; + if (reason & POWER_RESETREAS_SREQ_Msk) return "Soft Reset"; + if (reason & POWER_RESETREAS_LOCKUP_Msk) return "CPU Lockup"; + #ifdef POWER_RESETREAS_LPCOMP_Msk + if (reason & POWER_RESETREAS_LPCOMP_Msk) return "Wake from LPCOMP"; + #endif + #ifdef POWER_RESETREAS_VBUS_Msk + if (reason & POWER_RESETREAS_VBUS_Msk) return "Wake from VBUS"; + #endif + #ifdef POWER_RESETREAS_OFF_Msk + if (reason & POWER_RESETREAS_OFF_Msk) return "Wake from GPIO"; + #endif + #ifdef POWER_RESETREAS_DIF_Msk + if (reason & POWER_RESETREAS_DIF_Msk) return "Debug Interface"; + #endif + return "Cold Boot"; +} + +const char* NRF52Board::getShutdownReasonString(uint8_t reason) { + switch (reason) { + case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage"; + case SHUTDOWN_REASON_USER: return "User Request"; + case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection"; + } + return "Unknown"; +} + +bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) { + initPowerMgr(); + + // Read boot voltage + boot_voltage_mv = getBattMilliVolts(); + + if (config->voltage_bootlock == 0) return true; // Protection disabled + + // Skip check if externally powered + if (isExternalPowered()) { + MESH_DEBUG_PRINTLN("PWRMGT: Boot check skipped (external power)"); + boot_voltage_mv = getBattMilliVolts(); + return true; + } + + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage = %u mV (threshold = %u mV)", + boot_voltage_mv, config->voltage_bootlock); + + // Only trigger shutdown if reading is valid (>1000mV) AND below threshold + // This prevents spurious shutdowns on ADC glitches or uninitialized reads + if (boot_voltage_mv > 1000 && boot_voltage_mv < config->voltage_bootlock) { + MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown"); + + initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT); + return false; // Should never reach this + } + + return true; +} + +void NRF52Board::initiateShutdown(uint8_t reason) { + enterSystemOff(reason); +} + +void NRF52Board::enterSystemOff(uint8_t reason) { + MESH_DEBUG_PRINTLN("PWRMGT: Entering SYSTEMOFF (%s)", getShutdownReasonString(reason)); + + // Record shutdown reason in GPREGRET2 + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_gpregret_clr(1, 0xFF); + sd_power_gpregret_set(1, reason); + } else { + NRF_POWER->GPREGRET2 = reason; + } + + // Flush serial buffers + Serial.flush(); + delay(100); + + // Enter SYSTEMOFF + if (sd_enabled) { + uint32_t err = sd_power_system_off(); + if (err == NRF_ERROR_SOFTDEVICE_NOT_ENABLED) { //SoftDevice not enabled + sd_enabled = 0; + } + } + + if (!sd_enabled) { + // SoftDevice not available; write directly to POWER->SYSTEMOFF + NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter; + } + + // If we get here, something went wrong. Reset to recover. + NVIC_SystemReset(); +} + +void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) { + // LPCOMP is not managed by SoftDevice - direct register access required + // Halt and disable before reconfiguration + NRF_LPCOMP->TASKS_STOP = 1; + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled; + + // Select analog input (AIN0-7 maps to PSEL 0-7) + NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk; + + // Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) + NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk; + + // Detect UP events (voltage rises above threshold for battery recovery) + NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up; + + // Enable 50mV hysteresis for noise immunity + NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV; + + // Clear stale events/interrupts before enabling wake + NRF_LPCOMP->EVENTS_READY = 0; + NRF_LPCOMP->EVENTS_DOWN = 0; + NRF_LPCOMP->EVENTS_UP = 0; + NRF_LPCOMP->EVENTS_CROSS = 0; + + NRF_LPCOMP->INTENCLR = 0xFFFFFFFF; + NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk; + + // Enable LPCOMP + NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled; + NRF_LPCOMP->TASKS_START = 1; + + // Wait for comparator to settle before entering SYSTEMOFF + for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) { + delayMicroseconds(50); + } + + if (refsel == 7) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel); + } else if (refsel <= 6) { + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)", + ain_channel, refsel + 1); + } else { + uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1); + MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)", + ain_channel, ref_num); + } + + // Configure VBUS (USB power) wake alongside LPCOMP + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + if (sd_enabled) { + sd_power_usbdetected_enable(1); + } else { + NRF_POWER->EVENTS_USBDETECTED = 0; + NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk; + } + + MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured"); +} +#endif + void NRF52BoardDCDC::begin() { NRF52Board::begin(); diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0d6c0a43..1f02bace 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -5,15 +5,58 @@ #if defined(NRF52_PLATFORM) +#ifdef NRF52_POWER_MANAGEMENT +// Shutdown Reason Codes (stored in GPREGRET before SYSTEMOFF) +#define SHUTDOWN_REASON_NONE 0x00 +#define SHUTDOWN_REASON_LOW_VOLTAGE 0x4C // 'L' - Runtime low voltage threshold +#define SHUTDOWN_REASON_USER 0x55 // 'U' - User requested powerOff() +#define SHUTDOWN_REASON_BOOT_PROTECT 0x42 // 'B' - Boot voltage protection + +// Boards provide this struct with their hardware-specific settings and callbacks. +struct PowerMgtConfig { + // LPCOMP wake configuration (for voltage recovery from SYSTEMOFF) + uint8_t lpcomp_ain_channel; // AIN0-7 for voltage sensing pin + uint8_t lpcomp_refsel; // REFSEL value: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16 + + // Boot protection voltage threshold (millivolts) + // Set to 0 to disable boot protection + uint16_t voltage_bootlock; +}; +#endif + class NRF52Board : public mesh::MainBoard { +#ifdef NRF52_POWER_MANAGEMENT + void initPowerMgr(); +#endif + protected: uint8_t startup_reason; +#ifdef NRF52_POWER_MANAGEMENT + uint32_t reset_reason; // RESETREAS register value + uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF) + uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts) + + bool checkBootVoltage(const PowerMgtConfig* config); + void enterSystemOff(uint8_t reason); + void configureVoltageWake(uint8_t ain_channel, uint8_t refsel); + virtual void initiateShutdown(uint8_t reason); +#endif + public: virtual void begin(); virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + +#ifdef NRF52_POWER_MANAGEMENT + bool isExternalPowered() override; + uint16_t getBootVoltage() override { return boot_voltage_mv; } + virtual uint32_t getResetReason() const override { return reset_reason; } + uint8_t getShutdownReason() const override { return shutdown_reason; } + const char* getResetReasonString(uint32_t reason) override; + const char* getShutdownReasonString(uint8_t reason) override; +#endif }; /* diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 4995e7de..2a36bd90 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -3,6 +3,35 @@ #include <Arduino.h> #include <Wire.h> +#ifdef NRF52_POWER_MANAGEMENT +// Static configuration for power management +// Values come from variant.h defines +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void T114Board::initiateShutdown(uint8_t reason) { +#if ENV_INCLUDE_GPS == 1 + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, LOW); +#endif + digitalWrite(SX126X_POWER_EN, LOW); + + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + pinMode(PIN_BAT_CTL, OUTPUT); + digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + void T114Board::begin() { NRF52Board::begin(); NRF_POWER->DCDCEN = 1; @@ -21,6 +50,11 @@ void T114Board::begin() { #endif pinMode(SX126X_POWER_EN, OUTPUT); +#ifdef NRF52_POWER_MANAGEMENT + // Boot voltage protection check (may not return if voltage too low) + // We need to call this after we configure SX126X_POWER_EN as output but before we pull high + checkBootVoltage(&power_config); +#endif digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up } \ No newline at end of file diff --git a/variants/heltec_t114/T114Board.h b/variants/heltec_t114/T114Board.h index 74e26455..cf0f656d 100644 --- a/variants/heltec_t114/T114Board.h +++ b/variants/heltec_t114/T114Board.h @@ -10,6 +10,11 @@ #define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range class T114Board : public NRF52BoardOTA { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: T114Board() : NRF52BoardOTA("T114_OTA") {} void begin(); @@ -42,13 +47,13 @@ public: } void powerOff() override { - #ifdef LED_PIN +#ifdef LED_PIN digitalWrite(LED_PIN, HIGH); - #endif - #if ENV_INCLUDE_GPS == 1 +#endif +#if ENV_INCLUDE_GPS == 1 pinMode(GPS_EN, OUTPUT); digitalWrite(GPS_EN, LOW); - #endif +#endif sd_power_system_off(); } }; diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 7b6f5cee..20f5e8fe 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -11,6 +11,7 @@ build_flags = ${nrf52_base.build_flags} -I variants/heltec_t114 -I src/helpers/ui -D HELTEC_T114 + -D NRF52_POWER_MANAGEMENT -D P_LORA_DIO_1=20 -D P_LORA_NSS=24 -D P_LORA_RESET=25 diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index b3f760bb..aa7f4022 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -30,6 +30,14 @@ #define AREF_VOLTAGE (3.0) +// Power management boot protection threshold (millivolts) +// Set to 0 to disable boot protection +#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +// AIN2 = P0.04 = BATTERY_PIN / PIN_VBAT_READ +#define PWRMGT_LPCOMP_AIN 2 +#define PWRMGT_LPCOMP_REFSEL 1 // 2/8 VDD (~3.68-4.04V) + //////////////////////////////////////////////////////////////////////////////// // Number of pins diff --git a/variants/rak4631/RAK4631Board.cpp b/variants/rak4631/RAK4631Board.cpp index 65c54711..9fb47b43 100644 --- a/variants/rak4631/RAK4631Board.cpp +++ b/variants/rak4631/RAK4631Board.cpp @@ -3,6 +3,28 @@ #include "RAK4631Board.h" +#ifdef NRF52_POWER_MANAGEMENT +// Static configuration for power management +// Values set in variant.h defines +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void RAK4631Board::initiateShutdown(uint8_t reason) { + // Disable LoRa module power before shutdown + digitalWrite(SX126X_POWER_EN, LOW); + + if (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + void RAK4631Board::begin() { NRF52BoardDCDC::begin(); pinMode(PIN_VBAT_READ, INPUT); @@ -21,6 +43,11 @@ void RAK4631Board::begin() { Wire.begin(); pinMode(SX126X_POWER_EN, OUTPUT); +#ifdef NRF52_POWER_MANAGEMENT + // Boot voltage protection check (may not return if voltage too low) + // We need to call this after we configure SX126X_POWER_EN as output but before we pull high + checkBootVoltage(&power_config); +#endif digitalWrite(SX126X_POWER_EN, HIGH); delay(10); // give sx1262 some time to power up } \ No newline at end of file diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index a181256b..53a2a797 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -30,6 +30,11 @@ #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {} void begin(); diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 7293b4d4..9a9ab2dd 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -7,6 +7,7 @@ build_flags = ${nrf52_base.build_flags} -I variants/rak4631 -D RAK_4631 -D RAK_BOARD + -D NRF52_POWER_MANAGEMENT -D PIN_BOARD_SCL=14 -D PIN_BOARD_SDA=13 -D PIN_GPS_TX=PIN_SERIAL1_RX diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index e83d1339..b18335f8 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -104,6 +104,14 @@ extern "C" static const uint8_t A7 = PIN_A7; #define ADC_RESOLUTION 14 +// Power management boot protection threshold (millivolts) +// Set to 0 to disable boot protection +#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ +#define PWRMGT_LPCOMP_AIN 3 +#define PWRMGT_LPCOMP_REFSEL 4 // 5/8 VDD (~3.13-3.44V) + // Other pins #define PIN_AREF (2) #define PIN_NFC1 (9) diff --git a/variants/xiao_nrf52/XiaoNrf52Board.cpp b/variants/xiao_nrf52/XiaoNrf52Board.cpp index b7b60dc6..42ee6a87 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.cpp +++ b/variants/xiao_nrf52/XiaoNrf52Board.cpp @@ -5,12 +5,40 @@ #include "XiaoNrf52Board.h" +#ifdef NRF52_POWER_MANAGEMENT +// Static configuration for power management +// Values set in variant.h defines +const PowerMgtConfig power_config = { + .lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, + .lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, + .voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK +}; + +void XiaoNrf52Board::initiateShutdown(uint8_t reason) { + bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || + reason == SHUTDOWN_REASON_BOOT_PROTECT); + + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH); + + if (enable_lpcomp) { + configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); + } + + enterSystemOff(reason); +} +#endif // NRF52_POWER_MANAGEMENT + void XiaoNrf52Board::begin() { NRF52BoardDCDC::begin(); + // Configure battery voltage ADC pinMode(PIN_VBAT, INPUT); pinMode(VBAT_ENABLE, OUTPUT); - digitalWrite(VBAT_ENABLE, HIGH); + digitalWrite(VBAT_ENABLE, LOW); // Enable VBAT divider for reading + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(50); // Allow ADC to settle #ifdef PIN_USER_BTN pinMode(PIN_USER_BTN, INPUT_PULLUP); @@ -27,9 +55,20 @@ void XiaoNrf52Board::begin() { digitalWrite(P_LORA_TX_LED, HIGH); #endif - // pinMode(SX126X_POWER_EN, OUTPUT); - // digitalWrite(SX126X_POWER_EN, HIGH); - delay(10); // give sx1262 some time to power up +#ifdef NRF52_POWER_MANAGEMENT + // Boot voltage protection check (may not return if voltage too low) + checkBootVoltage(&power_config); +#endif + + delay(10); // Give sx1262 some time to power up +} + +uint16_t XiaoNrf52Board::getBattMilliVolts() { + // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging + // VBAT_ENABLE must be LOW to read battery voltage + digitalWrite(VBAT_ENABLE, LOW); + int adcvalue = analogRead(PIN_VBAT); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; } #endif \ No newline at end of file diff --git a/variants/xiao_nrf52/XiaoNrf52Board.h b/variants/xiao_nrf52/XiaoNrf52Board.h index 1c46dfee..db9ec380 100644 --- a/variants/xiao_nrf52/XiaoNrf52Board.h +++ b/variants/xiao_nrf52/XiaoNrf52Board.h @@ -6,7 +6,12 @@ #ifdef XIAO_NRF52 -class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA { +protected: +#if NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {} void begin(); @@ -20,21 +25,7 @@ public: } #endif - uint16_t getBattMilliVolts() override { - // Please read befor going further ;) - // https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging - - // We can't drive VBAT_ENABLE to HIGH as long - // as we don't know wether we are charging or not ... - // this is a 3mA loss (4/1500) - digitalWrite(VBAT_ENABLE, LOW); - int adcvalue = 0; - analogReadResolution(12); - analogReference(AR_INTERNAL_3_0); - delay(10); - adcvalue = analogRead(PIN_VBAT); - return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; - } + uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override { return "Seeed Xiao-nrf52"; diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index edbf6275..6e96018b 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -9,6 +9,7 @@ build_flags = ${nrf52_base.build_flags} -I variants/xiao_nrf52 -UENV_INCLUDE_GPS -D NRF52_PLATFORM + -D NRF52_POWER_MANAGEMENT -D XIAO_NRF52 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper diff --git a/variants/xiao_nrf52/variant.h b/variants/xiao_nrf52/variant.h index 3f4d7afe..25619b9e 100644 --- a/variants/xiao_nrf52/variant.h +++ b/variants/xiao_nrf52/variant.h @@ -75,6 +75,21 @@ static const uint8_t D10 = 10; #define AREF_VOLTAGE (3.0) #define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge +// Power management boot protection threshold (millivolts) +// Set to 0 to disable boot protection +#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage + +// LPCOMP wake configuration (voltage recovery from SYSTEMOFF) +#define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT +// IMPORTANT: The XIAO exposes battery via a resistor divider (ADC_MULTIPLIER = 3.0). +// LPCOMP measures the divided voltage, not the battery voltage directly. +// Vpin = VDD * (REFSEL fraction), and VBAT ≈ Vpin * ADC_MULTIPLIER. +// +// Using 3/8 VDD gives a wake threshold above the boot protection point: +// - If VDD ≈ 3.0V: VBAT ≈ (3.0 * 3/8) * 3 ≈ 3375mV +// - If VDD ≈ 3.3V: VBAT ≈ (3.3 * 3/8) * 3 ≈ 3712mV +#define PWRMGT_LPCOMP_REFSEL 2 // 3/8 VDD (~3.38-3.71V) + static const uint8_t A0 = PIN_A0; static const uint8_t A1 = PIN_A1; static const uint8_t A2 = PIN_A2; From 9dd52bd0cccb803d2dec9d6d276d5add53bb0b84 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 23 Jan 2026 23:40:24 +1100 Subject: [PATCH 410/546] build fix for room server with MESH_DEBUG=1 --- examples/simple_room_server/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 60dd1840..d18a802e 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -815,7 +815,7 @@ void MyMesh::loop() { if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) { c->extra.room.push_failures++; c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry) - MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures); + MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->extra.room.push_failures); } } // check next Round-Robin client, and sync next new post From e7c72c5c6ad0ca5b225e40b260c7f9bd1114d08a Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Fri, 23 Jan 2026 22:26:24 +0100 Subject: [PATCH 411/546] initial port of rak3112 --- variants/rak3112/RAK3112Board.h | 97 ++++++++++++++ variants/rak3112/platformio.ini | 223 ++++++++++++++++++++++++++++++++ variants/rak3112/target.cpp | 60 +++++++++ variants/rak3112/target.h | 30 +++++ 4 files changed, 410 insertions(+) create mode 100644 variants/rak3112/RAK3112Board.h create mode 100644 variants/rak3112/platformio.ini create mode 100644 variants/rak3112/target.cpp create mode 100644 variants/rak3112/target.h diff --git a/variants/rak3112/RAK3112Board.h b/variants/rak3112/RAK3112Board.h new file mode 100644 index 00000000..8dddbefd --- /dev/null +++ b/variants/rak3112/RAK3112Board.h @@ -0,0 +1,97 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/RefCountedDigitalPin.h> +#include <helpers/ESP32Board.h> + +// built-ins +#ifndef PIN_VBAT_READ + #define PIN_VBAT_READ 1 +#endif +#ifndef PIN_ADC_CTRL + #define PIN_ADC_CTRL 36 +#endif +#define PIN_ADC_CTRL_ACTIVE LOW +#define PIN_ADC_CTRL_INACTIVE HIGH + +#include <driver/rtc_io.h> + +class RAK3112Board : public ESP32Board { +private: + bool adc_active_state; + +public: + RefCountedDigitalPin periph_power; + + RAK3112Board() : periph_power(PIN_VEXT_EN) { } + + void begin() { + ESP32Board::begin(); + + // Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2) + pinMode(PIN_ADC_CTRL, INPUT); + adc_active_state = !digitalRead(PIN_ADC_CTRL); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive + + periph_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + startup_reason = BD_STARTUP_RX_PACKET; + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } + } + + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + void powerOff() override { + enterDeepSleep(0); + } + + uint16_t getBattMilliVolts() override { + analogReadResolution(10); + digitalWrite(PIN_ADC_CTRL, adc_active_state); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + digitalWrite(PIN_ADC_CTRL, !adc_active_state); + + return (5.42 * (3.3 / 1024.0) * raw) * 1000; + } + + const char* getManufacturerName() const override { + return "RAK 3112"; + } +}; diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini new file mode 100644 index 00000000..29ebdff2 --- /dev/null +++ b/variants/rak3112/platformio.ini @@ -0,0 +1,223 @@ +[rak3112] +extends = esp32_base +board = esp32-s3-devkitc-1 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/rak3112 + -D RAK_3112=1 + -D ESP32_CPU_FREQ=80 + -D P_LORA_DIO_1=47 + -D P_LORA_NSS=7 + -D P_LORA_RESET=8 + -D P_LORA_BUSY=48 + -D P_LORA_SCLK=5 + -D P_LORA_MISO=3 + -D P_LORA_MOSI=6 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D P_LORA_TX_LED=46 + -D PIN_BOARD_SDA=9 + -D PIN_BOARD_SCL=40 + -D PIN_USER_BTN=-1 + -D PIN_VEXT_EN=14 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_GPS_RX=43 + -D PIN_GPS_TX=44 +; -D PIN_GPS_EN=26 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/rak3112> + +<helpers/sensors> +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[env:RAK3112_repeater] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3112 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:RAK3112_repeater_bridge_rs232] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RS232 Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_RS232_BRIDGE=Serial2 + -D WITH_RS232_BRIDGE_RX=5 + -D WITH_RS232_BRIDGE_TX=6 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/bridges/RS232Bridge.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + +[env:RAK3112_repeater_bridge_espnow] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_repeater> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + +[env:RAK3112_room_server] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"RAK3112 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_room_server> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} + +[env:RAK3112_terminal_chat] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_companion_radio_usb] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_companion_radio_ble] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_companion_radio_wifi] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${rak3112.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:RAK3112_sensor] +extends = rak3112 +build_flags = + ${rak3112.build_flags} + -D ADVERT_NAME='"RAK3112 v3 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=33 + -D ENV_PIN_SCL=34 + -D DISPLAY_CLASS=SSD1306Display +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${rak3112.build_src_filter} + +<helpers/ui/SSD1306Display.cpp> + +<../examples/simple_sensor> +lib_deps = + ${rak3112.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/rak3112/target.cpp b/variants/rak3112/target.cpp new file mode 100644 index 00000000..634573b8 --- /dev/null +++ b/variants/rak3112/target.cpp @@ -0,0 +1,60 @@ +#include <Arduino.h> +#include "target.h" + +RAK3112Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/rak3112/target.h b/variants/rak3112/target.h new file mode 100644 index 00000000..eae90900 --- /dev/null +++ b/variants/rak3112/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <RAK3112Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#ifdef DISPLAY_CLASS + #include <helpers/ui/SSD1306Display.h> + #include <helpers/ui/MomentaryButton.h> +#endif + +extern RAK3112Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From 266f6ee8560083de8805a87228691edb9c2f159a Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Fri, 23 Jan 2026 22:41:47 +0100 Subject: [PATCH 412/546] fixed battery measurement --- variants/rak3112/RAK3112Board.h | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/variants/rak3112/RAK3112Board.h b/variants/rak3112/RAK3112Board.h index 8dddbefd..8ba3197c 100644 --- a/variants/rak3112/RAK3112Board.h +++ b/variants/rak3112/RAK3112Board.h @@ -13,6 +13,8 @@ #endif #define PIN_ADC_CTRL_ACTIVE LOW #define PIN_ADC_CTRL_INACTIVE HIGH +#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) +#define BATTERY_SAMPLES 8 #include <driver/rtc_io.h> @@ -77,18 +79,15 @@ public: } uint16_t getBattMilliVolts() override { - analogReadResolution(10); - digitalWrite(PIN_ADC_CTRL, adc_active_state); + analogReadResolution(12); uint32_t raw = 0; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < BATTERY_SAMPLES; i++) { raw += analogRead(PIN_VBAT_READ); } - raw = raw / 8; + raw = raw / BATTERY_SAMPLES; - digitalWrite(PIN_ADC_CTRL, !adc_active_state); - - return (5.42 * (3.3 / 1024.0) * raw) * 1000; + return (ADC_MULTIPLIER * raw) / 4096; } const char* getManufacturerName() const override { From f46f0d0ed137358752e6a9acaa14a30a0009793d Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 24 Jan 2026 22:08:05 +1100 Subject: [PATCH 413/546] * WIO tracker l1: BLE companion. default node name now MAC address --- variants/wio-tracker-l1/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 31c6bcb0..75651d69 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -95,6 +95,7 @@ build_flags = ${WioTrackerL1.build_flags} -D UI_HAS_JOYSTICK=1 -D PIN_BUZZER=12 -D QSPIFLASH=1 + -D ADVERT_NAME='"@@MAC"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} From 6336bd5b72b8f72d21c3d5f4d0e5d0fc09e9838a Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sun, 25 Jan 2026 01:31:53 +1100 Subject: [PATCH 414/546] refactor ClientACL and CommonCLI, add ClientACL::clear() --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/ClientACL.cpp | 14 +++++++++++++- src/helpers/ClientACL.h | 2 ++ src/helpers/CommonCLI.h | 6 ++++-- 9 files changed, 25 insertions(+), 9 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d926148d..78e6abcb 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -744,7 +744,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), + _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store), discover_limiter(4, 120), // max 4 every 2 minutes anon_limiter(4, 180) // max 4 every 3 minutes #if defined(WITH_RS232_BRIDGE) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f930ee7e..282fc8c2 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -86,11 +86,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; + ClientACL acl; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; uint8_t reply_path[MAX_PATH_SIZE]; int8_t reply_path_len; - ClientACL acl; TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8]; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 60dd1840..3e935fe6 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -587,7 +587,7 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) { MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng, mesh::RTCClock &rtc, mesh::MeshTables &tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { + _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { last_millis = 0; uptime_millis = 0; next_local_advert = next_flood_advert = 0; diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index e7f1fee8..f6adf01e 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -94,8 +94,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { unsigned long next_local_advert, next_flood_advert; bool _logging; NodePrefs _prefs; - CommonCLI _cli; ClientACL acl; + CommonCLI _cli; unsigned long dirty_contacts_expiry; uint8_t reply_data[MAX_PACKET_PAYLOAD]; unsigned long next_push; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 4995c55f..53d8326f 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -695,7 +695,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables), - _cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) + _cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) { next_local_advert = next_flood_advert = 0; dirty_contacts_expiry = 0; diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index c320eb44..6046a1df 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -133,9 +133,9 @@ private: FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; NodePrefs _prefs; + ClientACL acl; CommonCLI _cli; uint8_t reply_data[MAX_PACKET_PAYLOAD]; - ClientACL acl; unsigned long dirty_contacts_expiry; CayenneLPP telemetry; uint32_t last_read_time; diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 4ea19fd2..ffa717b2 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -12,6 +12,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { } void ClientACL::load(FILESYSTEM* _fs) { + _fs = fs; num_clients = 0; if (_fs->exists("/s_contacts")) { #if defined(RP2040_PLATFORM) @@ -50,7 +51,8 @@ void ClientACL::load(FILESYSTEM* _fs) { } } -void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) { +void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) { + _fs = fs; File file = openWrite(_fs, "/s_contacts"); if (file) { uint8_t unused[2]; @@ -74,6 +76,16 @@ void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) { } } +bool ClientACL::clear() { + if (!_fs) return false; // no filesystem, nothing to clear + if (_fs->exists("/s_contacts")) { + _fs->remove("/s_contacts"); + } + memset(clients, 0, sizeof(clients)); + num_clients = 0; + return true; +} + ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) { for (int i = 0; i < num_clients; i++) { if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index 1b650edd..34a0dd85 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -36,6 +36,7 @@ struct ClientInfo { #endif class ClientACL { + FILESYSTEM* _fs; ClientInfo clients[MAX_CLIENTS]; int num_clients; @@ -46,6 +47,7 @@ public: } void load(FILESYSTEM* _fs); void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL); + bool clear(); ClientInfo* getClient(const uint8_t* pubkey, int key_len); ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 3b1d05f9..b0530102 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -3,6 +3,7 @@ #include "Mesh.h" #include <helpers/IdentityStore.h> #include <helpers/SensorManager.h> +#include <helpers/ClientACL.h> #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) #define WITH_BRIDGE @@ -94,6 +95,7 @@ class CommonCLI { CommonCLICallbacks* _callbacks; mesh::MainBoard* _board; SensorManager* _sensors; + ClientACL* _acl; char tmp[PRV_KEY_SIZE*2 + 4]; mesh::RTCClock* getRTCClock() { return _rtc; } @@ -101,8 +103,8 @@ class CommonCLI { void loadPrefsInt(FILESYSTEM* _fs, const char* filename); public: - CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks) - : _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { } + CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks) + : _board(&board), _rtc(&rtc), _sensors(&sensors), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { } void loadPrefs(FILESYSTEM* _fs); void savePrefs(FILESYSTEM* _fs); From 988287bfd7c1a1e8c38e31ca37805ae1829d0d67 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sun, 25 Jan 2026 01:32:44 +1100 Subject: [PATCH 415/546] recalc ClientACL shared_secrets at startup --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 2 +- src/helpers/ClientACL.cpp | 5 +++-- src/helpers/ClientACL.h | 2 +- 5 files changed, 7 insertions(+), 6 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 78e6abcb..59c21ae7 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -808,7 +808,7 @@ void MyMesh::begin(FILESYSTEM *fs) { _fs = fs; // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + acl.load(_fs, self_id); // TODO: key_store.begin(); region_map.load(_fs); diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 3e935fe6..2f929dd5 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -637,7 +637,7 @@ void MyMesh::begin(FILESYSTEM *fs) { // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + acl.load(_fs, self_id); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 53d8326f..c384a761 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -736,7 +736,7 @@ void SensorMesh::begin(FILESYSTEM* fs) { // load persisted prefs _cli.loadPrefs(_fs); - acl.load(_fs); + acl.load(_fs, self_id); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_set_tx_power(_prefs.tx_power_dbm); diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index ffa717b2..55b70ca5 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -11,7 +11,7 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) { #endif } -void ClientACL::load(FILESYSTEM* _fs) { +void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) { _fs = fs; num_clients = 0; if (_fs->exists("/s_contacts")) { @@ -35,11 +35,12 @@ void ClientACL::load(FILESYSTEM* _fs) { success = success && (file.read(unused, 2) == 2); success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1); success = success && (file.read(c.out_path, 64) == 64); - success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); + success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); // will be recalculated below if (!success) break; // EOF c.id = mesh::Identity(pub_key); + self_id.calcSharedSecret(c.shared_secret, pub_key); // recalculate shared secrets in case our private key changed if (num_clients < MAX_CLIENTS) { clients[num_clients++] = c; } else { diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index 34a0dd85..dfbc3fce 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -45,7 +45,7 @@ public: memset(clients, 0, sizeof(clients)); num_clients = 0; } - void load(FILESYSTEM* _fs); + void load(FILESYSTEM* _fs, const mesh::LocalIdentity& self_id); void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL); bool clear(); From 96ef5e5efe48e7115e56ffca1707d3e07eb93901 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sun, 25 Jan 2026 00:51:48 +1100 Subject: [PATCH 416/546] allow set prv.key from remote, validate new prv.key --- examples/companion_radio/MyMesh.cpp | 22 +++++++------ examples/simple_repeater/MyMesh.cpp | 3 +- examples/simple_room_server/MyMesh.cpp | 3 +- examples/simple_sensor/SensorMesh.cpp | 3 +- src/Identity.cpp | 44 ++++++++++++++++++++++++++ src/Identity.h | 7 ++++ src/helpers/CommonCLI.cpp | 11 ++++--- 7 files changed, 73 insertions(+), 20 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9de91e45..2dad7866 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1294,16 +1294,20 @@ void MyMesh::handleCmdFrame(size_t len) { #endif } else if (cmd_frame[0] == CMD_IMPORT_PRIVATE_KEY && len >= 65) { #if ENABLE_PRIVATE_KEY_IMPORT - mesh::LocalIdentity identity; - identity.readFrom(&cmd_frame[1], 64); - if (_store->saveMainIdentity(identity)) { - self_id = identity; - writeOKFrame(); - // re-load contacts, to invalidate ecdh shared_secrets - resetContacts(); - _store->loadContacts(this); + if (!mesh::LocalIdentity::validatePrivateKey(&cmd_frame[1])) { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid key } else { - writeErrFrame(ERR_CODE_FILE_IO_ERROR); + mesh::LocalIdentity identity; + identity.readFrom(&cmd_frame[1], 64); + if (_store->saveMainIdentity(identity)) { + self_id = identity; + writeOKFrame(); + // re-load contacts, to invalidate ecdh shared_secrets + resetContacts(); + _store->loadContacts(this); + } else { + writeErrFrame(ERR_CODE_FILE_IO_ERROR); + } } #else writeDisabledFrame(); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 59c21ae7..b30072b8 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -968,7 +968,6 @@ void MyMesh::formatPacketStatsReply(char *reply) { } void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { - self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -978,7 +977,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void MyMesh::clearStats() { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 2f929dd5..9d93eade 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -720,7 +720,6 @@ void MyMesh::setTxPower(uint8_t power_dbm) { } void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { - self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -730,7 +729,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void MyMesh::clearStats() { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index c384a761..201532b9 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -765,7 +765,6 @@ bool SensorMesh::formatFileSystem() { } void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { - self_id = new_id; #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) IdentityStore store(*_fs, ""); #elif defined(ESP32) @@ -775,7 +774,7 @@ void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) { #else #error "need to define saveIdentity()" #endif - store.save("_main", self_id); + store.save("_main", new_id); } void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) { diff --git a/src/Identity.cpp b/src/Identity.cpp index 83298928..ea546274 100644 --- a/src/Identity.cpp +++ b/src/Identity.cpp @@ -48,6 +48,50 @@ LocalIdentity::LocalIdentity(RNG* rng) { ed25519_create_keypair(pub_key, prv_key, seed); } +bool LocalIdentity::validatePrivateKey(const uint8_t prv[64]) { + uint8_t pub[32]; + ed25519_derive_pub(pub, prv); // derive public key from given private key + + // disallow 00 or FF prefixed public keys + if (pub[0] == 0x00 || pub[0] == 0xFF) return false; + + // known good test client keypair + const uint8_t test_client_prv[64] = { + 0x70, 0x65, 0xe1, 0x8f, 0xd9, 0xfa, 0xbb, 0x70, + 0xc1, 0xed, 0x90, 0xdc, 0xa1, 0x99, 0x07, 0xde, + 0x69, 0x8c, 0x88, 0xb7, 0x09, 0xea, 0x14, 0x6e, + 0xaf, 0xd9, 0x3d, 0x9b, 0x83, 0x0c, 0x7b, 0x60, + 0xc4, 0x68, 0x11, 0x93, 0xc7, 0x9b, 0xbc, 0x39, + 0x94, 0x5b, 0xa8, 0x06, 0x41, 0x04, 0xbb, 0x61, + 0x8f, 0x8f, 0xd7, 0xa8, 0x4a, 0x0a, 0xf6, 0xf5, + 0x70, 0x33, 0xd6, 0xe8, 0xdd, 0xcd, 0x64, 0x71 + }; + const uint8_t test_client_pub[32] = { + 0x1e, 0xc7, 0x71, 0x75, 0xb0, 0x91, 0x8e, 0xd2, + 0x06, 0xf9, 0xae, 0x04, 0xec, 0x13, 0x6d, 0x6d, + 0x5d, 0x43, 0x15, 0xbb, 0x26, 0x30, 0x54, 0x27, + 0xf6, 0x45, 0xb4, 0x92, 0xe9, 0x35, 0x0c, 0x10 + }; + + uint8_t ss1[32], ss2[32]; + + // shared secret we calculte from test client pubkey and given private key + ed25519_key_exchange(ss1, test_client_pub, prv); + + // shared secret they calculate from our derived public key and test client private key + ed25519_key_exchange(ss2, pub, test_client_prv); + + // check that both shared secrets match + if (memcmp(ss1, ss2, 32) != 0) return false; + + // reject all-zero shared secret + for (int i = 0; i < 32; i++) { + if (ss1[i] != 0) return true; + } + + return false; +} + bool LocalIdentity::readFrom(Stream& s) { bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE); success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE); diff --git a/src/Identity.h b/src/Identity.h index 60e8783b..c3ffcd75 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -76,6 +76,13 @@ public: */ void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const; + /** + * \brief Validates that a given private key can be used for ECDH / shared-secret operations. + * \param prv IN - the private key to validate (must be PRV_KEY_SIZE bytes) + * \returns true, if the private key is valid for login. + */ + static bool validatePrivateKey(const uint8_t prv[64]); + bool readFrom(Stream& s); bool writeTo(Stream& s) const; void printTo(Stream& s) const; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 2fc93006..878561c5 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -416,17 +416,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password)); savePrefs(); strcpy(reply, "OK"); - } else if (sender_timestamp == 0 && - memcmp(config, "prv.key ", 8) == 0) { // from serial command line only + } else if (memcmp(config, "prv.key ", 8) == 0) { uint8_t prv_key[PRV_KEY_SIZE]; bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]); - if (success) { + // only allow rekey if key is valid + if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) { mesh::LocalIdentity new_id; new_id.readFrom(prv_key, PRV_KEY_SIZE); _callbacks->saveIdentity(new_id); - strcpy(reply, "OK"); + strcpy(reply, "OK, reboot to apply! New pubkey: "); + mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE); } else { - strcpy(reply, "Error, invalid key"); + strcpy(reply, "Error, bad key"); } } else if (memcmp(config, "name ", 5) == 0) { if (isValidName(&config[5])) { From c16bcd2fe38e2b309961a917f9d47db952f737ef Mon Sep 17 00:00:00 2001 From: Chris <cisien@cisien.com> Date: Sat, 24 Jan 2026 20:06:29 -0800 Subject: [PATCH 417/546] Expose a counter to track RadioLib receive errors This change counts when readData returns an err code other than RADIOLIB_ERR_NONE. In most cases this is going to be a CRC error. This counter is exposed in the `stats-packets` command, and in the repeater stats payload (4 additional bytes to the payload, which is now 56 bytes with this change. My incompetent robot claims the total payload size is 96 bytes (unverified but probably close). --- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.h | 1 + src/helpers/StatsFormatHelper.h | 5 +++-- src/helpers/radiolib/RadioLibWrappers.cpp | 1 + src/helpers/radiolib/RadioLibWrappers.h | 5 +++-- 5 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index d926148d..69875304 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -226,7 +226,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t stats.n_direct_dups = ((SimpleMeshTables *)getTables())->getNumDirectDups(); stats.n_flood_dups = ((SimpleMeshTables *)getTables())->getNumFloodDups(); stats.total_rx_air_time_secs = getReceiveAirTime() / 1000; - + stats.n_recv_errors = radio_driver.getPacketsRecvErrors(); memcpy(&reply_data[4], &stats, sizeof(stats)); return 4 + sizeof(stats); // reply_len diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f930ee7e..ad1c7feb 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -54,6 +54,7 @@ struct RepeaterStats { int16_t last_snr; // x 4 uint16_t n_direct_dups, n_flood_dups; uint32_t total_rx_air_time_secs; + uint32_t n_recv_errors; }; #ifndef MAX_CLIENTS diff --git a/src/helpers/StatsFormatHelper.h b/src/helpers/StatsFormatHelper.h index d0107f3b..5aa01da9 100644 --- a/src/helpers/StatsFormatHelper.h +++ b/src/helpers/StatsFormatHelper.h @@ -42,13 +42,14 @@ public: uint32_t n_recv_flood, uint32_t n_recv_direct) { sprintf(reply, - "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u}", + "{\"recv\":%u,\"sent\":%u,\"flood_tx\":%u,\"direct_tx\":%u,\"flood_rx\":%u,\"direct_rx\":%u,\"recv_errors\":%u}", driver.getPacketsRecv(), driver.getPacketsSent(), n_sent_flood, n_sent_direct, n_recv_flood, - n_recv_direct + n_recv_direct, + driver.getPacketsRecvErrors() ); } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index e3407821..cf3e1266 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -105,6 +105,7 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { if (err != RADIOLIB_ERR_NONE) { MESH_DEBUG_PRINTLN("RadioLibWrapper: error: readData(%d)", err); len = 0; + n_recv_errors++; } else { // Serial.print(" readData() -> "); Serial.println(len); n_recv++; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 3c26d372..9ac1bbae 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -7,7 +7,7 @@ class RadioLibWrapper : public mesh::Radio { protected: PhysicalLayer* _radio; mesh::MainBoard* _board; - uint32_t n_recv, n_sent; + uint32_t n_recv, n_sent, n_recv_errors; int16_t _noise_floor, _threshold; uint16_t _num_floor_samples; int32_t _floor_sample_sum; @@ -45,8 +45,9 @@ public: void loop() override; uint32_t getPacketsRecv() const { return n_recv; } + uint32_t getPacketsRecvErrors() const { return n_recv_errors; } uint32_t getPacketsSent() const { return n_sent; } - void resetStats() { n_recv = n_sent = 0; } + void resetStats() { n_recv = n_sent = n_recv_errors = 0; } virtual float getLastRSSI() const override; virtual float getLastSNR() const override; From 7ae164217c54eb8ab2ad67cc71e1f63ee6c7b960 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 25 Jan 2026 18:35:55 +1100 Subject: [PATCH 418/546] * region names now don't need '#' prefix. (SHA still adds a '#' for back compat) --- src/helpers/RegionMap.cpp | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index fbc5f017..35692762 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -11,7 +11,11 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { bool RegionMap::is_name_char(uint8_t c) { // accept all alpha-num or accented characters, but exclude most punctuation chars - return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; + return c == '-' || c == '$' || c == '#' || (c >= '0' && c <= '9') || c >= 'A'; +} + +static const char* skip_hash(const char* name) { + return *name == '#' ? name + 1 : name; } static File openWrite(FILESYSTEM* _fs, const char* filename) { @@ -127,11 +131,17 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param) TransportKey keys[4]; int num; - if (region->name[0] == '#') { // auto hashtag region + if (region->name[0] == '$') { // private region + num = _store->loadKeysFor(region->id, keys, 4); + } else if (region->name[0] == '#') { // auto hashtag region _store->getAutoKeyFor(region->id, region->name, keys[0]); num = 1; - } else { - num = _store->loadKeysFor(region->id, keys, 4); + } else { // new: implicit auto hashtag region + char tmp[sizeof(region->name)]; + tmp[0] = '#'; + strcpy(&tmp[1], region->name); + _store->getAutoKeyFor(region->id, tmp, keys[0]); + num = 1; } for (int j = 0; j < num; j++) { uint16_t code = keys[j].calcTransportCode(packet); @@ -147,9 +157,10 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) { RegionEntry* RegionMap::findByName(const char* name) { if (strcmp(name, "*") == 0) return &wildcard; + if (*name == '#') { name++; } // ignore the '#' when matching by name for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (strcmp(name, region->name) == 0) return region; + if (strcmp(name, skip_hash(region->name)) == 0) return region; } return NULL; // not found } @@ -157,11 +168,12 @@ RegionEntry* RegionMap::findByName(const char* name) { RegionEntry* RegionMap::findByNamePrefix(const char* prefix) { if (strcmp(prefix, "*") == 0) return &wildcard; + if (*prefix == '#') { prefix++; } // ignore the '#' when matching by name RegionEntry* partial = NULL; for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one - if (memcmp(prefix, region->name, strlen(prefix)) == 0) { + if (strcmp(prefix, skip_hash(region->name)) == 0) return region; // is a complete match, preference this one + if (memcmp(prefix, skip_hash(region->name), strlen(prefix)) == 0) { partial = region; } } @@ -220,9 +232,9 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream& } if (parent->flags & REGION_DENY_FLOOD) { - out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : ""); + out.printf("%s%s\n", skip_hash(parent->name), parent->id == home_id ? "^" : ""); } else { - out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : ""); + out.printf("%s%s F\n", skip_hash(parent->name), parent->id == home_id ? "^" : ""); } for (int i = 0; i < num_regions; i++) { @@ -247,9 +259,10 @@ int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) - int len = strlen(region->name); + const char* name = skip_hash(region->name); + int len = strlen(name); if ((dp - dest) + len + 2 < max_len) { // only append if name will fit - memcpy(dp, region->name, len); + memcpy(dp, name, len); dp += len; *dp++ = ','; } From c7ac16f0e371b92c111f15124a271a5cba1ec7b5 Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Mon, 26 Jan 2026 13:48:15 +0800 Subject: [PATCH 419/546] Add v4-tft code. --- src/helpers/ui/SSD1306Display.cpp | 14 +- src/helpers/ui/SSD1306Display.h | 9 +- src/helpers/ui/ST7789LCDDisplay.cpp | 5 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4/HeltecV4Board.cpp | 6 +- variants/heltec_v4/platformio.ini | 266 +++++++++++++++++++++++---- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 6 +- 8 files changed, 267 insertions(+), 45 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index c9da0cf8..4e7fd10a 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -7,6 +7,10 @@ bool SSD1306Display::i2c_probe(TwoWire& wire, uint8_t addr) { } bool SSD1306Display::begin() { + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } #ifdef DISPLAY_ROTATION display.setRotation(DISPLAY_ROTATION); #endif @@ -15,12 +19,18 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { display.ssd1306_command(SSD1306_DISPLAYON); - _isOn = true; + if (!_isOn) { + if (_peripher_power) _peripher_power->claim(); + _isOn = true; + } } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); - _isOn = false; + if (_isOn) { + if (_peripher_power) _peripher_power->release(); + _isOn = false; + } } void SSD1306Display::clear() { diff --git a/src/helpers/ui/SSD1306Display.h b/src/helpers/ui/SSD1306Display.h index 1a3a9602..d843da85 100644 --- a/src/helpers/ui/SSD1306Display.h +++ b/src/helpers/ui/SSD1306Display.h @@ -5,6 +5,7 @@ #include <Adafruit_GFX.h> #define SSD1306_NO_SPLASH #include <Adafruit_SSD1306.h> +#include <helpers/RefCountedDigitalPin.h> #ifndef PIN_OLED_RESET #define PIN_OLED_RESET 21 // Reset pin # (or -1 if sharing Arduino reset pin) @@ -18,10 +19,16 @@ class SSD1306Display : public DisplayDriver { Adafruit_SSD1306 display; bool _isOn; uint8_t _color; + RefCountedDigitalPin* _peripher_power; bool i2c_probe(TwoWire& wire, uint8_t addr); public: - SSD1306Display() : DisplayDriver(128, 64), display(128, 64, &Wire, PIN_OLED_RESET) { _isOn = false; } + SSD1306Display(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), + display(128, 64, &Wire, PIN_OLED_RESET), + _peripher_power(peripher_power) + { + _isOn = false; + } bool begin(); bool isOn() override { return _isOn; } diff --git a/src/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index 97d82f42..9fd0b23d 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -28,11 +28,14 @@ bool ST7789LCDDisplay::begin() { digitalWrite(PIN_TFT_LEDA_CTL, HIGH); } if (PIN_TFT_RST != -1) { + pinMode(PIN_TFT_RST, OUTPUT); + digitalWrite(PIN_TFT_RST, LOW); + delay(10); digitalWrite(PIN_TFT_RST, HIGH); } // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); #endif diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index a8077148..5b960ca1 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include <helpers/RefCountedDigitalPin.h> class ST7789LCDDisplay : public DisplayDriver { - #ifdef LILYGO_TDECK + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ public: { _isOn = false; } -#elif LILYGO_TDECK +#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f143db36..92f93437 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -86,5 +86,9 @@ void HeltecV4Board::begin() { } const char* HeltecV4Board::getManufacturerName() const { - return "Heltec V4"; + #ifdef HELTEC_LORA_V4_TFT + return "Heltec V4 TFT"; + #else + return "Heltec V4 OLED"; + #endif } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ecfd7889..ba759009 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,11 +20,9 @@ build_flags = -D P_LORA_PA_POWER=7 ;power en -D P_LORA_PA_EN=2 -D P_LORA_PA_TX_EN=46 ;enable tx - -D PIN_BOARD_SDA=17 - -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=HIGH + -D PIN_VEXT_EN_ACTIVE=LOW -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_DIO2_AS_RF_SWITCH=true @@ -47,10 +45,44 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:heltec_v4_repeater] +[heltec_v4_oled] extends = Heltec_lora32_v4 build_flags = ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D ENV_PIN_SDA=4 + -D ENV_PIN_SCL=3 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = ${Heltec_lora32_v4.lib_deps} + +[heltec_v4_tft] +extends = Heltec_lora32_v4 +build_flags = + ${Heltec_lora32_v4.build_flags} + -D HELTEC_LORA_V4_TFT + -D PIN_BOARD_SDA=4 + -D PIN_BOARD_SCL=3 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=18 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=21 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=15 + -D PIN_TFT_DC=16 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=33 +build_src_filter= ${Heltec_lora32_v4.build_src_filter} +lib_deps = + ${Heltec_lora32_v4.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:heltec_v4_repeater] +extends = heltec_v4_oled +build_flags = + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Repeater"' -D ADVERT_LAT=0.0 @@ -59,18 +91,18 @@ build_flags = -D MAX_NEIGHBOURS=50 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 [env:heltec_v4_repeater_bridge_espnow] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 @@ -81,18 +113,18 @@ build_flags = ; -D BRIDGE_DEBUG=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_room_server] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"Heltec Room"' -D ADVERT_LAT=0.0 @@ -101,50 +133,50 @@ build_flags = -D ROOM_PASSWORD='"hello"' ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_room_server> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} ${esp32_ota.lib_deps} [env:heltec_v4_terminal_chat] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_usb] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_ble] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -155,20 +187,20 @@ build_flags = -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_companion_radio_wifi] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} + ${heltec_v4_oled.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -176,24 +208,23 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' - -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} densaugeo/base64 @ ~1.4.0 [env:heltec_v4_sensor] -extends = Heltec_lora32_v4 +extends = heltec_v4_oled build_flags = - ${Heltec_lora32_v4.build_flags} - -D ADVERT_NAME='"Heltec v3 Sensor"' + ${heltec_v4_oled.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 -D ADMIN_PASSWORD='"password"' @@ -202,9 +233,172 @@ build_flags = -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -build_src_filter = ${Heltec_lora32_v4.build_src_filter} +build_src_filter = ${heltec_v4_oled.build_src_filter} +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_sensor> lib_deps = - ${Heltec_lora32_v4.lib_deps} + ${heltec_v4_oled.lib_deps} + ${esp32_ota.lib_deps} + + +[env:heltec_v4_tft_repeater] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + + +[env:heltec_v4_tft_repeater_bridge_espnow] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_room_server] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_tft_terminal_chat] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_usb] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_ble] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=ST7789LCDDisplay + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 ; dynamic, random PIN + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_companion_radio_wifi] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_tft_sensor] +extends = heltec_v4_tft +build_flags = + ${heltec_v4_tft.build_flags} + -D ADVERT_NAME='"Heltec v4 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=3 + -D ENV_PIN_SCL=4 + -D DISPLAY_CLASS=ST7789LCDDisplay +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${heltec_v4_tft.build_src_filter} + +<helpers/ui/ST7789LCDDisplay.cpp> + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_tft.lib_deps} ${esp32_ota.lib_deps} diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 015c3a8e..0d2bd497 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; + DISPLAY_CLASS display(&(board.periph_power)); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index a153b2af..00d2adab 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -9,7 +9,11 @@ #include <helpers/SensorManager.h> #include <helpers/sensors/EnvironmentSensorManager.h> #ifdef DISPLAY_CLASS - #include <helpers/ui/SSD1306Display.h> +#ifdef HELTEC_LORA_V4_OLED + #include <helpers/ui/SSD1306Display.h> +#elif defined(HELTEC_LORA_V4_TFT) + #include <helpers/ui/ST7789LCDDisplay.h> +#endif #include <helpers/ui/MomentaryButton.h> #endif From ed589f9620cf7a97c62ae64e9e42e48aca1375aa Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Mon, 26 Jan 2026 22:20:36 +1300 Subject: [PATCH 420/546] boot adverts are now zero hop instead of flood --- examples/simple_repeater/MyMesh.cpp | 8 ++++++-- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_repeater/main.cpp | 4 ++-- examples/simple_room_server/MyMesh.cpp | 8 ++++++-- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_room_server/main.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 8 ++++++-- examples/simple_sensor/SensorMesh.h | 2 +- examples/simple_sensor/main.cpp | 4 ++-- src/helpers/CommonCLI.cpp | 3 ++- src/helpers/CommonCLI.h | 2 +- 11 files changed, 30 insertions(+), 17 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b838bdcb..2d905511 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -854,10 +854,14 @@ bool MyMesh::formatFileSystem() { #endif } -void MyMesh::sendSelfAdvertisement(int delay_millis) { +void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + if (flood) { + sendFlood(pkt, delay_millis); + } else { + sendZeroHop(pkt, delay_millis); + } } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 38eb658d..60d22902 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -187,7 +187,7 @@ public: void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; - void sendSelfAdvertisement(int delay_millis) override; + void sendSelfAdvertisement(int delay_millis, bool flood) override; void updateAdvertTimer() override; void updateFloodAdvertTimer() override; diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 8c745613..d7e10fe2 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -87,8 +87,8 @@ void setup() { ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + // send out initial zero hop Advertisement to the mesh + the_mesh.sendSelfAdvertisement(16000, false); } void loop() { diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index bb62e618..22a3d208 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -675,10 +675,14 @@ bool MyMesh::formatFileSystem() { #endif } -void MyMesh::sendSelfAdvertisement(int delay_millis) { +void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + if (flood) { + sendFlood(pkt, delay_millis); + } else { + sendZeroHop(pkt, delay_millis); + } } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f6adf01e..4f3ed0e4 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -177,7 +177,7 @@ public: void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override; bool formatFileSystem() override; - void sendSelfAdvertisement(int delay_millis) override; + void sendSelfAdvertisement(int delay_millis, bool flood) override; void updateAdvertTimer() override; void updateFloodAdvertTimer() override; diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 1a3b4d6e..2c76ba0c 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -76,8 +76,8 @@ void setup() { ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + // send out initial zero hop Advertisement to the mesh + the_mesh.sendSelfAdvertisement(16000, false); } void loop() { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 201532b9..8e27323e 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -787,10 +787,14 @@ void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t revert_radio_at = futureMillis(2000 + timeout_mins*60*1000); // schedule when to revert radio params } -void SensorMesh::sendSelfAdvertisement(int delay_millis) { +void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { - sendFlood(pkt, delay_millis); + if (flood) { + sendFlood(pkt, delay_millis); + } else { + sendZeroHop(pkt, delay_millis); + } } else { MESH_DEBUG_PRINTLN("ERROR: unable to create advertisement packet!"); } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 6046a1df..eb2d90c5 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -60,7 +60,7 @@ public: NodePrefs* getNodePrefs() { return &_prefs; } void savePrefs() override { _cli.savePrefs(_fs); } bool formatFileSystem() override; - void sendSelfAdvertisement(int delay_millis) override; + void sendSelfAdvertisement(int delay_millis, bool flood) override; void updateAdvertTimer() override; void updateFloodAdvertTimer() override; void setLoggingOn(bool enable) override { } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index a5fcc148..ab2842e0 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -110,8 +110,8 @@ void setup() { ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif - // send out initial Advertisement to the mesh - the_mesh.sendSelfAdvertisement(16000); + // send out initial zero hop Advertisement to the mesh + the_mesh.sendSelfAdvertisement(16000, false); } void loop() { diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index a8dd9d09..db830285 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -197,7 +197,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { - _callbacks->sendSelfAdvertisement(1500); // longer delay, give CLI response time to be sent first + // send flood advert + _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first strcpy(reply, "OK - Advert sent"); } else if (memcmp(command, "clock sync", 10) == 0) { uint32_t curr = getRTCClock()->getCurrentTime(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index b0530102..8661d1e6 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -61,7 +61,7 @@ public: virtual const char* getBuildDate() = 0; virtual const char* getRole() = 0; virtual bool formatFileSystem() = 0; - virtual void sendSelfAdvertisement(int delay_millis) = 0; + virtual void sendSelfAdvertisement(int delay_millis, bool flood) = 0; virtual void updateAdvertTimer() = 0; virtual void updateFloodAdvertTimer() = 0; virtual void setLoggingOn(bool enable) = 0; From d13bc446de689277ce59208c9bcac90ad8e58453 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Mon, 26 Jan 2026 22:39:39 +1300 Subject: [PATCH 421/546] added build flag to enable/disable boot advert --- examples/simple_repeater/main.cpp | 2 ++ examples/simple_room_server/main.cpp | 2 ++ examples/simple_secure_chat/main.cpp | 2 ++ examples/simple_sensor/main.cpp | 2 ++ platformio.ini | 1 + 5 files changed, 9 insertions(+) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d7e10fe2..d55d6118 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -88,7 +88,9 @@ void setup() { #endif // send out initial zero hop Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvertisement(16000, false); +#endif } void loop() { diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index 2c76ba0c..825fb007 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -77,7 +77,9 @@ void setup() { #endif // send out initial zero hop Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvertisement(16000, false); +#endif } void loop() { diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index da1bac5b..018ec2a2 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -582,7 +582,9 @@ void setup() { the_mesh.showWelcome(); // send out initial Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvert(1200); // add slight delay +#endif } void loop() { diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index ab2842e0..330adcc2 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -111,7 +111,9 @@ void setup() { #endif // send out initial zero hop Advertisement to the mesh +#if ENABLE_ADVERT_ON_BOOT == 1 the_mesh.sendSelfAdvertisement(16000, false); +#endif } void loop() { diff --git a/platformio.ini b/platformio.ini index 75d37e86..743e357a 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,6 +27,7 @@ build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 -D LORA_FREQ=869.525 -D LORA_BW=250 -D LORA_SF=11 + -D ENABLE_ADVERT_ON_BOOT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 ; NOTE: comment these out for more secure firmware -D ENABLE_PRIVATE_KEY_EXPORT=1 -D RADIOLIB_EXCLUDE_CC1101=1 From 7e24bd00b9f768f3c537a0514263b85257fdffb4 Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Mon, 26 Jan 2026 23:05:10 +1300 Subject: [PATCH 422/546] increase maximum flood advert interval to 168 hours (7 days) --- src/helpers/CommonCLI.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index db830285..93baad5b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -422,8 +422,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch strcpy(reply, "OK"); } else if (memcmp(config, "flood.advert.interval ", 22) == 0) { int hours = _atoi(&config[22]); - if ((hours > 0 && hours < 3) || (hours > 48)) { - strcpy(reply, "Error: interval range is 3-48 hours"); + if ((hours > 0 && hours < 3) || (hours > 168)) { + strcpy(reply, "Error: interval range is 3-168 hours"); } else { _prefs->flood_advert_interval = (uint8_t)(hours); _callbacks->updateFloodAdvertTimer(); From 0805a47f35972636cac41466586771ba2e7c51a6 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper <m.wientapper@gmx.de> Date: Sat, 24 Jan 2026 21:56:33 +0100 Subject: [PATCH 423/546] Add output of region cmd via lora cli Add cli commands "region list {allowed|denied}" --- examples/simple_repeater/MyMesh.cpp | 23 +++++++++- src/helpers/RegionMap.cpp | 68 ++++++++++++++++++++++++++--- src/helpers/RegionMap.h | 6 ++- 3 files changed, 86 insertions(+), 11 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b30072b8..b0634008 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1068,8 +1068,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply const char* parts[4]; int n = mesh::Utils::parseTextParts(command, parts, 4, ' '); - if (n == 1 && sender_timestamp == 0) { - region_map.exportTo(Serial); + if (n == 1) { + region_map.exportTo(reply, 160); } else if (n >= 2 && strcmp(parts[1], "load") == 0) { temp_map.resetFrom(region_map); // rebuild regions in a temp instance memset(load_stack, 0, sizeof(load_stack)); @@ -1142,6 +1142,25 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - not found"); } + } else if (n >= 3 && strcmp(parts[1], "list") == 0) { + uint8_t mask = 0; + bool invert = false; + + if (strcmp(parts[2], "allowed") == 0) { + mask = REGION_DENY_FLOOD; + invert = false; // list regions that DON'T have DENY flag + } else if (strcmp(parts[2], "denied") == 0) { + mask = REGION_DENY_FLOOD; + invert = true; // list regions that DO have DENY flag + } else { + strcpy(reply, "Err - use 'allowed' or 'denied'"); + return; + } + + int len = region_map.exportNamesTo(reply, 160, mask, invert); + if (len == 0) { + strcpy(reply, "-none-"); + } } else { strcpy(reply, "Err - ??"); } diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 35692762..4ff8233e 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -2,6 +2,45 @@ #include <helpers/TxtDataHelpers.h> #include <SHA256.h> +// helper class for region map exporter, we emulate Stream with a safe buffer writer. + +class BufStream : public Stream { +public: + BufStream(char *buf, size_t max) + : _buf(buf), _max(max), _pos(0) { + if (_max > 0) _buf[0] = 0; + } + + size_t write(uint8_t c) override { + if (_pos + 1 >= _max) return 0; + _buf[_pos++] = c; + _buf[_pos] = 0; + return 1; + } + + size_t write(const uint8_t *buffer, size_t size) override { + size_t written = 0; + while (written < size) { + if (!write(buffer[written])) break; + written++; + } + return written; + } + + int available() override { return 0; } + int read() override { return -1; } + int peek() override { return -1; } + void flush() override {} + + size_t length() const { return _pos; } + +private: + char *_buf; + size_t _max; + size_t _pos; +}; + + RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) { next_id = 1; num_regions = 0; home_id = 0; wildcard.id = wildcard.parent = 0; @@ -249,25 +288,40 @@ void RegionMap::exportTo(Stream& out) const { printChildRegions(0, &wildcard, out); // recursive } -int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) { +size_t RegionMap::exportTo(char *dest, size_t max_len) const { + if (!dest || max_len == 0) return 0; + + BufStream bs(dest, max_len); + exportTo(bs); // ← reuse existing logic + return bs.length(); +} + +int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert) { char *dp = dest; - if ((wildcard.flags & mask) == 0) { + + // Check wildcard region + bool wildcard_matches = invert ? (wildcard.flags & mask) : !(wildcard.flags & mask); + if (wildcard_matches) { *dp++ = '*'; *dp++ = ','; } - for (int i = 0; i < num_regions; i++) { + for (int i = 0; i < num_regions; i++) { auto region = ®ions[i]; - if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param) - const char* name = skip_hash(region->name); - int len = strlen(name); + + // Check if region matches the filter criteria + bool region_matches = invert ? (region->flags & mask) : !(region->flags & mask); + + if (region_matches) { + int len = strlen(skip_hash(region->name)); if ((dp - dest) + len + 2 < max_len) { // only append if name will fit - memcpy(dp, name, len); + memcpy(dp, skip_hash(region->name), len); dp += len; *dp++ = ','; } } } + if (dp > dest) { dp--; } // don't include trailing comma *dp = 0; // set null terminator diff --git a/src/helpers/RegionMap.h b/src/helpers/RegionMap.h index 01174d09..3ebff1ba 100644 --- a/src/helpers/RegionMap.h +++ b/src/helpers/RegionMap.h @@ -49,7 +49,9 @@ public: int getCount() const { return num_regions; } const RegionEntry* getByIdx(int i) const { return ®ions[i]; } const RegionEntry* getRoot() const { return &wildcard; } - int exportNamesTo(char *dest, int max_len, uint8_t mask); + int exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert = false); - void exportTo(Stream& out) const; + void exportTo(Stream& out) const; + size_t exportTo(char *dest, size_t max_len) const; + }; From 5a20e8674f59697f46d5f8012eec8c6097b372c8 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 27 Jan 2026 14:04:12 +1100 Subject: [PATCH 424/546] support for meshtiny --- boards/meshtiny.json | 74 ++++++++++++++++++++++ variants/meshtiny/MeshtinyBoard.cpp | 44 +++++++++++++ variants/meshtiny/MeshtinyBoard.h | 66 +++++++++++++++++++ variants/meshtiny/platformio.ini | 68 ++++++++++++++++++++ variants/meshtiny/target.cpp | 47 ++++++++++++++ variants/meshtiny/target.h | 33 ++++++++++ variants/meshtiny/variant.cpp | 51 +++++++++++++++ variants/meshtiny/variant.h | 98 +++++++++++++++++++++++++++++ 8 files changed, 481 insertions(+) create mode 100644 boards/meshtiny.json create mode 100644 variants/meshtiny/MeshtinyBoard.cpp create mode 100644 variants/meshtiny/MeshtinyBoard.h create mode 100644 variants/meshtiny/platformio.ini create mode 100644 variants/meshtiny/target.cpp create mode 100644 variants/meshtiny/target.h create mode 100644 variants/meshtiny/variant.cpp create mode 100644 variants/meshtiny/variant.h diff --git a/boards/meshtiny.json b/boards/meshtiny.json new file mode 100644 index 00000000..0418dc3b --- /dev/null +++ b/boards/meshtiny.json @@ -0,0 +1,74 @@ +{ + "build": { + "arduino": { + "ldscript": "nrf52840_s140_v6.ld" + }, + "core": "nRF5", + "cpu": "cortex-m4", + "extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", + "f_cpu": "64000000L", + "hwids": [ + [ + "0x239A", + "0x8029" + ], + [ + "0x239A", + "0x0029" + ], + [ + "0x239A", + "0x002A" + ], + [ + "0x239A", + "0x802A" + ] + ], + "usb_product": "Meshtiny", + "mcu": "nrf52840", + "variant": "meshtiny", + "bsp": { + "name": "adafruit" + }, + "softdevice": { + "sd_flags": "-DS140", + "sd_name": "s140", + "sd_version": "6.1.1", + "sd_fwid": "0x00B6" + }, + "bootloader": { + "settings_addr": "0xFF000" + } + }, + "connectivity": [ + "bluetooth" + ], + "debug": { + "jlink_device": "nRF52840_xxAA", + "svd_path": "nrf52840.svd", + "openocd_target": "nrf52840-mdk-rs" + }, + "frameworks": [ + "arduino", + "freertos" + ], + "name": "Meshtiny", + "upload": { + "maximum_ram_size": 248832, + "maximum_size": 815104, + "speed": 115200, + "protocol": "nrfutil", + "protocols": [ + "jlink", + "nrfjprog", + "nrfutil", + "stlink" + ], + "use_1200bps_touch": true, + "require_upload_port": true, + "wait_for_upload_port": true + }, + "url": "https://shop.mtoolstec.com/product/meshtiny", + "vendor": "MTools Tec" +} diff --git a/variants/meshtiny/MeshtinyBoard.cpp b/variants/meshtiny/MeshtinyBoard.cpp new file mode 100644 index 00000000..a2cdcb08 --- /dev/null +++ b/variants/meshtiny/MeshtinyBoard.cpp @@ -0,0 +1,44 @@ +#include "MeshtinyBoard.h" + +#include <Arduino.h> +#include <Wire.h> +#include <bluefruit.h> + +static BLEDfu bledfu; + +static void connect_callback(uint16_t conn_handle) { + (void)conn_handle; + MESH_DEBUG_PRINTLN("BLE client connected"); +} + +static void disconnect_callback(uint16_t conn_handle, uint8_t reason) { + (void)conn_handle; + (void)reason; + + MESH_DEBUG_PRINTLN("BLE client disconnected"); +} + +void MeshtinyBoard::begin() { + NRF52BoardDCDC::begin(); + btn_prev_state = HIGH; + + pinMode(PIN_VBAT_READ, INPUT); // VBAT ADC input + + // Set all button pins to INPUT_PULLUP + pinMode(PIN_BUTTON1, INPUT_PULLUP); + pinMode(PIN_BUTTON2, INPUT_PULLUP); + pinMode(PIN_BUTTON3, INPUT_PULLUP); + pinMode(PIN_BUTTON4, INPUT_PULLUP); + +#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) + Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); +#endif + + Wire.begin(); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); // give sx1262 some time to power up +} + + diff --git a/variants/meshtiny/MeshtinyBoard.h b/variants/meshtiny/MeshtinyBoard.h new file mode 100644 index 00000000..a73c9ea3 --- /dev/null +++ b/variants/meshtiny/MeshtinyBoard.h @@ -0,0 +1,66 @@ +#pragma once + +#include <Arduino.h> +#include <MeshCore.h> +#include <helpers/NRF52Board.h> + +class MeshtinyBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +protected: + uint8_t btn_prev_state; + +public: + MeshtinyBoard() : NRF52BoardOTA("Meshtiny OTA") {} + void begin(); + +#if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on + } + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off + } +#endif + + uint16_t getBattMilliVolts() override { + int adcvalue = 0; + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(10); + adcvalue = analogRead(PIN_VBAT_READ); + return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096; + } + + const char *getManufacturerName() const override { return "Meshtiny"; } + + void reboot() override { NVIC_SystemReset(); } + + void powerOff() override { + +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW) { + delay(10); + } +#endif + +#ifdef PIN_3V3_EN + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, LOW); +#endif + + +#ifdef PIN_LED1 + digitalWrite(PIN_LED1, LOW); +#endif + +#ifdef PIN_LED2 + digitalWrite(PIN_LED2, LOW); +#endif + +#ifdef PIN_USER_BTN + nrf_gpio_cfg_sense_input(g_ADigitalPinMap[PIN_USER_BTN], NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); +#endif + + sd_power_system_off(); + } + +}; diff --git a/variants/meshtiny/platformio.ini b/variants/meshtiny/platformio.ini new file mode 100644 index 00000000..14e5c60d --- /dev/null +++ b/variants/meshtiny/platformio.ini @@ -0,0 +1,68 @@ +[Meshtiny] +extends = nrf52_base +board = meshtiny +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -I variants/meshtiny + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D PIN_3V3_EN=34 + -D MESHTINY + -D UI_HAS_JOYSTICK +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/meshtiny> + +<helpers/ui/SSD1306Display.cpp> + +<helpers/ui/buzzer.cpp> + +<helpers/sensors> +lib_deps = + ${nrf52_base.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:Meshtiny_companion_radio_usb] +extends = Meshtiny +build_flags = + ${Meshtiny.build_flags} + -I examples/companion_radio/ui-new + -D MESHTINY + -D PIN_BUZZER=30 + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Meshtiny.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Meshtiny.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:Meshtiny_companion_radio_ble] +extends = Meshtiny +build_flags = + ${Meshtiny.build_flags} + -I examples/companion_radio/ui-new + -D MESHTINY + -D PIN_BUZZER=30 + -D DISPLAY_CLASS=SSD1306Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +; -D BLE_DEBUG_LOGGING=1 +build_src_filter = ${Meshtiny.build_src_filter} + +<helpers/nrf52/SerialBLEInterface.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Meshtiny.lib_deps} + densaugeo/base64 @ ~1.4.0 diff --git a/variants/meshtiny/target.cpp b/variants/meshtiny/target.cpp new file mode 100644 index 00000000..5fc60eae --- /dev/null +++ b/variants/meshtiny/target.cpp @@ -0,0 +1,47 @@ +#include "target.h" + +#include <Arduino.h> +#include <helpers/ArduinoHelpers.h> + +MeshtinyBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(); + +#ifdef DISPLAY_CLASS +DISPLAY_CLASS display; + MomentaryButton user_btn(ENCODER_PRESS, 1000, true, true); + MomentaryButton joystick_left(ENCODER_LEFT, 1000, true, true); + MomentaryButton joystick_right(ENCODER_RIGHT, 1000, true, true); + MomentaryButton back_btn(PIN_SIDE_BUTTON, 1000, true, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/meshtiny/target.h b/variants/meshtiny/target.h new file mode 100644 index 00000000..8ee3ee86 --- /dev/null +++ b/variants/meshtiny/target.h @@ -0,0 +1,33 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <MeshtinyBoard.h> +#include <RadioLib.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#ifdef DISPLAY_CLASS +#include <helpers/ui/MomentaryButton.h> +#include <helpers/ui/SSD1306Display.h> +#endif +#include <helpers/sensors/EnvironmentSensorManager.h> + +extern MeshtinyBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; +extern MomentaryButton joystick_left; +extern MomentaryButton joystick_right; +extern MomentaryButton back_btn; +#endif + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/meshtiny/variant.cpp b/variants/meshtiny/variant.cpp new file mode 100644 index 00000000..7cec7dec --- /dev/null +++ b/variants/meshtiny/variant.cpp @@ -0,0 +1,51 @@ +/* + Copyright (c) 2014-2015 Arduino LLC. All right reserved. + Copyright (c) 2016 Sandeep Mistry All right reserved. + Copyright (c) 2018, Adafruit Industries (adafruit.com) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#include "variant.h" + +#include "nrf.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const uint32_t g_ADigitalPinMap[] = { + // P0 + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, + 29, 30, 31, + + // P1 + 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + // LED1 & LED2 +#ifdef PIN_LED1 + pinMode(PIN_LED1, OUTPUT); + ledOff(PIN_LED1); +#endif + +#ifdef PIN_LED2 + pinMode(PIN_LED2, OUTPUT); + ledOff(PIN_LED2); +#endif + + // 3V3 Power Rail - nothing connected on meshtiny + pinMode(PIN_3V3_EN, OUTPUT); + digitalWrite(PIN_3V3_EN, LOW); +} diff --git a/variants/meshtiny/variant.h b/variants/meshtiny/variant.h new file mode 100644 index 00000000..daa8eff5 --- /dev/null +++ b/variants/meshtiny/variant.h @@ -0,0 +1,98 @@ +#ifndef _MESHTINY_H_ +#define _MESHTINY_H_ + +/** Master clock frequency */ +#define VARIANT_MCK (64000000ul) + +#define USE_LFXO // Board uses 32khz crystal for LF + +/*---------------------------------------------------------------------------- + * Headers + *----------------------------------------------------------------------------*/ + +#include "WVariant.h" + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (6) +#define NUM_ANALOG_OUTPUTS (0) + +// LEDs +#define PIN_LED1 (35) // Green LED +#define PIN_LED2 (36) // Blue LED + +#define LED_RED (-1) +#define LED_GREEN PIN_LED1 +#define LED_BLUE (-1) // Disable annoying flashing caused by Bluefruit + +#define P_LORA_TX_LED PIN_LED2 // Blue LED +// #define PIN_STATUS_LED LED_GREEN // disable status led. +#define LED_BUILTIN LED_GREEN +#define PIN_LED LED_BUILTIN +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON HIGH + +// Buttons +#define PIN_BUTTON1 (9) // side button +#define PIN_BUTTON2 (4) // encoder left +#define PIN_BUTTON3 (26) // encoder right +#define PIN_BUTTON4 (28) // encoder press +#define PIN_SIDE_BUTTON PIN_BUTTON1 +#define ENCODER_LEFT PIN_BUTTON2 +#define ENCODER_RIGHT PIN_BUTTON3 +#define ENCODER_PRESS PIN_BUTTON4 +#define PIN_USER_BTN PIN_SIDE_BUTTON + +// VBAT sensing +#define PIN_VBAT_READ (5) +#define BATTERY_SENSE_RESOLUTION_BITS 12 +#define BATTERY_SENSE_RESOLUTION 4096.0 +#define AREF_VOLTAGE 3.0 +#define VBAT_AR_INTERNAL AR_INTERNAL_3_0 +#define ADC_MULTIPLIER 1.73 +#define ADC_RESOLUTION 14 + +// Serial interfaces +#define PIN_SERIAL1_RX (15) +#define PIN_SERIAL1_TX (16) +#define PIN_SERIAL2_RX (8) // Connected to Jlink CDC +#define PIN_SERIAL2_TX (6) + +// SPI Interfaces +#define SPI_INTERFACES_COUNT 2 + +#define PIN_SPI_MISO (45) +#define PIN_SPI_MOSI (44) +#define PIN_SPI_SCK (43) + +#define PIN_SPI1_MISO (29) +#define PIN_SPI1_MOSI (30) +#define PIN_SPI1_SCK (3) + +// LoRa SX1262 module pins +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI +#define P_LORA_DIO_1 (47) +#define P_LORA_RESET (38) +#define P_LORA_BUSY (46) +#define P_LORA_NSS (42) +#define SX126X_POWER_EN (37) + +#define SX126X_RXEN RADIOLIB_NC +#define SX126X_TXEN RADIOLIB_NC + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE (1.8f) + +// Wire Interfaces +#define WIRE_INTERFACES_COUNT 1 +#define PIN_WIRE_SDA (13) +#define PIN_WIRE_SCL (14) +#define PIN_BOARD_SDA (13) +#define PIN_BOARD_SCL (14) + +// Power control +#define PIN_3V3_EN (34) // nothing connected on meshtiny board + +#endif // _MESHTINY_H_ From 562750098832bf7fc62ec8e39e02e6e8704562ed Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 27 Jan 2026 15:22:18 +1100 Subject: [PATCH 425/546] * new "clkreboot" CLI command --- src/helpers/CommonCLI.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 93baad5b..42198b49 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -196,6 +196,10 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) { void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) { if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return + } else if (memcmp(command, "clkreboot", 9) == 0) { + // Reset clock + getRTCClock()->setCurrentTime(1715770351); // 15 May 2024, 8:50pm + _board->reboot(); // doesn't return } else if (memcmp(command, "advert", 6) == 0) { // send flood advert _callbacks->sendSelfAdvertisement(1500, true); // longer delay, give CLI response time to be sent first From 5ff6e813bd62f8565eee36eabf37e3b25e12f48e Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 27 Jan 2026 18:16:21 +1100 Subject: [PATCH 426/546] * Fix: RegionMap build fail on _max --- src/helpers/RegionMap.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/helpers/RegionMap.cpp b/src/helpers/RegionMap.cpp index 4ff8233e..2cc47e1d 100644 --- a/src/helpers/RegionMap.cpp +++ b/src/helpers/RegionMap.cpp @@ -6,13 +6,13 @@ class BufStream : public Stream { public: - BufStream(char *buf, size_t max) - : _buf(buf), _max(max), _pos(0) { - if (_max > 0) _buf[0] = 0; + BufStream(char *buf, size_t max_len) + : _buf(buf), _max_len(max_len), _pos(0) { + if (_max_len > 0) _buf[0] = 0; } size_t write(uint8_t c) override { - if (_pos + 1 >= _max) return 0; + if (_pos + 1 >= _max_len) return 0; _buf[_pos++] = c; _buf[_pos] = 0; return 1; @@ -36,7 +36,7 @@ public: private: char *_buf; - size_t _max; + size_t _max_len; size_t _pos; }; From 4a83a6658a83baf96d39e7530b8e8158a577a382 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Wed, 28 Jan 2026 00:59:42 +1100 Subject: [PATCH 427/546] build fix for meshtiny (nrf52board ota refactor) --- variants/meshtiny/MeshtinyBoard.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/meshtiny/MeshtinyBoard.h b/variants/meshtiny/MeshtinyBoard.h index a73c9ea3..b69c0e41 100644 --- a/variants/meshtiny/MeshtinyBoard.h +++ b/variants/meshtiny/MeshtinyBoard.h @@ -4,12 +4,12 @@ #include <MeshCore.h> #include <helpers/NRF52Board.h> -class MeshtinyBoard : public NRF52BoardDCDC, public NRF52BoardOTA { +class MeshtinyBoard : public NRF52BoardDCDC { protected: uint8_t btn_prev_state; public: - MeshtinyBoard() : NRF52BoardOTA("Meshtiny OTA") {} + MeshtinyBoard() : NRF52Board("Meshtiny OTA") {} void begin(); #if defined(P_LORA_TX_LED) From 3845a1c0219cc4011afa7671040c855eb7eeb44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Piotr=20Ma=C5=82ek?= <git@malek.pm> Date: Tue, 27 Jan 2026 16:29:31 +0100 Subject: [PATCH 428/546] Fix incorrect INA260 address in debug message --- src/helpers/sensors/EnvironmentSensorManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 8471d80d..a75d378c 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -284,7 +284,7 @@ bool EnvironmentSensorManager::begin() { INA260_initialized = true; } else { INA260_initialized = false; - MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA219_ADDRESS); + MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS); } #endif From 9665feeebfcf1e0b9236cc0d9c1b8ba0c39a5fb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Tue, 27 Jan 2026 16:57:54 +0000 Subject: [PATCH 429/546] Update runArgs in devcontainer.json --- .devcontainer/devcontainer.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fcde5048..8440247e 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -10,11 +10,12 @@ }, "runArgs": [ "--privileged", - // arch tty* is owned by uucp (986) - // debian tty* is owned by uucp (20) - no change needed - "--group-add=986", "--network=host", - "--volume=/dev/bus/usb:/dev/bus/usb:ro" + "--volume=/dev/bus/usb:/dev/bus/usb:ro", + // arch tty* is owned by uucp (986) + // debian tty* is owned by dialout (20) + "--group-add=20", + "--group-add=986" ], "postCreateCommand": { "platformio": "pipx install platformio" From edeafde51c5992ed67259901af013addd91a7f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Tue, 27 Jan 2026 19:36:12 +0000 Subject: [PATCH 430/546] Fix: Correct validation logic in isValidName function --- src/helpers/CommonCLI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 42198b49..10ab8669 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -16,7 +16,7 @@ static uint32_t _atoi(const char* sp) { static bool isValidName(const char *n) { while (*n) { - if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; + if (*n == '[' || *n == ']' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false; n++; } return true; From 4e1e8bbffb49b5a90256aaa8fc731010ca28d5e7 Mon Sep 17 00:00:00 2001 From: Chris <cisien@cisien.com> Date: Mon, 26 Jan 2026 19:38:06 -0800 Subject: [PATCH 431/546] Add a cli command reference document --- docs/cli_commands.md | 881 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 881 insertions(+) create mode 100644 docs/cli_commands.md diff --git a/docs/cli_commands.md b/docs/cli_commands.md new file mode 100644 index 00000000..6b4f6157 --- /dev/null +++ b/docs/cli_commands.md @@ -0,0 +1,881 @@ +# MeshCore Repeater & Room Server CLI Commands + +## Navigation + +- [Operational](#operational) +- [Neighbors](#neighbors-repeater-only) +- [Statistics](#statistics) +- [Logging](#logging) +- [Information](#info) +- [Configuration](#configuration) + - [Radio](#radio) + - [System](#system) + - [Routing](#routing) + - [ACL](#acl) + - [Region Management](#region-management-v110) + - [Region Examples](#region-examples) + - [GPS](#gps-when-gps-support-is-compiled-in) + - [Sensors](#sensors-when-sensor-support-is-compiled-in) + - [Bridge](#bridge-when-bridge-support-is-compiled-in) + +--- + +## Operational + +### Reboot the node +**Usage:** +- `reboot` + +--- + +### Reset the clock and reboot +**Usage:** +- `clkreboot` + +--- + +### Sync the clock with the remote device +**Usage:** +- `clock sync` + +--- + +### Display current time in UTC +**Usage:** +- `clock` + +--- + +### Set the time to a specific timestamp +**Usage:** +- `time <epoch_seconds>` + +**Parameters:** +- `epoc_seconds`: Unix epoc time + +--- + +### Send a flood advert +**Usage:** +- `advert` + +--- + +### Start an Over-The-Air (OTA) firmware update +**Usage:** +- `start ota` + +--- + +### Erase/Factory Reset +**Usage:** +- `erase` + +**Serial Only:** Yes + +**Warning:** _**This is destructive!**_ + +--- + +## Neighbors (Repeater Only) + +### List nearby neighbors +**Usage:** +- `neighbors` + +**Note:** The output of this command is limited to the 8 most recent adverts. + +**Note:** Each line is encoded as `{pubkey-prefix}:{timestamp}:{snr*4}` + +--- + +### Remove a neighbor +**Usage:** +- `neighbor.remove <pubkey_prefix>` + +**Parameters:** +- `pubkey_prefix`: The public key of the node to remove from the neighbors list + +--- + +## Statistics + +### Clear Stats +**Usage:** `clear stats` + +--- + +### System Stats - Battery, Uptime, Queue Length and Debug Flags +**Usage:** +- `stats-core` + +**Serial Only:** Yes + +--- + +### Radio Stats - Noise floor, Last RSSI/SNR, Airtime, Receive errors +**Usage:** `stats-radio` + +**Serial Only:** Yes + +--- + +### Packet stats - Packet counters: Received, Sent +**Usage:** `stats-packets` + +**Serial Only:** Yes + +--- + +## Logging + +### Begin capture of rx log to node storage +**Usage:** `log start` + +--- + +### End capture of rx log to node sotrage +**Usage:** `log stop` + +--- + +### Erase captured log +**Usage:** `log erase` + +--- + +### Print the captured log to the serial terminal +**Usage:** `log` + +**Serial Only:** Yes + +--- + +## Info + +### Get the Version +**Usage:** `ver` + +--- + +### Show the hardware name +**Usage:** `board` + +--- + +## Configuration + +### Radio + +#### View or change this node's radio parameters +**Usage:** +- `get radio` +- `set radio <freq>,<bw>,<sf>,<cr>` + +**Parameters:** +- `freq`: Frequency in MHz +- `bw`: Bandwidth in kHz +- `sf`: Spreading factor (5-12) +- `cr`: Coding rate (5-8) + +**Set by build flag:** `LORA_FREQ`, `LORA_BW`, `LORA_SF`, `LORA_CR` + +**Default:** `869.525,250,11,5` + +**Note:** Requires reboot to apply + +--- + +#### View or change this node's transmit power +**Usage:** +- `get tx` +- `set tx <dbm>` + +**Parameters:** +- `dbm`: Power level in dBm (1-22) + +**Set by build flag:** `LORA_TX_POWER` + +**Default:** Varies by board + +**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Referr to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** + +--- + +#### Change the radio parameters for a set duration +**Usage:** +- `tempradio <freq>,<bw>,<sf>,<cr>,<timeout_mins>` + +**Parameters:** +- `freq`: Frequency in MHz (300-2500) +- `bw`: Bandwidth in kHz (7.8-500) +- `sf`: Spreading factor (5-12) +- `cr`: Coding rate (5-8) +- `timeout_mins`: Duration in minutes (must be > 0) + +**Note:** This is not saved to preferences and will clear on reboot + +--- + +#### View or change this node's frequency +**Usage:** +- `get freq` +- `set freq <frequency>` + +**Parameters:** +- `frequency`: Frequency in MHz + +**Default:** `869.525` + +**Note:** Requires reboot to apply + +### System + +#### View or change this node's name +**Usage:** +- `get name` +- `set name <name>` + +**Parameters:** +- `name`: Node name + +**Set by build flag:** `ADVERT_NAME` + +**Default:** Varies by board + +**Note:** Max length varies. If a location is set, the max length is 24 bytes; 32 otherwise. Emoji and unicode characters may take more than one byte. + +--- + +#### View or change this node's latitude +**Usage:** +- `get lat` +- `set lat <degrees>` + +**Set by build flag:** `ADVERT_LAT` + +**Default:** `0` + +**Parameters:** +- `degrees`: Latitude in degrees + +--- + +#### View or change this node's longitude +**Usage:** +- `get lon` +- `set lon <degrees>` + +**Set by build flag:** `ADVERT_LON` + +**Default:** `0` + +**Parameters:** +- `degrees`: Longitude in degrees + +--- + +#### View or change this node's identity (Private Key) +**Usage:** +- `get prv.key` +- `set prv.key <private_key>` + +**Parameters:** +- `private_key`: Private key in hex format (64 hex characters) + +**Serial Only:** +- `get prv.key`: Yes +- `set prv.key`: No + +**Note:** Requires reboot to take effect after setting + +--- + +#### View or change this node's admin password +**Usage:** +- `get password` +- `set password <password>` + +**Parameters:** +- `password`: Admin password + +**Set by build flag:** `ADMIN_PASSWORD` + +**Default:** `password` + +**Note:** Echoed back for confirmation + +**Note:** Any node using this password will be added to the admin ACL list. + +--- + +#### View or change this node's guest password +**Usage:** +- `get guest.password` +- `set guest.password <password>` + +**Parameters:** +- `password`: Guest password + +**Set by build flag:** `ROOM_PASSWORD` (Room Server only) + +**Default:** `<blank>` + +--- + +#### View or change this node's owner info +**Usage:** +- `get owner.info` +- `set owner.info <text>` + +**Parameters:** +- `text`: Owner information text + +**Default:** `<blank>` + +**Note:** `|` characters are translated to newlines + +**Note:** Requires firmware 1.12.+ + +--- + +#### Fine-tune the battery reading +**Usage:** +- `get adc.multiplier` +- `set adc.multiplier <value>` + +**Parameters:** +- `value`: ADC multiplier (0.0-10.0) + +**Default:** `0.0` (value defined by board) + +**Note:** Returns "Error: unsupported by this board" if hardware doesn't support it + +--- + +#### View or change this node's power saving flag (Repeater Only) +**Usage:** +- `powersaving <state>` +- `powersaving` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `on` + +**Note:** When enabled, device enters sleep mode between radio transmissions + +--- + +### Routing + +#### View or change this node's repeat flag +**Usage:** +- `get repeat` +- `set repeat <state>` + +**Parameters:** + - `state`: `on`|`off` + +**Default:** `on` + +--- + +#### View or change the retransmit delay factor for flood traffic +**Usage:** +- `get txdelay` +- `set txdelay <value>` + +**Parameters:** +- `value`: Transmit delay factor (0-2) + +**Default:** `0.5` + +--- + +#### View or change the retransmit delay factor for direct traffic +**Usage:** +- `get direct.txdelay` +- `set direct.txdelay <value>` + +**Parameters:** +- `value`: Direct transmit delay factor (0-2) + +**Default:** `0.2` + +--- + +#### [Experimental] View or change the processing delay for received traffic +**Usage:** +- `get rxdelay` +- `set rxdelay <value>` + +**Parameters:** +- `value`: Receive delay base (0-20) + +**Default:** `0.0` + +--- + +#### View or change the airtime factor (duty cycle limit) +**Usage:** +- `get af` +- `set af <value>` + +**Parameters:** +- `value`: Airtime factor (0-9) + +**Default:** `1.0` + +--- + +#### View or change the local interference threshold +**Usage:** +- `get int.thresh` +- `set int.thresh <value>` + +**Parameters:** +- `value`: Interference threshold value + +**Default:** `0.0` + +--- + +#### View or change the AGC Reset Interval +**Usage:** +- `get agc.reset.interval` +- `set agc.reset.interval <value>` + +**Parameters:** +- `value`: Interval in seconds rounded down to a multiple of 4 (17 becomes 16) + +**Default:** `0.0` + +--- + +#### Enable or disable Multi-Acks support +**Usage:** +- `get multi.acks` +- `set multi.acks <state>` + +**Parameters:** +- `state`: `0` (disable) or `1` (enable) + +**Default:** `0` + +--- + +#### View or change the flood advert interval +**Usage:** +- `get flood.advert.interval` +- `set flood.advert.interval <hours>` + +**Parameters:** +- `hours`: Interval in hours (3-168) + +**Default:** `12` (Repeater) - `0` (Sensor) + +--- + +#### View or change the zero-hop advert interval +**Usage:** +- `get advert.interval` +- `set advert.interval <minutes>` + +**Parameters:** +- `minutes`: Interval in minutes rounded down to the nearest multiple of 2 (61 becomes 60) (60-240) + +**Default:** `0` + +--- + +#### Limit the number of hops for a flood message +**Usage:** +- `get flood.max` +- `set flood.max <value>` + +**Parameters:** +- `value`: Maximum flood hop count (0-64) + +**Default:** `64` + +--- + +### ACL + +#### Add, update or remove permissions for a companion +**Usage:** +- `setperm <pubkey> <permissions>` + +**Parameters:** +- `pubkey`: Companion public key +- `permissions`: + - `0`: Guest + - `1`: Read-only + - `2`: Read-write + - `3`: Admin + +**Note:** Removes the entry when `permissions` is omitted + +--- + +#### View the current ACL +**Usage:** +- `get acl` + +**Serial Only:** Yes + +--- + +#### View or change this room server's 'read-only' flag +**Usage:** +- `get allow.read.only` +- `set allow.read.only <state>` + +**Parameters:** +- `state`: `on` (enable) or `off` (disable) + +**Default:** `off` + +--- + +### Region Management (v1.10.+) + +#### Bulk-load region lists +**Usage:** +- `region load` +- `region load <name> [flood_flag]` + +**Parameters:** +- `name`: A name of a region. `*` represents the wildcard region + +**Note:** `flood_flag`: Optional `F` to allow flooding + +**Note:** Indentation creates parent-child relationships (max 8 levels) + +**Note:** `region load` with an empty name will not work remotely (it's interactive) + +--- + +#### Save any changes to regions made since reboot +**Usage:** +- `region save` + +--- + +#### Allow a region +**Usage:** +- `region allowf <name>` + +**Parameters:** +- `name`: Region name (or `*` for wildcard) + +**Note:** Setting on wildcard `*` allows packets without region transport codes + +--- + +#### Block a region +**Usage:** +- `region denyf <name>` + +**Parameters:** +- `name`: Region name (or `*` for wildcard) + +**Note:** Setting on wildcard `*` drops packets without region transport codes + +--- + +#### Show information for a region +**Usage:** +- `region get <name>` + +**Parameters:** +- `name`: Region name (or `*` for wildcard) + +--- + +#### View or change the home region for this node +**Usage:** +- `region home` +- `region home <name>` + +**Parameters:** +- `name`: Region name + +--- + +#### Create a new region +**Usage:** +- `region put <name> [parent_name]` + +**Parameters:** +- `name`: Region name +- `parent_name`: Parent region name (optional, defaults to wildcard) + +--- + +#### Remove a region +**Usage:** +- `region remove <name>` + +**Parameters:** +- `name`: Region name + +**Note:** Must remove all child regions before the region can be removed + +--- + +#### View all regions +**Usage:** +- `region list <filter>` + +**Serial Only:** Yes + +**Parameters:** +- `filter`: `allowed`|`denied` + +**Note:** Requires firmware 1.12.+ + +--- + +#### Dump all defined regions and flood permissions +**Usage:** +- `region` + +**Serial Only:** Yes + +--- + +### Region Examples + +**Example 1: Using F Flag with Named Public Region** +``` +region load +#Europe F +<blank line to end region load> +region save +``` + +**Explanation:** +- Creates a region named `#Europe` with flooding enabled +- Packets from this region will be flooded to other nodes + +--- + +**Example 2: Using Wildcard with F Flag** +``` +region load +* F +<blank line to end region load> +region save +``` + +**Explanation:** +- Creates a wildcard region `*` with flooding enabled +- Enables flooding for all regions automatically +- Applies only to packets without transport codes + +--- + +**Example 3: Using Wildcard Without F Flag** +``` +region load +* +<blank line to end region load> +region save +``` +**Explanation:** +- Creates a wildcard region `*` without flooding +- This region exists but doesn't affect packet distribution +- Used as a default/empty region + +--- + +**Example 4: Nested Public Region with F Flag** +``` +region load +#Europe F + #UK + #London + #Manchester + #France + #Paris + #Lyon +<blank line to end region load> +region save +``` + +**Explanation:** +- Creates `#Europe` region with flooding enabled +- Adds nested child regions (`#UK`, `#France`) +- All nested regions inherit the flooding flag from parent + +--- + +**Example 5: Wildcard with Nested Public Regions** +``` +region load +* F + #NorthAmerica + #USA + #NewYork + #California + #Canada + #Ontario + #Quebec +<blank line to end region load> +region save +``` + +**Explanation:** +- Creates wildcard region `*` with flooding enabled +- Adds nested `#NorthAmerica` hierarchy +- Enables flooding for all child regions automatically +- Useful for global networks with specific regional rules + +--- +### GPS (When GPS support is compiled in) + +#### View or change GPS state +**Usage:** +- `gps` +- `gps <state>` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `off` + +**Note:** Output format: `{status}, {fix}, {sat count}` (when enabled) + +--- + +#### Sync this node's clock with GPS time +**Usage:** +- `gps sync` + +--- + +#### Set this node's location based on the GPS coordinates +**Usage:** +- `gps setloc` + +--- + +#### View or change the GPS advert policy +**Usage:** +- `gps advert` +- `gps advert <policy>` + +**Parameters:** +- `policy`: `none`|`shared`|`prefs` + - `none`: don't include location in adverts + - `share`: share gps location (from SensorManager) + - `prefs`: location stored in node's lat and lon settings + +**Default:** `prefs` + +--- + +### Sensors (When sensor support is compiled in) + +#### View the list of sensors on this node +**Usage:** `sensor list [start]` + +**Parameters:** +- `start`: Optional starting index (defaults to 0) + +**Note:** Output format: `<var_name>=<value>\n` + +--- + +#### View or change thevalue of a sensor +**Usage:** +- `sensor get <key>` +- `sensor set <key> <value>` + +**Parameters:** +- `key`: Sensor setting name +- `value`: The value to set the sensor to + +--- + +### Bridge (When bridge support is compiled in) + +#### View or change the bridge enabled flag +**Usage:** +- `get bridge.enabled` +- `set bridge.enabled <state>` + +**Parameters:** +- `state`: `on`|`off` + +**Default:** `off` + +--- + +#### View the bridge source +**Usage:** +- `get bridge.source` + +--- + +#### Add a delay to packets routed through this bridge +**Usage:** +- `get bridge.delay` +- `set bridge.delay <ms>` + +**Parameters:** +- `ms`: Delay in milliseconds (0-10000) + +**Default:** `500` + +--- + +#### View or change the source of packets bridged to the external interface +**Usage:** +- `get bridge.source` +- `set bridge.source <source>` + +**Parameters:** +- `source`: + - `rx`: bridges received packets + - `tx`: bridges transmitted packets + +**Default:** `tx` + +--- + +#### View or change the speed of the bridge (RS-232 only) +**Usage:** +- `get bridge.baud` +- `set bridge.baud <rate>` + +**Parameters:** +- `rate`: Baud rate (`9600`, `19200`, `38400`, `57600`, or `115200`) + +**Default:** `115200` + +--- + +#### View or change the channel used for bridging (ESPNow only) +**Usage:** +- `get bridge.channel` +- `set bridge.channel <channel>` + +**Parameters:** +- `channel`: Channel number (1-14) + +--- + +#### Set the ESP-Now secret +**Usage:** +- `get bridge.secret` +- `set bridge.secret <secret>` + +**Parameters:** +- `secret`: 16-character encryption secret + +**Default:** Varies by board + +--- From d5a73b239437ed6a1c9f9512246169e8238b25c8 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Wed, 28 Jan 2026 17:18:39 +0100 Subject: [PATCH 432/546] fix: build errors because of changes in NRF52 base class --- variants/rak3401/RAK3401Board.h | 32 ++++++-------------------------- variants/rak3401/variant.h | 18 +++++++++++++----- 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 609393c3..20edf906 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -4,30 +4,6 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -// LoRa radio module pins for RAK13302 -#define P_LORA_SCLK 3 -#define P_LORA_MISO 29 -#define P_LORA_MOSI 30 -#define P_LORA_NSS 26 -#define P_LORA_DIO_1 10 -#define P_LORA_BUSY 9 -#define P_LORA_RESET 4 -#ifndef P_LORA_PA_EN - #define P_LORA_PA_EN 31 -#endif - -//#define PIN_GPS_SDA 13 //GPS SDA pin (output option) -//#define PIN_GPS_SCL 14 //GPS SCL pin (output option) -// #define PIN_GPS_TX 16 //GPS TX pin -// #define PIN_GPS_RX 15 //GPS RX pin -#define PIN_GPS_1PPS 17 //GPS PPS pin -#define GPS_BAUD_RATE 9600 -#define GPS_ADDRESS 0x42 //i2c address for GPS - -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - - // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) @@ -35,9 +11,13 @@ #define PIN_3V3_EN (34) #define WB_IO2 PIN_3V3_EN -class RAK3401Board : public NRF52BoardDCDC, public NRF52BoardOTA { +class RAK3401Board : public NRF52BoardDCDC { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif public: - RAK3401Board() : NRF52BoardOTA("RAK3401_OTA") {} + RAK3401Board() : NRF52Board("RAK3401_OTA") {} void begin(); #define BATTERY_SAMPLES 8 diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 9c182247..56fe0816 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -141,11 +141,6 @@ static const uint8_t AREF = PIN_AREF; #define EXTERNAL_FLASH_DEVICES IS25LP080D #define EXTERNAL_FLASH_USE_QSPI -#define P_LORA_SCK PIN_SPI1_SCK -#define P_LORA_MISO PIN_SPI1_MISO -#define P_LORA_MOSI PIN_SPI1_MOSI -#define P_LORA_CS 26 - #define USE_SX1262 #define SX126X_CS (26) #define SX126X_DIO1 (10) @@ -157,6 +152,15 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define P_LORA_SCLK PIN_SPI1_SCK +#define P_LORA_MISO PIN_SPI1_MISO +#define P_LORA_MOSI PIN_SPI1_MOSI +#define P_LORA_NSS SX126X_CS +#define P_LORA_DIO_1 SX126X_DIO1 +#define P_LORA_BUSY SX126X_BUSY +#define P_LORA_RESET SX126X_RESET +#define P_LORA_PA_EN 31 + // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings #define PIN_3V3_EN (34) @@ -173,6 +177,10 @@ static const uint8_t AREF = PIN_AREF; #define PIN_GPS_RX PIN_SERIAL1_RX #define PIN_GPS_TX PIN_SERIAL1_TX +#define PIN_GPS_1PPS PIN_GPS_PPS +#define GPS_BAUD_RATE 9600 +#define GPS_ADDRESS 0x42 //i2c address for GPS + // Battery // The battery sense is hooked to pin A0 (5) #define BATTERY_PIN PIN_A0 From f41872420e3166ffb8b5676d489934937717a4c4 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Wed, 28 Jan 2026 17:28:48 +0100 Subject: [PATCH 433/546] moved pindefs from board file to variant.h --- variants/rak4631/RAK4631Board.h | 21 ----------------- variants/rak4631/variant.h | 41 +++++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 33 deletions(-) diff --git a/variants/rak4631/RAK4631Board.h b/variants/rak4631/RAK4631Board.h index ff4a5b7d..7e67165b 100644 --- a/variants/rak4631/RAK4631Board.h +++ b/variants/rak4631/RAK4631Board.h @@ -4,27 +4,6 @@ #include <Arduino.h> #include <helpers/NRF52Board.h> -// LoRa radio module pins for RAK4631 -#define P_LORA_DIO_1 47 -#define P_LORA_NSS 42 -#define P_LORA_RESET RADIOLIB_NC // 38 -#define P_LORA_BUSY 46 -#define P_LORA_SCLK 43 -#define P_LORA_MISO 45 -#define P_LORA_MOSI 44 -#define SX126X_POWER_EN 37 - -//#define PIN_GPS_SDA 13 //GPS SDA pin (output option) -//#define PIN_GPS_SCL 14 //GPS SCL pin (output option) -//#define PIN_GPS_TX 16 //GPS TX pin -//#define PIN_GPS_RX 15 //GPS RX pin -#define PIN_GPS_1PPS 17 //GPS PPS pin -#define GPS_BAUD_RATE 9600 -#define GPS_ADDRESS 0x42 //i2c address for GPS - -#define SX126X_DIO2_AS_RF_SWITCH true -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 - // built-ins #define PIN_VBAT_READ 5 #define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000) diff --git a/variants/rak4631/variant.h b/variants/rak4631/variant.h index b18335f8..142d93e9 100644 --- a/variants/rak4631/variant.h +++ b/variants/rak4631/variant.h @@ -144,6 +144,19 @@ extern "C" static const uint8_t MISO = PIN_SPI_MISO; static const uint8_t SCK = PIN_SPI_SCK; +// LoRa radio module pins for RAK4631 +#define P_LORA_DIO_1 (47) +#define P_LORA_NSS (42) +#define P_LORA_RESET (-1) +#define P_LORA_BUSY (46) +#define P_LORA_SCLK (43) +#define P_LORA_MISO (45) +#define P_LORA_MOSI (44) +#define SX126X_POWER_EN (37) + +#define SX126X_DIO2_AS_RF_SWITCH true +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + /* * Wire Interfaces */ @@ -155,19 +168,23 @@ extern "C" #define PIN_WIRE1_SDA (24) #define PIN_WIRE1_SCL (25) - // QSPI Pins - // QSPI occupied by GPIO's - #define PIN_QSPI_SCK 3 // 19 - #define PIN_QSPI_CS 26 // 17 - #define PIN_QSPI_IO0 30 // 20 - #define PIN_QSPI_IO1 29 // 21 - #define PIN_QSPI_IO2 28 // 22 - #define PIN_QSPI_IO3 2 // 23 +// QSPI Pins +// QSPI occupied by GPIO's +#define PIN_QSPI_SCK 3 // 19 +#define PIN_QSPI_CS 26 // 17 +#define PIN_QSPI_IO0 30 // 20 +#define PIN_QSPI_IO1 29 // 21 +#define PIN_QSPI_IO2 28 // 22 +#define PIN_QSPI_IO3 2 // 23 - // On-board QSPI Flash - // No onboard flash - #define EXTERNAL_FLASH_DEVICES IS25LP080D - #define EXTERNAL_FLASH_USE_QSPI +// On-board QSPI Flash +// No onboard flash +#define EXTERNAL_FLASH_DEVICES IS25LP080D +#define EXTERNAL_FLASH_USE_QSPI + +#define PIN_GPS_1PPS 17 //GPS PPS pin +#define GPS_BAUD_RATE 9600 +#define GPS_ADDRESS 0x42 //i2c address for GPS #ifdef __cplusplus } From dd2a9044f3c0a3e608eefc8325a2ea511973ba55 Mon Sep 17 00:00:00 2001 From: Max Litruv Boonzaayer <litruv@gmail.com> Date: Thu, 29 Jan 2026 08:02:26 +1100 Subject: [PATCH 434/546] Refactor display scaling definitions for HELTEC_VISION_MASTER_T190 --- src/helpers/ui/ST7789Display.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index 7ea35187..f7d20b8a 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -10,8 +10,13 @@ #define Y_OFFSET 1 // Vertical offset to prevent top row cutoff #endif -#define SCALE_X 1.875f // 240 / 128 -#define SCALE_Y 2.109375f // 135 / 64 +#ifdef HELTEC_VISION_MASTER_T190 + #define SCALE_X 2.5f // 320 / 128 + #define SCALE_Y 2.65625f // 170 / 64 +#else + #define SCALE_X 1.875f // 240 / 128 + #define SCALE_Y 2.109375f // 135 / 64 +#endif bool ST7789Display::begin() { if(!_isOn) { From f7e54ea7971121ab86501962f3e283889fafcf52 Mon Sep 17 00:00:00 2001 From: Steven Linn <smlucf@gmail.com> Date: Wed, 28 Jan 2026 13:24:22 -0700 Subject: [PATCH 435/546] Add LilyGO T-Beam 1W Support --- boards/t_beam_1w.json | 50 ++++++ variants/lilygo_tbeam_1w/TBeam1WBoard.cpp | 71 ++++++++ variants/lilygo_tbeam_1w/TBeam1WBoard.h | 45 ++++++ variants/lilygo_tbeam_1w/pins_arduino.h | 26 +++ variants/lilygo_tbeam_1w/platformio.ini | 189 ++++++++++++++++++++++ variants/lilygo_tbeam_1w/target.cpp | 64 ++++++++ variants/lilygo_tbeam_1w/target.h | 27 ++++ variants/lilygo_tbeam_1w/variant.h | 96 +++++++++++ 8 files changed, 568 insertions(+) create mode 100644 boards/t_beam_1w.json create mode 100644 variants/lilygo_tbeam_1w/TBeam1WBoard.cpp create mode 100644 variants/lilygo_tbeam_1w/TBeam1WBoard.h create mode 100644 variants/lilygo_tbeam_1w/pins_arduino.h create mode 100644 variants/lilygo_tbeam_1w/platformio.ini create mode 100644 variants/lilygo_tbeam_1w/target.cpp create mode 100644 variants/lilygo_tbeam_1w/target.h create mode 100644 variants/lilygo_tbeam_1w/variant.h diff --git a/boards/t_beam_1w.json b/boards/t_beam_1w.json new file mode 100644 index 00000000..2f1159aa --- /dev/null +++ b/boards/t_beam_1w.json @@ -0,0 +1,50 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DLILYGO_TBEAM_1W", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [ + [ + "0x303A", + "0x1001" + ] + ], + "mcu": "esp32s3", + "variant": "lilygo_tbeam_1w" + }, + "connectivity": [ + "wifi", + "bluetooth", + "lora" + ], + "debug": { + "openocd_target": "esp32s3.cfg" + }, + "frameworks": [ + "arduino" + ], + "name": "LilyGo TBeam-1W", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "maximum_size": 16777216, + "require_upload_port": true, + "speed": 921600 + }, + "url": "http://www.lilygo.cn/", + "vendor": "LilyGo" +} diff --git a/variants/lilygo_tbeam_1w/TBeam1WBoard.cpp b/variants/lilygo_tbeam_1w/TBeam1WBoard.cpp new file mode 100644 index 00000000..1719d733 --- /dev/null +++ b/variants/lilygo_tbeam_1w/TBeam1WBoard.cpp @@ -0,0 +1,71 @@ +#include "TBeam1WBoard.h" + +void TBeam1WBoard::begin() { + ESP32Board::begin(); + + // Power on radio module (must be done before radio init) + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + radio_powered = true; + delay(10); // Allow radio to power up + + // RF switch RXEN pin handled by RadioLib via setRfSwitchPins() + + // Initialize LED + pinMode(LED_PIN, OUTPUT); + digitalWrite(LED_PIN, LOW); + + // Initialize fan control (on by default - 1W PA can overheat) + pinMode(FAN_CTRL_PIN, OUTPUT); + digitalWrite(FAN_CTRL_PIN, HIGH); +} + +void TBeam1WBoard::onBeforeTransmit() { + // RF switching handled by RadioLib via SX126X_DIO2_AS_RF_SWITCH and setRfSwitchPins() + digitalWrite(LED_PIN, HIGH); // TX LED on +} + +void TBeam1WBoard::onAfterTransmit() { + digitalWrite(LED_PIN, LOW); // TX LED off +} + +uint16_t TBeam1WBoard::getBattMilliVolts() { + // T-Beam 1W uses 7.4V battery with voltage divider + // ADC reads through divider - adjust multiplier based on actual divider ratio + analogReadResolution(12); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(BATTERY_PIN); + } + raw = raw / 8; + // Assuming voltage divider ratio from ADC_MULTIPLIER + // 3.3V reference, 12-bit ADC (4095 max) + return static_cast<uint16_t>((raw * 3300 * ADC_MULTIPLIER) / 4095); +} + +const char* TBeam1WBoard::getManufacturerName() const { + return "LilyGo T-Beam 1W"; +} + +void TBeam1WBoard::powerOff() { + // Turn off radio LNA (CTRL pin must be LOW when not receiving) + digitalWrite(SX126X_RXEN, LOW); + + // Turn off radio power + digitalWrite(SX126X_POWER_EN, LOW); + radio_powered = false; + + // Turn off LED and fan + digitalWrite(LED_PIN, LOW); + digitalWrite(FAN_CTRL_PIN, LOW); + + ESP32Board::powerOff(); +} + +void TBeam1WBoard::setFanEnabled(bool enabled) { + digitalWrite(FAN_CTRL_PIN, enabled ? HIGH : LOW); +} + +bool TBeam1WBoard::isFanEnabled() const { + return digitalRead(FAN_CTRL_PIN) == HIGH; +} diff --git a/variants/lilygo_tbeam_1w/TBeam1WBoard.h b/variants/lilygo_tbeam_1w/TBeam1WBoard.h new file mode 100644 index 00000000..d999dfd4 --- /dev/null +++ b/variants/lilygo_tbeam_1w/TBeam1WBoard.h @@ -0,0 +1,45 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/ESP32Board.h> +#include "variant.h" + +// LilyGo T-Beam 1W with SX1262 + external PA (XY16P35 module) +// +// Power architecture (LDO is separate chip on T-Beam board, not inside XY16P35): +// +// VCC (+4.0~+8.0V) ──┬──────────────────► XY16P35 VCC pin 5 (PA direct) +// (USB or Battery) │ +// │ ┌───────────┐ +// └──►│ LDO Chip │──► +3.3V ──► XY16P35 (SX1262 + LNA) +// │ EN=GPIO40 │ +// └───────────┘ +// LDO_EN (GPIO 40): H @ +1.2V~VIN, active high, not floating +// +// Control signals: +// - LDO_EN (GPIO 40): HIGH enables LDO → powers SX1262 + LNA +// - TCXO_EN (DIO3): HIGH enables TCXO (set to 1.8V per Meshtastic) +// - CTL (GPIO 21): HIGH=RX (LNA on), LOW=TX (LNA off) +// - DIO2: AUTO via SX126X_DIO2_AS_RF_SWITCH (TX path) +// +// Power notes: +// - PA needs VCC 4.0-8.0V for full 32dBm output +// - USB-C (3.9-6V) marginal; 7.4V battery recommended +// - Battery must support 2A+ discharge for high-power TX + +class TBeam1WBoard : public ESP32Board { +private: + bool radio_powered = false; + +public: + void begin(); + void onBeforeTransmit() override; + void onAfterTransmit() override; + uint16_t getBattMilliVolts() override; + const char* getManufacturerName() const override; + void powerOff() override; + + // Fan control methods + void setFanEnabled(bool enabled); + bool isFanEnabled() const; +}; diff --git a/variants/lilygo_tbeam_1w/pins_arduino.h b/variants/lilygo_tbeam_1w/pins_arduino.h new file mode 100644 index 00000000..c6f596f4 --- /dev/null +++ b/variants/lilygo_tbeam_1w/pins_arduino.h @@ -0,0 +1,26 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include <stdint.h> + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial (USB CDC) +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +// I2C for OLED and sensors +static const uint8_t SDA = 8; +static const uint8_t SCL = 9; + +// Default SPI mapped to Radio/SD +static const uint8_t SS = 15; // LoRa CS +static const uint8_t MOSI = 11; +static const uint8_t MISO = 12; +static const uint8_t SCK = 13; + +// SD Card CS +#define SDCARD_CS 10 + +#endif /* Pins_Arduino_h */ diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini new file mode 100644 index 00000000..4b72b5e7 --- /dev/null +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -0,0 +1,189 @@ +[LilyGo_TBeam_1W] +extends = esp32_base +board = t_beam_1w +build_flags = + ${esp32_base.build_flags} + -I variants/lilygo_tbeam_1w + -D TBEAM_1W + + ; Radio - SX1262 with high-power PA (32dBm max output) + ; Note: Set SX1262 output to 22dBm max, external PA provides additional gain + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=1 + -D P_LORA_NSS=15 + -D P_LORA_RESET=3 + -D P_LORA_BUSY=38 + -D P_LORA_SCLK=13 + -D P_LORA_MISO=12 + -D P_LORA_MOSI=11 + + ; RF switch configuration: + ; DIO2 controls TX path (PA enable) via SX126X_DIO2_AS_RF_SWITCH + ; GPIO21 controls RX path (LNA enable) via SX126X_RXEN + ; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_RXEN=21 + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + + ; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total + -D LORA_TX_POWER=22 + + ; Display - SH1106 OLED at 0x3C + -D DISPLAY_CLASS=SH1106Display + + ; I2C pins + -D PIN_BOARD_SDA=8 + -D PIN_BOARD_SCL=9 + + ; GPS - L76K module + ; GNSS_TXD (IO5) = GPS transmits → MCU RX + ; GNSS_RXD (IO6) = GPS receives → MCU TX + -D PIN_GPS_TX=5 + -D PIN_GPS_RX=6 + -D PIN_GPS_EN=16 + -D ENV_INCLUDE_GPS=1 + + ; User interface + -D PIN_USER_BTN=17 + +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/lilygo_tbeam_1w> + +<helpers/ui/SH1106Display.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<helpers/sensors> + +lib_deps = + ${esp32_base.lib_deps} + adafruit/Adafruit SH110X @ ~2.1.13 + stevemarple/MicroNMEA @ ~2.0.6 + +; === LILYGO T-Beam 1W Repeater === +[env:LilyGo_TBeam_1W_repeater] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -D ADVERT_NAME='"T-Beam 1W Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + ${esp32_ota.lib_deps} + +; === LILYGO T-Beam 1W Room Server === +[env:LilyGo_TBeam_1W_room_server] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -D ADVERT_NAME='"T-Beam 1W Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + ${esp32_ota.lib_deps} + +; === LILYGO T-Beam 1W Companion Radio (USB) === +[env:LilyGo_TBeam_1W_companion_radio_usb] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; === LILYGO T-Beam 1W Companion Radio (BLE) === +[env:LilyGo_TBeam_1W_companion_radio_ble] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=256 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; === LILYGO T-Beam 1W Companion Radio (WiFi) === +[env:LilyGo_TBeam_1W_companion_radio_wifi] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<helpers/esp32/*.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; === LILYGO T-Beam 1W Repeater with ESPNow Bridge === +[env:LilyGo_TBeam_1W_repeater_bridge_espnow] +extends = LilyGo_TBeam_1W +build_flags = + ${LilyGo_TBeam_1W.build_flags} + -D ADVERT_NAME='"T-Beam 1W ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 + -D PERSISTANT_GPS=1 + -D ENV_SKIP_GPS_DETECT=1 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<helpers/bridges/ESPNowBridge.cpp> + +<../examples/simple_repeater> +lib_deps = + ${LilyGo_TBeam_1W.lib_deps} + ${esp32_ota.lib_deps} diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp new file mode 100644 index 00000000..fcdb42ed --- /dev/null +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -0,0 +1,64 @@ +#include <Arduino.h> +#include "target.h" + +TBeam1WBoard board; + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +static SPIClass spi; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include <helpers/sensors/MicroNMEALocationProvider.h> + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + + // Initialize SPI for radio + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + + // GPS serial initialized by EnvironmentSensorManager::begin() + + bool success = radio.std_init(&spi); + if (success) { + // T-Beam 1W has external PA requiring longer ramp time (>800us recommended) + // RADIOLIB_SX126X_PA_RAMP_800U = 0x05 + radio.setTxParams(LORA_TX_POWER, RADIOLIB_SX126X_PA_RAMP_800U); + } + return success; +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} diff --git a/variants/lilygo_tbeam_1w/target.h b/variants/lilygo_tbeam_1w/target.h new file mode 100644 index 00000000..2c3e8970 --- /dev/null +++ b/variants/lilygo_tbeam_1w/target.h @@ -0,0 +1,27 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/sensors/EnvironmentSensorManager.h> +#include "TBeam1WBoard.h" + +#ifdef DISPLAY_CLASS + #include <helpers/ui/SH1106Display.h> + #include <helpers/ui/MomentaryButton.h> + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +extern TBeam1WBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_1w/variant.h b/variants/lilygo_tbeam_1w/variant.h new file mode 100644 index 00000000..c05b1696 --- /dev/null +++ b/variants/lilygo_tbeam_1w/variant.h @@ -0,0 +1,96 @@ +// LilyGo T-Beam-1W variant.h +// Configuration based on Meshtastic PR #8967 and LilyGO documentation + +#pragma once + +// I2C for OLED display (SH1106 at 0x3C) +#define I2C_SDA 8 +#define I2C_SCL 9 + +// GPS - Quectel L76K +// GNSS_TXD (IO5) = GPS transmits → MCU RX (setPins rxPin) +// GNSS_RXD (IO6) = GPS receives → MCU TX (setPins txPin) +#define PIN_GPS_TX 5 // MCU receives from GPS TX +#define PIN_GPS_RX 6 // MCU transmits to GPS RX +#define PIN_GPS_PPS 7 // GPS PPS output +#define PIN_GPS_EN 16 // GPS wake-up/enable (GPS_EN_PIN in LilyGO code) +#define HAS_GPS 1 +#define GPS_BAUDRATE 9600 + +// Buttons +#define BUTTON_PIN 0 // BUTTON 1 (boot) +#define BUTTON_PIN_ALT 17 // BUTTON 2 + +// SPI (shared by LoRa and SD) +#define SPI_MOSI 11 +#define SPI_SCK 13 +#define SPI_MISO 12 +#define SPI_CS 10 + +// SD Card +#define HAS_SDCARD +#define SDCARD_USE_SPI1 +#define SDCARD_CS SPI_CS + +// LoRa Radio - SX1262 with 1W PA +#define USE_SX1262 + +#define LORA_SCK SPI_SCK +#define LORA_MISO SPI_MISO +#define LORA_MOSI SPI_MOSI +#define LORA_CS 15 +#define LORA_RESET 3 +#define LORA_DIO1 1 +#define LORA_BUSY 38 + +// CRITICAL: Radio power enable - MUST be HIGH before lora.begin()! +// GPIO 40 powers the SX1262 + PA module via LDO +#define SX126X_POWER_EN 40 + +#ifdef USE_SX1262 +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_BUSY +#define SX126X_RESET LORA_RESET + +// RF switching configuration for 1W PA module +// DIO2 controls PA (via SX126X_DIO2_AS_RF_SWITCH) +// CTRL PIN (GPIO 21) controls LNA - must be HIGH during RX +// Truth table: DIO2=1,CTRL=0 -> TX (PA on, LNA off) +// DIO2=0,CTRL=1 -> RX (PA off, LNA on) +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_RXEN 21 // LNA enable - HIGH during RX + +// TCXO voltage - required for radio init +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define SX126X_MAX_POWER 22 +#endif + +// LED +#define LED_PIN 18 +#define LED_STATE_ON 1 // HIGH = ON + +// Battery ADC +#define BATTERY_PIN 4 +#define ADC_CHANNEL ADC1_GPIO4_CHANNEL +#define BATTERY_SENSE_SAMPLES 30 +#define ADC_MULTIPLIER 3.0 + +// NTC temperature sensor +#define NTC_PIN 14 + +// Fan control +#define FAN_CTRL_PIN 41 + +// PA Ramp Time - T-Beam 1W requires >800us stabilization (default is 200us) +// Value 0x05 = RADIOLIB_SX126X_PA_RAMP_800U +#define SX126X_PA_RAMP_US 0x05 + +// Display - SH1106 OLED (128x64) +#define USE_SH1106 +#define OLED_WIDTH 128 +#define OLED_HEIGHT 64 + +// 32768 Hz crystal present +#define HAS_32768HZ 1 From 44e7c092c8bef63eede999481ede41f4c728a737 Mon Sep 17 00:00:00 2001 From: Steven Linn <smlucf@gmail.com> Date: Wed, 28 Jan 2026 14:23:36 -0700 Subject: [PATCH 436/546] Add battery min/max voltage parameter support --- examples/companion_radio/ui-new/UITask.cpp | 10 ++++++++-- examples/companion_radio/ui-orig/UITask.cpp | 10 ++++++++-- variants/lilygo_tbeam_1w/platformio.ini | 4 ++++ 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 8077627f..0690b45a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -103,8 +103,14 @@ class HomeScreen : public UIScreen { void renderBatteryIndicator(DisplayDriver& display, uint16_t batteryMilliVolts) { // Convert millivolts to percentage - const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) - const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) +#ifndef BATT_MIN_MILLIVOLTS + #define BATT_MIN_MILLIVOLTS 3000 +#endif +#ifndef BATT_MAX_MILLIVOLTS + #define BATT_MAX_MILLIVOLTS 4200 +#endif + const int minMilliVolts = BATT_MIN_MILLIVOLTS; + const int maxMilliVolts = BATT_MAX_MILLIVOLTS; int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 39cbf23a..3ad36fb0 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -149,8 +149,14 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i void UITask::renderBatteryIndicator(uint16_t batteryMilliVolts) { // Convert millivolts to percentage - const int minMilliVolts = 3000; // Minimum voltage (e.g., 3.0V) - const int maxMilliVolts = 4200; // Maximum voltage (e.g., 4.2V) +#ifndef BATT_MIN_MILLIVOLTS + #define BATT_MIN_MILLIVOLTS 3000 +#endif +#ifndef BATT_MAX_MILLIVOLTS + #define BATT_MAX_MILLIVOLTS 4200 +#endif + const int minMilliVolts = BATT_MIN_MILLIVOLTS; + const int maxMilliVolts = BATT_MAX_MILLIVOLTS; int batteryPercentage = ((batteryMilliVolts - minMilliVolts) * 100) / (maxMilliVolts - minMilliVolts); if (batteryPercentage < 0) batteryPercentage = 0; // Clamp to 0% if (batteryPercentage > 100) batteryPercentage = 100; // Clamp to 100% diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 4b72b5e7..618a32a8 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -31,6 +31,10 @@ build_flags = ; TX power: 22dBm to SX1262, PA module adds ~10dB for 32dBm total -D LORA_TX_POWER=22 + ; Battery - 2S 7.4V LiPo (6.0V min, 8.4V max) + -D BATT_MIN_MILLIVOLTS=6000 + -D BATT_MAX_MILLIVOLTS=8400 + ; Display - SH1106 OLED at 0x3C -D DISPLAY_CLASS=SH1106Display From a9a8299e14172769dcc304d6d64fe203acaa01ce Mon Sep 17 00:00:00 2001 From: Steven Linn <smlucf@gmail.com> Date: Wed, 28 Jan 2026 14:24:53 -0700 Subject: [PATCH 437/546] Set LilyGO T-Beam 1W to use TX0 3.0V (within reference +2.85V~+3.15V) --- variants/lilygo_tbeam_1w/platformio.ini | 2 +- variants/lilygo_tbeam_1w/variant.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 618a32a8..cf17ae8b 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -24,7 +24,7 @@ build_flags = ; Truth table: DIO2=1,RXEN=0 → TX | DIO2=0,RXEN=1 → RX -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_RXEN=21 - -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_DIO3_TCXO_VOLTAGE=3.0 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 diff --git a/variants/lilygo_tbeam_1w/variant.h b/variants/lilygo_tbeam_1w/variant.h index c05b1696..f6807e56 100644 --- a/variants/lilygo_tbeam_1w/variant.h +++ b/variants/lilygo_tbeam_1w/variant.h @@ -62,7 +62,7 @@ #define SX126X_RXEN 21 // LNA enable - HIGH during RX // TCXO voltage - required for radio init -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 +#define SX126X_DIO3_TCXO_VOLTAGE 3.0 #define SX126X_MAX_POWER 22 #endif From 465776d66758e9608db1402b747e841d37b205b7 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 29 Jan 2026 21:12:31 +1100 Subject: [PATCH 438/546] * ver 1.12.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index a2b0033f..95265a19 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 8 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "30 Nov 2025" +#define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.11.0" +#define FIRMWARE_VERSION "v1.12.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 60d22902..0d5cd28a 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 "30 Nov 2025" + #define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.11.0" + #define FIRMWARE_VERSION "v1.12.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 4f3ed0e4..f470e55e 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "30 Nov 2025" + #define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.11.0" + #define FIRMWARE_VERSION "v1.12.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index eb2d90c5..ed352345 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "30 Nov 2025" + #define FIRMWARE_BUILD_DATE "29 Jan 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.11.0" + #define FIRMWARE_VERSION "v1.12.0" #endif #define FIRMWARE_ROLE "sensor" From 3a7ccc085d5dab694a0eeeb61b5bf341f56ab958 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Thu, 29 Jan 2026 15:32:51 +0100 Subject: [PATCH 439/546] fixed build errors and typos/inconsistencies --- variants/thinknode_m3/ThinknodeM3Board.cpp | 22 +++++++++--- variants/thinknode_m3/ThinknodeM3Board.h | 42 ++++++++++------------ variants/thinknode_m3/target.cpp | 16 ++++----- variants/thinknode_m3/target.h | 4 +-- variants/thinknode_m6/ThinkNodeM6Board.h | 13 ++++--- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinknodeM3Board.cpp index d7ecd62d..ac513ade 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.cpp +++ b/variants/thinknode_m3/ThinknodeM3Board.cpp @@ -1,14 +1,28 @@ #include <Arduino.h> -#include "ThinknodeM3Board.h" +#include "ThinkNodeM3Board.h" #include <Wire.h> #include <bluefruit.h> -void ThinknodeM3Board::begin() { - Nrf52BoardDCDC::begin(); +void ThinkNodeM3Board::begin() { + NRF52Board::begin(); btn_prev_state = HIGH; Wire.begin(); delay(10); // give sx1262 some time to power up -} \ No newline at end of file +} + +uint16_t ThinkNodeM3Board::getBattMilliVolts() { + int adcvalue = 0; + + analogReference(AR_INTERNAL_2_4); + analogReadResolution(ADC_RESOLUTION); + delay(10); + + // ADC range is 0..2400mV and resolution is 12-bit (0..4095) + adcvalue = analogRead(PIN_VBAT_READ); + // Convert the raw value to compensated mv, taking the resistor- + // divider into account (providing the actual LIPO voltage) + return (uint16_t)((float)adcvalue * ADC_FACTOR); +} diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinknodeM3Board.h index 62694087..1435d31d 100644 --- a/variants/thinknode_m3/ThinknodeM3Board.h +++ b/variants/thinknode_m3/ThinknodeM3Board.h @@ -6,38 +6,26 @@ #define ADC_FACTOR ((1000.0*ADC_MULTIPLIER*AREF_VOLTAGE)/ADC_MAX) -class ThinknodeM3Board : public Nrf52BoardDCDC { +class ThinkNodeM3Board : public NRF52BoardDCDC { protected: +#if NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif uint8_t btn_prev_state; public: + ThinkNodeM3Board() : NRF52Board("THINKNODE_M3_OTA") {} void begin(); - - uint16_t getBattMilliVolts() override { - int adcvalue = 0; - - analogReference(AR_INTERNAL_2_4); - analogReadResolution(ADC_RESOLUTION); - delay(10); - - // ADC range is 0..2400mV and resolution is 12-bit (0..4095) - adcvalue = analogRead(PIN_VBAT_READ); - // Convert the raw value to compensated mv, taking the resistor- - // divider into account (providing the actual LIPO voltage) - return (uint16_t)((float)adcvalue * ADC_FACTOR); - } + uint16_t getBattMilliVolts() override; #if defined(P_LORA_TX_LED) -#if !defined(P_LORA_TX_LED_ON) -#define P_LORA_TX_LED_ON HIGH -#endif void onBeforeTransmit() override { - digitalWrite(P_LORA_TX_LED, P_LORA_TX_LED_ON); // turn TX LED on + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on } void onAfterTransmit() override { - digitalWrite(P_LORA_TX_LED, !P_LORA_TX_LED_ON); // turn TX LED off + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } - #endif +#endif const char* getManufacturerName() const override { return "Elecrow ThinkNode M3"; @@ -54,5 +42,13 @@ public: return 0; } - void powerOff() override { sd_power_system_off(); } -}; \ No newline at end of file + void powerOff() override { + // turn off all leds, sd_power_system_off will not do this for us + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, LOW); + #endif + + // power off board + sd_power_system_off(); + } +}; diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index c6708e4d..91d186dc 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -2,7 +2,7 @@ #include "target.h" #include <helpers/sensors/MicroNMEALocationProvider.h> -ThinknodeM3Board board; +ThinkNodeM3Board board; RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); @@ -30,26 +30,26 @@ static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = { RADIOLIB_LR11X0_DIO5, RADIOLIB_LR11X0_DIO6, RADIOLIB_NC, - RADIOLIB_NC, + RADIOLIB_NC, RADIOLIB_NC }; static const Module::RfSwitchMode_t rfswitch_table[] = { - // mode DIO5 DIO6 - { LR11x0::MODE_STBY, {LOW , LOW }}, + // mode DIO5 DIO6 + { LR11x0::MODE_STBY, {LOW , LOW }}, { LR11x0::MODE_RX, {HIGH, LOW }}, { LR11x0::MODE_TX, {HIGH, HIGH }}, { LR11x0::MODE_TX_HP, {LOW , HIGH }}, - { LR11x0::MODE_TX_HF, {LOW , LOW }}, + { LR11x0::MODE_TX_HF, {LOW , LOW }}, { LR11x0::MODE_GNSS, {LOW , LOW }}, - { LR11x0::MODE_WIFI, {LOW , LOW }}, + { LR11x0::MODE_WIFI, {LOW , LOW }}, END_OF_MODE_TABLE, }; #endif bool radio_init() { rtc_clock.begin(Wire); - + #ifdef LR11X0_DIO3_TCXO_VOLTAGE float tcxo = LR11X0_DIO3_TCXO_VOLTAGE; #else @@ -64,7 +64,7 @@ bool radio_init() { Serial.println(status); return false; // fail } - + radio.setCRC(2); radio.explicitHeader(); diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h index f60a85b0..23e99581 100644 --- a/variants/thinknode_m3/target.h +++ b/variants/thinknode_m3/target.h @@ -3,7 +3,7 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> #include <helpers/radiolib/RadioLibWrappers.h> -#include "ThinknodeM3Board.h" +#include "ThinkNodeM3Board.h" #include <helpers/radiolib/CustomLR1110Wrapper.h> #include <helpers/ArduinoHelpers.h> #include <helpers/sensors/EnvironmentSensorManager.h> @@ -17,7 +17,7 @@ extern NullDisplayDriver display; #endif -extern ThinknodeM3Board board; +extern ThinkNodeM3Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern EnvironmentSensorManager sensors; diff --git a/variants/thinknode_m6/ThinkNodeM6Board.h b/variants/thinknode_m6/ThinkNodeM6Board.h index c03e1fbc..32baa2a0 100644 --- a/variants/thinknode_m6/ThinkNodeM6Board.h +++ b/variants/thinknode_m6/ThinkNodeM6Board.h @@ -12,9 +12,14 @@ #define PIN_VBAT_READ BATTERY_PIN #define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB) -class ThinkNodeM6Board : public Nrf52BoardOTA { +class ThinkNodeM6Board : public NRF52BoardDCDC { +protected: +#if NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: - ThinkNodeM6Board() : NRF52BoardOTA("THINKNODE_M1_OTA") {} + ThinkNodeM6Board() : NRF52Board("THINKNODE_M6_OTA") {} void begin(); uint16_t getBattMilliVolts() override; @@ -25,10 +30,10 @@ public: void onAfterTransmit() override { digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off } - #endif +#endif const char* getManufacturerName() const override { - return "Elecrow ThinkNode-M6"; + return "Elecrow ThinkNode M6"; } void powerOff() override { From 2a321b53ebfeea2d0b2a9160f944564633b8f9aa Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Thu, 29 Jan 2026 16:00:19 +0100 Subject: [PATCH 440/546] renamed board files --- .../thinknode_m3/{ThinknodeM3Board.cpp => ThinkNodeM3Board.cpp} | 0 variants/thinknode_m3/{ThinknodeM3Board.h => ThinkNodeM3Board.h} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename variants/thinknode_m3/{ThinknodeM3Board.cpp => ThinkNodeM3Board.cpp} (100%) rename variants/thinknode_m3/{ThinknodeM3Board.h => ThinkNodeM3Board.h} (100%) diff --git a/variants/thinknode_m3/ThinknodeM3Board.cpp b/variants/thinknode_m3/ThinkNodeM3Board.cpp similarity index 100% rename from variants/thinknode_m3/ThinknodeM3Board.cpp rename to variants/thinknode_m3/ThinkNodeM3Board.cpp diff --git a/variants/thinknode_m3/ThinknodeM3Board.h b/variants/thinknode_m3/ThinkNodeM3Board.h similarity index 100% rename from variants/thinknode_m3/ThinknodeM3Board.h rename to variants/thinknode_m3/ThinkNodeM3Board.h From c345f1da8e201bcac4aec02a56529192d15000ec Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Fri, 30 Jan 2026 00:12:04 +0100 Subject: [PATCH 441/546] Revert "Remove _serial->isConnected() logic from buzzer notifications" --- examples/companion_radio/MyMesh.cpp | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 2dad7866..e0537707 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -330,10 +330,11 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); _serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE); } - } + } else { #ifdef DISPLAY_CLASS - if (_ui && !_prefs.buzzer_quiet) _ui->notify(UIEventType::newContactMessage); //buzz if enabled + if (_ui) _ui->notify(UIEventType::newContactMessage); #endif + } // add inbound-path to mem cache if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid @@ -440,7 +441,9 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN; if (should_display && _ui) { _ui->newMsg(path_len, from.name, text, offline_queue_len); - if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::contactMessage); //buzz if enabled + if (!_serial->isConnected()) { + _ui->notify(UIEventType::contactMessage); + } } #endif } @@ -525,8 +528,11 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe uint8_t frame[1]; frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle' _serial->writeFrame(frame, 1); + } else { +#ifdef DISPLAY_CLASS + if (_ui) _ui->notify(UIEventType::channelMessage); +#endif } - #ifdef DISPLAY_CLASS // Get the channel name from the channel index const char *channel_name = "Unknown"; @@ -534,10 +540,7 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe if (getChannel(channel_idx, channel_details)) { channel_name = channel_details.name; } - if (_ui) { - _ui->newMsg(path_len, channel_name, text, offline_queue_len); - if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled - } + if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len); #endif } @@ -796,7 +799,6 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.bw = LORA_BW; _prefs.cr = LORA_CR; _prefs.tx_power_dbm = LORA_TX_POWER; - _prefs.buzzer_quiet = 0; _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default //_prefs.rx_delay_base = 10.0f; enable once new algo fixed @@ -836,7 +838,6 @@ void MyMesh::begin(bool has_display) { _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); - _prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours From c7eea3915d5d23e8cbe5271d3ae4ccc11f4bc68b Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 30 Jan 2026 15:07:40 +1100 Subject: [PATCH 442/546] fix: remove esp_wifi.h from esp32board.h saves ~500 bytes of dram and allows Tbeam to compile again --- examples/companion_radio/main.cpp | 1 + src/helpers/ESP32Board.cpp | 1 + src/helpers/ESP32Board.h | 12 ++++++------ 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 7e636ace..eff9efca 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -194,6 +194,7 @@ void setup() { ); #ifdef WIFI_SSID + board.setInhibitSleep(true); // prevent sleep when WiFi is active WiFi.begin(WIFI_SSID, WIFI_PWD); serial_interface.begin(TCP_PORT); #elif defined(BLE_PIN_CODE) diff --git a/src/helpers/ESP32Board.cpp b/src/helpers/ESP32Board.cpp index 4dce467c..e0ca1d0e 100644 --- a/src/helpers/ESP32Board.cpp +++ b/src/helpers/ESP32Board.cpp @@ -11,6 +11,7 @@ #include <SPIFFS.h> bool ESP32Board::startOTAUpdate(const char* id, char reply[]) { + inhibit_sleep = true; // prevent sleep during OTA WiFi.softAP("MeshCore-OTA", NULL); sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str()); diff --git a/src/helpers/ESP32Board.h b/src/helpers/ESP32Board.h index 01b4c980..bade3e89 100644 --- a/src/helpers/ESP32Board.h +++ b/src/helpers/ESP32Board.h @@ -8,12 +8,12 @@ #include <rom/rtc.h> #include <sys/time.h> #include <Wire.h> -#include "esp_wifi.h" #include "driver/rtc_io.h" class ESP32Board : public mesh::MainBoard { protected: uint8_t startup_reason; + bool inhibit_sleep = false; public: void begin() { @@ -72,11 +72,7 @@ public: } void sleep(uint32_t secs) override { - // To check for WiFi status to see if there is active OTA - wifi_mode_t mode; - esp_err_t err = esp_wifi_get_mode(&mode); - - if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep + if (!inhibit_sleep) { enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet } } @@ -126,6 +122,10 @@ public: } bool startOTAUpdate(const char* id, char reply[]) override; + + void setInhibitSleep(bool inhibit) { + inhibit_sleep = inhibit; + } }; class ESP32RTCClock : public mesh::RTCClock { From 019bbf74d336995653aa97cb15d2d5a7b3d612e9 Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Thu, 29 Jan 2026 20:44:11 -0800 Subject: [PATCH 443/546] Add recv_errors to CMD_GET_STATS STATS_TYPE_PACKETS response Append uint32_t recv_errors (RadioLib receive/CRC errors) to packet stats binary frame. Frame size 26 -> 30 bytes. Update stats_binary_frames.md and Python/TypeScript parsing examples for backward compatibility (accept >=26). --- docs/stats_binary_frames.md | 26 +++++++++++++++++++++----- examples/companion_radio/MyMesh.cpp | 2 ++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/stats_binary_frames.md b/docs/stats_binary_frames.md index 1b409912..f3b17da9 100644 --- a/docs/stats_binary_frames.md +++ b/docs/stats_binary_frames.md @@ -94,7 +94,7 @@ struct StatsRadio { ## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2) -**Total Frame Size:** 26 bytes +**Total Frame Size:** 26 bytes (legacy) or 30 bytes (includes `recv_errors`) | Offset | Size | Type | Field Name | Description | Range/Notes | |--------|------|------|------------|-------------|-------------| @@ -106,12 +106,14 @@ struct StatsRadio { | 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 | | 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 | | 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 | +| 26 | 4 | uint32_t | recv_errors | Receive/CRC errors (RadioLib); present only in 30-byte frame | 0 - 4,294,967,295 | ### Notes - Counters are cumulative from boot and may wrap. - `recv = flood_rx + direct_rx` - `sent = flood_tx + direct_tx` +- Clients should accept frame length ≥ 26; if length ≥ 30, parse `recv_errors` at offset 26. ### Example Structure (C/C++) @@ -125,6 +127,7 @@ struct StatsPackets { uint32_t direct_tx; uint32_t flood_rx; uint32_t direct_rx; + uint32_t recv_errors; // present when frame size is 30 } __attribute__((packed)); ``` @@ -183,11 +186,12 @@ def parse_stats_radio(frame): } def parse_stats_packets(frame): - """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)""" + """Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 or 30 bytes)""" + assert len(frame) >= 26, "STATS_TYPE_PACKETS frame too short" response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \ - struct.unpack('<B B I I I I I I', frame) + struct.unpack('<B B I I I I I I', frame[:26]) assert response_code == 24 and stats_type == 2, "Invalid response type" - return { + result = { 'recv': recv, 'sent': sent, 'flood_tx': flood_tx, @@ -195,6 +199,10 @@ def parse_stats_packets(frame): 'flood_rx': flood_rx, 'direct_rx': direct_rx } + if len(frame) >= 30: + (recv_errors,) = struct.unpack('<I', frame[26:30]) + result['recv_errors'] = recv_errors + return result ``` --- @@ -251,6 +259,7 @@ interface StatsPackets { direct_tx: number; flood_rx: number; direct_rx: number; + recv_errors?: number; // present when frame is 30 bytes } function parseStatsCore(buffer: ArrayBuffer): StatsCore { @@ -286,12 +295,15 @@ function parseStatsRadio(buffer: ArrayBuffer): StatsRadio { function parseStatsPackets(buffer: ArrayBuffer): StatsPackets { const view = new DataView(buffer); + if (buffer.byteLength < 26) { + throw new Error('STATS_TYPE_PACKETS frame too short'); + } const response_code = view.getUint8(0); const stats_type = view.getUint8(1); if (response_code !== 24 || stats_type !== 2) { throw new Error('Invalid response type'); } - return { + const result: StatsPackets = { recv: view.getUint32(2, true), sent: view.getUint32(6, true), flood_tx: view.getUint32(10, true), @@ -299,6 +311,10 @@ function parseStatsPackets(buffer: ArrayBuffer): StatsPackets { flood_rx: view.getUint32(18, true), direct_rx: view.getUint32(22, true) }; + if (buffer.byteLength >= 30) { + result.recv_errors = view.getUint32(26, true); + } + return result; } ``` diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 2dad7866..cfe3b77d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1688,12 +1688,14 @@ void MyMesh::handleCmdFrame(size_t len) { uint32_t n_sent_direct = getNumSentDirect(); uint32_t n_recv_flood = getNumRecvFlood(); uint32_t n_recv_direct = getNumRecvDirect(); + uint32_t n_recv_errors = radio_driver.getPacketsRecvErrors(); memcpy(&out_frame[i], &recv, 4); i += 4; memcpy(&out_frame[i], &sent, 4); i += 4; memcpy(&out_frame[i], &n_sent_flood, 4); i += 4; memcpy(&out_frame[i], &n_sent_direct, 4); i += 4; memcpy(&out_frame[i], &n_recv_flood, 4); i += 4; memcpy(&out_frame[i], &n_recv_direct, 4); i += 4; + memcpy(&out_frame[i], &n_recv_errors, 4); i += 4; _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type From c786cfe613b37506dc48caac9273171d8a4df5b1 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 31 Jan 2026 10:22:32 +0100 Subject: [PATCH 444/546] Add KISS Modem firmware --- README.md | 2 + docs/kiss_modem_protocol.md | 110 +++++++++ examples/kiss_modem/KissModem.cpp | 362 ++++++++++++++++++++++++++++++ examples/kiss_modem/KissModem.h | 124 ++++++++++ examples/kiss_modem/main.cpp | 108 +++++++++ 5 files changed, 706 insertions(+) create mode 100644 docs/kiss_modem_protocol.md create mode 100644 examples/kiss_modem/KissModem.cpp create mode 100644 examples/kiss_modem/KissModem.h create mode 100644 examples/kiss_modem/main.cpp diff --git a/README.md b/README.md index d3bcbbef..9d47bffe 100644 --- a/README.md +++ b/README.md @@ -39,9 +39,11 @@ For developers; - Clone and open the MeshCore repository in Visual Studio Code. - See the example applications you can modify and run: - [Companion Radio](./examples/companion_radio) - For use with an external chat app, over BLE, USB or WiFi. + - [KISS Modem](./examples/kiss_modem) - Serial KISS protocol bridge for host applications. ([protocol docs](./docs/kiss_modem_protocol.md)) - [Simple Repeater](./examples/simple_repeater) - Extends network coverage by relaying messages. - [Simple Room Server](./examples/simple_room_server) - A simple BBS server for shared Posts. - [Simple Secure Chat](./examples/simple_secure_chat) - Secure terminal based text communication between devices. + - [Simple Sensor](./examples/simple_sensor) - Remote sensor node with telemetry and alerting. The Simple Secure Chat example can be interacted with through the Serial Monitor in Visual Studio Code, or with a Serial USB Terminal on Android. diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md new file mode 100644 index 00000000..f85bfe6c --- /dev/null +++ b/docs/kiss_modem_protocol.md @@ -0,0 +1,110 @@ +# MeshCore KISS Modem Protocol + +Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore packets over LoRa and cryptographic operations using the modem's identity. + +## Serial Configuration + +115200 baud, 8N1, no flow control. + +## Frame Format + +Standard KISS framing with byte stuffing. + +| Byte | Name | Description | +|------|------|-------------| +| `0xC0` | FEND | Frame delimiter | +| `0xDB` | FESC | Escape character | +| `0xDC` | TFEND | Escaped FEND (FESC + TFEND = 0xC0) | +| `0xDD` | TFESC | Escaped FESC (FESC + TFESC = 0xDB) | + +``` +┌──────┬─────────┬──────────────┬──────┐ +│ FEND │ Command │ Data (escaped)│ FEND │ +│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ +└──────┴─────────┴──────────────┴──────┘ +``` + +Maximum unescaped frame size: 512 bytes. + +## Commands + +### Request Commands (Host → Modem) + +| Command | Value | Data | +|---------|-------|------| +| `CMD_DATA` | `0x00` | Packet (2-255 bytes) | +| `CMD_GET_IDENTITY` | `0x01` | - | +| `CMD_GET_RANDOM` | `0x02` | Length (1 byte, 1-64) | +| `CMD_VERIFY_SIGNATURE` | `0x03` | PubKey (32) + Signature (64) + Data | +| `CMD_SIGN_DATA` | `0x04` | Data to sign | +| `CMD_ENCRYPT_DATA` | `0x05` | Key (32) + Plaintext | +| `CMD_DECRYPT_DATA` | `0x06` | Key (32) + MAC (2) + Ciphertext | +| `CMD_KEY_EXCHANGE` | `0x07` | Remote PubKey (32) | +| `CMD_HASH` | `0x08` | Data to hash | +| `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | +| `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | +| `CMD_SET_SYNC_WORD` | `0x0B` | Sync word (1) | +| `CMD_GET_RADIO` | `0x0C` | - | +| `CMD_GET_TX_POWER` | `0x0D` | - | +| `CMD_GET_SYNC_WORD` | `0x0E` | - | +| `CMD_GET_VERSION` | `0x0F` | - | + +### Response Commands (Modem → Host) + +| Command | Value | Data | +|---------|-------|------| +| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | +| `RESP_IDENTITY` | `0x11` | PubKey (32) | +| `RESP_RANDOM` | `0x12` | Random bytes (1-64) | +| `RESP_VERIFY` | `0x13` | Result (1): 0x00=invalid, 0x01=valid | +| `RESP_SIGNATURE` | `0x14` | Signature (64) | +| `RESP_ENCRYPTED` | `0x15` | MAC (2) + Ciphertext | +| `RESP_DECRYPTED` | `0x16` | Plaintext | +| `RESP_SHARED_SECRET` | `0x17` | Shared secret (32) | +| `RESP_HASH` | `0x18` | SHA-256 hash (32) | +| `RESP_OK` | `0x19` | - | +| `RESP_RADIO` | `0x1A` | Freq (4) + BW (4) + SF (1) + CR (1) | +| `RESP_TX_POWER` | `0x1B` | Power dBm (1) | +| `RESP_SYNC_WORD` | `0x1C` | Sync word (1) | +| `RESP_VERSION` | `0x1D` | Version (1) + Reserved (1) | +| `RESP_ERROR` | `0x1E` | Error code (1) | +| `RESP_TX_DONE` | `0x1F` | Result (1): 0x00=failed, 0x01=success | + +## Error Codes + +| Code | Value | Description | +|------|-------|-------------| +| `ERR_INVALID_LENGTH` | `0x01` | Request data too short | +| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | +| `ERR_NO_CALLBACK` | `0x03` | Radio callback not set | +| `ERR_MAC_FAILED` | `0x04` | MAC verification failed | +| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | +| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | + +## Data Formats + +### Radio Parameters (CMD_SET_RADIO / RESP_RADIO) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Frequency | 4 bytes | Hz (e.g., 869618000) | +| Bandwidth | 4 bytes | Hz (e.g., 62500) | +| SF | 1 byte | Spreading factor (5-12) | +| CR | 1 byte | Coding rate (5-8) | + +### Received Packet (CMD_DATA response) + +| Field | Size | Description | +|-------|------|-------------| +| SNR | 1 byte | Signal-to-noise × 4, signed | +| RSSI | 1 byte | Signal strength dBm, signed | +| Packet | variable | Raw MeshCore packet | + +## Notes + +- Modem generates identity on first boot (stored in flash) +- SNR values multiplied by 4 for 0.25 dB precision +- Wait for `RESP_TX_DONE` before sending next packet +- See [packet_structure.md](./packet_structure.md) for packet format diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp new file mode 100644 index 00000000..4e227d7f --- /dev/null +++ b/examples/kiss_modem/KissModem.cpp @@ -0,0 +1,362 @@ +#include "KissModem.h" + +KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng) + : _serial(serial), _identity(identity), _rng(rng) { + _rx_len = 0; + _rx_escaped = false; + _rx_active = false; + _has_pending_tx = false; + _pending_tx_len = 0; + _setRadioCallback = nullptr; + _setTxPowerCallback = nullptr; + _setSyncWordCallback = nullptr; + _config = {0, 0, 0, 0, 0, 0x12}; +} + +void KissModem::begin() { + _rx_len = 0; + _rx_escaped = false; + _rx_active = false; + _has_pending_tx = false; +} + +void KissModem::writeByte(uint8_t b) { + if (b == KISS_FEND) { + _serial.write(KISS_FESC); + _serial.write(KISS_TFEND); + } else if (b == KISS_FESC) { + _serial.write(KISS_FESC); + _serial.write(KISS_TFESC); + } else { + _serial.write(b); + } +} + +void KissModem::writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len) { + _serial.write(KISS_FEND); + writeByte(cmd); + for (uint16_t i = 0; i < len; i++) { + writeByte(data[i]); + } + _serial.write(KISS_FEND); +} + +void KissModem::writeErrorFrame(uint8_t error_code) { + writeFrame(RESP_ERROR, &error_code, 1); +} + +void KissModem::loop() { + while (_serial.available()) { + uint8_t b = _serial.read(); + + if (b == KISS_FEND) { + if (_rx_active && _rx_len > 0) { + processFrame(); + } + _rx_len = 0; + _rx_escaped = false; + _rx_active = true; + continue; + } + + if (!_rx_active) continue; + + if (b == KISS_FESC) { + _rx_escaped = true; + continue; + } + + if (_rx_escaped) { + _rx_escaped = false; + if (b == KISS_TFEND) b = KISS_FEND; + else if (b == KISS_TFESC) b = KISS_FESC; + } + + if (_rx_len < KISS_MAX_FRAME_SIZE) { + _rx_buf[_rx_len++] = b; + } + } +} + +void KissModem::processFrame() { + if (_rx_len < 1) return; + + uint8_t cmd = _rx_buf[0]; + const uint8_t* data = &_rx_buf[1]; + uint16_t data_len = _rx_len - 1; + + switch (cmd) { + case CMD_DATA: + if (data_len < 2) { + writeErrorFrame(ERR_INVALID_LENGTH); + } else if (data_len > KISS_MAX_PACKET_SIZE) { + writeErrorFrame(ERR_INVALID_LENGTH); + } else { + memcpy(_pending_tx, data, data_len); + _pending_tx_len = data_len; + _has_pending_tx = true; + } + break; + case CMD_GET_IDENTITY: + handleGetIdentity(); + break; + case CMD_GET_RANDOM: + handleGetRandom(data, data_len); + break; + case CMD_VERIFY_SIGNATURE: + handleVerifySignature(data, data_len); + break; + case CMD_SIGN_DATA: + handleSignData(data, data_len); + break; + case CMD_ENCRYPT_DATA: + handleEncryptData(data, data_len); + break; + case CMD_DECRYPT_DATA: + handleDecryptData(data, data_len); + break; + case CMD_KEY_EXCHANGE: + handleKeyExchange(data, data_len); + break; + case CMD_HASH: + handleHash(data, data_len); + break; + case CMD_SET_RADIO: + handleSetRadio(data, data_len); + break; + case CMD_SET_TX_POWER: + handleSetTxPower(data, data_len); + break; + case CMD_SET_SYNC_WORD: + handleSetSyncWord(data, data_len); + break; + case CMD_GET_RADIO: + handleGetRadio(); + break; + case CMD_GET_TX_POWER: + handleGetTxPower(); + break; + case CMD_GET_SYNC_WORD: + handleGetSyncWord(); + break; + case CMD_GET_VERSION: + handleGetVersion(); + break; + default: + writeErrorFrame(ERR_UNKNOWN_CMD); + break; + } +} + +void KissModem::handleGetIdentity() { + writeFrame(RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); +} + +void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t requested = data[0]; + if (requested < 1 || requested > 64) { + writeErrorFrame(ERR_INVALID_PARAM); + return; + } + + uint8_t buf[64]; + _rng.random(buf, requested); + writeFrame(RESP_RANDOM, buf, requested); +} + +void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + mesh::Identity signer(data); + const uint8_t* signature = data + PUB_KEY_SIZE; + const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE; + uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; + + uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; + writeFrame(RESP_VERIFY, &result, 1); +} + +void KissModem::handleSignData(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t signature[SIGNATURE_SIZE]; + _identity.sign(signature, data, len); + writeFrame(RESP_SIGNATURE, signature, SIGNATURE_SIZE); +} + +void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE + 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + const uint8_t* key = data; + const uint8_t* plaintext = data + PUB_KEY_SIZE; + uint16_t plaintext_len = len - PUB_KEY_SIZE; + + uint8_t buf[KISS_MAX_FRAME_SIZE]; + int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); + + if (encrypted_len > 0) { + writeFrame(RESP_ENCRYPTED, buf, encrypted_len); + } else { + writeErrorFrame(ERR_ENCRYPT_FAILED); + } +} + +void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + const uint8_t* key = data; + const uint8_t* ciphertext = data + PUB_KEY_SIZE; + uint16_t ciphertext_len = len - PUB_KEY_SIZE; + + uint8_t buf[KISS_MAX_FRAME_SIZE]; + int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); + + if (decrypted_len > 0) { + writeFrame(RESP_DECRYPTED, buf, decrypted_len); + } else { + writeErrorFrame(ERR_MAC_FAILED); + } +} + +void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { + if (len < PUB_KEY_SIZE) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t shared_secret[PUB_KEY_SIZE]; + _identity.calcSharedSecret(shared_secret, data); + writeFrame(RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); +} + +void KissModem::handleHash(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + + uint8_t hash[32]; + mesh::Utils::sha256(hash, 32, data, len); + writeFrame(RESP_HASH, hash, 32); +} + +bool KissModem::getPacketToSend(uint8_t* packet, uint16_t* len) { + if (!_has_pending_tx) return false; + + memcpy(packet, _pending_tx, _pending_tx_len); + *len = _pending_tx_len; + _has_pending_tx = false; + return true; +} + +void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { + uint8_t buf[2 + KISS_MAX_PACKET_SIZE]; + buf[0] = (uint8_t)snr; + buf[1] = (uint8_t)rssi; + memcpy(&buf[2], packet, len); + writeFrame(CMD_DATA, buf, 2 + len); +} + +void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { + if (len < 10) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_setRadioCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint32_t freq_hz, bw_hz; + memcpy(&freq_hz, data, 4); + memcpy(&bw_hz, data + 4, 4); + uint8_t sf = data[8]; + uint8_t cr = data[9]; + + _config.freq_hz = freq_hz; + _config.bw_hz = bw_hz; + _config.sf = sf; + _config.cr = cr; + + float freq = freq_hz / 1000000.0f; + float bw = bw_hz / 1000.0f; + + _setRadioCallback(freq, bw, sf, cr); + writeFrame(RESP_OK, nullptr, 0); +} + +void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_setTxPowerCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + _config.tx_power = data[0]; + _setTxPowerCallback(data[0]); + writeFrame(RESP_OK, nullptr, 0); +} + +void KissModem::handleSetSyncWord(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_setSyncWordCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + _config.sync_word = data[0]; + _setSyncWordCallback(data[0]); + writeFrame(RESP_OK, nullptr, 0); +} + +void KissModem::handleGetRadio() { + uint8_t buf[10]; + memcpy(buf, &_config.freq_hz, 4); + memcpy(buf + 4, &_config.bw_hz, 4); + buf[8] = _config.sf; + buf[9] = _config.cr; + writeFrame(RESP_RADIO, buf, 10); +} + +void KissModem::handleGetTxPower() { + writeFrame(RESP_TX_POWER, &_config.tx_power, 1); +} + +void KissModem::handleGetSyncWord() { + writeFrame(RESP_SYNC_WORD, &_config.sync_word, 1); +} + +void KissModem::handleGetVersion() { + uint8_t buf[2]; + buf[0] = KISS_FIRMWARE_VERSION; + buf[1] = 0; + writeFrame(RESP_VERSION, buf, 2); +} + +void KissModem::onTxComplete(bool success) { + uint8_t result = success ? 0x01 : 0x00; + writeFrame(RESP_TX_DONE, &result, 1); +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h new file mode 100644 index 00000000..34d9577f --- /dev/null +++ b/examples/kiss_modem/KissModem.h @@ -0,0 +1,124 @@ +#pragma once + +#include <Arduino.h> +#include <Identity.h> +#include <Utils.h> + +#define KISS_FEND 0xC0 +#define KISS_FESC 0xDB +#define KISS_TFEND 0xDC +#define KISS_TFESC 0xDD + +#define KISS_MAX_FRAME_SIZE 512 +#define KISS_MAX_PACKET_SIZE 255 + +#define CMD_DATA 0x00 +#define CMD_GET_IDENTITY 0x01 +#define CMD_GET_RANDOM 0x02 +#define CMD_VERIFY_SIGNATURE 0x03 +#define CMD_SIGN_DATA 0x04 +#define CMD_ENCRYPT_DATA 0x05 +#define CMD_DECRYPT_DATA 0x06 +#define CMD_KEY_EXCHANGE 0x07 +#define CMD_HASH 0x08 +#define CMD_SET_RADIO 0x09 +#define CMD_SET_TX_POWER 0x0A +#define CMD_SET_SYNC_WORD 0x0B +#define CMD_GET_RADIO 0x0C +#define CMD_GET_TX_POWER 0x0D +#define CMD_GET_SYNC_WORD 0x0E +#define CMD_GET_VERSION 0x0F + +#define RESP_IDENTITY 0x11 +#define RESP_RANDOM 0x12 +#define RESP_VERIFY 0x13 +#define RESP_SIGNATURE 0x14 +#define RESP_ENCRYPTED 0x15 +#define RESP_DECRYPTED 0x16 +#define RESP_SHARED_SECRET 0x17 +#define RESP_HASH 0x18 +#define RESP_OK 0x19 +#define RESP_RADIO 0x1A +#define RESP_TX_POWER 0x1B +#define RESP_SYNC_WORD 0x1C +#define RESP_VERSION 0x1D +#define RESP_ERROR 0x1E +#define RESP_TX_DONE 0x1F + +#define ERR_INVALID_LENGTH 0x01 +#define ERR_INVALID_PARAM 0x02 +#define ERR_NO_CALLBACK 0x03 +#define ERR_MAC_FAILED 0x04 +#define ERR_UNKNOWN_CMD 0x05 +#define ERR_ENCRYPT_FAILED 0x06 + +#define KISS_FIRMWARE_VERSION 1 + +typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); +typedef void (*SetTxPowerCallback)(uint8_t power); +typedef void (*SetSyncWordCallback)(uint8_t syncWord); + +struct RadioConfig { + uint32_t freq_hz; + uint32_t bw_hz; + uint8_t sf; + uint8_t cr; + uint8_t tx_power; + uint8_t sync_word; +}; + +class KissModem { + Stream& _serial; + mesh::LocalIdentity& _identity; + mesh::RNG& _rng; + + uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; + uint16_t _rx_len; + bool _rx_escaped; + bool _rx_active; + + uint8_t _pending_tx[KISS_MAX_PACKET_SIZE]; + uint16_t _pending_tx_len; + bool _has_pending_tx; + + SetRadioCallback _setRadioCallback; + SetTxPowerCallback _setTxPowerCallback; + SetSyncWordCallback _setSyncWordCallback; + + RadioConfig _config; + + void writeByte(uint8_t b); + void writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len); + void writeErrorFrame(uint8_t error_code); + void processFrame(); + + void handleGetIdentity(); + void handleGetRandom(const uint8_t* data, uint16_t len); + void handleVerifySignature(const uint8_t* data, uint16_t len); + void handleSignData(const uint8_t* data, uint16_t len); + void handleEncryptData(const uint8_t* data, uint16_t len); + void handleDecryptData(const uint8_t* data, uint16_t len); + void handleKeyExchange(const uint8_t* data, uint16_t len); + void handleHash(const uint8_t* data, uint16_t len); + void handleSetRadio(const uint8_t* data, uint16_t len); + void handleSetTxPower(const uint8_t* data, uint16_t len); + void handleSetSyncWord(const uint8_t* data, uint16_t len); + void handleGetRadio(); + void handleGetTxPower(); + void handleGetSyncWord(); + void handleGetVersion(); + +public: + KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); + + void begin(); + void loop(); + + void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } + void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } + void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } + + bool getPacketToSend(uint8_t* packet, uint16_t* len); + void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); + void onTxComplete(bool success); +}; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp new file mode 100644 index 00000000..2f843a99 --- /dev/null +++ b/examples/kiss_modem/main.cpp @@ -0,0 +1,108 @@ +#include <Arduino.h> +#include <target.h> +#include <helpers/ArduinoHelpers.h> +#include <helpers/IdentityStore.h> +#include "KissModem.h" + +#if defined(NRF52_PLATFORM) + #include <InternalFileSystem.h> +#elif defined(RP2040_PLATFORM) + #include <LittleFS.h> +#elif defined(ESP32) + #include <SPIFFS.h> +#endif + +StdRNG rng; +mesh::LocalIdentity identity; +KissModem* modem; + +void halt() { + while (1) ; +} + +void loadOrCreateIdentity() { +#if defined(NRF52_PLATFORM) + InternalFS.begin(); + IdentityStore store(InternalFS, ""); +#elif defined(ESP32) + SPIFFS.begin(true); + IdentityStore store(SPIFFS, "/identity"); +#elif defined(RP2040_PLATFORM) + LittleFS.begin(); + IdentityStore store(LittleFS, "/identity"); + store.begin(); +#else + #error "Filesystem not defined" +#endif + + if (!store.load("_main", identity)) { + identity = radio_new_identity(); + while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) { + identity = radio_new_identity(); + } + store.save("_main", identity); + } +} + +void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) { + radio_set_params(freq, bw, sf, cr); +} + +void onSetTxPower(uint8_t power) { + radio_set_tx_power(power); +} + +void onSetSyncWord(uint8_t sync_word) { + radio_set_sync_word(sync_word); +} + +void setup() { + board.begin(); + + if (!radio_init()) { + halt(); + } + + radio_driver.begin(); + + rng.begin(radio_get_rng_seed()); + loadOrCreateIdentity(); + + Serial.begin(115200); + uint32_t start = millis(); + while (!Serial && millis() - start < 3000) delay(10); + delay(100); + + modem = new KissModem(Serial, identity, rng); + modem->setRadioCallback(onSetRadio); + modem->setTxPowerCallback(onSetTxPower); + modem->setSyncWordCallback(onSetSyncWord); + modem->begin(); +} + +void loop() { + modem->loop(); + + uint8_t packet[KISS_MAX_PACKET_SIZE]; + uint16_t len; + + if (modem->getPacketToSend(packet, &len)) { + radio_driver.startSendRaw(packet, len); + while (!radio_driver.isSendComplete()) { + delay(1); + } + radio_driver.onSendFinished(); + modem->onTxComplete(true); + } + + uint8_t rx_buf[256]; + int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); + + if (rx_len > 0) { + int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); + int8_t rssi = (int8_t)radio_driver.getLastRSSI(); + modem->onPacketReceived(snr, rssi, rx_buf, rx_len); + } + + radio_driver.loop(); +} From c5b1d30280c837214f18db2e19bfcae9151568a1 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sat, 31 Jan 2026 23:48:28 +1100 Subject: [PATCH 445/546] t114: remove extra DCDC enable --- variants/heltec_t114/T114Board.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/variants/heltec_t114/T114Board.cpp b/variants/heltec_t114/T114Board.cpp index 2a36bd90..c03d39af 100644 --- a/variants/heltec_t114/T114Board.cpp +++ b/variants/heltec_t114/T114Board.cpp @@ -34,7 +34,6 @@ void T114Board::initiateShutdown(uint8_t reason) { void T114Board::begin() { NRF52Board::begin(); - NRF_POWER->DCDCEN = 1; pinMode(PIN_VBAT_READ, INPUT); From e6e1b810f874491b1e7cf96869f5076069ddc6fa Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 27 Jan 2026 17:51:30 +1100 Subject: [PATCH 446/546] add DataStore::deleteBlobByKey() --- examples/companion_radio/DataStore.cpp | 16 ++++++++++++++++ examples/companion_radio/DataStore.h | 1 + examples/companion_radio/MyMesh.cpp | 2 ++ 3 files changed, 19 insertions(+) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index f61f53ae..6cc77671 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -560,6 +560,9 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } return false; // error } +bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { + return true; // this is just a stub on NRF52/STM32 platforms +} #else uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { char path[64]; @@ -598,4 +601,17 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src } return false; // error } + +bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { + char path[64]; + char fname[18]; + + if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) + mesh::Utils::toHex(fname, key, key_len); + sprintf(path, "/bl/%s", fname); + + _fs->remove(path); + + return true; // return true even if file did not exist +} #endif diff --git a/examples/companion_radio/DataStore.h b/examples/companion_radio/DataStore.h index 62580942..58b4d5d2 100644 --- a/examples/companion_radio/DataStore.h +++ b/examples/companion_radio/DataStore.h @@ -42,6 +42,7 @@ public: void migrateToSecondaryFS(); uint8_t getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]); bool putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len); + bool deleteBlobByKey(const uint8_t key[], int key_len); File openRead(const char* filename); File openRead(FILESYSTEM* fs, const char* filename); bool removeFile(const char* filename); diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 1e4115da..9bb747e7 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -307,6 +307,7 @@ bool MyMesh::shouldOverwriteWhenFull() const { } void MyMesh::onContactOverwrite(const uint8_t* pub_key) { + _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage if (_serial->isConnected()) { out_frame[0] = PUSH_CODE_CONTACT_DELETED; memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE); @@ -1124,6 +1125,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); if (recipient && removeContact(*recipient)) { + _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); } else { From 31ba971c60e7367de3265a0f006edacbc2ebbe4c Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 27 Jan 2026 17:53:05 +1100 Subject: [PATCH 447/546] only store advblob when adding/updating contacts --- src/helpers/BaseChatMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index aebfc1b6..6de7469d 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -131,7 +131,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, plen = packet->writeTo(temp_buf); packet->header = save; } - putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); bool is_new = false; // true = not in contacts[], false = exists in contacts[] if (from == NULL) { @@ -157,6 +156,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, from->shared_secret_valid = false; } // update + putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen); StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name)); from->type = parser.getType(); if (parser.hasLatLon()) { From 8d5eaf500d8e744d204339be078f10074213d9e2 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Tue, 27 Jan 2026 19:31:07 +1100 Subject: [PATCH 448/546] add makeBlobPath inline helper for esp32 --- examples/companion_radio/DataStore.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 6cc77671..c0f2c021 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -564,13 +564,16 @@ bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { return true; // this is just a stub on NRF52/STM32 platforms } #else -uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { - char path[64]; +inline void makeBlobPath(const uint8_t key[], int key_len, char* path, size_t path_size) { char fname[18]; - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) mesh::Utils::toHex(fname, key, key_len); sprintf(path, "/bl/%s", fname); +} + +uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_buf[]) { + char path[64]; + makeBlobPath(key, key_len, path, sizeof(path)); if (_fs->exists(path)) { File f = openRead(_fs, path); @@ -585,11 +588,7 @@ uint8_t DataStore::getBlobByKey(const uint8_t key[], int key_len, uint8_t dest_b bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src_buf[], uint8_t len) { char path[64]; - char fname[18]; - - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) - mesh::Utils::toHex(fname, key, key_len); - sprintf(path, "/bl/%s", fname); + makeBlobPath(key, key_len, path, sizeof(path)); File f = openWrite(_fs, path); if (f) { @@ -604,11 +603,7 @@ bool DataStore::putBlobByKey(const uint8_t key[], int key_len, const uint8_t src bool DataStore::deleteBlobByKey(const uint8_t key[], int key_len) { char path[64]; - char fname[18]; - - if (key_len > 8) key_len = 8; // just use first 8 bytes (prefix) - mesh::Utils::toHex(fname, key, key_len); - sprintf(path, "/bl/%s", fname); + makeBlobPath(key, key_len, path, sizeof(path)); _fs->remove(path); From b5248faec4872a52001ee1ed425492db043ee943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Sat, 31 Jan 2026 13:45:58 +0000 Subject: [PATCH 449/546] Revert "Merge pull request #1428 from etienn01/update-t114-i2c" This reverts commit 616eb57b163f2123727347ab0425e1ad4fbca564, reversing changes made to 537acd7ea144ee077595c1171cd96770eb924b67. This patch needs to be reverted because it boot freezes t114 433Mhz variant. --- variants/heltec_t114/variant.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index ac9dbbe6..aa7f4022 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -58,8 +58,8 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (16) // P0.16 -#define PIN_WIRE_SCL (13) // P0.13 +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From 1bcb52bab318926b014d0a46d98ebc2f35ff5e3f Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 31 Jan 2026 15:05:25 +0100 Subject: [PATCH 450/546] Add new commands and responses for RSSI, channel status, airtime, noise floor, statistics, battery, and sensors. --- docs/kiss_modem_protocol.md | 74 +++++++++++++---- examples/kiss_modem/KissModem.cpp | 128 ++++++++++++++++++++++++++++++ examples/kiss_modem/KissModem.h | 78 ++++++++++++++---- examples/kiss_modem/main.cpp | 46 +++++++++++ 4 files changed, 294 insertions(+), 32 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index f85bfe6c..e80c3b29 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -48,27 +48,43 @@ Maximum unescaped frame size: 512 bytes. | `CMD_GET_TX_POWER` | `0x0D` | - | | `CMD_GET_SYNC_WORD` | `0x0E` | - | | `CMD_GET_VERSION` | `0x0F` | - | +| `CMD_GET_CURRENT_RSSI` | `0x10` | - | +| `CMD_IS_CHANNEL_BUSY` | `0x11` | - | +| `CMD_GET_AIRTIME` | `0x12` | Packet length (1) | +| `CMD_GET_NOISE_FLOOR` | `0x13` | - | +| `CMD_GET_STATS` | `0x14` | - | +| `CMD_GET_BATTERY` | `0x15` | - | +| `CMD_PING` | `0x16` | - | +| `CMD_GET_SENSORS` | `0x17` | Permissions (1) | ### Response Commands (Modem → Host) | Command | Value | Data | |---------|-------|------| | `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | -| `RESP_IDENTITY` | `0x11` | PubKey (32) | -| `RESP_RANDOM` | `0x12` | Random bytes (1-64) | -| `RESP_VERIFY` | `0x13` | Result (1): 0x00=invalid, 0x01=valid | -| `RESP_SIGNATURE` | `0x14` | Signature (64) | -| `RESP_ENCRYPTED` | `0x15` | MAC (2) + Ciphertext | -| `RESP_DECRYPTED` | `0x16` | Plaintext | -| `RESP_SHARED_SECRET` | `0x17` | Shared secret (32) | -| `RESP_HASH` | `0x18` | SHA-256 hash (32) | -| `RESP_OK` | `0x19` | - | -| `RESP_RADIO` | `0x1A` | Freq (4) + BW (4) + SF (1) + CR (1) | -| `RESP_TX_POWER` | `0x1B` | Power dBm (1) | -| `RESP_SYNC_WORD` | `0x1C` | Sync word (1) | -| `RESP_VERSION` | `0x1D` | Version (1) + Reserved (1) | -| `RESP_ERROR` | `0x1E` | Error code (1) | -| `RESP_TX_DONE` | `0x1F` | Result (1): 0x00=failed, 0x01=success | +| `RESP_IDENTITY` | `0x21` | PubKey (32) | +| `RESP_RANDOM` | `0x22` | Random bytes (1-64) | +| `RESP_VERIFY` | `0x23` | Result (1): 0x00=invalid, 0x01=valid | +| `RESP_SIGNATURE` | `0x24` | Signature (64) | +| `RESP_ENCRYPTED` | `0x25` | MAC (2) + Ciphertext | +| `RESP_DECRYPTED` | `0x26` | Plaintext | +| `RESP_SHARED_SECRET` | `0x27` | Shared secret (32) | +| `RESP_HASH` | `0x28` | SHA-256 hash (32) | +| `RESP_OK` | `0x29` | - | +| `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | +| `RESP_TX_POWER` | `0x2B` | Power dBm (1) | +| `RESP_SYNC_WORD` | `0x2C` | Sync word (1) | +| `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | +| `RESP_ERROR` | `0x2E` | Error code (1) | +| `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | +| `RESP_CURRENT_RSSI` | `0x30` | RSSI dBm (1, signed) | +| `RESP_CHANNEL_BUSY` | `0x31` | Result (1): 0x00=clear, 0x01=busy | +| `RESP_AIRTIME` | `0x32` | Milliseconds (4) | +| `RESP_NOISE_FLOOR` | `0x33` | dBm (2, signed) | +| `RESP_STATS` | `0x34` | RX (4) + TX (4) + Errors (4) | +| `RESP_BATTERY` | `0x35` | Millivolts (2) | +| `RESP_PONG` | `0x36` | - | +| `RESP_SENSORS` | `0x37` | CayenneLPP payload | ## Error Codes @@ -76,10 +92,11 @@ Maximum unescaped frame size: 512 bytes. |------|-------|-------------| | `ERR_INVALID_LENGTH` | `0x01` | Request data too short | | `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | -| `ERR_NO_CALLBACK` | `0x03` | Radio callback not set | +| `ERR_NO_CALLBACK` | `0x03` | Feature not available | | `ERR_MAC_FAILED` | `0x04` | MAC verification failed | | `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | | `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | +| `ERR_TX_PENDING` | `0x07` | TX already pending | ## Data Formats @@ -102,9 +119,34 @@ All values little-endian. | RSSI | 1 byte | Signal strength dBm, signed | | Packet | variable | Raw MeshCore packet | +### Stats (RESP_STATS) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| RX | 4 bytes | Packets received | +| TX | 4 bytes | Packets transmitted | +| Errors | 4 bytes | Receive errors | + +### Sensor Permissions (CMD_GET_SENSORS) + +| Bit | Value | Description | +|-----|-------|-------------| +| 0 | `0x01` | Base (battery) | +| 1 | `0x02` | Location (GPS) | +| 2 | `0x04` | Environment (temp, humidity, pressure) | + +Use `0x07` for all permissions. + +### Sensor Data (RESP_SENSORS) + +Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. + ## Notes - Modem generates identity on first boot (stored in flash) - SNR values multiplied by 4 for 0.25 dB precision - Wait for `RESP_TX_DONE` before sending next packet +- Sending `CMD_DATA` while TX is pending returns `ERR_TX_PENDING` - See [packet_structure.md](./packet_structure.md) for packet format diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 4e227d7f..c6e2f2bd 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -10,6 +10,13 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _setRadioCallback = nullptr; _setTxPowerCallback = nullptr; _setSyncWordCallback = nullptr; + _getCurrentRssiCallback = nullptr; + _isChannelBusyCallback = nullptr; + _getAirtimeCallback = nullptr; + _getNoiseFloorCallback = nullptr; + _getStatsCallback = nullptr; + _getBatteryCallback = nullptr; + _getSensorsCallback = nullptr; _config = {0, 0, 0, 0, 0, 0x12}; } @@ -91,6 +98,8 @@ void KissModem::processFrame() { writeErrorFrame(ERR_INVALID_LENGTH); } else if (data_len > KISS_MAX_PACKET_SIZE) { writeErrorFrame(ERR_INVALID_LENGTH); + } else if (_has_pending_tx) { + writeErrorFrame(ERR_TX_PENDING); } else { memcpy(_pending_tx, data, data_len); _pending_tx_len = data_len; @@ -142,6 +151,30 @@ void KissModem::processFrame() { case CMD_GET_VERSION: handleGetVersion(); break; + case CMD_GET_CURRENT_RSSI: + handleGetCurrentRssi(); + break; + case CMD_IS_CHANNEL_BUSY: + handleIsChannelBusy(); + break; + case CMD_GET_AIRTIME: + handleGetAirtime(data, data_len); + break; + case CMD_GET_NOISE_FLOOR: + handleGetNoiseFloor(); + break; + case CMD_GET_STATS: + handleGetStats(); + break; + case CMD_GET_BATTERY: + handleGetBattery(); + break; + case CMD_PING: + handlePing(); + break; + case CMD_GET_SENSORS: + handleGetSensors(data, data_len); + break; default: writeErrorFrame(ERR_UNKNOWN_CMD); break; @@ -360,3 +393,98 @@ void KissModem::onTxComplete(bool success) { uint8_t result = success ? 0x01 : 0x00; writeFrame(RESP_TX_DONE, &result, 1); } + +void KissModem::handleGetCurrentRssi() { + if (!_getCurrentRssiCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + float rssi = _getCurrentRssiCallback(); + int8_t rssi_byte = (int8_t)rssi; + writeFrame(RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); +} + +void KissModem::handleIsChannelBusy() { + if (!_isChannelBusyCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint8_t busy = _isChannelBusyCallback() ? 0x01 : 0x00; + writeFrame(RESP_CHANNEL_BUSY, &busy, 1); +} + +void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_getAirtimeCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint8_t packet_len = data[0]; + uint32_t airtime = _getAirtimeCallback(packet_len); + writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); +} + +void KissModem::handleGetNoiseFloor() { + if (!_getNoiseFloorCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + int16_t noise_floor = _getNoiseFloorCallback(); + writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); +} + +void KissModem::handleGetStats() { + if (!_getStatsCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint32_t rx, tx, errors; + _getStatsCallback(&rx, &tx, &errors); + uint8_t buf[12]; + memcpy(buf, &rx, 4); + memcpy(buf + 4, &tx, 4); + memcpy(buf + 8, &errors, 4); + writeFrame(RESP_STATS, buf, 12); +} + +void KissModem::handleGetBattery() { + if (!_getBatteryCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint16_t mv = _getBatteryCallback(); + writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); +} + +void KissModem::handlePing() { + writeFrame(RESP_PONG, nullptr, 0); +} + +void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeErrorFrame(ERR_INVALID_LENGTH); + return; + } + if (!_getSensorsCallback) { + writeErrorFrame(ERR_NO_CALLBACK); + return; + } + + uint8_t permissions = data[0]; + uint8_t buf[255]; + uint8_t result_len = _getSensorsCallback(permissions, buf, 255); + if (result_len > 0) { + writeFrame(RESP_SENSORS, buf, result_len); + } else { + writeFrame(RESP_SENSORS, nullptr, 0); + } +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 34d9577f..e223d92d 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -28,22 +28,38 @@ #define CMD_GET_TX_POWER 0x0D #define CMD_GET_SYNC_WORD 0x0E #define CMD_GET_VERSION 0x0F +#define CMD_GET_CURRENT_RSSI 0x10 +#define CMD_IS_CHANNEL_BUSY 0x11 +#define CMD_GET_AIRTIME 0x12 +#define CMD_GET_NOISE_FLOOR 0x13 +#define CMD_GET_STATS 0x14 +#define CMD_GET_BATTERY 0x15 +#define CMD_PING 0x16 +#define CMD_GET_SENSORS 0x17 -#define RESP_IDENTITY 0x11 -#define RESP_RANDOM 0x12 -#define RESP_VERIFY 0x13 -#define RESP_SIGNATURE 0x14 -#define RESP_ENCRYPTED 0x15 -#define RESP_DECRYPTED 0x16 -#define RESP_SHARED_SECRET 0x17 -#define RESP_HASH 0x18 -#define RESP_OK 0x19 -#define RESP_RADIO 0x1A -#define RESP_TX_POWER 0x1B -#define RESP_SYNC_WORD 0x1C -#define RESP_VERSION 0x1D -#define RESP_ERROR 0x1E -#define RESP_TX_DONE 0x1F +#define RESP_IDENTITY 0x21 +#define RESP_RANDOM 0x22 +#define RESP_VERIFY 0x23 +#define RESP_SIGNATURE 0x24 +#define RESP_ENCRYPTED 0x25 +#define RESP_DECRYPTED 0x26 +#define RESP_SHARED_SECRET 0x27 +#define RESP_HASH 0x28 +#define RESP_OK 0x29 +#define RESP_RADIO 0x2A +#define RESP_TX_POWER 0x2B +#define RESP_SYNC_WORD 0x2C +#define RESP_VERSION 0x2D +#define RESP_ERROR 0x2E +#define RESP_TX_DONE 0x2F +#define RESP_CURRENT_RSSI 0x30 +#define RESP_CHANNEL_BUSY 0x31 +#define RESP_AIRTIME 0x32 +#define RESP_NOISE_FLOOR 0x33 +#define RESP_STATS 0x34 +#define RESP_BATTERY 0x35 +#define RESP_PONG 0x36 +#define RESP_SENSORS 0x37 #define ERR_INVALID_LENGTH 0x01 #define ERR_INVALID_PARAM 0x02 @@ -51,12 +67,20 @@ #define ERR_MAC_FAILED 0x04 #define ERR_UNKNOWN_CMD 0x05 #define ERR_ENCRYPT_FAILED 0x06 +#define ERR_TX_PENDING 0x07 -#define KISS_FIRMWARE_VERSION 1 +#define KISS_FIRMWARE_VERSION 2 typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef void (*SetSyncWordCallback)(uint8_t syncWord); +typedef float (*GetCurrentRssiCallback)(); +typedef bool (*IsChannelBusyCallback)(); +typedef uint32_t (*GetAirtimeCallback)(uint8_t len); +typedef int16_t (*GetNoiseFloorCallback)(); +typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); +typedef uint16_t (*GetBatteryCallback)(); +typedef uint8_t (*GetSensorsCallback)(uint8_t permissions, uint8_t* buffer, uint8_t max_len); struct RadioConfig { uint32_t freq_hz; @@ -84,6 +108,13 @@ class KissModem { SetRadioCallback _setRadioCallback; SetTxPowerCallback _setTxPowerCallback; SetSyncWordCallback _setSyncWordCallback; + GetCurrentRssiCallback _getCurrentRssiCallback; + IsChannelBusyCallback _isChannelBusyCallback; + GetAirtimeCallback _getAirtimeCallback; + GetNoiseFloorCallback _getNoiseFloorCallback; + GetStatsCallback _getStatsCallback; + GetBatteryCallback _getBatteryCallback; + GetSensorsCallback _getSensorsCallback; RadioConfig _config; @@ -107,6 +138,14 @@ class KissModem { void handleGetTxPower(); void handleGetSyncWord(); void handleGetVersion(); + void handleGetCurrentRssi(); + void handleIsChannelBusy(); + void handleGetAirtime(const uint8_t* data, uint16_t len); + void handleGetNoiseFloor(); + void handleGetStats(); + void handleGetBattery(); + void handlePing(); + void handleGetSensors(const uint8_t* data, uint16_t len); public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); @@ -117,6 +156,13 @@ public: void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } + void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } + void setIsChannelBusyCallback(IsChannelBusyCallback cb) { _isChannelBusyCallback = cb; } + void setGetAirtimeCallback(GetAirtimeCallback cb) { _getAirtimeCallback = cb; } + void setGetNoiseFloorCallback(GetNoiseFloorCallback cb) { _getNoiseFloorCallback = cb; } + void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } + void setGetBatteryCallback(GetBatteryCallback cb) { _getBatteryCallback = cb; } + void setGetSensorsCallback(GetSensorsCallback cb) { _getSensorsCallback = cb; } bool getPacketToSend(uint8_t* packet, uint16_t* len); void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 2f843a99..0a54c9d3 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -2,6 +2,7 @@ #include <target.h> #include <helpers/ArduinoHelpers.h> #include <helpers/IdentityStore.h> +#include <CayenneLPP.h> #include "KissModem.h" #if defined(NRF52_PLATFORM) @@ -56,6 +57,42 @@ void onSetSyncWord(uint8_t sync_word) { radio_set_sync_word(sync_word); } +float onGetCurrentRssi() { + return radio_driver.getCurrentRSSI(); +} + +bool onIsChannelBusy() { + return radio_driver.isReceiving(); +} + +uint32_t onGetAirtime(uint8_t len) { + return radio_driver.getEstAirtimeFor(len); +} + +int16_t onGetNoiseFloor() { + return radio_driver.getNoiseFloor(); +} + +void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { + *rx = radio_driver.getPacketsRecv(); + *tx = radio_driver.getPacketsSent(); + *errors = radio_driver.getPacketsRecvErrors(); +} + +uint16_t onGetBattery() { + return board.getBattMilliVolts(); +} + +uint8_t onGetSensors(uint8_t permissions, uint8_t* buffer, uint8_t max_len) { + CayenneLPP telemetry(max_len); + if (sensors.querySensors(permissions, telemetry)) { + uint8_t len = telemetry.getSize(); + memcpy(buffer, telemetry.getBuffer(), len); + return len; + } + return 0; +} + void setup() { board.begin(); @@ -73,10 +110,19 @@ void setup() { while (!Serial && millis() - start < 3000) delay(10); delay(100); + sensors.begin(); + modem = new KissModem(Serial, identity, rng); modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); modem->setSyncWordCallback(onSetSyncWord); + modem->setGetCurrentRssiCallback(onGetCurrentRssi); + modem->setIsChannelBusyCallback(onIsChannelBusy); + modem->setGetAirtimeCallback(onGetAirtime); + modem->setGetNoiseFloorCallback(onGetNoiseFloor); + modem->setGetStatsCallback(onGetStats); + modem->setGetBatteryCallback(onGetBattery); + modem->setGetSensorsCallback(onGetSensors); modem->begin(); } From 240b5ea1e33fdc44808c87a268e4295cfa474ded Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 31 Jan 2026 15:08:25 +0100 Subject: [PATCH 451/546] Refactor KissModem to integrate radio and sensor management directly, removing callback dependencies. --- examples/kiss_modem/KissModem.cpp | 49 +++++++------------------------ examples/kiss_modem/KissModem.h | 25 +++++----------- examples/kiss_modem/main.cpp | 34 +-------------------- 3 files changed, 20 insertions(+), 88 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index c6e2f2bd..d11e8217 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -1,7 +1,9 @@ #include "KissModem.h" +#include <CayenneLPP.h> -KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng) - : _serial(serial), _identity(identity), _rng(rng) { +KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, + mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors) + : _serial(serial), _identity(identity), _rng(rng), _radio(radio), _board(board), _sensors(sensors) { _rx_len = 0; _rx_escaped = false; _rx_active = false; @@ -11,12 +13,7 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _setTxPowerCallback = nullptr; _setSyncWordCallback = nullptr; _getCurrentRssiCallback = nullptr; - _isChannelBusyCallback = nullptr; - _getAirtimeCallback = nullptr; - _getNoiseFloorCallback = nullptr; _getStatsCallback = nullptr; - _getBatteryCallback = nullptr; - _getSensorsCallback = nullptr; _config = {0, 0, 0, 0, 0, 0x12}; } @@ -406,12 +403,7 @@ void KissModem::handleGetCurrentRssi() { } void KissModem::handleIsChannelBusy() { - if (!_isChannelBusyCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - uint8_t busy = _isChannelBusyCallback() ? 0x01 : 0x00; + uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; writeFrame(RESP_CHANNEL_BUSY, &busy, 1); } @@ -420,23 +412,14 @@ void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { writeErrorFrame(ERR_INVALID_LENGTH); return; } - if (!_getAirtimeCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } uint8_t packet_len = data[0]; - uint32_t airtime = _getAirtimeCallback(packet_len); + uint32_t airtime = _radio.getEstAirtimeFor(packet_len); writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); } void KissModem::handleGetNoiseFloor() { - if (!_getNoiseFloorCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - int16_t noise_floor = _getNoiseFloorCallback(); + int16_t noise_floor = _radio.getNoiseFloor(); writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); } @@ -456,12 +439,7 @@ void KissModem::handleGetStats() { } void KissModem::handleGetBattery() { - if (!_getBatteryCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - uint16_t mv = _getBatteryCallback(); + uint16_t mv = _board.getBattMilliVolts(); writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); } @@ -474,16 +452,11 @@ void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { writeErrorFrame(ERR_INVALID_LENGTH); return; } - if (!_getSensorsCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } uint8_t permissions = data[0]; - uint8_t buf[255]; - uint8_t result_len = _getSensorsCallback(permissions, buf, 255); - if (result_len > 0) { - writeFrame(RESP_SENSORS, buf, result_len); + CayenneLPP telemetry(255); + if (_sensors.querySensors(permissions, telemetry)) { + writeFrame(RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); } else { writeFrame(RESP_SENSORS, nullptr, 0); } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index e223d92d..bc7560f4 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -3,6 +3,8 @@ #include <Arduino.h> #include <Identity.h> #include <Utils.h> +#include <Mesh.h> +#include <helpers/SensorManager.h> #define KISS_FEND 0xC0 #define KISS_FESC 0xDB @@ -69,18 +71,13 @@ #define ERR_ENCRYPT_FAILED 0x06 #define ERR_TX_PENDING 0x07 -#define KISS_FIRMWARE_VERSION 2 +#define KISS_FIRMWARE_VERSION 1 typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef void (*SetSyncWordCallback)(uint8_t syncWord); typedef float (*GetCurrentRssiCallback)(); -typedef bool (*IsChannelBusyCallback)(); -typedef uint32_t (*GetAirtimeCallback)(uint8_t len); -typedef int16_t (*GetNoiseFloorCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); -typedef uint16_t (*GetBatteryCallback)(); -typedef uint8_t (*GetSensorsCallback)(uint8_t permissions, uint8_t* buffer, uint8_t max_len); struct RadioConfig { uint32_t freq_hz; @@ -95,6 +92,9 @@ class KissModem { Stream& _serial; mesh::LocalIdentity& _identity; mesh::RNG& _rng; + mesh::Radio& _radio; + mesh::MainBoard& _board; + SensorManager& _sensors; uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; uint16_t _rx_len; @@ -109,12 +109,7 @@ class KissModem { SetTxPowerCallback _setTxPowerCallback; SetSyncWordCallback _setSyncWordCallback; GetCurrentRssiCallback _getCurrentRssiCallback; - IsChannelBusyCallback _isChannelBusyCallback; - GetAirtimeCallback _getAirtimeCallback; - GetNoiseFloorCallback _getNoiseFloorCallback; GetStatsCallback _getStatsCallback; - GetBatteryCallback _getBatteryCallback; - GetSensorsCallback _getSensorsCallback; RadioConfig _config; @@ -148,7 +143,8 @@ class KissModem { void handleGetSensors(const uint8_t* data, uint16_t len); public: - KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng); + KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, + mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors); void begin(); void loop(); @@ -157,12 +153,7 @@ public: void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } - void setIsChannelBusyCallback(IsChannelBusyCallback cb) { _isChannelBusyCallback = cb; } - void setGetAirtimeCallback(GetAirtimeCallback cb) { _getAirtimeCallback = cb; } - void setGetNoiseFloorCallback(GetNoiseFloorCallback cb) { _getNoiseFloorCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } - void setGetBatteryCallback(GetBatteryCallback cb) { _getBatteryCallback = cb; } - void setGetSensorsCallback(GetSensorsCallback cb) { _getSensorsCallback = cb; } bool getPacketToSend(uint8_t* packet, uint16_t* len); void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 0a54c9d3..e81161bf 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -2,7 +2,6 @@ #include <target.h> #include <helpers/ArduinoHelpers.h> #include <helpers/IdentityStore.h> -#include <CayenneLPP.h> #include "KissModem.h" #if defined(NRF52_PLATFORM) @@ -61,38 +60,12 @@ float onGetCurrentRssi() { return radio_driver.getCurrentRSSI(); } -bool onIsChannelBusy() { - return radio_driver.isReceiving(); -} - -uint32_t onGetAirtime(uint8_t len) { - return radio_driver.getEstAirtimeFor(len); -} - -int16_t onGetNoiseFloor() { - return radio_driver.getNoiseFloor(); -} - void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *rx = radio_driver.getPacketsRecv(); *tx = radio_driver.getPacketsSent(); *errors = radio_driver.getPacketsRecvErrors(); } -uint16_t onGetBattery() { - return board.getBattMilliVolts(); -} - -uint8_t onGetSensors(uint8_t permissions, uint8_t* buffer, uint8_t max_len) { - CayenneLPP telemetry(max_len); - if (sensors.querySensors(permissions, telemetry)) { - uint8_t len = telemetry.getSize(); - memcpy(buffer, telemetry.getBuffer(), len); - return len; - } - return 0; -} - void setup() { board.begin(); @@ -112,17 +85,12 @@ void setup() { sensors.begin(); - modem = new KissModem(Serial, identity, rng); + modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); modem->setSyncWordCallback(onSetSyncWord); modem->setGetCurrentRssiCallback(onGetCurrentRssi); - modem->setIsChannelBusyCallback(onIsChannelBusy); - modem->setGetAirtimeCallback(onGetAirtime); - modem->setGetNoiseFloorCallback(onGetNoiseFloor); modem->setGetStatsCallback(onGetStats); - modem->setGetBatteryCallback(onGetBattery); - modem->setGetSensorsCallback(onGetSensors); modem->begin(); } From 2b754d4295330f1699e7152384def49a8c8e4408 Mon Sep 17 00:00:00 2001 From: Matthias Wientapper <m.wientapper@gmx.de> Date: Sat, 31 Jan 2026 23:17:48 +0100 Subject: [PATCH 452/546] cli_commands.md: `region` available via remote cli in 1.12.0 changed with #1476 --- docs/cli_commands.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 6b4f6157..c316bd6c 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -642,7 +642,7 @@ **Usage:** - `region` -**Serial Only:** Yes +**Serial Only:** For firmware older than 1.12.0 --- From a342ab8437e19ea792e51e46cd14c333c1c8b609 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Sun, 1 Feb 2026 14:46:55 +1100 Subject: [PATCH 453/546] nrf52: allow repeater to sleep when idle --- examples/simple_repeater/main.cpp | 9 ++++++--- src/helpers/NRF52Board.cpp | 26 ++++++++++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d55d6118..eb4b5b09 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -127,14 +127,17 @@ void loop() { #endif rtc_clock.tick(); - if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled - the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep - if (!the_mesh.hasPendingWork()) { // No pending work. Safe to sleep + if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { + #if defined(NRF52_PLATFORM) + board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible + #else + if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet lastActive = millis(); nextSleepinSecs = 5; // Default: To work for 5s and sleep again } else { nextSleepinSecs += 5; // When there is pending work, to work another 5s } + #endif } } diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 6915c856..1db858f5 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -251,6 +251,32 @@ void NRF52BoardDCDC::begin() { } } +void NRF52Board::sleep(uint32_t secs) { + // Clear FPU interrupt flags to avoid insomnia + // see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html + #if (__FPU_USED == 1) + __set_FPSCR(__get_FPSCR() & ~(0x0000009F)); + (void) __get_FPSCR(); + NVIC_ClearPendingIRQ(FPU_IRQn); + #endif + + // On nRF52, we use event-driven sleep instead of timed sleep + // The 'secs' parameter is ignored - we wake on any interrupt + uint8_t sd_enabled = 0; + sd_softdevice_is_enabled(&sd_enabled); + + if (sd_enabled) { + // first call processes pending softdevice events, second call sleeps. + sd_app_evt_wait(); + sd_app_evt_wait(); + } else { + // softdevice is disabled, use raw WFE + __SEV(); + __WFE(); + __WFE(); + } +} + // Temperature from NRF52 MCU float NRF52Board::getMCUTemperature() { NRF_TEMP->TASKS_START = 1; // Start temperature measurement diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 1c70d8f0..0332af07 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -51,6 +51,7 @@ public: virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } virtual bool startOTAUpdate(const char *id, char reply[]) override; + virtual void sleep(uint32_t secs) override; #ifdef NRF52_POWER_MANAGEMENT bool isExternalPowered() override; From 223930765cbb0dabcd171444f54db6f6d6771cf1 Mon Sep 17 00:00:00 2001 From: Jan Pinkas <jan.pinkas@gmail.com> Date: Sun, 1 Feb 2026 09:00:01 +0100 Subject: [PATCH 454/546] Enable I2C sensors and EnvironmentSensorManager for Heltec T114 --- variants/heltec_t114/platformio.ini | 5 +++ variants/heltec_t114/target.cpp | 67 +++-------------------------- variants/heltec_t114/target.h | 18 ++------ 3 files changed, 14 insertions(+), 76 deletions(-) diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index 20f5e8fe..dd1f8bb3 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -6,6 +6,7 @@ extends = nrf52_base board = heltec_t114 board_build.ldscript = boards/nrf52840_s140_v6.ld build_flags = ${nrf52_base.build_flags} + ${sensor_base.build_flags} -I lib/nrf52/s140_nrf52_6.1.1_API/include -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 -I variants/heltec_t114 @@ -35,11 +36,15 @@ build_flags = ${nrf52_base.build_flags} -D PIN_GPS_EN=21 -D PIN_GPS_RESET=38 -D PIN_GPS_RESET_ACTIVE=LOW + -D PIN_BOARD_SDA=16 + -D PIN_BOARD_SCL=13 build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> + +<helpers/sensors> +<../variants/heltec_t114> lib_deps = ${nrf52_base.lib_deps} + ${sensor_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit GFX Library @ ^1.12.1 debug_tool = jlink diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index c3341103..23b9b667 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -45,26 +45,12 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } -void T114SensorManager::start_gps() { - if (!gps_active) { - gps_active = true; - _location->begin(); - } -} - -void T114SensorManager::stop_gps() { - if (gps_active) { - gps_active = false; - _location->stop(); - } -} - bool T114SensorManager::begin() { Serial1.begin(9600); // Try to detect if GPS is physically connected to determine if we should expose the setting - pinMode(GPS_EN, OUTPUT); - digitalWrite(GPS_EN, HIGH); // Power on GPS + pinMode(PIN_GPS_EN, OUTPUT); + digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS // Give GPS a moment to power up and send data delay(1500); @@ -77,57 +63,16 @@ bool T114SensorManager::begin() { } else { MESH_DEBUG_PRINTLN("No GPS detected"); } - digitalWrite(GPS_EN, LOW); // Power off GPS until the setting is changed + digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed - return true; + return EnvironmentSensorManager::begin(); } bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { + EnvironmentSensorManager::querySensors(requester_permissions, telemetry); + if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } return true; } - -void T114SensorManager::loop() { - static long next_gps_update = 0; - - _location->loop(); - - if (millis() > next_gps_update) { - if (_location->isValid()) { - node_lat = ((double)_location->getLatitude())/1000000.; - node_lon = ((double)_location->getLongitude())/1000000.; - node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); - } - next_gps_update = millis() + 1000; - } -} - -int T114SensorManager::getNumSettings() const { - return gps_detected ? 1 : 0; // only show GPS setting if GPS is detected -} - -const char* T114SensorManager::getSettingName(int i) const { - return (gps_detected && i == 0) ? "gps" : NULL; -} - -const char* T114SensorManager::getSettingValue(int i) const { - if (gps_detected && i == 0) { - return gps_active ? "1" : "0"; - } - return NULL; -} - -bool T114SensorManager::setSettingValue(const char* name, const char* value) { - if (gps_detected && strcmp(name, "gps") == 0) { - if (strcmp(value, "0") == 0) { - stop_gps(); - } else { - start_gps(); - } - return true; - } - return false; // not supported -} diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 6306cd69..24de81ee 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -6,7 +6,7 @@ #include <T114Board.h> #include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> -#include <helpers/SensorManager.h> +#include <helpers/sensors/EnvironmentSensorManager.h> #include <helpers/sensors/LocationProvider.h> #ifdef DISPLAY_CLASS @@ -18,23 +18,11 @@ #endif #endif -class T114SensorManager : public SensorManager { - bool gps_active = false; - bool gps_detected = false; - LocationProvider* _location; - - void start_gps(); - void stop_gps(); +class T114SensorManager : public EnvironmentSensorManager { public: - T114SensorManager(LocationProvider &location): _location(&location) { } + T114SensorManager(LocationProvider &location): EnvironmentSensorManager(location) { } bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; - void loop() override; - LocationProvider* getLocationProvider() override { return gps_detected ? _location : NULL; } - int getNumSettings() const override; - const char* getSettingName(int i) const override; - const char* getSettingValue(int i) const override; - bool setSettingValue(const char* name, const char* value) override; }; extern T114Board board; From e15503d50d07fbceca16234078596669f0477820 Mon Sep 17 00:00:00 2001 From: Quency-D <hj_zzns@163.com> Date: Mon, 2 Feb 2026 14:19:42 +0800 Subject: [PATCH 455/546] Fix low power consumption issues --- src/helpers/ui/SSD1306Display.cpp | 12 +++++++++--- variants/heltec_v4/platformio.ini | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index 4e7fd10a..f585feb0 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -18,17 +18,23 @@ bool SSD1306Display::begin() { } void SSD1306Display::turnOn() { - display.ssd1306_command(SSD1306_DISPLAYON); if (!_isOn) { - if (_peripher_power) _peripher_power->claim(); + if (_peripher_power) { + _peripher_power->claim(); + begin(); + } _isOn = true; } + display.ssd1306_command(SSD1306_DISPLAYON); } void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); if (_isOn) { - if (_peripher_power) _peripher_power->release(); + if (_peripher_power) { + if (PIN_OLED_RESET >= 0) digitalWrite(PIN_OLED_RESET, LOW); + _peripher_power->release(); + } _isOn = false; } } diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index ba759009..fe971f06 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -52,6 +52,7 @@ build_flags = -D HELTEC_LORA_V4_OLED -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 + -D PIN_OLED_RESET=21 -D ENV_PIN_SDA=4 -D ENV_PIN_SCL=3 build_src_filter= ${Heltec_lora32_v4.build_src_filter} From f0ba14ff7580fd8dfc866ec0490606485390cb3c Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Mon, 2 Feb 2026 18:05:26 +0100 Subject: [PATCH 456/546] Remove sync word handling from KissModem. --- examples/kiss_modem/KissModem.cpp | 28 +--------------------------- examples/kiss_modem/KissModem.h | 9 --------- examples/kiss_modem/main.cpp | 5 ----- 3 files changed, 1 insertion(+), 41 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index d11e8217..d9c71bf8 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -11,10 +11,9 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _pending_tx_len = 0; _setRadioCallback = nullptr; _setTxPowerCallback = nullptr; - _setSyncWordCallback = nullptr; _getCurrentRssiCallback = nullptr; _getStatsCallback = nullptr; - _config = {0, 0, 0, 0, 0, 0x12}; + _config = {0, 0, 0, 0, 0}; } void KissModem::begin() { @@ -133,18 +132,12 @@ void KissModem::processFrame() { case CMD_SET_TX_POWER: handleSetTxPower(data, data_len); break; - case CMD_SET_SYNC_WORD: - handleSetSyncWord(data, data_len); - break; case CMD_GET_RADIO: handleGetRadio(); break; case CMD_GET_TX_POWER: handleGetTxPower(); break; - case CMD_GET_SYNC_WORD: - handleGetSyncWord(); - break; case CMD_GET_VERSION: handleGetVersion(); break; @@ -347,21 +340,6 @@ void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { writeFrame(RESP_OK, nullptr, 0); } -void KissModem::handleSetSyncWord(const uint8_t* data, uint16_t len) { - if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); - return; - } - if (!_setSyncWordCallback) { - writeErrorFrame(ERR_NO_CALLBACK); - return; - } - - _config.sync_word = data[0]; - _setSyncWordCallback(data[0]); - writeFrame(RESP_OK, nullptr, 0); -} - void KissModem::handleGetRadio() { uint8_t buf[10]; memcpy(buf, &_config.freq_hz, 4); @@ -375,10 +353,6 @@ void KissModem::handleGetTxPower() { writeFrame(RESP_TX_POWER, &_config.tx_power, 1); } -void KissModem::handleGetSyncWord() { - writeFrame(RESP_SYNC_WORD, &_config.sync_word, 1); -} - void KissModem::handleGetVersion() { uint8_t buf[2]; buf[0] = KISS_FIRMWARE_VERSION; diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index bc7560f4..170bb0c2 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -25,10 +25,8 @@ #define CMD_HASH 0x08 #define CMD_SET_RADIO 0x09 #define CMD_SET_TX_POWER 0x0A -#define CMD_SET_SYNC_WORD 0x0B #define CMD_GET_RADIO 0x0C #define CMD_GET_TX_POWER 0x0D -#define CMD_GET_SYNC_WORD 0x0E #define CMD_GET_VERSION 0x0F #define CMD_GET_CURRENT_RSSI 0x10 #define CMD_IS_CHANNEL_BUSY 0x11 @@ -50,7 +48,6 @@ #define RESP_OK 0x29 #define RESP_RADIO 0x2A #define RESP_TX_POWER 0x2B -#define RESP_SYNC_WORD 0x2C #define RESP_VERSION 0x2D #define RESP_ERROR 0x2E #define RESP_TX_DONE 0x2F @@ -75,7 +72,6 @@ typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); -typedef void (*SetSyncWordCallback)(uint8_t syncWord); typedef float (*GetCurrentRssiCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); @@ -85,7 +81,6 @@ struct RadioConfig { uint8_t sf; uint8_t cr; uint8_t tx_power; - uint8_t sync_word; }; class KissModem { @@ -107,7 +102,6 @@ class KissModem { SetRadioCallback _setRadioCallback; SetTxPowerCallback _setTxPowerCallback; - SetSyncWordCallback _setSyncWordCallback; GetCurrentRssiCallback _getCurrentRssiCallback; GetStatsCallback _getStatsCallback; @@ -128,10 +122,8 @@ class KissModem { void handleHash(const uint8_t* data, uint16_t len); void handleSetRadio(const uint8_t* data, uint16_t len); void handleSetTxPower(const uint8_t* data, uint16_t len); - void handleSetSyncWord(const uint8_t* data, uint16_t len); void handleGetRadio(); void handleGetTxPower(); - void handleGetSyncWord(); void handleGetVersion(); void handleGetCurrentRssi(); void handleIsChannelBusy(); @@ -151,7 +143,6 @@ public: void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } - void setSyncWordCallback(SetSyncWordCallback cb) { _setSyncWordCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index e81161bf..959222b9 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -52,10 +52,6 @@ void onSetTxPower(uint8_t power) { radio_set_tx_power(power); } -void onSetSyncWord(uint8_t sync_word) { - radio_set_sync_word(sync_word); -} - float onGetCurrentRssi() { return radio_driver.getCurrentRSSI(); } @@ -88,7 +84,6 @@ void setup() { modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); - modem->setSyncWordCallback(onSetSyncWord); modem->setGetCurrentRssiCallback(onGetCurrentRssi); modem->setGetStatsCallback(onGetStats); modem->begin(); From 84e68cf4cb2dc06f83c861863621e0749ff18700 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Mon, 2 Feb 2026 22:58:55 +0100 Subject: [PATCH 457/546] initial port of M5Stack Unit C6L, update pioarduino to newer bugfix release --- platformio.ini | 6 +- variants/m5stack_unit_c6l/UnitC6LBoard.cpp | 49 ++++++++++ variants/m5stack_unit_c6l/UnitC6LBoard.h | 15 +++ variants/m5stack_unit_c6l/platformio.ini | 104 +++++++++++++++++++++ variants/m5stack_unit_c6l/target.h | 21 +++++ 5 files changed, 192 insertions(+), 3 deletions(-) create mode 100644 variants/m5stack_unit_c6l/UnitC6LBoard.cpp create mode 100644 variants/m5stack_unit_c6l/UnitC6LBoard.h create mode 100644 variants/m5stack_unit_c6l/platformio.ini create mode 100644 variants/m5stack_unit_c6l/target.h diff --git a/platformio.ini b/platformio.ini index 743e357a..69883271 100644 --- a/platformio.ini +++ b/platformio.ini @@ -68,10 +68,10 @@ lib_deps = file://arch/esp32/AsyncElegantOTA ; esp32c6 uses arduino framework 3.x -; WARNING: experimental. pioarduino on esp32c6 needs work - it's not considered stable and has issues. +; WARNING: experimental. May not work as stable as other platforms. [esp32c6_base] extends = esp32_base -platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.12/platform-espressif32.zip +platform = https://github.com/pioarduino/platform-espressif32/releases/download/53.03.13-1/platform-espressif32.zip ; ----------------- NRF52 --------------------- @@ -80,7 +80,7 @@ extends = arduino_base platform = nordicnrf52 platform_packages = framework-arduinoadafruitnrf52 @ 1.10700.0 -extra_scripts = +extra_scripts = create-uf2.py arch/nrf52/extra_scripts/patch_bluefruit.py build_flags = ${arduino_base.build_flags} diff --git a/variants/m5stack_unit_c6l/UnitC6LBoard.cpp b/variants/m5stack_unit_c6l/UnitC6LBoard.cpp new file mode 100644 index 00000000..6538ef48 --- /dev/null +++ b/variants/m5stack_unit_c6l/UnitC6LBoard.cpp @@ -0,0 +1,49 @@ +#include <Arduino.h> +#include "target.h" + +UnitC6LBoard board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(0); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +SensorManager sensors; + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI); + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(uint8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/m5stack_unit_c6l/UnitC6LBoard.h b/variants/m5stack_unit_c6l/UnitC6LBoard.h new file mode 100644 index 00000000..a4ea3ee6 --- /dev/null +++ b/variants/m5stack_unit_c6l/UnitC6LBoard.h @@ -0,0 +1,15 @@ +#pragma once + +#include <Arduino.h> +#include <helpers/ESP32Board.h> + +class UnitC6LBoard : public ESP32Board { +public: + void begin() { + ESP32Board::begin(); + } + + const char* getManufacturerName() const override { + return "Unit C6L"; + } +}; diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini new file mode 100644 index 00000000..bbfdb4a1 --- /dev/null +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -0,0 +1,104 @@ +[M5Stack_Unit_C6L] +extends = esp32c6_base +board = esp32-c6-devkitm-1 +board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +build_flags = + ${esp32c6_base.build_flags} + ${sensor_base.build_flags} + -I variants/M5Stack_Unit_C6L + -D P_LORA_TX_LED=15 + -D P_LORA_SCLK=20 + -D P_LORA_MISO=22 + -D P_LORA_MOSI=21 + -D P_LORA_NSS=23 + -D P_LORA_DIO_1=7 + -D P_LORA_BUSY=19 + -D P_LORA_RESET=-1 + -D PIN_BUZZER=11 + -D PIN_BOARD_SDA=16 + -D PIN_BOARD_SCL=17 + -D SX126X_RXEN=5 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D DISABLE_WIFI_OTA=1 + -D GPS_RX=4 + -D GPS_TX=5 +build_src_filter = ${esp32c6_base.build_src_filter} + +<../variants/m5stack_unit_c6l> + +<UnitC6LBoard.cpp> +lib_deps = + ${esp32c6_base.lib_deps} + ${sensor_base.lib_deps} + +[env:M5Stack_Unit_C6L_repeater] +extends = M5Stack_Unit_C6L +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${M5Stack_Unit_C6L.build_flags} + -D ADVERT_NAME='"Unit C6L Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} +; ${esp32_ota.lib_deps} + +[env:M5Stack_Unit_C6L_room_server] +extends = M5Stack_Unit_C6L +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${M5Stack_Unit_C6L.build_flags} + -D ADVERT_NAME='"Unit C6L Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} +; ${esp32_ota.lib_deps} + +[env:M5Stack_Unit_C6L_companion_radio_ble] +extends = M5Stack_Unit_C6L +build_flags = ${M5Stack_Unit_C6L.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<helpers/esp32/*.cpp> + -<helpers/esp32/ESPNOWRadio.cpp> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:M5Stack_Unit_C6L_companion_radio_usb] +extends = M5Stack_Unit_C6L +build_flags = ${M5Stack_Unit_C6L.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<helpers/esp32/*.cpp> + -<helpers/esp32/ESPNOWRadio.cpp> + +<../examples/companion_radio/*.cpp> +lib_deps = + ${M5Stack_Unit_C6L.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 diff --git a/variants/m5stack_unit_c6l/target.h b/variants/m5stack_unit_c6l/target.h new file mode 100644 index 00000000..1f4e9ae3 --- /dev/null +++ b/variants/m5stack_unit_c6l/target.h @@ -0,0 +1,21 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include <RadioLib.h> +#include <UnitC6LBoard.h> +#include <helpers/radiolib/RadioLibWrappers.h> +#include <helpers/ESP32Board.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/SensorManager.h> + +extern UnitC6LBoard board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern SensorManager sensors; + +bool radio_init(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(uint8_t dbm); +mesh::LocalIdentity radio_new_identity(); From 598489be471e978ea76d6f759f6367124a0b335a Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Mon, 26 Jan 2026 16:41:08 +1100 Subject: [PATCH 458/546] refactor ui with ring buffer and display most recent --- examples/companion_radio/ui-new/UITask.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 0690b45a..ae2d9375 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -458,15 +458,17 @@ class MsgPreviewScreen : public UIScreen { }; #define MAX_UNREAD_MSGS 32 int num_unread; + int head = MAX_UNREAD_MSGS - 1; // index of latest unread message MsgEntry unread[MAX_UNREAD_MSGS]; public: MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc) : _task(task), _rtc(rtc) { num_unread = 0; } void addPreview(uint8_t path_len, const char* from_name, const char* msg) { - if (num_unread >= MAX_UNREAD_MSGS) return; // full + head = (head + 1) % MAX_UNREAD_MSGS; + if (num_unread < MAX_UNREAD_MSGS) num_unread++; - auto p = &unread[num_unread++]; + auto p = &unread[head]; p->timestamp = _rtc->getCurrentTime(); if (path_len == 0xFF) { sprintf(p->origin, "(D) %s:", from_name); @@ -484,7 +486,7 @@ public: sprintf(tmp, "Unread: %d", num_unread); display.print(tmp); - auto p = &unread[0]; + auto p = &unread[head]; int secs = _rtc->getCurrentTime() - p->timestamp; if (secs < 60) { @@ -520,14 +522,10 @@ public: bool handleInput(char c) override { if (c == KEY_NEXT || c == KEY_RIGHT) { + head = (head + MAX_UNREAD_MSGS - 1) % MAX_UNREAD_MSGS; num_unread--; if (num_unread == 0) { _task->gotoHomeScreen(); - } else { - // delete first/curr item from unread queue - for (int i = 0; i < num_unread; i++) { - unread[i] = unread[i + 1]; - } } return true; } From 0fb570338f57b6b2fdb5426f92e6e61c58e9db38 Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Tue, 3 Feb 2026 20:58:37 -0800 Subject: [PATCH 459/546] fix(kiss): periodic noise floor calibration and AGC reset - Trigger noise floor calibration every 2s and AGC reset every 30s in main loop. - Reorder loop to match Dispatcher: calibrate + radio.loop() before AGC reset and recvRaw() so RSSI is never sampled right after startReceive(). - Update protocol doc with calibration intervals and typical noise floor range. - Variant platformio.ini updates (heltec_v3, rak4631). --- docs/kiss_modem_protocol.md | 20 ++++++++++++++++---- examples/kiss_modem/main.cpp | 23 +++++++++++++++++++---- variants/heltec_v3/platformio.ini | 9 +++++++++ variants/rak4631/platformio.ini | 11 ++++++++++- 4 files changed, 54 insertions(+), 9 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index e80c3b29..067e1539 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -28,6 +28,8 @@ Maximum unescaped frame size: 512 bytes. ## Commands +Command and response codes below are taken from `examples/kiss_modem/KissModem.h` and the switch in `KissModem::processFrame()`. + ### Request Commands (Host → Modem) | Command | Value | Data | @@ -43,10 +45,10 @@ Maximum unescaped frame size: 512 bytes. | `CMD_HASH` | `0x08` | Data to hash | | `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | | `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | -| `CMD_SET_SYNC_WORD` | `0x0B` | Sync word (1) | +| *reserved* | `0x0B` | *(not implemented)* | | `CMD_GET_RADIO` | `0x0C` | - | | `CMD_GET_TX_POWER` | `0x0D` | - | -| `CMD_GET_SYNC_WORD` | `0x0E` | - | +| *reserved* | `0x0E` | *(not implemented)* | | `CMD_GET_VERSION` | `0x0F` | - | | `CMD_GET_CURRENT_RSSI` | `0x10` | - | | `CMD_IS_CHANNEL_BUSY` | `0x11` | - | @@ -73,7 +75,7 @@ Maximum unescaped frame size: 512 bytes. | `RESP_OK` | `0x29` | - | | `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | | `RESP_TX_POWER` | `0x2B` | Power dBm (1) | -| `RESP_SYNC_WORD` | `0x2C` | Sync word (1) | +| *reserved* | `0x2C` | *(not implemented)* | | `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | | `RESP_ERROR` | `0x2E` | Error code (1) | | `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | @@ -119,9 +121,19 @@ All values little-endian. | RSSI | 1 byte | Signal strength dBm, signed | | Packet | variable | Raw MeshCore packet | +### Noise Floor (RESP_NOISE_FLOOR) + +Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. + +| Field | Size | Description | +|--------------|------|--------------------------------| +| Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | + +The modem recalibrates the noise floor periodically (every 2 s) from RX samples when idle. The receiver AGC is also reset periodically (every 30 s) so RSSI and noise floor do not drift to the minimum (-120). Typical range after calibration is about -120 to -90 dBm. Values may be 0 or briefly stale until the radio has been in receive mode long enough to collect 64 samples. + ### Stats (RESP_STATS) -All values little-endian. +Response to `CMD_GET_STATS` (0x14). All values little-endian. | Field | Size | Description | |-------|------|-------------| diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 959222b9..13855309 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,9 +12,14 @@ #include <SPIFFS.h> #endif +#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 // match Dispatcher default +#define AGC_RESET_INTERVAL_MS 30000 // periodic RX restart so AGC doesn't drift (repeater uses same via prefs) + StdRNG rng; mesh::LocalIdentity identity; KissModem* modem; +static uint32_t next_noise_floor_calib_ms = 0; +static uint32_t next_agc_reset_ms = 0; void halt() { while (1) ; @@ -94,7 +99,16 @@ void loop() { uint8_t packet[KISS_MAX_PACKET_SIZE]; uint16_t len; - + + // Match Dispatcher order: noise floor calib + loop() first, so we never sample RSSI in the same + // iteration as startReceive() (AGC reset -> recvRaw below). Sampling right after startReceive() + // can yield settling/cold RSSI and drive the floor toward -120. + if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { + radio_driver.triggerNoiseFloorCalibrate(0); // 0 = no interference threshold (KISS has no prefs) + next_noise_floor_calib_ms = millis(); + } + radio_driver.loop(); + if (modem->getPacketToSend(packet, &len)) { radio_driver.startSendRaw(packet, len); while (!radio_driver.isSendComplete()) { @@ -104,14 +118,15 @@ void loop() { modem->onTxComplete(true); } + if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { + radio_driver.resetAGC(); // next recvRaw() will startReceive() and reset AGC so RSSI/noise floor don't stick at -120 + next_agc_reset_ms = millis(); + } uint8_t rx_buf[256]; int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - if (rx_len > 0) { int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } - - radio_driver.loop(); } diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 6b61eff5..4d299104 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -367,3 +367,12 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter} lib_deps = ${Heltec_lora32_v3.lib_deps} ${esp32_ota.lib_deps} + +[env:Heltec_v3_kiss_modem] +extends = Heltec_lora32_v3 +build_flags = + ${Heltec_lora32_v3.build_flags} +build_src_filter = ${Heltec_lora32_v3.build_src_filter} + +<../examples/kiss_modem/> +lib_deps = + ${Heltec_lora32_v3.lib_deps} \ No newline at end of file diff --git a/variants/rak4631/platformio.ini b/variants/rak4631/platformio.ini index 9a9ab2dd..737ef565 100644 --- a/variants/rak4631/platformio.ini +++ b/variants/rak4631/platformio.ini @@ -183,4 +183,13 @@ build_flags = -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} +<helpers/ui/SSD1306Display.cpp> - +<../examples/simple_sensor> \ No newline at end of file + +<../examples/simple_sensor> + +[env:RAK_4631_kiss_modem] +extends = rak4631 +build_flags = + ${rak4631.build_flags} +build_src_filter = ${rak4631.build_src_filter} + +<../examples/kiss_modem/> +lib_deps = + ${rak4631.lib_deps} \ No newline at end of file From 5cb26b91f6b35ebb452887fa458513790052ff57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Thu, 5 Feb 2026 13:35:04 +0000 Subject: [PATCH 460/546] Refactor Heltec T114 sensor management --- examples/simple_repeater/main.cpp | 7 ++++ variants/heltec_t114/platformio.ini | 12 +++--- variants/heltec_t114/target.cpp | 62 +++++++++++------------------ variants/heltec_t114/target.h | 17 +++----- variants/heltec_t114/variant.h | 9 +++-- 5 files changed, 48 insertions(+), 59 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d55d6118..c053b68b 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -29,6 +29,12 @@ void setup() { board.begin(); +#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) + // give some extra time for serial to settle so + // boot debug messages can be seen on terminal + delay(5000); +#endif + // For power saving lastActive = millis(); // mark last active time since boot @@ -42,6 +48,7 @@ void setup() { #endif if (!radio_init()) { + MESH_DEBUG_PRINTLN("Radio init failed!"); halt(); } diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index dd1f8bb3..b985030f 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -29,15 +29,13 @@ build_flags = ${nrf52_base.build_flags} -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D DISPLAY_CLASS=NullDisplayDriver - -D ST7789 -D PIN_GPS_RX=39 -D PIN_GPS_TX=37 -D PIN_GPS_EN=21 -D PIN_GPS_RESET=38 -D PIN_GPS_RESET_ACTIVE=LOW - -D PIN_BOARD_SDA=16 - -D PIN_BOARD_SCL=13 + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL build_src_filter = ${nrf52_base.build_src_filter} +<helpers/*.cpp> +<helpers/sensors> @@ -45,8 +43,6 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${nrf52_base.lib_deps} ${sensor_base.lib_deps} - stevemarple/MicroNMEA @ ^2.0.6 - adafruit/Adafruit GFX Library @ ^1.12.1 debug_tool = jlink upload_protocol = nrfutil @@ -105,6 +101,7 @@ board_upload.maximum_size = 712704 build_flags = ${Heltec_t114.build_flags} -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 @@ -127,6 +124,7 @@ board_upload.maximum_size = 712704 build_flags = ${Heltec_t114.build_flags} -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ; -D BLE_PIN_CODE=123456 @@ -149,6 +147,7 @@ extends = Heltec_t114 board = heltec_t114 board_build.ldscript = boards/nrf52840_s140_v6.ld build_flags = ${Heltec_t114.build_flags} + -D ST7789 -D HELTEC_T114_WITH_DISPLAY -D DISPLAY_CLASS=ST7789Display build_src_filter = ${Heltec_t114.build_src_filter} @@ -158,6 +157,7 @@ build_src_filter = ${Heltec_t114.build_src_filter} +<helpers/ui/OLEDDisplayFonts.cpp> lib_deps = ${Heltec_t114.lib_deps} + adafruit/Adafruit SSD1306 @ ^2.5.13 debug_tool = jlink upload_protocol = nrfutil diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 23b9b667..cd280dda 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -1,28 +1,46 @@ -#include <Arduino.h> #include "target.h" + +#include <Arduino.h> #include <helpers/ArduinoHelpers.h> + +#ifdef ENV_INCLUDE_GPS #include <helpers/sensors/MicroNMEALocationProvider.h> +#endif T114Board board; +#if defined(P_LORA_SCLK) RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); +#else +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); -T114SensorManager sensors = T114SensorManager(nmea); + +#if ENV_INCLUDE_GPS +#include <helpers/sensors/MicroNMEALocationProvider.h> +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors; +#endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display; - MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +DISPLAY_CLASS display; +MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif bool radio_init() { rtc_clock.begin(Wire); +#if defined(P_LORA_SCLK) return radio.std_init(&SPI); +#else + return radio.std_init(); +#endif } uint32_t radio_get_rng_seed() { @@ -42,37 +60,5 @@ void radio_set_tx_power(uint8_t dbm) { mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); - return mesh::LocalIdentity(&rng); // create new random identity -} - -bool T114SensorManager::begin() { - Serial1.begin(9600); - - // Try to detect if GPS is physically connected to determine if we should expose the setting - pinMode(PIN_GPS_EN, OUTPUT); - digitalWrite(PIN_GPS_EN, HIGH); // Power on GPS - - // Give GPS a moment to power up and send data - delay(1500); - - // We'll consider GPS detected if we see any data on Serial1 - gps_detected = (Serial1.available() > 0); - - if (gps_detected) { - MESH_DEBUG_PRINTLN("GPS detected"); - } else { - MESH_DEBUG_PRINTLN("No GPS detected"); - } - digitalWrite(PIN_GPS_EN, LOW); // Power off GPS until the setting is changed - - return EnvironmentSensorManager::begin(); -} - -bool T114SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { - EnvironmentSensorManager::querySensors(requester_permissions, telemetry); - - if (requester_permissions & TELEM_PERM_LOCATION) { // does requester have permission? - telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); - } - return true; + return mesh::LocalIdentity(&rng); // create new random identity } diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 24de81ee..94f990ec 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -2,10 +2,10 @@ #define RADIOLIB_STATIC_ONLY 1 #include <RadioLib.h> -#include <helpers/radiolib/RadioLibWrappers.h> #include <T114Board.h> -#include <helpers/radiolib/CustomSX1262Wrapper.h> #include <helpers/AutoDiscoverRTCClock.h> +#include <helpers/radiolib/CustomSX1262Wrapper.h> +#include <helpers/radiolib/RadioLibWrappers.h> #include <helpers/sensors/EnvironmentSensorManager.h> #include <helpers/sensors/LocationProvider.h> @@ -18,21 +18,14 @@ #endif #endif -class T114SensorManager : public EnvironmentSensorManager { -public: - T114SensorManager(LocationProvider &location): EnvironmentSensorManager(location) { } - bool begin() override; - bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; -}; - extern T114Board board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; -extern T114SensorManager sensors; +extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS - extern DISPLAY_CLASS display; - extern MomentaryButton user_btn; +extern DISPLAY_CLASS display; +extern MomentaryButton user_btn; #endif bool radio_init(); diff --git a/variants/heltec_t114/variant.h b/variants/heltec_t114/variant.h index aa7f4022..bfb4484d 100644 --- a/variants/heltec_t114/variant.h +++ b/variants/heltec_t114/variant.h @@ -14,7 +14,7 @@ #define USE_LFXO // 32.768 kHz crystal oscillator #define VARIANT_MCK (64000000ul) -#define WIRE_INTERFACES_COUNT (1) +#define WIRE_INTERFACES_COUNT (2) //////////////////////////////////////////////////////////////////////////////// // Power @@ -58,8 +58,11 @@ //////////////////////////////////////////////////////////////////////////////// // I2C pin definition -#define PIN_WIRE_SDA (26) // P0.26 -#define PIN_WIRE_SCL (27) // P0.27 +#define PIN_WIRE_SDA (26) // P0.26 +#define PIN_WIRE_SCL (27) // P0.27 + +#define PIN_WIRE1_SDA (7) // P0.8 +#define PIN_WIRE1_SCL (8) // P0.7 //////////////////////////////////////////////////////////////////////////////// // SPI pin definition From c0b81b9ad867dd0b018c4a513b0424995226fb97 Mon Sep 17 00:00:00 2001 From: Adam Gessaman <adam@gessaman.com> Date: Thu, 5 Feb 2026 09:46:30 -0800 Subject: [PATCH 461/546] Clean up comments on kiss noise floor changes. --- docs/kiss_modem_protocol.md | 4 +--- examples/kiss_modem/main.cpp | 12 +++++------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 067e1539..00b0bf90 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -28,8 +28,6 @@ Maximum unescaped frame size: 512 bytes. ## Commands -Command and response codes below are taken from `examples/kiss_modem/KissModem.h` and the switch in `KissModem::processFrame()`. - ### Request Commands (Host → Modem) | Command | Value | Data | @@ -129,7 +127,7 @@ Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. |--------------|------|--------------------------------| | Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | -The modem recalibrates the noise floor periodically (every 2 s) from RX samples when idle. The receiver AGC is also reset periodically (every 30 s) so RSSI and noise floor do not drift to the minimum (-120). Typical range after calibration is about -120 to -90 dBm. Values may be 0 or briefly stale until the radio has been in receive mode long enough to collect 64 samples. +The modem recalibrates the noise floor every two seconds with an AGC reset every 30 seconds. ### Stats (RESP_STATS) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 13855309..3a610460 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,8 +12,8 @@ #include <SPIFFS.h> #endif -#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 // match Dispatcher default -#define AGC_RESET_INTERVAL_MS 30000 // periodic RX restart so AGC doesn't drift (repeater uses same via prefs) +#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 +#define AGC_RESET_INTERVAL_MS 30000 StdRNG rng; mesh::LocalIdentity identity; @@ -100,11 +100,9 @@ void loop() { uint8_t packet[KISS_MAX_PACKET_SIZE]; uint16_t len; - // Match Dispatcher order: noise floor calib + loop() first, so we never sample RSSI in the same - // iteration as startReceive() (AGC reset -> recvRaw below). Sampling right after startReceive() - // can yield settling/cold RSSI and drive the floor toward -120. + // trigger noise floor calibration if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { - radio_driver.triggerNoiseFloorCalibrate(0); // 0 = no interference threshold (KISS has no prefs) + radio_driver.triggerNoiseFloorCalibrate(0); next_noise_floor_calib_ms = millis(); } radio_driver.loop(); @@ -119,7 +117,7 @@ void loop() { } if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { - radio_driver.resetAGC(); // next recvRaw() will startReceive() and reset AGC so RSSI/noise floor don't stick at -120 + radio_driver.resetAGC(); next_agc_reset_ms = millis(); } uint8_t rx_buf[256]; From d0720c63c2cfa687f7d3a525f4c7a8c5152e44d6 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel.nieboer@onior.com> Date: Sat, 3 Jan 2026 20:35:14 +0100 Subject: [PATCH 462/546] Allow negative tx power Like SX1262 allows -9 dBm lowest, some allow lower but that probably isn't useful --- examples/companion_radio/MyMesh.cpp | 2 +- examples/companion_radio/NodePrefs.h | 2 +- examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_repeater/MyMesh.h | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.h | 2 +- examples/simple_secure_chat/main.cpp | 4 ++-- examples/simple_sensor/SensorMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.h | 2 +- src/helpers/CommonCLI.cpp | 4 ++-- src/helpers/CommonCLI.h | 4 ++-- variants/ebyte_eora_s3/target.cpp | 2 +- variants/ebyte_eora_s3/target.h | 2 +- variants/generic-e22/target.cpp | 2 +- variants/generic-e22/target.h | 2 +- variants/generic_espnow/target.cpp | 2 +- variants/generic_espnow/target.h | 2 +- variants/heltec_ct62/target.cpp | 2 +- variants/heltec_ct62/target.h | 2 +- variants/heltec_e213/target.cpp | 2 +- variants/heltec_e213/target.h | 2 +- variants/heltec_e290/target.cpp | 2 +- variants/heltec_e290/target.h | 2 +- variants/heltec_mesh_solar/target.cpp | 2 +- variants/heltec_mesh_solar/target.h | 2 +- variants/heltec_t114/target.cpp | 2 +- variants/heltec_t114/target.h | 2 +- variants/heltec_t190/target.cpp | 2 +- variants/heltec_t190/target.h | 2 +- variants/heltec_tracker/target.cpp | 2 +- variants/heltec_tracker/target.h | 2 +- variants/heltec_tracker_v2/target.cpp | 2 +- variants/heltec_tracker_v2/target.h | 2 +- variants/heltec_v2/target.cpp | 2 +- variants/heltec_v2/target.h | 2 +- variants/heltec_v3/target.cpp | 2 +- variants/heltec_v3/target.h | 2 +- variants/heltec_v4/target.cpp | 2 +- variants/heltec_v4/target.h | 2 +- variants/heltec_wireless_paper/target.cpp | 2 +- variants/heltec_wireless_paper/target.h | 2 +- variants/ikoka_handheld_nrf/target.cpp | 2 +- variants/ikoka_handheld_nrf/target.h | 2 +- variants/ikoka_nano_nrf/target.cpp | 2 +- variants/ikoka_nano_nrf/target.h | 2 +- variants/ikoka_stick_nrf/target.cpp | 2 +- variants/ikoka_stick_nrf/target.h | 2 +- variants/keepteen_lt1/target.cpp | 2 +- variants/keepteen_lt1/target.h | 2 +- variants/lilygo_t3s3/target.cpp | 2 +- variants/lilygo_t3s3/target.h | 2 +- variants/lilygo_t3s3_sx1276/target.cpp | 2 +- variants/lilygo_t3s3_sx1276/target.h | 2 +- variants/lilygo_tbeam_1w/target.cpp | 2 +- variants/lilygo_tbeam_1w/target.h | 2 +- variants/lilygo_tbeam_SX1262/target.cpp | 2 +- variants/lilygo_tbeam_SX1262/target.h | 2 +- variants/lilygo_tbeam_SX1276/target.cpp | 2 +- variants/lilygo_tbeam_SX1276/target.h | 2 +- variants/lilygo_tbeam_supreme_SX1262/target.cpp | 2 +- variants/lilygo_tbeam_supreme_SX1262/target.h | 2 +- variants/lilygo_tdeck/target.cpp | 2 +- variants/lilygo_tdeck/target.h | 2 +- variants/lilygo_techo/target.cpp | 2 +- variants/lilygo_techo/target.h | 2 +- variants/lilygo_techo_lite/target.cpp | 2 +- variants/lilygo_techo_lite/target.h | 2 +- variants/lilygo_tlora_c6/target.cpp | 2 +- variants/lilygo_tlora_c6/target.h | 2 +- variants/lilygo_tlora_v2_1/target.cpp | 2 +- variants/lilygo_tlora_v2_1/target.h | 2 +- variants/mesh_pocket/target.cpp | 2 +- variants/mesh_pocket/target.h | 2 +- variants/meshadventurer/target.cpp | 2 +- variants/meshadventurer/target.h | 2 +- variants/meshtiny/target.cpp | 2 +- variants/meshtiny/target.h | 2 +- variants/minewsemi_me25ls01/target.cpp | 2 +- variants/minewsemi_me25ls01/target.h | 2 +- variants/nano_g2_ultra/target.cpp | 2 +- variants/nano_g2_ultra/target.h | 2 +- variants/nibble_screen_connect/target.cpp | 2 +- variants/nibble_screen_connect/target.h | 2 +- variants/promicro/target.cpp | 2 +- variants/promicro/target.h | 2 +- variants/rak11310/target.cpp | 2 +- variants/rak11310/target.h | 2 +- variants/rak3112/target.cpp | 2 +- variants/rak3112/target.h | 2 +- variants/rak3401/target.cpp | 2 +- variants/rak3401/target.h | 2 +- variants/rak3x72/target.cpp | 2 +- variants/rak3x72/target.h | 2 +- variants/rak4631/target.cpp | 2 +- variants/rak4631/target.h | 2 +- variants/rak_wismesh_tag/target.cpp | 2 +- variants/rak_wismesh_tag/target.h | 2 +- variants/rpi_picow/target.cpp | 2 +- variants/rpi_picow/target.h | 2 +- variants/sensecap_indicator-espnow/target.cpp | 2 +- variants/sensecap_indicator-espnow/target.h | 2 +- variants/sensecap_solar/target.cpp | 2 +- variants/sensecap_solar/target.h | 2 +- variants/station_g2/target.cpp | 2 +- variants/station_g2/target.h | 2 +- variants/t1000-e/target.cpp | 2 +- variants/t1000-e/target.h | 2 +- variants/tenstar_c3/target.cpp | 2 +- variants/tenstar_c3/target.h | 2 +- variants/thinknode_m1/target.cpp | 2 +- variants/thinknode_m1/target.h | 2 +- variants/thinknode_m2/target.cpp | 2 +- variants/thinknode_m2/target.h | 2 +- variants/thinknode_m3/target.cpp | 2 +- variants/thinknode_m3/target.h | 2 +- variants/thinknode_m5/target.cpp | 2 +- variants/thinknode_m5/target.h | 2 +- variants/thinknode_m6/target.cpp | 2 +- variants/thinknode_m6/target.h | 2 +- variants/tiny_relay/target.cpp | 2 +- variants/tiny_relay/target.h | 2 +- variants/waveshare_rp2040_lora/target.cpp | 2 +- variants/waveshare_rp2040_lora/target.h | 2 +- variants/wio-e5-dev/target.cpp | 2 +- variants/wio-e5-dev/target.h | 2 +- variants/wio-e5-mini/target.cpp | 2 +- variants/wio-e5-mini/target.h | 2 +- variants/wio-tracker-l1/target.cpp | 2 +- variants/wio-tracker-l1/target.h | 2 +- variants/wio_wm1110/target.cpp | 2 +- variants/wio_wm1110/target.h | 2 +- variants/xiao_c3/target.cpp | 2 +- variants/xiao_c3/target.h | 2 +- variants/xiao_c6/XiaoC6Board.cpp | 2 +- variants/xiao_c6/target.h | 2 +- variants/xiao_nrf52/target.cpp | 2 +- variants/xiao_nrf52/target.h | 2 +- variants/xiao_rp2040/target.cpp | 2 +- variants/xiao_rp2040/target.h | 2 +- variants/xiao_s3_wio/target.cpp | 2 +- variants/xiao_s3_wio/target.h | 2 +- 141 files changed, 144 insertions(+), 144 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 9bb747e7..f8e90be5 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -838,7 +838,7 @@ void MyMesh::begin(bool has_display) { _prefs.bw = constrain(_prefs.bw, 7.8f, 500.0f); _prefs.sf = constrain(_prefs.sf, 5, 12); _prefs.cr = constrain(_prefs.cr, 5, 8); - _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER); + _prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER); _prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1 _prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 62cd4164..d7ddd92a 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -17,7 +17,7 @@ struct NodePrefs { // persisted to file uint8_t multi_acks; uint8_t manual_add_contacts; float bw; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t telemetry_mode_base; uint8_t telemetry_mode_loc; uint8_t telemetry_mode_env; diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6d957cc0..8220ef0d 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -899,7 +899,7 @@ void MyMesh::dumpLogFile() { } } -void MyMesh::setTxPower(uint8_t power_dbm) { +void MyMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 0d5cd28a..7a51b4a9 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -198,7 +198,7 @@ public: } void dumpLogFile() override; - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char *reply) override; void removeNeighbor(const uint8_t* pubkey, int key_len) override; void formatStatsReply(char *reply) override; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 22a3d208..598b14de 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -719,7 +719,7 @@ void MyMesh::dumpLogFile() { } } -void MyMesh::setTxPower(uint8_t power_dbm) { +void MyMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index f470e55e..b4529e77 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -188,7 +188,7 @@ public: } void dumpLogFile() override; - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index 018ec2a2..a389ec74 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -66,7 +66,7 @@ struct NodePrefs { // persisted to file char node_name[32]; double node_lat, node_lon; float freq; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t unused[3]; }; @@ -290,7 +290,7 @@ public: } float getFreqPref() const { return _prefs.freq; } - uint8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } + int8_t getTxPowerPref() const { return _prefs.tx_power_dbm; } void begin(FILESYSTEM& fs) { _fs = &fs; diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index 8e27323e..f05fb245 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -815,7 +815,7 @@ void SensorMesh::updateFloodAdvertTimer() { } } -void SensorMesh::setTxPower(uint8_t power_dbm) { +void SensorMesh::setTxPower(int8_t power_dbm) { radio_set_tx_power(power_dbm); } diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ed352345..4bc0d784 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -66,7 +66,7 @@ public: void setLoggingOn(bool enable) override { } void eraseLogFile() override { } void dumpLogFile() override { } - void setTxPower(uint8_t power_dbm) override; + void setTxPower(int8_t power_dbm) override; void formatNeighborsReply(char *reply) override { strcpy(reply, "not supported"); } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 10ab8669..6dcf7018 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -92,7 +92,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->bw = constrain(_prefs->bw, 7.8f, 500.0f); _prefs->sf = constrain(_prefs->sf, 5, 12); _prefs->cr = constrain(_prefs->cr, 5, 8); - _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30); + _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); @@ -326,7 +326,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } *reply = 0; // set null terminator } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { - sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm); + sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { sprintf(reply, "> %s", StrHelper::ftoa(_prefs->freq)); } else if (memcmp(config, "public.key", 10) == 0) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 8661d1e6..146e1c6e 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -19,7 +19,7 @@ struct NodePrefs { // persisted to file double node_lat, node_lon; char password[16]; float freq; - uint8_t tx_power_dbm; + int8_t tx_power_dbm; uint8_t disable_fwd; uint8_t advert_interval; // minutes / 2 uint8_t flood_advert_interval; // hours @@ -67,7 +67,7 @@ public: virtual void setLoggingOn(bool enable) = 0; virtual void eraseLogFile() = 0; virtual void dumpLogFile() = 0; - virtual void setTxPower(uint8_t power_dbm) = 0; + virtual void setTxPower(int8_t power_dbm) = 0; virtual void formatNeighborsReply(char *reply) = 0; virtual void removeNeighbor(const uint8_t* pubkey, int key_len) { // no op by default diff --git a/variants/ebyte_eora_s3/target.cpp b/variants/ebyte_eora_s3/target.cpp index 647f5997..501f560b 100644 --- a/variants/ebyte_eora_s3/target.cpp +++ b/variants/ebyte_eora_s3/target.cpp @@ -75,7 +75,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ebyte_eora_s3/target.h b/variants/ebyte_eora_s3/target.h index f184c757..892c3de3 100644 --- a/variants/ebyte_eora_s3/target.h +++ b/variants/ebyte_eora_s3/target.h @@ -25,5 +25,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/generic-e22/target.cpp b/variants/generic-e22/target.cpp index e0253779..f76bb979 100644 --- a/variants/generic-e22/target.cpp +++ b/variants/generic-e22/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/generic-e22/target.h b/variants/generic-e22/target.h index 442706f3..5ad13054 100644 --- a/variants/generic-e22/target.h +++ b/variants/generic-e22/target.h @@ -17,5 +17,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/generic_espnow/target.cpp b/variants/generic_espnow/target.cpp index 6b5d4e44..f42085c0 100644 --- a/variants/generic_espnow/target.cpp +++ b/variants/generic_espnow/target.cpp @@ -25,7 +25,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { // no-op } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio_driver.setTxPower(dbm); } diff --git a/variants/generic_espnow/target.h b/variants/generic_espnow/target.h index 99b6f577..1ebd0837 100644 --- a/variants/generic_espnow/target.h +++ b/variants/generic_espnow/target.h @@ -12,5 +12,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_ct62/target.cpp b/variants/heltec_ct62/target.cpp index a8c15f5f..5cc621a1 100644 --- a/variants/heltec_ct62/target.cpp +++ b/variants/heltec_ct62/target.cpp @@ -27,7 +27,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_ct62/target.h b/variants/heltec_ct62/target.h index 9639ab2d..34130ae7 100644 --- a/variants/heltec_ct62/target.h +++ b/variants/heltec_ct62/target.h @@ -16,5 +16,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_e213/target.cpp b/variants/heltec_e213/target.cpp index 23561850..c9233431 100644 --- a/variants/heltec_e213/target.cpp +++ b/variants/heltec_e213/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_e213/target.h b/variants/heltec_e213/target.h index 9ecdc212..14969c0f 100644 --- a/variants/heltec_e213/target.h +++ b/variants/heltec_e213/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_e290/target.cpp b/variants/heltec_e290/target.cpp index 92b02092..b0c9630c 100644 --- a/variants/heltec_e290/target.cpp +++ b/variants/heltec_e290/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_e290/target.h b/variants/heltec_e290/target.h index 60770112..5d423fc0 100644 --- a/variants/heltec_e290/target.h +++ b/variants/heltec_e290/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index ad79f717..9852b68f 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index e301a273..f1921abf 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -42,5 +42,5 @@ extern SolarSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index c3341103..160d00b6 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -36,7 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_t114/target.h b/variants/heltec_t114/target.h index 6306cd69..187675e9 100644 --- a/variants/heltec_t114/target.h +++ b/variants/heltec_t114/target.h @@ -50,5 +50,5 @@ extern T114SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_t190/target.cpp b/variants/heltec_t190/target.cpp index b9357594..d22f8b8c 100644 --- a/variants/heltec_t190/target.cpp +++ b/variants/heltec_t190/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_t190/target.h b/variants/heltec_t190/target.h index 8a5fc716..83e03570 100644 --- a/variants/heltec_t190/target.h +++ b/variants/heltec_t190/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 5ba9a8fb..25c2634b 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -47,7 +47,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 23fab16e..5296fb2c 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -43,5 +43,5 @@ extern HWTSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_tracker_v2/target.cpp b/variants/heltec_tracker_v2/target.cpp index da397fb7..c2e26b20 100644 --- a/variants/heltec_tracker_v2/target.cpp +++ b/variants/heltec_tracker_v2/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_tracker_v2/target.h b/variants/heltec_tracker_v2/target.h index 190404ef..5b799e78 100644 --- a/variants/heltec_tracker_v2/target.h +++ b/variants/heltec_tracker_v2/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index df71d3f4..c5a04752 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -43,7 +43,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_v2/target.h b/variants/heltec_v2/target.h index 48d750be..788dac72 100644 --- a/variants/heltec_v2/target.h +++ b/variants/heltec_v2/target.h @@ -25,5 +25,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index 78b88197..cdd2535e 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_v3/target.h b/variants/heltec_v3/target.h index 739aecfe..21a209f9 100644 --- a/variants/heltec_v3/target.h +++ b/variants/heltec_v3/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 0d2bd497..f971cc60 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_v4/target.h b/variants/heltec_v4/target.h index 00d2adab..5016588d 100644 --- a/variants/heltec_v4/target.h +++ b/variants/heltec_v4/target.h @@ -30,5 +30,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_wireless_paper/target.cpp b/variants/heltec_wireless_paper/target.cpp index dd2d51c0..06f548fc 100644 --- a/variants/heltec_wireless_paper/target.cpp +++ b/variants/heltec_wireless_paper/target.cpp @@ -43,7 +43,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index b89c486f..65739e77 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -25,5 +25,5 @@ extern MomentaryButton user_btn; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp index efa6669f..48244e17 100644 --- a/variants/ikoka_handheld_nrf/target.cpp +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -36,7 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ikoka_handheld_nrf/target.h b/variants/ikoka_handheld_nrf/target.h index a28ca81a..d4af956e 100644 --- a/variants/ikoka_handheld_nrf/target.h +++ b/variants/ikoka_handheld_nrf/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_nano_nrf/target.cpp b/variants/ikoka_nano_nrf/target.cpp index aed59182..be20cfb4 100644 --- a/variants/ikoka_nano_nrf/target.cpp +++ b/variants/ikoka_nano_nrf/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ikoka_nano_nrf/target.h b/variants/ikoka_nano_nrf/target.h index 9b4e908e..7949ab63 100644 --- a/variants/ikoka_nano_nrf/target.h +++ b/variants/ikoka_nano_nrf/target.h @@ -24,5 +24,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index bd803399..4f6befc6 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/ikoka_stick_nrf/target.h b/variants/ikoka_stick_nrf/target.h index c276e89f..fab82592 100644 --- a/variants/ikoka_stick_nrf/target.h +++ b/variants/ikoka_stick_nrf/target.h @@ -24,5 +24,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp index e72abf08..e2e183a7 100644 --- a/variants/keepteen_lt1/target.cpp +++ b/variants/keepteen_lt1/target.cpp @@ -40,7 +40,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/keepteen_lt1/target.h b/variants/keepteen_lt1/target.h index 0f1aa756..f2468d34 100644 --- a/variants/keepteen_lt1/target.h +++ b/variants/keepteen_lt1/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index 1c7b3b09..28481188 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_t3s3/target.h b/variants/lilygo_t3s3/target.h index f184c757..892c3de3 100644 --- a/variants/lilygo_t3s3/target.h +++ b/variants/lilygo_t3s3/target.h @@ -25,5 +25,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_t3s3_sx1276/target.cpp b/variants/lilygo_t3s3_sx1276/target.cpp index 042ff206..e7fe07a0 100644 --- a/variants/lilygo_t3s3_sx1276/target.cpp +++ b/variants/lilygo_t3s3_sx1276/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_t3s3_sx1276/target.h b/variants/lilygo_t3s3_sx1276/target.h index 98a0fe35..2df4b3ed 100644 --- a/variants/lilygo_t3s3_sx1276/target.h +++ b/variants/lilygo_t3s3_sx1276/target.h @@ -25,5 +25,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp index fcdb42ed..8cb6bdfa 100644 --- a/variants/lilygo_tbeam_1w/target.cpp +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -54,7 +54,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_1w/target.h b/variants/lilygo_tbeam_1w/target.h index 2c3e8970..99a75031 100644 --- a/variants/lilygo_tbeam_1w/target.h +++ b/variants/lilygo_tbeam_1w/target.h @@ -23,5 +23,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index a8caecb3..f85049d7 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -45,7 +45,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_SX1262/target.h b/variants/lilygo_tbeam_SX1262/target.h index 5f33abb8..e5b3e445 100644 --- a/variants/lilygo_tbeam_SX1262/target.h +++ b/variants/lilygo_tbeam_SX1262/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_SX1276/target.cpp b/variants/lilygo_tbeam_SX1276/target.cpp index 0a7517a2..5fe82e11 100644 --- a/variants/lilygo_tbeam_SX1276/target.cpp +++ b/variants/lilygo_tbeam_SX1276/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_SX1276/target.h b/variants/lilygo_tbeam_SX1276/target.h index b382b652..cd4480dc 100644 --- a/variants/lilygo_tbeam_SX1276/target.h +++ b/variants/lilygo_tbeam_SX1276/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 8ad306f1..6fec6f58 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -42,7 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.h b/variants/lilygo_tbeam_supreme_SX1262/target.h index c6ffa0a6..200a5690 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.h +++ b/variants/lilygo_tbeam_supreme_SX1262/target.h @@ -23,5 +23,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index 50ffa735..731ecfd8 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -45,7 +45,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tdeck/target.h b/variants/lilygo_tdeck/target.h index 4640925f..c31d0d0f 100644 --- a/variants/lilygo_tdeck/target.h +++ b/variants/lilygo_tdeck/target.h @@ -27,5 +27,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/lilygo_techo/target.cpp b/variants/lilygo_techo/target.cpp index 2ebc0641..12d222ff 100644 --- a/variants/lilygo_techo/target.cpp +++ b/variants/lilygo_techo/target.cpp @@ -42,7 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_techo/target.h b/variants/lilygo_techo/target.h index 2b6ed45f..d978d522 100644 --- a/variants/lilygo_techo/target.h +++ b/variants/lilygo_techo/target.h @@ -27,5 +27,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp index 6979e347..40a94526 100644 --- a/variants/lilygo_techo_lite/target.cpp +++ b/variants/lilygo_techo_lite/target.cpp @@ -41,7 +41,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_techo_lite/target.h b/variants/lilygo_techo_lite/target.h index 2b6ed45f..d978d522 100644 --- a/variants/lilygo_techo_lite/target.h +++ b/variants/lilygo_techo_lite/target.h @@ -27,5 +27,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tlora_c6/target.cpp b/variants/lilygo_tlora_c6/target.cpp index e12c58b5..3566fbe4 100644 --- a/variants/lilygo_tlora_c6/target.cpp +++ b/variants/lilygo_tlora_c6/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tlora_c6/target.h b/variants/lilygo_tlora_c6/target.h index c26d5958..1cb52fbc 100644 --- a/variants/lilygo_tlora_c6/target.h +++ b/variants/lilygo_tlora_c6/target.h @@ -16,5 +16,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index 65a78c19..ead62e79 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/lilygo_tlora_v2_1/target.h b/variants/lilygo_tlora_v2_1/target.h index 326a0dee..cb7d861d 100644 --- a/variants/lilygo_tlora_v2_1/target.h +++ b/variants/lilygo_tlora_v2_1/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/mesh_pocket/target.cpp b/variants/mesh_pocket/target.cpp index a7f6c7fb..6fabb317 100644 --- a/variants/mesh_pocket/target.cpp +++ b/variants/mesh_pocket/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/mesh_pocket/target.h b/variants/mesh_pocket/target.h index 2aa95669..6ab5d9c2 100644 --- a/variants/mesh_pocket/target.h +++ b/variants/mesh_pocket/target.h @@ -26,7 +26,7 @@ extern AutoDiscoverRTCClock rtc_clock; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); extern SensorManager sensors; diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index 0e3b03f2..0edd4403 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -41,7 +41,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/meshadventurer/target.h b/variants/meshadventurer/target.h index 31bc4066..9d1ffca8 100644 --- a/variants/meshadventurer/target.h +++ b/variants/meshadventurer/target.h @@ -45,5 +45,5 @@ extern MASensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/meshtiny/target.cpp b/variants/meshtiny/target.cpp index 5fc60eae..9188db17 100644 --- a/variants/meshtiny/target.cpp +++ b/variants/meshtiny/target.cpp @@ -37,7 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/meshtiny/target.h b/variants/meshtiny/target.h index 8ee3ee86..31f8505d 100644 --- a/variants/meshtiny/target.h +++ b/variants/meshtiny/target.h @@ -29,5 +29,5 @@ extern MomentaryButton back_btn; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/minewsemi_me25ls01/target.cpp b/variants/minewsemi_me25ls01/target.cpp index 13306762..fcec1941 100644 --- a/variants/minewsemi_me25ls01/target.cpp +++ b/variants/minewsemi_me25ls01/target.cpp @@ -88,7 +88,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/minewsemi_me25ls01/target.h b/variants/minewsemi_me25ls01/target.h index a5da5823..ea7383e2 100644 --- a/variants/minewsemi_me25ls01/target.h +++ b/variants/minewsemi_me25ls01/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index 81e7744f..aad10c50 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -36,7 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index 3e58b900..6e354127 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -45,5 +45,5 @@ extern MomentaryButton user_btn; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/nibble_screen_connect/target.cpp b/variants/nibble_screen_connect/target.cpp index 1980e039..6edaaad7 100644 --- a/variants/nibble_screen_connect/target.cpp +++ b/variants/nibble_screen_connect/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/nibble_screen_connect/target.h b/variants/nibble_screen_connect/target.h index 66e69901..f31efb8d 100644 --- a/variants/nibble_screen_connect/target.h +++ b/variants/nibble_screen_connect/target.h @@ -25,6 +25,6 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index b26320e4..61eab91c 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -40,7 +40,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/promicro/target.h b/variants/promicro/target.h index 38c4b4e8..d379927e 100644 --- a/variants/promicro/target.h +++ b/variants/promicro/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak11310/target.cpp b/variants/rak11310/target.cpp index dba5bff2..67432998 100644 --- a/variants/rak11310/target.cpp +++ b/variants/rak11310/target.cpp @@ -29,7 +29,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak11310/target.h b/variants/rak11310/target.h index fe45c3f2..7c25cd90 100644 --- a/variants/rak11310/target.h +++ b/variants/rak11310/target.h @@ -16,5 +16,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3112/target.cpp b/variants/rak3112/target.cpp index 634573b8..6cddfce5 100644 --- a/variants/rak3112/target.cpp +++ b/variants/rak3112/target.cpp @@ -50,7 +50,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak3112/target.h b/variants/rak3112/target.h index eae90900..e7d85de9 100644 --- a/variants/rak3112/target.h +++ b/variants/rak3112/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index 52f3a3d5..ec4fc28c 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -48,7 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak3401/target.h b/variants/rak3401/target.h index 32f17cd1..bb7f5dc4 100644 --- a/variants/rak3401/target.h +++ b/variants/rak3401/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak3x72/target.cpp b/variants/rak3x72/target.cpp index 446783aa..48e7f422 100644 --- a/variants/rak3x72/target.cpp +++ b/variants/rak3x72/target.cpp @@ -66,7 +66,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak3x72/target.h b/variants/rak3x72/target.h index e0c1441e..3ba1cf42 100644 --- a/variants/rak3x72/target.h +++ b/variants/rak3x72/target.h @@ -52,5 +52,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index bc7465fd..ea6a2bd4 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -48,7 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak4631/target.h b/variants/rak4631/target.h index aa6be664..eeb3e094 100644 --- a/variants/rak4631/target.h +++ b/variants/rak4631/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rak_wismesh_tag/target.cpp b/variants/rak_wismesh_tag/target.cpp index 2bd30864..9646375e 100644 --- a/variants/rak_wismesh_tag/target.cpp +++ b/variants/rak_wismesh_tag/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rak_wismesh_tag/target.h b/variants/rak_wismesh_tag/target.h index 150d0831..a51b3092 100644 --- a/variants/rak_wismesh_tag/target.h +++ b/variants/rak_wismesh_tag/target.h @@ -23,5 +23,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/rpi_picow/target.cpp b/variants/rpi_picow/target.cpp index abb1485d..e3d4bf09 100644 --- a/variants/rpi_picow/target.cpp +++ b/variants/rpi_picow/target.cpp @@ -29,7 +29,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/rpi_picow/target.h b/variants/rpi_picow/target.h index 17dbb35f..706578a4 100644 --- a/variants/rpi_picow/target.h +++ b/variants/rpi_picow/target.h @@ -16,5 +16,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index efdaac61..6674c180 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -37,7 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { // no-op } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio_driver.setTxPower(dbm); } diff --git a/variants/sensecap_indicator-espnow/target.h b/variants/sensecap_indicator-espnow/target.h index bb78e923..a56dec7b 100644 --- a/variants/sensecap_indicator-espnow/target.h +++ b/variants/sensecap_indicator-espnow/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/sensecap_solar/target.cpp b/variants/sensecap_solar/target.cpp index 6bd7d31a..2c2ff0dc 100644 --- a/variants/sensecap_solar/target.cpp +++ b/variants/sensecap_solar/target.cpp @@ -29,7 +29,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/sensecap_solar/target.h b/variants/sensecap_solar/target.h index 90d60ba5..f4a98801 100644 --- a/variants/sensecap_solar/target.h +++ b/variants/sensecap_solar/target.h @@ -17,5 +17,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 3f0c1404..026b25de 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -51,7 +51,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/station_g2/target.h b/variants/station_g2/target.h index 2bf7016d..01428d58 100644 --- a/variants/station_g2/target.h +++ b/variants/station_g2/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 82d958b5..da8fa48b 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -85,7 +85,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 27351b94..d4e3c02c 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -43,5 +43,5 @@ extern T1000SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/tenstar_c3/target.cpp b/variants/tenstar_c3/target.cpp index a29780f0..d4f189b5 100644 --- a/variants/tenstar_c3/target.cpp +++ b/variants/tenstar_c3/target.cpp @@ -38,7 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/tenstar_c3/target.h b/variants/tenstar_c3/target.h index fa29e52b..e503564b 100644 --- a/variants/tenstar_c3/target.h +++ b/variants/tenstar_c3/target.h @@ -17,5 +17,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index c3b1abc2..ec2438d4 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -35,7 +35,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index 8425369d..92661d09 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -45,5 +45,5 @@ extern ThinkNodeM1SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m2/target.cpp b/variants/thinknode_m2/target.cpp index cb3c1624..e7e36d05 100644 --- a/variants/thinknode_m2/target.cpp +++ b/variants/thinknode_m2/target.cpp @@ -46,7 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m2/target.h b/variants/thinknode_m2/target.h index b05def8a..77ebbfde 100644 --- a/variants/thinknode_m2/target.h +++ b/variants/thinknode_m2/target.h @@ -26,7 +26,7 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index 91d186dc..ca2b0aa0 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -89,7 +89,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m3/target.h b/variants/thinknode_m3/target.h index 23e99581..4124761c 100644 --- a/variants/thinknode_m3/target.h +++ b/variants/thinknode_m3/target.h @@ -25,5 +25,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index 8208d2c4..a7a049ef 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -53,7 +53,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m5/target.h b/variants/thinknode_m5/target.h index 2af42095..a228cc9f 100644 --- a/variants/thinknode_m5/target.h +++ b/variants/thinknode_m5/target.h @@ -29,7 +29,7 @@ extern PCA9557 expander; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); \ No newline at end of file diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp index c14dd300..36ca8618 100644 --- a/variants/thinknode_m6/target.cpp +++ b/variants/thinknode_m6/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/thinknode_m6/target.h b/variants/thinknode_m6/target.h index 38b1fed1..fb129988 100644 --- a/variants/thinknode_m6/target.h +++ b/variants/thinknode_m6/target.h @@ -27,5 +27,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/tiny_relay/target.cpp b/variants/tiny_relay/target.cpp index f738ac17..313dfaa9 100644 --- a/variants/tiny_relay/target.cpp +++ b/variants/tiny_relay/target.cpp @@ -70,7 +70,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/tiny_relay/target.h b/variants/tiny_relay/target.h index 82747cdc..d1583712 100644 --- a/variants/tiny_relay/target.h +++ b/variants/tiny_relay/target.h @@ -55,5 +55,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/waveshare_rp2040_lora/target.cpp b/variants/waveshare_rp2040_lora/target.cpp index 7bc1d043..a9121b0c 100644 --- a/variants/waveshare_rp2040_lora/target.cpp +++ b/variants/waveshare_rp2040_lora/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/waveshare_rp2040_lora/target.h b/variants/waveshare_rp2040_lora/target.h index aed55893..fe1903de 100644 --- a/variants/waveshare_rp2040_lora/target.h +++ b/variants/waveshare_rp2040_lora/target.h @@ -17,5 +17,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio-e5-dev/target.cpp b/variants/wio-e5-dev/target.cpp index 42e900e4..3e59b6ce 100644 --- a/variants/wio-e5-dev/target.cpp +++ b/variants/wio-e5-dev/target.cpp @@ -63,7 +63,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio-e5-dev/target.h b/variants/wio-e5-dev/target.h index 5fdd0aba..1d1fc5cb 100644 --- a/variants/wio-e5-dev/target.h +++ b/variants/wio-e5-dev/target.h @@ -29,5 +29,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio-e5-mini/target.cpp b/variants/wio-e5-mini/target.cpp index 0e2358b8..2e95ad6d 100644 --- a/variants/wio-e5-mini/target.cpp +++ b/variants/wio-e5-mini/target.cpp @@ -61,7 +61,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio-e5-mini/target.h b/variants/wio-e5-mini/target.h index 921c38d3..a4e5fb60 100644 --- a/variants/wio-e5-mini/target.h +++ b/variants/wio-e5-mini/target.h @@ -60,5 +60,5 @@ extern WIOE5SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 64866de0..4575a76c 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -44,7 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio-tracker-l1/target.h b/variants/wio-tracker-l1/target.h index 97e575d8..e2347647 100644 --- a/variants/wio-tracker-l1/target.h +++ b/variants/wio-tracker-l1/target.h @@ -33,5 +33,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp index c659d708..457d5bda 100644 --- a/variants/wio_wm1110/target.cpp +++ b/variants/wio_wm1110/target.cpp @@ -81,7 +81,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/wio_wm1110/target.h b/variants/wio_wm1110/target.h index 9bd4a22b..8712a0ef 100644 --- a/variants/wio_wm1110/target.h +++ b/variants/wio_wm1110/target.h @@ -16,6 +16,6 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index fe3f7196..f8ee3d92 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -46,7 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_c3/target.h b/variants/xiao_c3/target.h index a7ef4421..57e3b81c 100644 --- a/variants/xiao_c3/target.h +++ b/variants/xiao_c3/target.h @@ -16,5 +16,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_c6/XiaoC6Board.cpp b/variants/xiao_c6/XiaoC6Board.cpp index 555fed62..5710c4cc 100644 --- a/variants/xiao_c6/XiaoC6Board.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_c6/target.h b/variants/xiao_c6/target.h index 0fbb0bb2..28b46538 100644 --- a/variants/xiao_c6/target.h +++ b/variants/xiao_c6/target.h @@ -17,5 +17,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index c9c02d21..a8f4162e 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -34,7 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_nrf52/target.h b/variants/xiao_nrf52/target.h index e1ea2a6b..f4076c34 100644 --- a/variants/xiao_nrf52/target.h +++ b/variants/xiao_nrf52/target.h @@ -22,5 +22,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_rp2040/target.cpp b/variants/xiao_rp2040/target.cpp index b7c19975..6c9a9143 100644 --- a/variants/xiao_rp2040/target.cpp +++ b/variants/xiao_rp2040/target.cpp @@ -39,7 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_rp2040/target.h b/variants/xiao_rp2040/target.h index 33b3766c..528c4441 100644 --- a/variants/xiao_rp2040/target.h +++ b/variants/xiao_rp2040/target.h @@ -17,5 +17,5 @@ extern SensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 26cd27ac..50981ab6 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -46,7 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setCodingRate(cr); } -void radio_set_tx_power(uint8_t dbm) { +void radio_set_tx_power(int8_t dbm) { radio.setOutputPower(dbm); } diff --git a/variants/xiao_s3_wio/target.h b/variants/xiao_s3_wio/target.h index c3227368..fffd1683 100644 --- a/variants/xiao_s3_wio/target.h +++ b/variants/xiao_s3_wio/target.h @@ -26,5 +26,5 @@ extern EnvironmentSensorManager sensors; bool radio_init(); uint32_t radio_get_rng_seed(); void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); -void radio_set_tx_power(uint8_t dbm); +void radio_set_tx_power(int8_t dbm); mesh::LocalIdentity radio_new_identity(); From 0b1fd580f12c1d16538ea0648efbe3cb7b7ebafc Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Fri, 6 Feb 2026 11:35:05 +0100 Subject: [PATCH 463/546] Fix double claim, eliminate dead code at compile time --- src/helpers/ui/SSD1306Display.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/helpers/ui/SSD1306Display.cpp b/src/helpers/ui/SSD1306Display.cpp index f585feb0..464b2642 100644 --- a/src/helpers/ui/SSD1306Display.cpp +++ b/src/helpers/ui/SSD1306Display.cpp @@ -19,11 +19,9 @@ bool SSD1306Display::begin() { void SSD1306Display::turnOn() { if (!_isOn) { - if (_peripher_power) { - _peripher_power->claim(); - begin(); - } - _isOn = true; + if (_peripher_power) _peripher_power->claim(); + _isOn = true; // set before begin() to prevent double claim + if (_peripher_power) begin(); // re-init display after power was cut } display.ssd1306_command(SSD1306_DISPLAYON); } @@ -32,7 +30,9 @@ void SSD1306Display::turnOff() { display.ssd1306_command(SSD1306_DISPLAYOFF); if (_isOn) { if (_peripher_power) { - if (PIN_OLED_RESET >= 0) digitalWrite(PIN_OLED_RESET, LOW); +#if PIN_OLED_RESET >= 0 + digitalWrite(PIN_OLED_RESET, LOW); +#endif _peripher_power->release(); } _isOn = false; From 5dcc377b775dd2bf192521c97bcf04f05f4bbe04 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 6 Feb 2026 11:07:10 +0100 Subject: [PATCH 464/546] Rewrite KISS modem to be fully spec-compliant --- docs/kiss_modem_protocol.md | 229 +++++++++------- examples/kiss_modem/KissModem.cpp | 425 ++++++++++++++++++----------- examples/kiss_modem/KissModem.h | 166 ++++++----- examples/kiss_modem/main.cpp | 54 ++-- variants/xiao_nrf52/platformio.ini | 11 +- 5 files changed, 533 insertions(+), 352 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 00b0bf90..e042053c 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -1,6 +1,6 @@ # MeshCore KISS Modem Protocol -Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore packets over LoRa and cryptographic operations using the modem's identity. +Standard KISS TNC firmware for MeshCore LoRa radios. Compatible with any KISS client (Direwolf, APRSdroid, YAAC, etc.) for sending and receiving raw packets. MeshCore-specific extensions (cryptography, radio configuration, telemetry) are available through the standard SetHardware (0x06) command. ## Serial Configuration @@ -8,7 +8,7 @@ Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore ## Frame Format -Standard KISS framing with byte stuffing. +Standard KISS framing per the KA9Q/K3MC specification. | Byte | Name | Description | |------|------|-------------| @@ -18,89 +18,146 @@ Standard KISS framing with byte stuffing. | `0xDD` | TFESC | Escaped FESC (FESC + TFESC = 0xDB) | ``` -┌──────┬─────────┬──────────────┬──────┐ -│ FEND │ Command │ Data (escaped)│ FEND │ -│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ -└──────┴─────────┴──────────────┴──────┘ +┌──────┬───────────┬──────────────┬──────┐ +│ FEND │ Type Byte │ Data (escaped)│ FEND │ +│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ +└──────┴───────────┴──────────────┴──────┘ ``` +### Type Byte + +The type byte is split into two nibbles: + +| Bits | Field | Description | +|------|-------|-------------| +| 7-4 | Port | Port number (0 for single-port TNC) | +| 3-0 | Command | Command number | + Maximum unescaped frame size: 512 bytes. -## Commands +## Standard KISS Commands -### Request Commands (Host → Modem) +### Host to TNC -| Command | Value | Data | -|---------|-------|------| -| `CMD_DATA` | `0x00` | Packet (2-255 bytes) | -| `CMD_GET_IDENTITY` | `0x01` | - | -| `CMD_GET_RANDOM` | `0x02` | Length (1 byte, 1-64) | -| `CMD_VERIFY_SIGNATURE` | `0x03` | PubKey (32) + Signature (64) + Data | -| `CMD_SIGN_DATA` | `0x04` | Data to sign | -| `CMD_ENCRYPT_DATA` | `0x05` | Key (32) + Plaintext | -| `CMD_DECRYPT_DATA` | `0x06` | Key (32) + MAC (2) + Ciphertext | -| `CMD_KEY_EXCHANGE` | `0x07` | Remote PubKey (32) | -| `CMD_HASH` | `0x08` | Data to hash | -| `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | -| `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | -| *reserved* | `0x0B` | *(not implemented)* | -| `CMD_GET_RADIO` | `0x0C` | - | -| `CMD_GET_TX_POWER` | `0x0D` | - | -| *reserved* | `0x0E` | *(not implemented)* | -| `CMD_GET_VERSION` | `0x0F` | - | -| `CMD_GET_CURRENT_RSSI` | `0x10` | - | -| `CMD_IS_CHANNEL_BUSY` | `0x11` | - | -| `CMD_GET_AIRTIME` | `0x12` | Packet length (1) | -| `CMD_GET_NOISE_FLOOR` | `0x13` | - | -| `CMD_GET_STATS` | `0x14` | - | -| `CMD_GET_BATTERY` | `0x15` | - | -| `CMD_PING` | `0x16` | - | -| `CMD_GET_SENSORS` | `0x17` | Permissions (1) | +| Command | Value | Data | Description | +|---------|-------|------|-------------| +| Data | `0x00` | Raw packet | Queue packet for transmission | +| TXDELAY | `0x01` | Delay (1 byte) | Transmitter keyup delay in 10ms units (default: 50 = 500ms) | +| Persistence | `0x02` | P (1 byte) | CSMA persistence parameter 0-255 (default: 63) | +| SlotTime | `0x03` | Interval (1 byte) | CSMA slot interval in 10ms units (default: 10 = 100ms) | +| TXtail | `0x04` | Delay (1 byte) | Post-TX hold time in 10ms units (default: 0) | +| FullDuplex | `0x05` | Mode (1 byte) | 0 = half duplex, nonzero = full duplex (default: 0) | +| SetHardware | `0x06` | Sub-command + data | MeshCore extensions (see below) | +| Return | `0xFF` | - | Exit KISS mode (no-op) | -### Response Commands (Modem → Host) +### TNC to Host -| Command | Value | Data | -|---------|-------|------| -| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | -| `RESP_IDENTITY` | `0x21` | PubKey (32) | -| `RESP_RANDOM` | `0x22` | Random bytes (1-64) | -| `RESP_VERIFY` | `0x23` | Result (1): 0x00=invalid, 0x01=valid | -| `RESP_SIGNATURE` | `0x24` | Signature (64) | -| `RESP_ENCRYPTED` | `0x25` | MAC (2) + Ciphertext | -| `RESP_DECRYPTED` | `0x26` | Plaintext | -| `RESP_SHARED_SECRET` | `0x27` | Shared secret (32) | -| `RESP_HASH` | `0x28` | SHA-256 hash (32) | -| `RESP_OK` | `0x29` | - | -| `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | -| `RESP_TX_POWER` | `0x2B` | Power dBm (1) | -| *reserved* | `0x2C` | *(not implemented)* | -| `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | -| `RESP_ERROR` | `0x2E` | Error code (1) | -| `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | -| `RESP_CURRENT_RSSI` | `0x30` | RSSI dBm (1, signed) | -| `RESP_CHANNEL_BUSY` | `0x31` | Result (1): 0x00=clear, 0x01=busy | -| `RESP_AIRTIME` | `0x32` | Milliseconds (4) | -| `RESP_NOISE_FLOOR` | `0x33` | dBm (2, signed) | -| `RESP_STATS` | `0x34` | RX (4) + TX (4) + Errors (4) | -| `RESP_BATTERY` | `0x35` | Millivolts (2) | -| `RESP_PONG` | `0x36` | - | -| `RESP_SENSORS` | `0x37` | CayenneLPP payload | +| Type | Value | Data | Description | +|------|-------|------|-------------| +| Data | `0x00` | Raw packet | Received packet from radio | -## Error Codes +Data frames carry raw packet data only, with no metadata prepended. + +### CSMA Behavior + +The TNC implements p-persistent CSMA for half-duplex operation: + +1. When a packet is queued, monitor carrier detect +2. When the channel clears, generate a random value 0-255 +3. If the value is less than or equal to P (Persistence), wait TXDELAY then transmit +4. Otherwise, wait SlotTime and repeat from step 1 + +In full-duplex mode, CSMA is bypassed and packets transmit after TXDELAY. + +## SetHardware Extensions (0x06) + +MeshCore-specific functionality uses the standard KISS SetHardware command. The first byte of SetHardware data is a sub-command. Standard KISS clients ignore these frames. + +### Frame Format + +``` +┌──────┬──────┬─────────────┬──────────────┬──────┐ +│ FEND │ 0x06 │ Sub-command │ Data (escaped)│ FEND │ +│ 0xC0 │ │ 1 byte │ variable │ 0xC0 │ +└──────┴──────┴─────────────┴──────────────┴──────┘ +``` + +### Request Sub-commands (Host to TNC) + +| Sub-command | Value | Data | +|-------------|-------|------| +| GetIdentity | `0x01` | - | +| GetRandom | `0x02` | Length (1 byte, 1-64) | +| VerifySignature | `0x03` | PubKey (32) + Signature (64) + Data | +| SignData | `0x04` | Data to sign | +| EncryptData | `0x05` | Key (32) + Plaintext | +| DecryptData | `0x06` | Key (32) + MAC (2) + Ciphertext | +| KeyExchange | `0x07` | Remote PubKey (32) | +| Hash | `0x08` | Data to hash | +| SetRadio | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | +| SetTxPower | `0x0A` | Power dBm (1) | +| GetRadio | `0x0C` | - | +| GetTxPower | `0x0D` | - | +| GetVersion | `0x0F` | - | +| GetCurrentRssi | `0x10` | - | +| IsChannelBusy | `0x11` | - | +| GetAirtime | `0x12` | Packet length (1) | +| GetNoiseFloor | `0x13` | - | +| GetStats | `0x14` | - | +| GetBattery | `0x15` | - | +| Ping | `0x16` | - | +| GetSensors | `0x17` | Permissions (1) | + +### Response Sub-commands (TNC to Host) + +| Sub-command | Value | Data | +|-------------|-------|------| +| Identity | `0x21` | PubKey (32) | +| Random | `0x22` | Random bytes (1-64) | +| Verify | `0x23` | Result (1): 0x00=invalid, 0x01=valid | +| Signature | `0x24` | Signature (64) | +| Encrypted | `0x25` | MAC (2) + Ciphertext | +| Decrypted | `0x26` | Plaintext | +| SharedSecret | `0x27` | Shared secret (32) | +| Hash | `0x28` | SHA-256 hash (32) | +| OK | `0x29` | - | +| Radio | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | +| TxPower | `0x2B` | Power dBm (1) | +| Version | `0x2D` | Version (1) + Reserved (1) | +| Error | `0x2E` | Error code (1) | +| TxDone | `0x2F` | Result (1): 0x00=failed, 0x01=success | +| CurrentRssi | `0x30` | RSSI dBm (1, signed) | +| ChannelBusy | `0x31` | Result (1): 0x00=clear, 0x01=busy | +| Airtime | `0x32` | Milliseconds (4) | +| NoiseFloor | `0x33` | dBm (2, signed) | +| Stats | `0x34` | RX (4) + TX (4) + Errors (4) | +| Battery | `0x35` | Millivolts (2) | +| Pong | `0x36` | - | +| Sensors | `0x37` | CayenneLPP payload | +| RxMeta | `0x38` | SNR (1) + RSSI (1) | + +### Error Codes | Code | Value | Description | |------|-------|-------------| -| `ERR_INVALID_LENGTH` | `0x01` | Request data too short | -| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | -| `ERR_NO_CALLBACK` | `0x03` | Feature not available | -| `ERR_MAC_FAILED` | `0x04` | MAC verification failed | -| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | -| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | -| `ERR_TX_PENDING` | `0x07` | TX already pending | +| InvalidLength | `0x01` | Request data too short | +| InvalidParam | `0x02` | Invalid parameter value | +| NoCallback | `0x03` | Feature not available | +| MacFailed | `0x04` | MAC verification failed | +| UnknownCmd | `0x05` | Unknown sub-command | +| EncryptFailed | `0x06` | Encryption failed | + +### Unsolicited Events + +The TNC sends these SetHardware frames without a preceding request: + +**TxDone (0x2F)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. + +**RxMeta (0x38)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. ## Data Formats -### Radio Parameters (CMD_SET_RADIO / RESP_RADIO) +### Radio Parameters (SetRadio / Radio response) All values little-endian. @@ -111,27 +168,9 @@ All values little-endian. | SF | 1 byte | Spreading factor (5-12) | | CR | 1 byte | Coding rate (5-8) | -### Received Packet (CMD_DATA response) +### Stats (Stats response) -| Field | Size | Description | -|-------|------|-------------| -| SNR | 1 byte | Signal-to-noise × 4, signed | -| RSSI | 1 byte | Signal strength dBm, signed | -| Packet | variable | Raw MeshCore packet | - -### Noise Floor (RESP_NOISE_FLOOR) - -Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. - -| Field | Size | Description | -|--------------|------|--------------------------------| -| Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | - -The modem recalibrates the noise floor every two seconds with an AGC reset every 30 seconds. - -### Stats (RESP_STATS) - -Response to `CMD_GET_STATS` (0x14). All values little-endian. +All values little-endian. | Field | Size | Description | |-------|------|-------------| @@ -139,7 +178,7 @@ Response to `CMD_GET_STATS` (0x14). All values little-endian. | TX | 4 bytes | Packets transmitted | | Errors | 4 bytes | Receive errors | -### Sensor Permissions (CMD_GET_SENSORS) +### Sensor Permissions (GetSensors) | Bit | Value | Description | |-----|-------|-------------| @@ -149,14 +188,14 @@ Response to `CMD_GET_STATS` (0x14). All values little-endian. Use `0x07` for all permissions. -### Sensor Data (RESP_SENSORS) +### Sensor Data (Sensors response) Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. ## Notes - Modem generates identity on first boot (stored in flash) -- SNR values multiplied by 4 for 0.25 dB precision -- Wait for `RESP_TX_DONE` before sending next packet -- Sending `CMD_DATA` while TX is pending returns `ERR_TX_PENDING` +- SNR values in RxMeta are multiplied by 4 for 0.25 dB precision +- TxDone is sent as a SetHardware event after each transmission +- Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames - See [packet_structure.md](./packet_structure.md) for packet format diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index d9c71bf8..9915ec5e 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -9,10 +9,20 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _rx_active = false; _has_pending_tx = false; _pending_tx_len = 0; + _txdelay = KISS_DEFAULT_TXDELAY; + _persistence = KISS_DEFAULT_PERSISTENCE; + _slottime = KISS_DEFAULT_SLOTTIME; + _txtail = 0; + _fullduplex = 0; + _tx_state = TX_IDLE; + _tx_timer = 0; _setRadioCallback = nullptr; _setTxPowerCallback = nullptr; _getCurrentRssiCallback = nullptr; _getStatsCallback = nullptr; + _sendPacketCallback = nullptr; + _isSendCompleteCallback = nullptr; + _onSendFinishedCallback = nullptr; _config = {0, 0, 0, 0, 0}; } @@ -21,6 +31,7 @@ void KissModem::begin() { _rx_escaped = false; _rx_active = false; _has_pending_tx = false; + _tx_state = TX_IDLE; } void KissModem::writeByte(uint8_t b) { @@ -35,23 +46,33 @@ void KissModem::writeByte(uint8_t b) { } } -void KissModem::writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len) { +void KissModem::writeFrame(uint8_t type, const uint8_t* data, uint16_t len) { _serial.write(KISS_FEND); - writeByte(cmd); + writeByte(type); for (uint16_t i = 0; i < len; i++) { writeByte(data[i]); } _serial.write(KISS_FEND); } -void KissModem::writeErrorFrame(uint8_t error_code) { - writeFrame(RESP_ERROR, &error_code, 1); +void KissModem::writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { + _serial.write(KISS_FEND); + writeByte(KISS_CMD_SETHARDWARE); + writeByte(sub_cmd); + for (uint16_t i = 0; i < len; i++) { + writeByte(data[i]); + } + _serial.write(KISS_FEND); +} + +void KissModem::writeHardwareError(uint8_t error_code) { + writeHardwareFrame(HW_RESP_ERROR, &error_code, 1); } void KissModem::loop() { while (_serial.available()) { uint8_t b = _serial.read(); - + if (b == KISS_FEND) { if (_rx_active && _rx_len > 0) { processFrame(); @@ -61,283 +82,368 @@ void KissModem::loop() { _rx_active = true; continue; } - + if (!_rx_active) continue; - + if (b == KISS_FESC) { _rx_escaped = true; continue; } - + if (_rx_escaped) { _rx_escaped = false; if (b == KISS_TFEND) b = KISS_FEND; else if (b == KISS_TFESC) b = KISS_FESC; + else continue; } - + if (_rx_len < KISS_MAX_FRAME_SIZE) { _rx_buf[_rx_len++] = b; } } + + processTx(); } void KissModem::processFrame() { if (_rx_len < 1) return; - - uint8_t cmd = _rx_buf[0]; + + uint8_t type_byte = _rx_buf[0]; + + if (type_byte == KISS_CMD_RETURN) return; + + uint8_t port = (type_byte >> 4) & 0x0F; + uint8_t cmd = type_byte & 0x0F; + + if (port != 0) return; + const uint8_t* data = &_rx_buf[1]; uint16_t data_len = _rx_len - 1; - + switch (cmd) { - case CMD_DATA: - if (data_len < 2) { - writeErrorFrame(ERR_INVALID_LENGTH); - } else if (data_len > KISS_MAX_PACKET_SIZE) { - writeErrorFrame(ERR_INVALID_LENGTH); - } else if (_has_pending_tx) { - writeErrorFrame(ERR_TX_PENDING); - } else { + case KISS_CMD_DATA: + if (data_len > 0 && data_len <= KISS_MAX_PACKET_SIZE && !_has_pending_tx) { memcpy(_pending_tx, data, data_len); _pending_tx_len = data_len; _has_pending_tx = true; } break; - case CMD_GET_IDENTITY: - handleGetIdentity(); + + case KISS_CMD_TXDELAY: + if (data_len >= 1) _txdelay = data[0]; break; - case CMD_GET_RANDOM: - handleGetRandom(data, data_len); + + case KISS_CMD_PERSISTENCE: + if (data_len >= 1) _persistence = data[0]; break; - case CMD_VERIFY_SIGNATURE: - handleVerifySignature(data, data_len); + + case KISS_CMD_SLOTTIME: + if (data_len >= 1) _slottime = data[0]; break; - case CMD_SIGN_DATA: - handleSignData(data, data_len); + + case KISS_CMD_TXTAIL: + if (data_len >= 1) _txtail = data[0]; break; - case CMD_ENCRYPT_DATA: - handleEncryptData(data, data_len); + + case KISS_CMD_FULLDUPLEX: + if (data_len >= 1) _fullduplex = data[0]; break; - case CMD_DECRYPT_DATA: - handleDecryptData(data, data_len); - break; - case CMD_KEY_EXCHANGE: - handleKeyExchange(data, data_len); - break; - case CMD_HASH: - handleHash(data, data_len); - break; - case CMD_SET_RADIO: - handleSetRadio(data, data_len); - break; - case CMD_SET_TX_POWER: - handleSetTxPower(data, data_len); - break; - case CMD_GET_RADIO: - handleGetRadio(); - break; - case CMD_GET_TX_POWER: - handleGetTxPower(); - break; - case CMD_GET_VERSION: - handleGetVersion(); - break; - case CMD_GET_CURRENT_RSSI: - handleGetCurrentRssi(); - break; - case CMD_IS_CHANNEL_BUSY: - handleIsChannelBusy(); - break; - case CMD_GET_AIRTIME: - handleGetAirtime(data, data_len); - break; - case CMD_GET_NOISE_FLOOR: - handleGetNoiseFloor(); - break; - case CMD_GET_STATS: - handleGetStats(); - break; - case CMD_GET_BATTERY: - handleGetBattery(); - break; - case CMD_PING: - handlePing(); - break; - case CMD_GET_SENSORS: - handleGetSensors(data, data_len); + + case KISS_CMD_SETHARDWARE: + if (data_len >= 1) { + handleHardwareCommand(data[0], data + 1, data_len - 1); + } break; + default: - writeErrorFrame(ERR_UNKNOWN_CMD); break; } } +void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len) { + switch (sub_cmd) { + case HW_CMD_GET_IDENTITY: + handleGetIdentity(); + break; + case HW_CMD_GET_RANDOM: + handleGetRandom(data, len); + break; + case HW_CMD_VERIFY_SIGNATURE: + handleVerifySignature(data, len); + break; + case HW_CMD_SIGN_DATA: + handleSignData(data, len); + break; + case HW_CMD_ENCRYPT_DATA: + handleEncryptData(data, len); + break; + case HW_CMD_DECRYPT_DATA: + handleDecryptData(data, len); + break; + case HW_CMD_KEY_EXCHANGE: + handleKeyExchange(data, len); + break; + case HW_CMD_HASH: + handleHash(data, len); + break; + case HW_CMD_SET_RADIO: + handleSetRadio(data, len); + break; + case HW_CMD_SET_TX_POWER: + handleSetTxPower(data, len); + break; + case HW_CMD_GET_RADIO: + handleGetRadio(); + break; + case HW_CMD_GET_TX_POWER: + handleGetTxPower(); + break; + case HW_CMD_GET_VERSION: + handleGetVersion(); + break; + case HW_CMD_GET_CURRENT_RSSI: + handleGetCurrentRssi(); + break; + case HW_CMD_IS_CHANNEL_BUSY: + handleIsChannelBusy(); + break; + case HW_CMD_GET_AIRTIME: + handleGetAirtime(data, len); + break; + case HW_CMD_GET_NOISE_FLOOR: + handleGetNoiseFloor(); + break; + case HW_CMD_GET_STATS: + handleGetStats(); + break; + case HW_CMD_GET_BATTERY: + handleGetBattery(); + break; + case HW_CMD_PING: + handlePing(); + break; + case HW_CMD_GET_SENSORS: + handleGetSensors(data, len); + break; + default: + writeHardwareError(HW_ERR_UNKNOWN_CMD); + break; + } +} + +void KissModem::processTx() { + switch (_tx_state) { + case TX_IDLE: + if (_has_pending_tx) { + if (_fullduplex) { + _tx_timer = millis(); + _tx_state = TX_DELAY; + } else { + _tx_state = TX_WAIT_CLEAR; + } + } + break; + + case TX_WAIT_CLEAR: + if (!_radio.isReceiving()) { + uint8_t rand_val; + _rng.random(&rand_val, 1); + if (rand_val <= _persistence) { + _tx_timer = millis(); + _tx_state = TX_DELAY; + } else { + _tx_timer = millis(); + _tx_state = TX_SLOT_WAIT; + } + } + break; + + case TX_SLOT_WAIT: + if (millis() - _tx_timer >= (uint32_t)_slottime * 10) { + _tx_state = TX_WAIT_CLEAR; + } + break; + + case TX_DELAY: + if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) { + if (_sendPacketCallback) { + _sendPacketCallback(_pending_tx, _pending_tx_len); + _tx_state = TX_SENDING; + } else { + _has_pending_tx = false; + _tx_state = TX_IDLE; + } + } + break; + + case TX_SENDING: + if (_isSendCompleteCallback && _isSendCompleteCallback()) { + if (_onSendFinishedCallback) _onSendFinishedCallback(); + uint8_t result = 0x01; + writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); + _has_pending_tx = false; + _tx_state = TX_IDLE; + } + break; + } +} + +void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { + writeFrame(KISS_CMD_DATA, packet, len); + uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; + writeHardwareFrame(HW_RESP_RX_META, meta, 2); +} + void KissModem::handleGetIdentity() { - writeFrame(RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); } void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t requested = data[0]; if (requested < 1 || requested > 64) { - writeErrorFrame(ERR_INVALID_PARAM); + writeHardwareError(HW_ERR_INVALID_PARAM); return; } - + uint8_t buf[64]; _rng.random(buf, requested); - writeFrame(RESP_RANDOM, buf, requested); + writeHardwareFrame(HW_RESP_RANDOM, buf, requested); } void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + mesh::Identity signer(data); const uint8_t* signature = data + PUB_KEY_SIZE; const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE; uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; - + uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; - writeFrame(RESP_VERIFY, &result, 1); + writeHardwareFrame(HW_RESP_VERIFY, &result, 1); } void KissModem::handleSignData(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t signature[SIGNATURE_SIZE]; _identity.sign(signature, data, len); - writeFrame(RESP_SIGNATURE, signature, SIGNATURE_SIZE); + writeHardwareFrame(HW_RESP_SIGNATURE, signature, SIGNATURE_SIZE); } void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + const uint8_t* key = data; const uint8_t* plaintext = data + PUB_KEY_SIZE; uint16_t plaintext_len = len - PUB_KEY_SIZE; - + uint8_t buf[KISS_MAX_FRAME_SIZE]; int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); - + if (encrypted_len > 0) { - writeFrame(RESP_ENCRYPTED, buf, encrypted_len); + writeHardwareFrame(HW_RESP_ENCRYPTED, buf, encrypted_len); } else { - writeErrorFrame(ERR_ENCRYPT_FAILED); + writeHardwareError(HW_ERR_ENCRYPT_FAILED); } } void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + const uint8_t* key = data; const uint8_t* ciphertext = data + PUB_KEY_SIZE; uint16_t ciphertext_len = len - PUB_KEY_SIZE; - + uint8_t buf[KISS_MAX_FRAME_SIZE]; int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); - + if (decrypted_len > 0) { - writeFrame(RESP_DECRYPTED, buf, decrypted_len); + writeHardwareFrame(HW_RESP_DECRYPTED, buf, decrypted_len); } else { - writeErrorFrame(ERR_MAC_FAILED); + writeHardwareError(HW_ERR_MAC_FAILED); } } void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { if (len < PUB_KEY_SIZE) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t shared_secret[PUB_KEY_SIZE]; _identity.calcSharedSecret(shared_secret, data); - writeFrame(RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); } void KissModem::handleHash(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t hash[32]; mesh::Utils::sha256(hash, 32, data, len); - writeFrame(RESP_HASH, hash, 32); -} - -bool KissModem::getPacketToSend(uint8_t* packet, uint16_t* len) { - if (!_has_pending_tx) return false; - - memcpy(packet, _pending_tx, _pending_tx_len); - *len = _pending_tx_len; - _has_pending_tx = false; - return true; -} - -void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { - uint8_t buf[2 + KISS_MAX_PACKET_SIZE]; - buf[0] = (uint8_t)snr; - buf[1] = (uint8_t)rssi; - memcpy(&buf[2], packet, len); - writeFrame(CMD_DATA, buf, 2 + len); + writeHardwareFrame(HW_RESP_HASH, hash, 32); } void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { if (len < 10) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } if (!_setRadioCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + uint32_t freq_hz, bw_hz; memcpy(&freq_hz, data, 4); memcpy(&bw_hz, data + 4, 4); uint8_t sf = data[8]; uint8_t cr = data[9]; - + _config.freq_hz = freq_hz; _config.bw_hz = bw_hz; _config.sf = sf; _config.cr = cr; - + float freq = freq_hz / 1000000.0f; float bw = bw_hz / 1000.0f; - + _setRadioCallback(freq, bw, sf, cr); - writeFrame(RESP_OK, nullptr, 0); + writeHardwareFrame(HW_RESP_OK, nullptr, 0); } void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } if (!_setTxPowerCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + _config.tx_power = data[0]; _setTxPowerCallback(data[0]); - writeFrame(RESP_OK, nullptr, 0); + writeHardwareFrame(HW_RESP_OK, nullptr, 0); } void KissModem::handleGetRadio() { @@ -346,92 +452,87 @@ void KissModem::handleGetRadio() { memcpy(buf + 4, &_config.bw_hz, 4); buf[8] = _config.sf; buf[9] = _config.cr; - writeFrame(RESP_RADIO, buf, 10); + writeHardwareFrame(HW_RESP_RADIO, buf, 10); } void KissModem::handleGetTxPower() { - writeFrame(RESP_TX_POWER, &_config.tx_power, 1); + writeHardwareFrame(HW_RESP_TX_POWER, &_config.tx_power, 1); } void KissModem::handleGetVersion() { uint8_t buf[2]; buf[0] = KISS_FIRMWARE_VERSION; buf[1] = 0; - writeFrame(RESP_VERSION, buf, 2); -} - -void KissModem::onTxComplete(bool success) { - uint8_t result = success ? 0x01 : 0x00; - writeFrame(RESP_TX_DONE, &result, 1); + writeHardwareFrame(HW_RESP_VERSION, buf, 2); } void KissModem::handleGetCurrentRssi() { if (!_getCurrentRssiCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + float rssi = _getCurrentRssiCallback(); int8_t rssi_byte = (int8_t)rssi; - writeFrame(RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); + writeHardwareFrame(HW_RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); } void KissModem::handleIsChannelBusy() { uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; - writeFrame(RESP_CHANNEL_BUSY, &busy, 1); + writeHardwareFrame(HW_RESP_CHANNEL_BUSY, &busy, 1); } void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t packet_len = data[0]; uint32_t airtime = _radio.getEstAirtimeFor(packet_len); - writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); + writeHardwareFrame(HW_RESP_AIRTIME, (uint8_t*)&airtime, 4); } void KissModem::handleGetNoiseFloor() { int16_t noise_floor = _radio.getNoiseFloor(); - writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); + writeHardwareFrame(HW_RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); } void KissModem::handleGetStats() { if (!_getStatsCallback) { - writeErrorFrame(ERR_NO_CALLBACK); + writeHardwareError(HW_ERR_NO_CALLBACK); return; } - + uint32_t rx, tx, errors; _getStatsCallback(&rx, &tx, &errors); uint8_t buf[12]; memcpy(buf, &rx, 4); memcpy(buf + 4, &tx, 4); memcpy(buf + 8, &errors, 4); - writeFrame(RESP_STATS, buf, 12); + writeHardwareFrame(HW_RESP_STATS, buf, 12); } void KissModem::handleGetBattery() { uint16_t mv = _board.getBattMilliVolts(); - writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); + writeHardwareFrame(HW_RESP_BATTERY, (uint8_t*)&mv, 2); } void KissModem::handlePing() { - writeFrame(RESP_PONG, nullptr, 0); + writeHardwareFrame(HW_RESP_PONG, nullptr, 0); } void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { if (len < 1) { - writeErrorFrame(ERR_INVALID_LENGTH); + writeHardwareError(HW_ERR_INVALID_LENGTH); return; } - + uint8_t permissions = data[0]; CayenneLPP telemetry(255); if (_sensors.querySensors(permissions, telemetry)) { - writeFrame(RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); + writeHardwareFrame(HW_RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); } else { - writeFrame(RESP_SENSORS, nullptr, 0); + writeHardwareFrame(HW_RESP_SENSORS, nullptr, 0); } } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 170bb0c2..98f3c300 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -11,62 +11,74 @@ #define KISS_TFEND 0xDC #define KISS_TFESC 0xDD -#define KISS_MAX_FRAME_SIZE 512 +#define KISS_MAX_FRAME_SIZE 512 #define KISS_MAX_PACKET_SIZE 255 -#define CMD_DATA 0x00 -#define CMD_GET_IDENTITY 0x01 -#define CMD_GET_RANDOM 0x02 -#define CMD_VERIFY_SIGNATURE 0x03 -#define CMD_SIGN_DATA 0x04 -#define CMD_ENCRYPT_DATA 0x05 -#define CMD_DECRYPT_DATA 0x06 -#define CMD_KEY_EXCHANGE 0x07 -#define CMD_HASH 0x08 -#define CMD_SET_RADIO 0x09 -#define CMD_SET_TX_POWER 0x0A -#define CMD_GET_RADIO 0x0C -#define CMD_GET_TX_POWER 0x0D -#define CMD_GET_VERSION 0x0F -#define CMD_GET_CURRENT_RSSI 0x10 -#define CMD_IS_CHANNEL_BUSY 0x11 -#define CMD_GET_AIRTIME 0x12 -#define CMD_GET_NOISE_FLOOR 0x13 -#define CMD_GET_STATS 0x14 -#define CMD_GET_BATTERY 0x15 -#define CMD_PING 0x16 -#define CMD_GET_SENSORS 0x17 +#define KISS_CMD_DATA 0x00 +#define KISS_CMD_TXDELAY 0x01 +#define KISS_CMD_PERSISTENCE 0x02 +#define KISS_CMD_SLOTTIME 0x03 +#define KISS_CMD_TXTAIL 0x04 +#define KISS_CMD_FULLDUPLEX 0x05 +#define KISS_CMD_SETHARDWARE 0x06 +#define KISS_CMD_RETURN 0xFF -#define RESP_IDENTITY 0x21 -#define RESP_RANDOM 0x22 -#define RESP_VERIFY 0x23 -#define RESP_SIGNATURE 0x24 -#define RESP_ENCRYPTED 0x25 -#define RESP_DECRYPTED 0x26 -#define RESP_SHARED_SECRET 0x27 -#define RESP_HASH 0x28 -#define RESP_OK 0x29 -#define RESP_RADIO 0x2A -#define RESP_TX_POWER 0x2B -#define RESP_VERSION 0x2D -#define RESP_ERROR 0x2E -#define RESP_TX_DONE 0x2F -#define RESP_CURRENT_RSSI 0x30 -#define RESP_CHANNEL_BUSY 0x31 -#define RESP_AIRTIME 0x32 -#define RESP_NOISE_FLOOR 0x33 -#define RESP_STATS 0x34 -#define RESP_BATTERY 0x35 -#define RESP_PONG 0x36 -#define RESP_SENSORS 0x37 +#define KISS_DEFAULT_TXDELAY 50 +#define KISS_DEFAULT_PERSISTENCE 63 +#define KISS_DEFAULT_SLOTTIME 10 -#define ERR_INVALID_LENGTH 0x01 -#define ERR_INVALID_PARAM 0x02 -#define ERR_NO_CALLBACK 0x03 -#define ERR_MAC_FAILED 0x04 -#define ERR_UNKNOWN_CMD 0x05 -#define ERR_ENCRYPT_FAILED 0x06 -#define ERR_TX_PENDING 0x07 +#define HW_CMD_GET_IDENTITY 0x01 +#define HW_CMD_GET_RANDOM 0x02 +#define HW_CMD_VERIFY_SIGNATURE 0x03 +#define HW_CMD_SIGN_DATA 0x04 +#define HW_CMD_ENCRYPT_DATA 0x05 +#define HW_CMD_DECRYPT_DATA 0x06 +#define HW_CMD_KEY_EXCHANGE 0x07 +#define HW_CMD_HASH 0x08 +#define HW_CMD_SET_RADIO 0x09 +#define HW_CMD_SET_TX_POWER 0x0A +#define HW_CMD_GET_RADIO 0x0C +#define HW_CMD_GET_TX_POWER 0x0D +#define HW_CMD_GET_VERSION 0x0F +#define HW_CMD_GET_CURRENT_RSSI 0x10 +#define HW_CMD_IS_CHANNEL_BUSY 0x11 +#define HW_CMD_GET_AIRTIME 0x12 +#define HW_CMD_GET_NOISE_FLOOR 0x13 +#define HW_CMD_GET_STATS 0x14 +#define HW_CMD_GET_BATTERY 0x15 +#define HW_CMD_PING 0x16 +#define HW_CMD_GET_SENSORS 0x17 + +#define HW_RESP_IDENTITY 0x21 +#define HW_RESP_RANDOM 0x22 +#define HW_RESP_VERIFY 0x23 +#define HW_RESP_SIGNATURE 0x24 +#define HW_RESP_ENCRYPTED 0x25 +#define HW_RESP_DECRYPTED 0x26 +#define HW_RESP_SHARED_SECRET 0x27 +#define HW_RESP_HASH 0x28 +#define HW_RESP_OK 0x29 +#define HW_RESP_RADIO 0x2A +#define HW_RESP_TX_POWER 0x2B +#define HW_RESP_VERSION 0x2D +#define HW_RESP_ERROR 0x2E +#define HW_RESP_TX_DONE 0x2F +#define HW_RESP_CURRENT_RSSI 0x30 +#define HW_RESP_CHANNEL_BUSY 0x31 +#define HW_RESP_AIRTIME 0x32 +#define HW_RESP_NOISE_FLOOR 0x33 +#define HW_RESP_STATS 0x34 +#define HW_RESP_BATTERY 0x35 +#define HW_RESP_PONG 0x36 +#define HW_RESP_SENSORS 0x37 +#define HW_RESP_RX_META 0x38 + +#define HW_ERR_INVALID_LENGTH 0x01 +#define HW_ERR_INVALID_PARAM 0x02 +#define HW_ERR_NO_CALLBACK 0x03 +#define HW_ERR_MAC_FAILED 0x04 +#define HW_ERR_UNKNOWN_CMD 0x05 +#define HW_ERR_ENCRYPT_FAILED 0x06 #define KISS_FIRMWARE_VERSION 1 @@ -74,6 +86,9 @@ typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef float (*GetCurrentRssiCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); +typedef void (*SendPacketCallback)(const uint8_t* data, uint16_t len); +typedef bool (*IsSendCompleteCallback)(); +typedef void (*OnSendFinishedCallback)(); struct RadioConfig { uint32_t freq_hz; @@ -83,6 +98,14 @@ struct RadioConfig { uint8_t tx_power; }; +enum TxState { + TX_IDLE, + TX_WAIT_CLEAR, + TX_SLOT_WAIT, + TX_DELAY, + TX_SENDING +}; + class KissModem { Stream& _serial; mesh::LocalIdentity& _identity; @@ -90,28 +113,43 @@ class KissModem { mesh::Radio& _radio; mesh::MainBoard& _board; SensorManager& _sensors; - + uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; uint16_t _rx_len; bool _rx_escaped; bool _rx_active; - + uint8_t _pending_tx[KISS_MAX_PACKET_SIZE]; uint16_t _pending_tx_len; bool _has_pending_tx; + uint8_t _txdelay; + uint8_t _persistence; + uint8_t _slottime; + uint8_t _txtail; + uint8_t _fullduplex; + + TxState _tx_state; + uint32_t _tx_timer; + SetRadioCallback _setRadioCallback; SetTxPowerCallback _setTxPowerCallback; GetCurrentRssiCallback _getCurrentRssiCallback; GetStatsCallback _getStatsCallback; - + SendPacketCallback _sendPacketCallback; + IsSendCompleteCallback _isSendCompleteCallback; + OnSendFinishedCallback _onSendFinishedCallback; + RadioConfig _config; void writeByte(uint8_t b); - void writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len); - void writeErrorFrame(uint8_t error_code); + void writeFrame(uint8_t type, const uint8_t* data, uint16_t len); + void writeHardwareFrame(uint8_t sub_cmd, const uint8_t* data, uint16_t len); + void writeHardwareError(uint8_t error_code); void processFrame(); - + void handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint16_t len); + void processTx(); + void handleGetIdentity(); void handleGetRandom(const uint8_t* data, uint16_t len); void handleVerifySignature(const uint8_t* data, uint16_t len); @@ -137,16 +175,18 @@ class KissModem { public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors); - + void begin(); void loop(); - + void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } - - bool getPacketToSend(uint8_t* packet, uint16_t* len); + void setSendPacketCallback(SendPacketCallback cb) { _sendPacketCallback = cb; } + void setIsSendCompleteCallback(IsSendCompleteCallback cb) { _isSendCompleteCallback = cb; } + void setOnSendFinishedCallback(OnSendFinishedCallback cb) { _onSendFinishedCallback = cb; } + void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); - void onTxComplete(bool success); + bool isTxBusy() const { return _tx_state != TX_IDLE; } }; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 3a610460..160adc69 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,14 +12,9 @@ #include <SPIFFS.h> #endif -#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 -#define AGC_RESET_INTERVAL_MS 30000 - StdRNG rng; mesh::LocalIdentity identity; KissModem* modem; -static uint32_t next_noise_floor_calib_ms = 0; -static uint32_t next_agc_reset_ms = 0; void halt() { while (1) ; @@ -67,6 +62,18 @@ void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *errors = radio_driver.getPacketsRecvErrors(); } +void onSendPacket(const uint8_t* data, uint16_t len) { + radio_driver.startSendRaw(data, len); +} + +bool onIsSendComplete() { + return radio_driver.isSendComplete(); +} + +void onSendFinished() { + radio_driver.onSendFinished(); +} + void setup() { board.begin(); @@ -91,40 +98,25 @@ void setup() { modem->setTxPowerCallback(onSetTxPower); modem->setGetCurrentRssiCallback(onGetCurrentRssi); modem->setGetStatsCallback(onGetStats); + modem->setSendPacketCallback(onSendPacket); + modem->setIsSendCompleteCallback(onIsSendComplete); + modem->setOnSendFinishedCallback(onSendFinished); modem->begin(); } void loop() { modem->loop(); - uint8_t packet[KISS_MAX_PACKET_SIZE]; - uint16_t len; + if (!modem->isTxBusy()) { + uint8_t rx_buf[256]; + int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - // trigger noise floor calibration - if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { - radio_driver.triggerNoiseFloorCalibrate(0); - next_noise_floor_calib_ms = millis(); - } - radio_driver.loop(); - - if (modem->getPacketToSend(packet, &len)) { - radio_driver.startSendRaw(packet, len); - while (!radio_driver.isSendComplete()) { - delay(1); + if (rx_len > 0) { + int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); + int8_t rssi = (int8_t)radio_driver.getLastRSSI(); + modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } - radio_driver.onSendFinished(); - modem->onTxComplete(true); } - if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { - radio_driver.resetAGC(); - next_agc_reset_ms = millis(); - } - uint8_t rx_buf[256]; - int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - if (rx_len > 0) { - int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); - int8_t rssi = (int8_t)radio_driver.getLastRSSI(); - modem->onPacketReceived(snr, rssi, rx_buf, rx_len); - } + radio_driver.loop(); } diff --git a/variants/xiao_nrf52/platformio.ini b/variants/xiao_nrf52/platformio.ini index 6e96018b..fe2f546e 100644 --- a/variants/xiao_nrf52/platformio.ini +++ b/variants/xiao_nrf52/platformio.ini @@ -107,4 +107,13 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Xiao_nrf52.build_src_filter} - +<../examples/simple_room_server/*.cpp> \ No newline at end of file + +<../examples/simple_room_server/*.cpp> + +[env:Xiao_nrf52_kiss_modem] +extends = Xiao_nrf52 +build_flags = + ${Xiao_nrf52.build_flags} +build_src_filter = ${Xiao_nrf52.build_src_filter} + +<../examples/kiss_modem/*.cpp> +lib_deps = + ${Xiao_nrf52.lib_deps} \ No newline at end of file From f78617dbdb9d2c8332f59633509685eebd80f9a6 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 6 Feb 2026 11:13:28 +0100 Subject: [PATCH 465/546] Add periodic noise floor calibration and AGC reset --- examples/kiss_modem/main.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 160adc69..62c1658f 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -12,9 +12,14 @@ #include <SPIFFS.h> #endif +#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 +#define AGC_RESET_INTERVAL_MS 30000 + StdRNG rng; mesh::LocalIdentity identity; KissModem* modem; +static uint32_t next_noise_floor_calib_ms = 0; +static uint32_t next_agc_reset_ms = 0; void halt() { while (1) ; @@ -107,16 +112,24 @@ void setup() { void loop() { modem->loop(); + if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { + radio_driver.triggerNoiseFloorCalibrate(0); + next_noise_floor_calib_ms = millis(); + } + radio_driver.loop(); + if (!modem->isTxBusy()) { + if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { + radio_driver.resetAGC(); + next_agc_reset_ms = millis(); + } + uint8_t rx_buf[256]; int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); - if (rx_len > 0) { int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } } - - radio_driver.loop(); } From 203d86f87d7ccbd40997050e57af8815ae55917c Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 6 Feb 2026 11:19:16 +0100 Subject: [PATCH 466/546] Update documentation. --- docs/kiss_modem_protocol.md | 50 +++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index e042053c..9d8c31c6 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -168,6 +168,38 @@ All values little-endian. | SF | 1 byte | Spreading factor (5-12) | | CR | 1 byte | Coding rate (5-8) | +### Version (Version response) + +| Field | Size | Description | +|-------|------|-------------| +| Version | 1 byte | Firmware version | +| Reserved | 1 byte | Always 0 | + +### Encrypted (Encrypted response) + +| Field | Size | Description | +|-------|------|-------------| +| MAC | 2 bytes | HMAC-SHA256 truncated to 2 bytes | +| Ciphertext | variable | AES-128-CBC encrypted data | + +### Airtime (Airtime response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Airtime | 4 bytes | uint32_t, estimated air time in milliseconds | + +### Noise Floor (NoiseFloor response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Noise floor | 2 bytes | int16_t, dBm (signed) | + +The modem recalibrates the noise floor every 2 seconds with an AGC reset every 30 seconds. + ### Stats (Stats response) All values little-endian. @@ -178,6 +210,14 @@ All values little-endian. | TX | 4 bytes | Packets transmitted | | Errors | 4 bytes | Receive errors | +### Battery (Battery response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Millivolts | 2 bytes | uint16_t, battery voltage in mV | + ### Sensor Permissions (GetSensors) | Bit | Value | Description | @@ -192,9 +232,19 @@ Use `0x07` for all permissions. Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. +## Cryptographic Algorithms + +| Operation | Algorithm | +|-----------|-----------| +| Identity / Signing / Verification | Ed25519 | +| Key Exchange | X25519 (ECDH) | +| Encryption | AES-128-CBC + HMAC-SHA256 (MAC truncated to 2 bytes) | +| Hashing | SHA-256 | + ## Notes - Modem generates identity on first boot (stored in flash) +- All multi-byte values are little-endian unless stated otherwise - SNR values in RxMeta are multiplied by 4 for 0.25 dB precision - TxDone is sent as a SetHardware event after each transmission - Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames From 02ddc05c30e781917863e26f68920839e9cf77bb Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 6 Feb 2026 11:43:59 +0100 Subject: [PATCH 467/546] Reorganise KISS protocol to close gaps. --- docs/kiss_modem_protocol.md | 79 ++++++++++++++++++++----------- examples/kiss_modem/KissModem.cpp | 31 ++++++++++++ examples/kiss_modem/KissModem.h | 58 +++++++++++++---------- 3 files changed, 116 insertions(+), 52 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 9d8c31c6..55e89129 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -96,17 +96,20 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The | Hash | `0x08` | Data to hash | | SetRadio | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | | SetTxPower | `0x0A` | Power dBm (1) | -| GetRadio | `0x0C` | - | -| GetTxPower | `0x0D` | - | -| GetVersion | `0x0F` | - | -| GetCurrentRssi | `0x10` | - | -| IsChannelBusy | `0x11` | - | -| GetAirtime | `0x12` | Packet length (1) | -| GetNoiseFloor | `0x13` | - | -| GetStats | `0x14` | - | -| GetBattery | `0x15` | - | -| Ping | `0x16` | - | -| GetSensors | `0x17` | Permissions (1) | +| GetRadio | `0x0B` | - | +| GetTxPower | `0x0C` | - | +| GetCurrentRssi | `0x0D` | - | +| IsChannelBusy | `0x0E` | - | +| GetAirtime | `0x0F` | Packet length (1) | +| GetNoiseFloor | `0x10` | - | +| GetVersion | `0x11` | - | +| GetStats | `0x12` | - | +| GetBattery | `0x13` | - | +| GetMCUTemp | `0x14` | - | +| GetSensors | `0x15` | Permissions (1) | +| GetDeviceName | `0x16` | - | +| Ping | `0x17` | - | +| Reboot | `0x18` | - | ### Response Sub-commands (TNC to Host) @@ -121,20 +124,22 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The | SharedSecret | `0x27` | Shared secret (32) | | Hash | `0x28` | SHA-256 hash (32) | | OK | `0x29` | - | -| Radio | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | -| TxPower | `0x2B` | Power dBm (1) | -| Version | `0x2D` | Version (1) + Reserved (1) | -| Error | `0x2E` | Error code (1) | -| TxDone | `0x2F` | Result (1): 0x00=failed, 0x01=success | -| CurrentRssi | `0x30` | RSSI dBm (1, signed) | -| ChannelBusy | `0x31` | Result (1): 0x00=clear, 0x01=busy | -| Airtime | `0x32` | Milliseconds (4) | -| NoiseFloor | `0x33` | dBm (2, signed) | -| Stats | `0x34` | RX (4) + TX (4) + Errors (4) | -| Battery | `0x35` | Millivolts (2) | -| Pong | `0x36` | - | -| Sensors | `0x37` | CayenneLPP payload | -| RxMeta | `0x38` | SNR (1) + RSSI (1) | +| Error | `0x2A` | Error code (1) | +| Radio | `0x2B` | Freq (4) + BW (4) + SF (1) + CR (1) | +| TxPower | `0x2C` | Power dBm (1) | +| CurrentRssi | `0x2D` | RSSI dBm (1, signed) | +| ChannelBusy | `0x2E` | Result (1): 0x00=clear, 0x01=busy | +| Airtime | `0x2F` | Milliseconds (4) | +| NoiseFloor | `0x30` | dBm (2, signed) | +| Version | `0x31` | Version (1) + Reserved (1) | +| Stats | `0x32` | RX (4) + TX (4) + Errors (4) | +| Battery | `0x33` | Millivolts (2) | +| MCUTemp | `0x34` | Temperature (2, signed) | +| Sensors | `0x35` | CayenneLPP payload | +| DeviceName | `0x36` | Name (variable, UTF-8) | +| Pong | `0x37` | - | +| TxDone | `0x38` | Result (1): 0x00=failed, 0x01=success | +| RxMeta | `0x39` | SNR (1) + RSSI (1) | ### Error Codes @@ -151,9 +156,9 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The The TNC sends these SetHardware frames without a preceding request: -**TxDone (0x2F)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. +**TxDone (0x38)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. -**RxMeta (0x38)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. +**RxMeta (0x39)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. ## Data Formats @@ -218,6 +223,26 @@ All values little-endian. |-------|------|-------------| | Millivolts | 2 bytes | uint16_t, battery voltage in mV | +### MCU Temperature (MCUTemp response) + +All values little-endian. + +| Field | Size | Description | +|-------|------|-------------| +| Temperature | 2 bytes | int16_t, tenths of °C (e.g., 253 = 25.3°C) | + +Returns `NoCallback` error if the board does not support temperature readings. + +### Device Name (DeviceName response) + +| Field | Size | Description | +|-------|------|-------------| +| Name | variable | UTF-8 string, no null terminator | + +### Reboot + +Sends an `OK` response, flushes serial, then reboots the device. The host should expect the connection to drop. + ### Sensor Permissions (GetSensors) | Bit | Value | Description | diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 9915ec5e..31bb8a1e 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -225,6 +225,15 @@ void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint case HW_CMD_GET_SENSORS: handleGetSensors(data, len); break; + case HW_CMD_GET_MCU_TEMP: + handleGetMCUTemp(); + break; + case HW_CMD_REBOOT: + handleReboot(); + break; + case HW_CMD_GET_DEVICE_NAME: + handleGetDeviceName(); + break; default: writeHardwareError(HW_ERR_UNKNOWN_CMD); break; @@ -536,3 +545,25 @@ void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { writeHardwareFrame(HW_RESP_SENSORS, nullptr, 0); } } + +void KissModem::handleGetMCUTemp() { + float temp = _board.getMCUTemperature(); + if (isnan(temp)) { + writeHardwareError(HW_ERR_NO_CALLBACK); + return; + } + int16_t temp_tenths = (int16_t)(temp * 10.0f); + writeHardwareFrame(HW_RESP_MCU_TEMP, (uint8_t*)&temp_tenths, 2); +} + +void KissModem::handleReboot() { + writeHardwareFrame(HW_RESP_OK, nullptr, 0); + _serial.flush(); + delay(50); + _board.reboot(); +} + +void KissModem::handleGetDeviceName() { + const char* name = _board.getManufacturerName(); + writeHardwareFrame(HW_RESP_DEVICE_NAME, (const uint8_t*)name, strlen(name)); +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 98f3c300..6b56b91e 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -37,17 +37,20 @@ #define HW_CMD_HASH 0x08 #define HW_CMD_SET_RADIO 0x09 #define HW_CMD_SET_TX_POWER 0x0A -#define HW_CMD_GET_RADIO 0x0C -#define HW_CMD_GET_TX_POWER 0x0D -#define HW_CMD_GET_VERSION 0x0F -#define HW_CMD_GET_CURRENT_RSSI 0x10 -#define HW_CMD_IS_CHANNEL_BUSY 0x11 -#define HW_CMD_GET_AIRTIME 0x12 -#define HW_CMD_GET_NOISE_FLOOR 0x13 -#define HW_CMD_GET_STATS 0x14 -#define HW_CMD_GET_BATTERY 0x15 -#define HW_CMD_PING 0x16 -#define HW_CMD_GET_SENSORS 0x17 +#define HW_CMD_GET_RADIO 0x0B +#define HW_CMD_GET_TX_POWER 0x0C +#define HW_CMD_GET_CURRENT_RSSI 0x0D +#define HW_CMD_IS_CHANNEL_BUSY 0x0E +#define HW_CMD_GET_AIRTIME 0x0F +#define HW_CMD_GET_NOISE_FLOOR 0x10 +#define HW_CMD_GET_VERSION 0x11 +#define HW_CMD_GET_STATS 0x12 +#define HW_CMD_GET_BATTERY 0x13 +#define HW_CMD_GET_MCU_TEMP 0x14 +#define HW_CMD_GET_SENSORS 0x15 +#define HW_CMD_GET_DEVICE_NAME 0x16 +#define HW_CMD_PING 0x17 +#define HW_CMD_REBOOT 0x18 #define HW_RESP_IDENTITY 0x21 #define HW_RESP_RANDOM 0x22 @@ -58,20 +61,22 @@ #define HW_RESP_SHARED_SECRET 0x27 #define HW_RESP_HASH 0x28 #define HW_RESP_OK 0x29 -#define HW_RESP_RADIO 0x2A -#define HW_RESP_TX_POWER 0x2B -#define HW_RESP_VERSION 0x2D -#define HW_RESP_ERROR 0x2E -#define HW_RESP_TX_DONE 0x2F -#define HW_RESP_CURRENT_RSSI 0x30 -#define HW_RESP_CHANNEL_BUSY 0x31 -#define HW_RESP_AIRTIME 0x32 -#define HW_RESP_NOISE_FLOOR 0x33 -#define HW_RESP_STATS 0x34 -#define HW_RESP_BATTERY 0x35 -#define HW_RESP_PONG 0x36 -#define HW_RESP_SENSORS 0x37 -#define HW_RESP_RX_META 0x38 +#define HW_RESP_ERROR 0x2A +#define HW_RESP_RADIO 0x2B +#define HW_RESP_TX_POWER 0x2C +#define HW_RESP_CURRENT_RSSI 0x2D +#define HW_RESP_CHANNEL_BUSY 0x2E +#define HW_RESP_AIRTIME 0x2F +#define HW_RESP_NOISE_FLOOR 0x30 +#define HW_RESP_VERSION 0x31 +#define HW_RESP_STATS 0x32 +#define HW_RESP_BATTERY 0x33 +#define HW_RESP_MCU_TEMP 0x34 +#define HW_RESP_SENSORS 0x35 +#define HW_RESP_DEVICE_NAME 0x36 +#define HW_RESP_PONG 0x37 +#define HW_RESP_TX_DONE 0x38 +#define HW_RESP_RX_META 0x39 #define HW_ERR_INVALID_LENGTH 0x01 #define HW_ERR_INVALID_PARAM 0x02 @@ -171,6 +176,9 @@ class KissModem { void handleGetBattery(); void handlePing(); void handleGetSensors(const uint8_t* data, uint16_t len); + void handleGetMCUTemp(); + void handleReboot(); + void handleGetDeviceName(); public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, From 1af013c741f1461d3179162ce21ab2f6f6a26721 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 6 Feb 2026 12:36:13 +0100 Subject: [PATCH 468/546] Clarify data frame limitations in KISS modem documentation. --- docs/kiss_modem_protocol.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 55e89129..9652f976 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -56,7 +56,7 @@ Maximum unescaped frame size: 512 bytes. |------|-------|------|-------------| | Data | `0x00` | Raw packet | Received packet from radio | -Data frames carry raw packet data only, with no metadata prepended. +Data frames carry raw packet data only, with no metadata prepended. The Data command payload is limited to 255 bytes to match the MeshCore maximum transmission unit (MAX_TRANS_UNIT); frames larger than 255 bytes are silently dropped. The KISS specification recommends at least 1024 bytes for general-purpose TNCs; this modem is intended for MeshCore packets only, whose protocol MTU is 255 bytes. ### CSMA Behavior @@ -268,6 +268,7 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs. ## Notes +- Data payload limit (255 bytes) matches MeshCore MAX_TRANS_UNIT; no change needed for KISS “1024+ recommended” (that applies to general TNCs, not MeshCore) - Modem generates identity on first boot (stored in flash) - All multi-byte values are little-endian unless stated otherwise - SNR values in RxMeta are multiplied by 4 for 0.25 dB precision From f445b5acdc17ee6664cee77d5b113e2e54412eb2 Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Fri, 6 Feb 2026 16:31:20 -0800 Subject: [PATCH 469/546] fix(kiss_modem): improve RX delivery and noise floor sampling --- examples/kiss_modem/KissModem.cpp | 5 +++++ examples/kiss_modem/KissModem.h | 2 ++ examples/kiss_modem/main.cpp | 32 +++++++++++++++++++++---------- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 31bb8a1e..94e4fe83 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -99,6 +99,11 @@ void KissModem::loop() { if (_rx_len < KISS_MAX_FRAME_SIZE) { _rx_buf[_rx_len++] = b; + } else { + /* Buffer full with no FEND; reset so we don't stay stuck ignoring input. */ + _rx_len = 0; + _rx_escaped = false; + _rx_active = false; } } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 6b56b91e..674b68f7 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -197,4 +197,6 @@ public: void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); bool isTxBusy() const { return _tx_state != TX_IDLE; } + /** True only when radio is actually transmitting; use to skip recvRaw in main loop. */ + bool isActuallyTransmitting() const { return _tx_state == TX_SENDING; } }; diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 62c1658f..adb20146 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -112,16 +112,12 @@ void setup() { void loop() { modem->loop(); - if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { - radio_driver.triggerNoiseFloorCalibrate(0); - next_noise_floor_calib_ms = millis(); - } - radio_driver.loop(); - - if (!modem->isTxBusy()) { - if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { - radio_driver.resetAGC(); - next_agc_reset_ms = millis(); + if (!modem->isActuallyTransmitting()) { + if (!modem->isTxBusy()) { + if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { + radio_driver.resetAGC(); + next_agc_reset_ms = millis(); + } } uint8_t rx_buf[256]; @@ -131,5 +127,21 @@ void loop() { int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } + /* Sample noise floor right after drain: we're in STATE_RX so wrapper can collect. */ + for (int i = 0; i < 16; i++) { + radio_driver.loop(); + } + } + + /* Trigger starts a new 64-sample calibration window every 2s. */ + if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { + radio_driver.triggerNoiseFloorCalibrate(0); + next_noise_floor_calib_ms = millis(); + } + radio_driver.loop(); + if (!modem->isActuallyTransmitting()) { + for (int i = 0; i < 15; i++) { + radio_driver.loop(); + } } } From 49e7516145a750f0c0d11f6cdf5ddcc8973fb6ae Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Fri, 6 Feb 2026 14:17:54 +0100 Subject: [PATCH 470/546] Add KISS UART support --- examples/kiss_modem/main.cpp | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index adb20146..514897c3 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -11,6 +11,9 @@ #elif defined(ESP32) #include <SPIFFS.h> #endif +#if defined(KISS_UART_RX) && defined(KISS_UART_TX) + #include <HardwareSerial.h> +#endif #define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 #define AGC_RESET_INTERVAL_MS 30000 @@ -91,14 +94,35 @@ void setup() { rng.begin(radio_get_rng_seed()); loadOrCreateIdentity(); + sensors.begin(); + +#if defined(KISS_UART_RX) && defined(KISS_UART_TX) +#if defined(ESP32) + Serial1.setPins(KISS_UART_RX, KISS_UART_TX); + Serial1.begin(115200); +#elif defined(NRF52_PLATFORM) + ((Uart *)&Serial1)->setPins(KISS_UART_RX, KISS_UART_TX); + Serial1.begin(115200); +#elif defined(RP2040_PLATFORM) + ((SerialUART *)&Serial1)->setRX(KISS_UART_RX); + ((SerialUART *)&Serial1)->setTX(KISS_UART_TX); + Serial1.begin(115200); +#elif defined(STM32_PLATFORM) + ((HardwareSerial *)&Serial1)->setRx(KISS_UART_RX); + ((HardwareSerial *)&Serial1)->setTx(KISS_UART_TX); + Serial1.begin(115200); +#else + #error "KISS UART not supported on this platform" +#endif + modem = new KissModem(Serial1, identity, rng, radio_driver, board, sensors); +#else Serial.begin(115200); uint32_t start = millis(); while (!Serial && millis() - start < 3000) delay(10); delay(100); - - sensors.begin(); - modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); +#endif + modem->setRadioCallback(onSetRadio); modem->setTxPowerCallback(onSetTxPower); modem->setGetCurrentRssiCallback(onGetCurrentRssi); From 7982d1ce1f0da6a97620a0fe4a223e1a77cf6dfc Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 7 Feb 2026 10:21:32 +0100 Subject: [PATCH 471/546] Use high-bit convention for hardware response codes --- examples/kiss_modem/KissModem.h | 57 ++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 674b68f7..f364f3ce 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -52,31 +52,38 @@ #define HW_CMD_PING 0x17 #define HW_CMD_REBOOT 0x18 -#define HW_RESP_IDENTITY 0x21 -#define HW_RESP_RANDOM 0x22 -#define HW_RESP_VERIFY 0x23 -#define HW_RESP_SIGNATURE 0x24 -#define HW_RESP_ENCRYPTED 0x25 -#define HW_RESP_DECRYPTED 0x26 -#define HW_RESP_SHARED_SECRET 0x27 -#define HW_RESP_HASH 0x28 -#define HW_RESP_OK 0x29 -#define HW_RESP_ERROR 0x2A -#define HW_RESP_RADIO 0x2B -#define HW_RESP_TX_POWER 0x2C -#define HW_RESP_CURRENT_RSSI 0x2D -#define HW_RESP_CHANNEL_BUSY 0x2E -#define HW_RESP_AIRTIME 0x2F -#define HW_RESP_NOISE_FLOOR 0x30 -#define HW_RESP_VERSION 0x31 -#define HW_RESP_STATS 0x32 -#define HW_RESP_BATTERY 0x33 -#define HW_RESP_MCU_TEMP 0x34 -#define HW_RESP_SENSORS 0x35 -#define HW_RESP_DEVICE_NAME 0x36 -#define HW_RESP_PONG 0x37 -#define HW_RESP_TX_DONE 0x38 -#define HW_RESP_RX_META 0x39 +/* Response code = command code | 0x80. Generic / unsolicited use 0xF0+. */ +#define HW_RESP(cmd) ((cmd) | 0x80) + +#define HW_RESP_IDENTITY HW_RESP(HW_CMD_GET_IDENTITY) /* 0x81 */ +#define HW_RESP_RANDOM HW_RESP(HW_CMD_GET_RANDOM) /* 0x82 */ +#define HW_RESP_VERIFY HW_RESP(HW_CMD_VERIFY_SIGNATURE) /* 0x83 */ +#define HW_RESP_SIGNATURE HW_RESP(HW_CMD_SIGN_DATA) /* 0x84 */ +#define HW_RESP_ENCRYPTED HW_RESP(HW_CMD_ENCRYPT_DATA) /* 0x85 */ +#define HW_RESP_DECRYPTED HW_RESP(HW_CMD_DECRYPT_DATA) /* 0x86 */ +#define HW_RESP_SHARED_SECRET HW_RESP(HW_CMD_KEY_EXCHANGE) /* 0x87 */ +#define HW_RESP_HASH HW_RESP(HW_CMD_HASH) /* 0x88 */ +#define HW_RESP_RADIO HW_RESP(HW_CMD_GET_RADIO) /* 0x8B */ +#define HW_RESP_TX_POWER HW_RESP(HW_CMD_GET_TX_POWER) /* 0x8C */ +#define HW_RESP_CURRENT_RSSI HW_RESP(HW_CMD_GET_CURRENT_RSSI) /* 0x8D */ +#define HW_RESP_CHANNEL_BUSY HW_RESP(HW_CMD_IS_CHANNEL_BUSY) /* 0x8E */ +#define HW_RESP_AIRTIME HW_RESP(HW_CMD_GET_AIRTIME) /* 0x8F */ +#define HW_RESP_NOISE_FLOOR HW_RESP(HW_CMD_GET_NOISE_FLOOR) /* 0x90 */ +#define HW_RESP_VERSION HW_RESP(HW_CMD_GET_VERSION) /* 0x91 */ +#define HW_RESP_STATS HW_RESP(HW_CMD_GET_STATS) /* 0x92 */ +#define HW_RESP_BATTERY HW_RESP(HW_CMD_GET_BATTERY) /* 0x93 */ +#define HW_RESP_MCU_TEMP HW_RESP(HW_CMD_GET_MCU_TEMP) /* 0x94 */ +#define HW_RESP_SENSORS HW_RESP(HW_CMD_GET_SENSORS) /* 0x95 */ +#define HW_RESP_DEVICE_NAME HW_RESP(HW_CMD_GET_DEVICE_NAME) /* 0x96 */ +#define HW_RESP_PONG HW_RESP(HW_CMD_PING) /* 0x97 */ + +/* Generic responses (shared by multiple commands) */ +#define HW_RESP_OK 0xF0 +#define HW_RESP_ERROR 0xF1 + +/* Unsolicited notifications (no corresponding request) */ +#define HW_RESP_TX_DONE 0xF8 +#define HW_RESP_RX_META 0xF9 #define HW_ERR_INVALID_LENGTH 0x01 #define HW_ERR_INVALID_PARAM 0x02 From 5ccd99e25f6926e515280c5bb1d154d305948d48 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 7 Feb 2026 10:21:36 +0100 Subject: [PATCH 472/546] Add toggleable per-packet signal reporting --- examples/kiss_modem/KissModem.cpp | 28 ++++++++++++++++++++++++++-- examples/kiss_modem/KissModem.h | 6 ++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 94e4fe83..ebe2e98f 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -24,6 +24,7 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _isSendCompleteCallback = nullptr; _onSendFinishedCallback = nullptr; _config = {0, 0, 0, 0, 0}; + _signal_report_enabled = true; } void KissModem::begin() { @@ -239,6 +240,12 @@ void KissModem::handleHardwareCommand(uint8_t sub_cmd, const uint8_t* data, uint case HW_CMD_GET_DEVICE_NAME: handleGetDeviceName(); break; + case HW_CMD_SET_SIGNAL_REPORT: + handleSetSignalReport(data, len); + break; + case HW_CMD_GET_SIGNAL_REPORT: + handleGetSignalReport(); + break; default: writeHardwareError(HW_ERR_UNKNOWN_CMD); break; @@ -304,8 +311,10 @@ void KissModem::processTx() { void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { writeFrame(KISS_CMD_DATA, packet, len); - uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; - writeHardwareFrame(HW_RESP_RX_META, meta, 2); + if (_signal_report_enabled) { + uint8_t meta[2] = { (uint8_t)snr, (uint8_t)rssi }; + writeHardwareFrame(HW_RESP_RX_META, meta, 2); + } } void KissModem::handleGetIdentity() { @@ -572,3 +581,18 @@ void KissModem::handleGetDeviceName() { const char* name = _board.getManufacturerName(); writeHardwareFrame(HW_RESP_DEVICE_NAME, (const uint8_t*)name, strlen(name)); } + +void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) { + if (len < 1) { + writeHardwareError(HW_ERR_INVALID_LENGTH); + return; + } + _signal_report_enabled = (data[0] != 0x00); + uint8_t val = _signal_report_enabled ? 0x01 : 0x00; + writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); +} + +void KissModem::handleGetSignalReport() { + uint8_t val = _signal_report_enabled ? 0x01 : 0x00; + writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); +} diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index f364f3ce..6a5e25dd 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -51,6 +51,8 @@ #define HW_CMD_GET_DEVICE_NAME 0x16 #define HW_CMD_PING 0x17 #define HW_CMD_REBOOT 0x18 +#define HW_CMD_SET_SIGNAL_REPORT 0x19 +#define HW_CMD_GET_SIGNAL_REPORT 0x1A /* Response code = command code | 0x80. Generic / unsolicited use 0xF0+. */ #define HW_RESP(cmd) ((cmd) | 0x80) @@ -76,6 +78,7 @@ #define HW_RESP_SENSORS HW_RESP(HW_CMD_GET_SENSORS) /* 0x95 */ #define HW_RESP_DEVICE_NAME HW_RESP(HW_CMD_GET_DEVICE_NAME) /* 0x96 */ #define HW_RESP_PONG HW_RESP(HW_CMD_PING) /* 0x97 */ +#define HW_RESP_SIGNAL_REPORT HW_RESP(HW_CMD_GET_SIGNAL_REPORT) /* 0x9A */ /* Generic responses (shared by multiple commands) */ #define HW_RESP_OK 0xF0 @@ -153,6 +156,7 @@ class KissModem { OnSendFinishedCallback _onSendFinishedCallback; RadioConfig _config; + bool _signal_report_enabled; void writeByte(uint8_t b); void writeFrame(uint8_t type, const uint8_t* data, uint16_t len); @@ -186,6 +190,8 @@ class KissModem { void handleGetMCUTemp(); void handleReboot(); void handleGetDeviceName(); + void handleSetSignalReport(const uint8_t* data, uint16_t len); + void handleGetSignalReport(); public: KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, From 362b5eb0a100a8e295fe1085df88d508466c4e37 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 7 Feb 2026 10:26:08 +0100 Subject: [PATCH 473/546] Update protocol docs for new response codes and signal reporting --- docs/kiss_modem_protocol.md | 59 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 27 deletions(-) diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 9652f976..6a08614f 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -110,36 +110,41 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The | GetDeviceName | `0x16` | - | | Ping | `0x17` | - | | Reboot | `0x18` | - | +| SetSignalReport | `0x19` | Enable (1): 0x00=disable, nonzero=enable | +| GetSignalReport | `0x1A` | - | ### Response Sub-commands (TNC to Host) +Response codes use the high-bit convention: `response = command | 0x80`. Generic and unsolicited responses use the `0xF0`+ range. + | Sub-command | Value | Data | |-------------|-------|------| -| Identity | `0x21` | PubKey (32) | -| Random | `0x22` | Random bytes (1-64) | -| Verify | `0x23` | Result (1): 0x00=invalid, 0x01=valid | -| Signature | `0x24` | Signature (64) | -| Encrypted | `0x25` | MAC (2) + Ciphertext | -| Decrypted | `0x26` | Plaintext | -| SharedSecret | `0x27` | Shared secret (32) | -| Hash | `0x28` | SHA-256 hash (32) | -| OK | `0x29` | - | -| Error | `0x2A` | Error code (1) | -| Radio | `0x2B` | Freq (4) + BW (4) + SF (1) + CR (1) | -| TxPower | `0x2C` | Power dBm (1) | -| CurrentRssi | `0x2D` | RSSI dBm (1, signed) | -| ChannelBusy | `0x2E` | Result (1): 0x00=clear, 0x01=busy | -| Airtime | `0x2F` | Milliseconds (4) | -| NoiseFloor | `0x30` | dBm (2, signed) | -| Version | `0x31` | Version (1) + Reserved (1) | -| Stats | `0x32` | RX (4) + TX (4) + Errors (4) | -| Battery | `0x33` | Millivolts (2) | -| MCUTemp | `0x34` | Temperature (2, signed) | -| Sensors | `0x35` | CayenneLPP payload | -| DeviceName | `0x36` | Name (variable, UTF-8) | -| Pong | `0x37` | - | -| TxDone | `0x38` | Result (1): 0x00=failed, 0x01=success | -| RxMeta | `0x39` | SNR (1) + RSSI (1) | +| Identity | `0x81` | PubKey (32) | +| Random | `0x82` | Random bytes (1-64) | +| Verify | `0x83` | Result (1): 0x00=invalid, 0x01=valid | +| Signature | `0x84` | Signature (64) | +| Encrypted | `0x85` | MAC (2) + Ciphertext | +| Decrypted | `0x86` | Plaintext | +| SharedSecret | `0x87` | Shared secret (32) | +| Hash | `0x88` | SHA-256 hash (32) | +| Radio | `0x8B` | Freq (4) + BW (4) + SF (1) + CR (1) | +| TxPower | `0x8C` | Power dBm (1) | +| CurrentRssi | `0x8D` | RSSI dBm (1, signed) | +| ChannelBusy | `0x8E` | Result (1): 0x00=clear, 0x01=busy | +| Airtime | `0x8F` | Milliseconds (4) | +| NoiseFloor | `0x90` | dBm (2, signed) | +| Version | `0x91` | Version (1) + Reserved (1) | +| Stats | `0x92` | RX (4) + TX (4) + Errors (4) | +| Battery | `0x93` | Millivolts (2) | +| MCUTemp | `0x94` | Temperature (2, signed) | +| Sensors | `0x95` | CayenneLPP payload | +| DeviceName | `0x96` | Name (variable, UTF-8) | +| Pong | `0x97` | - | +| SignalReport | `0x9A` | Status (1): 0x00=disabled, 0x01=enabled | +| OK | `0xF0` | - | +| Error | `0xF1` | Error code (1) | +| TxDone | `0xF8` | Result (1): 0x00=failed, 0x01=success | +| RxMeta | `0xF9` | SNR (1) + RSSI (1) | ### Error Codes @@ -156,9 +161,9 @@ MeshCore-specific functionality uses the standard KISS SetHardware command. The The TNC sends these SetHardware frames without a preceding request: -**TxDone (0x38)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. +**TxDone (0xF8)**: Sent after a packet has been transmitted. Contains a single byte: 0x01 for success, 0x00 for failure. -**RxMeta (0x39)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Standard KISS clients ignore this frame. +**RxMeta (0xF9)**: Sent immediately after each standard data frame (type 0x00) with metadata for the received packet. Contains SNR (1 byte, signed, value x4 for 0.25 dB precision) followed by RSSI (1 byte, signed, dBm). Enabled by default; can be toggled with SetSignalReport. Standard KISS clients ignore this frame. ## Data Formats From 00b44c41148e00e73b0289a78662987b44ae4809 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 7 Feb 2026 14:22:21 +0100 Subject: [PATCH 474/546] Remove redundant send/complete/finished callbacks, use Radio interface directly --- examples/kiss_modem/KissModem.cpp | 16 ++++------------ examples/kiss_modem/KissModem.h | 9 --------- examples/kiss_modem/main.cpp | 15 --------------- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index ebe2e98f..08ed9b90 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -20,9 +20,6 @@ KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& r _setTxPowerCallback = nullptr; _getCurrentRssiCallback = nullptr; _getStatsCallback = nullptr; - _sendPacketCallback = nullptr; - _isSendCompleteCallback = nullptr; - _onSendFinishedCallback = nullptr; _config = {0, 0, 0, 0, 0}; _signal_report_enabled = true; } @@ -287,19 +284,14 @@ void KissModem::processTx() { case TX_DELAY: if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) { - if (_sendPacketCallback) { - _sendPacketCallback(_pending_tx, _pending_tx_len); - _tx_state = TX_SENDING; - } else { - _has_pending_tx = false; - _tx_state = TX_IDLE; - } + _radio.startSendRaw(_pending_tx, _pending_tx_len); + _tx_state = TX_SENDING; } break; case TX_SENDING: - if (_isSendCompleteCallback && _isSendCompleteCallback()) { - if (_onSendFinishedCallback) _onSendFinishedCallback(); + if (_radio.isSendComplete()) { + _radio.onSendFinished(); uint8_t result = 0x01; writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); _has_pending_tx = false; diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 6a5e25dd..88741e1f 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -101,9 +101,6 @@ typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); typedef void (*SetTxPowerCallback)(uint8_t power); typedef float (*GetCurrentRssiCallback)(); typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); -typedef void (*SendPacketCallback)(const uint8_t* data, uint16_t len); -typedef bool (*IsSendCompleteCallback)(); -typedef void (*OnSendFinishedCallback)(); struct RadioConfig { uint32_t freq_hz; @@ -151,9 +148,6 @@ class KissModem { SetTxPowerCallback _setTxPowerCallback; GetCurrentRssiCallback _getCurrentRssiCallback; GetStatsCallback _getStatsCallback; - SendPacketCallback _sendPacketCallback; - IsSendCompleteCallback _isSendCompleteCallback; - OnSendFinishedCallback _onSendFinishedCallback; RadioConfig _config; bool _signal_report_enabled; @@ -204,9 +198,6 @@ public: void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } - void setSendPacketCallback(SendPacketCallback cb) { _sendPacketCallback = cb; } - void setIsSendCompleteCallback(IsSendCompleteCallback cb) { _isSendCompleteCallback = cb; } - void setOnSendFinishedCallback(OnSendFinishedCallback cb) { _onSendFinishedCallback = cb; } void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); bool isTxBusy() const { return _tx_state != TX_IDLE; } diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 514897c3..15888b90 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -70,18 +70,6 @@ void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { *errors = radio_driver.getPacketsRecvErrors(); } -void onSendPacket(const uint8_t* data, uint16_t len) { - radio_driver.startSendRaw(data, len); -} - -bool onIsSendComplete() { - return radio_driver.isSendComplete(); -} - -void onSendFinished() { - radio_driver.onSendFinished(); -} - void setup() { board.begin(); @@ -127,9 +115,6 @@ void setup() { modem->setTxPowerCallback(onSetTxPower); modem->setGetCurrentRssiCallback(onGetCurrentRssi); modem->setGetStatsCallback(onGetStats); - modem->setSendPacketCallback(onSendPacket); - modem->setIsSendCompleteCallback(onIsSendComplete); - modem->setOnSendFinishedCallback(onSendFinished); modem->begin(); } From 5157daf1c1ae341a36d1efa21e307aef6c682321 Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 7 Feb 2026 14:24:34 +0100 Subject: [PATCH 475/546] Remove individual HW_RESP_* defines, use HW_RESP() macro directly --- examples/kiss_modem/KissModem.cpp | 48 +++++++++++++++---------------- examples/kiss_modem/KissModem.h | 23 --------------- 2 files changed, 24 insertions(+), 47 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index 08ed9b90..b4251046 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -310,7 +310,7 @@ void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, } void KissModem::handleGetIdentity() { - writeHardwareFrame(HW_RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP(HW_CMD_GET_IDENTITY), _identity.pub_key, PUB_KEY_SIZE); } void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { @@ -327,7 +327,7 @@ void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { uint8_t buf[64]; _rng.random(buf, requested); - writeHardwareFrame(HW_RESP_RANDOM, buf, requested); + writeHardwareFrame(HW_RESP(HW_CMD_GET_RANDOM), buf, requested); } void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { @@ -342,7 +342,7 @@ void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_VERIFY, &result, 1); + writeHardwareFrame(HW_RESP(HW_CMD_VERIFY_SIGNATURE), &result, 1); } void KissModem::handleSignData(const uint8_t* data, uint16_t len) { @@ -353,7 +353,7 @@ void KissModem::handleSignData(const uint8_t* data, uint16_t len) { uint8_t signature[SIGNATURE_SIZE]; _identity.sign(signature, data, len); - writeHardwareFrame(HW_RESP_SIGNATURE, signature, SIGNATURE_SIZE); + writeHardwareFrame(HW_RESP(HW_CMD_SIGN_DATA), signature, SIGNATURE_SIZE); } void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { @@ -370,7 +370,7 @@ void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); if (encrypted_len > 0) { - writeHardwareFrame(HW_RESP_ENCRYPTED, buf, encrypted_len); + writeHardwareFrame(HW_RESP(HW_CMD_ENCRYPT_DATA), buf, encrypted_len); } else { writeHardwareError(HW_ERR_ENCRYPT_FAILED); } @@ -390,7 +390,7 @@ void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); if (decrypted_len > 0) { - writeHardwareFrame(HW_RESP_DECRYPTED, buf, decrypted_len); + writeHardwareFrame(HW_RESP(HW_CMD_DECRYPT_DATA), buf, decrypted_len); } else { writeHardwareError(HW_ERR_MAC_FAILED); } @@ -404,7 +404,7 @@ void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { uint8_t shared_secret[PUB_KEY_SIZE]; _identity.calcSharedSecret(shared_secret, data); - writeHardwareFrame(HW_RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); + writeHardwareFrame(HW_RESP(HW_CMD_KEY_EXCHANGE), shared_secret, PUB_KEY_SIZE); } void KissModem::handleHash(const uint8_t* data, uint16_t len) { @@ -415,7 +415,7 @@ void KissModem::handleHash(const uint8_t* data, uint16_t len) { uint8_t hash[32]; mesh::Utils::sha256(hash, 32, data, len); - writeHardwareFrame(HW_RESP_HASH, hash, 32); + writeHardwareFrame(HW_RESP(HW_CMD_HASH), hash, 32); } void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { @@ -467,18 +467,18 @@ void KissModem::handleGetRadio() { memcpy(buf + 4, &_config.bw_hz, 4); buf[8] = _config.sf; buf[9] = _config.cr; - writeHardwareFrame(HW_RESP_RADIO, buf, 10); + writeHardwareFrame(HW_RESP(HW_CMD_GET_RADIO), buf, 10); } void KissModem::handleGetTxPower() { - writeHardwareFrame(HW_RESP_TX_POWER, &_config.tx_power, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_TX_POWER), &_config.tx_power, 1); } void KissModem::handleGetVersion() { uint8_t buf[2]; buf[0] = KISS_FIRMWARE_VERSION; buf[1] = 0; - writeHardwareFrame(HW_RESP_VERSION, buf, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_VERSION), buf, 2); } void KissModem::handleGetCurrentRssi() { @@ -489,12 +489,12 @@ void KissModem::handleGetCurrentRssi() { float rssi = _getCurrentRssiCallback(); int8_t rssi_byte = (int8_t)rssi; - writeHardwareFrame(HW_RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_CURRENT_RSSI), (uint8_t*)&rssi_byte, 1); } void KissModem::handleIsChannelBusy() { uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_CHANNEL_BUSY, &busy, 1); + writeHardwareFrame(HW_RESP(HW_CMD_IS_CHANNEL_BUSY), &busy, 1); } void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { @@ -505,12 +505,12 @@ void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { uint8_t packet_len = data[0]; uint32_t airtime = _radio.getEstAirtimeFor(packet_len); - writeHardwareFrame(HW_RESP_AIRTIME, (uint8_t*)&airtime, 4); + writeHardwareFrame(HW_RESP(HW_CMD_GET_AIRTIME), (uint8_t*)&airtime, 4); } void KissModem::handleGetNoiseFloor() { int16_t noise_floor = _radio.getNoiseFloor(); - writeHardwareFrame(HW_RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_NOISE_FLOOR), (uint8_t*)&noise_floor, 2); } void KissModem::handleGetStats() { @@ -525,16 +525,16 @@ void KissModem::handleGetStats() { memcpy(buf, &rx, 4); memcpy(buf + 4, &tx, 4); memcpy(buf + 8, &errors, 4); - writeHardwareFrame(HW_RESP_STATS, buf, 12); + writeHardwareFrame(HW_RESP(HW_CMD_GET_STATS), buf, 12); } void KissModem::handleGetBattery() { uint16_t mv = _board.getBattMilliVolts(); - writeHardwareFrame(HW_RESP_BATTERY, (uint8_t*)&mv, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_BATTERY), (uint8_t*)&mv, 2); } void KissModem::handlePing() { - writeHardwareFrame(HW_RESP_PONG, nullptr, 0); + writeHardwareFrame(HW_RESP(HW_CMD_PING), nullptr, 0); } void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { @@ -546,9 +546,9 @@ void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { uint8_t permissions = data[0]; CayenneLPP telemetry(255); if (_sensors.querySensors(permissions, telemetry)) { - writeHardwareFrame(HW_RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SENSORS), telemetry.getBuffer(), telemetry.getSize()); } else { - writeHardwareFrame(HW_RESP_SENSORS, nullptr, 0); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SENSORS), nullptr, 0); } } @@ -559,7 +559,7 @@ void KissModem::handleGetMCUTemp() { return; } int16_t temp_tenths = (int16_t)(temp * 10.0f); - writeHardwareFrame(HW_RESP_MCU_TEMP, (uint8_t*)&temp_tenths, 2); + writeHardwareFrame(HW_RESP(HW_CMD_GET_MCU_TEMP), (uint8_t*)&temp_tenths, 2); } void KissModem::handleReboot() { @@ -571,7 +571,7 @@ void KissModem::handleReboot() { void KissModem::handleGetDeviceName() { const char* name = _board.getManufacturerName(); - writeHardwareFrame(HW_RESP_DEVICE_NAME, (const uint8_t*)name, strlen(name)); + writeHardwareFrame(HW_RESP(HW_CMD_GET_DEVICE_NAME), (const uint8_t*)name, strlen(name)); } void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) { @@ -581,10 +581,10 @@ void KissModem::handleSetSignalReport(const uint8_t* data, uint16_t len) { } _signal_report_enabled = (data[0] != 0x00); uint8_t val = _signal_report_enabled ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SIGNAL_REPORT), &val, 1); } void KissModem::handleGetSignalReport() { uint8_t val = _signal_report_enabled ? 0x01 : 0x00; - writeHardwareFrame(HW_RESP_SIGNAL_REPORT, &val, 1); + writeHardwareFrame(HW_RESP(HW_CMD_GET_SIGNAL_REPORT), &val, 1); } diff --git a/examples/kiss_modem/KissModem.h b/examples/kiss_modem/KissModem.h index 88741e1f..60566add 100644 --- a/examples/kiss_modem/KissModem.h +++ b/examples/kiss_modem/KissModem.h @@ -57,29 +57,6 @@ /* Response code = command code | 0x80. Generic / unsolicited use 0xF0+. */ #define HW_RESP(cmd) ((cmd) | 0x80) -#define HW_RESP_IDENTITY HW_RESP(HW_CMD_GET_IDENTITY) /* 0x81 */ -#define HW_RESP_RANDOM HW_RESP(HW_CMD_GET_RANDOM) /* 0x82 */ -#define HW_RESP_VERIFY HW_RESP(HW_CMD_VERIFY_SIGNATURE) /* 0x83 */ -#define HW_RESP_SIGNATURE HW_RESP(HW_CMD_SIGN_DATA) /* 0x84 */ -#define HW_RESP_ENCRYPTED HW_RESP(HW_CMD_ENCRYPT_DATA) /* 0x85 */ -#define HW_RESP_DECRYPTED HW_RESP(HW_CMD_DECRYPT_DATA) /* 0x86 */ -#define HW_RESP_SHARED_SECRET HW_RESP(HW_CMD_KEY_EXCHANGE) /* 0x87 */ -#define HW_RESP_HASH HW_RESP(HW_CMD_HASH) /* 0x88 */ -#define HW_RESP_RADIO HW_RESP(HW_CMD_GET_RADIO) /* 0x8B */ -#define HW_RESP_TX_POWER HW_RESP(HW_CMD_GET_TX_POWER) /* 0x8C */ -#define HW_RESP_CURRENT_RSSI HW_RESP(HW_CMD_GET_CURRENT_RSSI) /* 0x8D */ -#define HW_RESP_CHANNEL_BUSY HW_RESP(HW_CMD_IS_CHANNEL_BUSY) /* 0x8E */ -#define HW_RESP_AIRTIME HW_RESP(HW_CMD_GET_AIRTIME) /* 0x8F */ -#define HW_RESP_NOISE_FLOOR HW_RESP(HW_CMD_GET_NOISE_FLOOR) /* 0x90 */ -#define HW_RESP_VERSION HW_RESP(HW_CMD_GET_VERSION) /* 0x91 */ -#define HW_RESP_STATS HW_RESP(HW_CMD_GET_STATS) /* 0x92 */ -#define HW_RESP_BATTERY HW_RESP(HW_CMD_GET_BATTERY) /* 0x93 */ -#define HW_RESP_MCU_TEMP HW_RESP(HW_CMD_GET_MCU_TEMP) /* 0x94 */ -#define HW_RESP_SENSORS HW_RESP(HW_CMD_GET_SENSORS) /* 0x95 */ -#define HW_RESP_DEVICE_NAME HW_RESP(HW_CMD_GET_DEVICE_NAME) /* 0x96 */ -#define HW_RESP_PONG HW_RESP(HW_CMD_PING) /* 0x97 */ -#define HW_RESP_SIGNAL_REPORT HW_RESP(HW_CMD_GET_SIGNAL_REPORT) /* 0x9A */ - /* Generic responses (shared by multiple commands) */ #define HW_RESP_OK 0xF0 #define HW_RESP_ERROR 0xF1 From f6ebbd978e0b27f5db15f2013cb4b5e0410a8edd Mon Sep 17 00:00:00 2001 From: ViezeVingertjes <michael.overhorst@gmail.com> Date: Sat, 7 Feb 2026 14:32:11 +0100 Subject: [PATCH 476/546] Remove redundant locals in handleSetRadio --- examples/kiss_modem/KissModem.cpp | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/examples/kiss_modem/KissModem.cpp b/examples/kiss_modem/KissModem.cpp index b4251046..5e8b00d5 100644 --- a/examples/kiss_modem/KissModem.cpp +++ b/examples/kiss_modem/KissModem.cpp @@ -428,21 +428,12 @@ void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { return; } - uint32_t freq_hz, bw_hz; - memcpy(&freq_hz, data, 4); - memcpy(&bw_hz, data + 4, 4); - uint8_t sf = data[8]; - uint8_t cr = data[9]; + memcpy(&_config.freq_hz, data, 4); + memcpy(&_config.bw_hz, data + 4, 4); + _config.sf = data[8]; + _config.cr = data[9]; - _config.freq_hz = freq_hz; - _config.bw_hz = bw_hz; - _config.sf = sf; - _config.cr = cr; - - float freq = freq_hz / 1000000.0f; - float bw = bw_hz / 1000.0f; - - _setRadioCallback(freq, bw, sf, cr); + _setRadioCallback(_config.freq_hz / 1000000.0f, _config.bw_hz / 1000.0f, _config.sf, _config.cr); writeHardwareFrame(HW_RESP_OK, nullptr, 0); } From c4c287d01bd67dbc65028f21ca0892b89228bf21 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Sat, 7 Feb 2026 15:39:24 +0100 Subject: [PATCH 477/546] Bridge always has work (prevents sleep) --- examples/simple_repeater/MyMesh.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 6d957cc0..40b54555 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -1219,5 +1219,8 @@ void MyMesh::loop() { // To check if there is pending work bool MyMesh::hasPendingWork() const { +#if defined(WITH_BRIDGE) + if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep +#endif return _mgr->getOutboundCount(0xFFFFFFFF) > 0; } From 23b4baa0665cdd762b1a909d1f1ce9f0a22d4fd8 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Sat, 7 Feb 2026 16:04:01 +0100 Subject: [PATCH 478/546] Enable register patch heltec tracker v2 --- variants/heltec_tracker_v2/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 36de671e..25d16f2f 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -26,6 +26,7 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_REGISTER_PATCH=1 -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=0 From 776131e263d102675111dd6f51e2bf6be3f8b7cb Mon Sep 17 00:00:00 2001 From: agessaman <adam@gessaman.com> Date: Sat, 7 Feb 2026 07:42:52 -0800 Subject: [PATCH 479/546] simplify kiss noise floor sampling --- examples/kiss_modem/main.cpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 15888b90..35079592 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -136,21 +136,11 @@ void loop() { int8_t rssi = (int8_t)radio_driver.getLastRSSI(); modem->onPacketReceived(snr, rssi, rx_buf, rx_len); } - /* Sample noise floor right after drain: we're in STATE_RX so wrapper can collect. */ - for (int i = 0; i < 16; i++) { - radio_driver.loop(); - } } - /* Trigger starts a new 64-sample calibration window every 2s. */ if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { radio_driver.triggerNoiseFloorCalibrate(0); next_noise_floor_calib_ms = millis(); } radio_driver.loop(); - if (!modem->isActuallyTransmitting()) { - for (int i = 0; i < 15; i++) { - radio_driver.loop(); - } - } } From e8646f5ede777bc883079eec42d6989aa14b8f7a Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Sat, 7 Feb 2026 16:58:06 +0100 Subject: [PATCH 480/546] Parse as signed int --- examples/companion_radio/MyMesh.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f8e90be5..96716091 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1228,10 +1228,11 @@ void MyMesh::handleCmdFrame(size_t len) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } } else if (cmd_frame[0] == CMD_SET_RADIO_TX_POWER) { - if (cmd_frame[1] > MAX_LORA_TX_POWER) { + int8_t power = (int8_t)cmd_frame[1]; + if (power < -9 || power > MAX_LORA_TX_POWER) { writeErrFrame(ERR_CODE_ILLEGAL_ARG); } else { - _prefs.tx_power_dbm = cmd_frame[1]; + _prefs.tx_power_dbm = power; savePrefs(); radio_set_tx_power(_prefs.tx_power_dbm); writeOKFrame(); From fcfbb458f82a5ca5f49a8225ebede2f84ec7a240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Br=C3=A1zio?= <jbrazio@gmail.com> Date: Sat, 7 Feb 2026 21:26:28 +0000 Subject: [PATCH 481/546] Refactor environment names and build flags for RAK variants --- variants/rak11310/platformio.ini | 15 +++++++------ variants/rak3112/platformio.ini | 37 ++++++++------------------------ variants/rak3401/platformio.ini | 1 - variants/rak3x72/platformio.ini | 7 +++--- 4 files changed, 21 insertions(+), 39 deletions(-) diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index df99ea84..950b46ef 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -7,6 +7,7 @@ board = rakwireless_rak11300 board_build.filesystem_size = 0.5m build_flags = ${rp2040_base.build_flags} -I variants/rak11310 + -D RAK_11310 -D ARDUINO_RAKWIRELESS_RAK11300=1 -D SX126X_CURRENT_LIMIT=140 -D RADIO_CLASS=CustomSX1262 @@ -34,7 +35,7 @@ build_src_filter = ${rp2040_base.build_src_filter} +<../variants/rak11310> lib_deps = ${rp2040_base.lib_deps} -[env:rak11310_repeater] +[env:RAK_11310_repeater] extends = rak11310 build_flags = ${rak11310.build_flags} -D ADVERT_NAME='"RAK11310 Repeater"' @@ -47,7 +48,7 @@ build_flags = ${rak11310.build_flags} build_src_filter = ${rak11310.build_src_filter} +<../examples/simple_repeater> -[env:rak11310_repeater_bridge_rs232] +[env:RAK_11310_repeater_bridge_rs232] extends = rak11310 build_flags = ${rak11310.build_flags} -D ADVERT_NAME='"RS232 Bridge"' @@ -65,7 +66,7 @@ build_src_filter = ${rak11310.build_src_filter} +<helpers/bridges/RS232Bridge.cpp> +<../examples/simple_repeater> -[env:rak11310_room_server] +[env:RAK_11310_room_server] extends = rak11310 build_flags = ${rak11310.build_flags} -D ADVERT_NAME='"RAK11310 Room"' @@ -78,7 +79,7 @@ build_flags = ${rak11310.build_flags} build_src_filter = ${rak11310.build_src_filter} +<../examples/simple_room_server> -[env:rak11310_companion_radio_usb] +[env:RAK_11310_companion_radio_usb] extends = rak11310 build_flags = ${rak11310.build_flags} -D MAX_CONTACTS=100 @@ -90,7 +91,7 @@ build_src_filter = ${rak11310.build_src_filter} lib_deps = ${rak11310.lib_deps} densaugeo/base64 @ ~1.4.0 -; [env:rak11310_companion_radio_ble] +; [env:RAK_11310_companion_radio_ble] ; extends = rak11310 ; build_flags = ${rak11310.build_flags} ; -D MAX_CONTACTS=100 @@ -104,7 +105,7 @@ lib_deps = ${rak11310.lib_deps} ; lib_deps = ${rak11310.lib_deps} ; densaugeo/base64 @ ~1.4.0 -; [env:rak11310_companion_radio_wifi] +; [env:RAK_11310_companion_radio_wifi] ; extends = rak11310 ; build_flags = ${rak11310.build_flags} ; -D MAX_CONTACTS=100 @@ -119,7 +120,7 @@ lib_deps = ${rak11310.lib_deps} ; lib_deps = ${rak11310.lib_deps} ; densaugeo/base64 @ ~1.4.0 -[env:rak11310_terminal_chat] +[env:RAK_11310_terminal_chat] extends = rak11310 build_flags = ${rak11310.build_flags} -D MAX_CONTACTS=100 diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index 29ebdff2..bc43ee04 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -36,11 +36,10 @@ lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} -[env:RAK3112_repeater] +[env:RAK_3112_repeater] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"RAK3112 Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -49,18 +48,16 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 -[env:RAK3112_repeater_bridge_rs232] +[env:RAK_3112_repeater_bridge_rs232] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"RS232 Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -74,17 +71,15 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} +<helpers/bridges/RS232Bridge.cpp> - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} -[env:RAK3112_repeater_bridge_espnow] +[env:RAK_3112_repeater_bridge_espnow] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"ESPNow Bridge"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -96,17 +91,15 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} +<helpers/bridges/ESPNowBridge.cpp> - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_repeater> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} -[env:RAK3112_room_server] +[env:RAK_3112_room_server] extends = rak3112 build_flags = ${rak3112.build_flags} - -D DISPLAY_CLASS=SSD1306Display -D ADVERT_NAME='"RAK3112 Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -115,13 +108,12 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_room_server> lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} -[env:RAK3112_terminal_chat] +[env:RAK_3112_terminal_chat] extends = rak3112 build_flags = ${rak3112.build_flags} @@ -135,33 +127,29 @@ lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_companion_radio_usb] +[env:RAK_3112_companion_radio_usb] extends = rak3112 build_flags = ${rak3112.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> - +<helpers/ui/MomentaryButton.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_companion_radio_ble] +[env:RAK_3112_companion_radio_ble] extends = rak3112 build_flags = ${rak3112.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display -D BLE_PIN_CODE=123456 ; dynamic, random PIN -D AUTO_SHUTDOWN_MILLIVOLTS=3400 -D BLE_DEBUG_LOGGING=1 @@ -169,8 +157,6 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> - +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -178,14 +164,13 @@ lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_companion_radio_wifi] +[env:RAK_3112_companion_radio_wifi] extends = rak3112 build_flags = ${rak3112.build_flags} -I examples/companion_radio/ui-new -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 - -D DISPLAY_CLASS=SSD1306Display -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' @@ -193,8 +178,6 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> - +<helpers/ui/MomentaryButton.cpp> +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> @@ -202,7 +185,7 @@ lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 -[env:RAK3112_sensor] +[env:RAK_3112_sensor] extends = rak3112 build_flags = ${rak3112.build_flags} @@ -212,11 +195,9 @@ build_flags = -D ADMIN_PASSWORD='"password"' -D ENV_PIN_SDA=33 -D ENV_PIN_SCL=34 - -D DISPLAY_CLASS=SSD1306Display ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} - +<helpers/ui/SSD1306Display.cpp> +<../examples/simple_sensor> lib_deps = ${rak3112.lib_deps} diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 30d35d0b..7467ceb9 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -6,7 +6,6 @@ build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak3401 -D RAK_3401 - -D RAK13302 -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index a6260089..12ea413a 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -3,6 +3,7 @@ extends = stm32_base board = rak3172 board_upload.maximum_size = 229376 ; 32kb for FS build_flags = ${stm32_base.build_flags} + -D RAK_3X72 -D RADIO_CLASS=CustomSTM32WLx -D WRAPPER_CLASS=CustomSTM32WLxWrapper -D SPI_INTERFACES_COUNT=0 @@ -13,7 +14,7 @@ build_flags = ${stm32_base.build_flags} build_src_filter = ${stm32_base.build_src_filter} +<../variants/rak3x72> -[env:rak3x72_repeater] +[env:RAK_3x72_repeater] extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Repeater"' @@ -22,7 +23,7 @@ build_flags = ${rak3x72.build_flags} build_src_filter = ${rak3x72.build_src_filter} +<../examples/simple_repeater/*.cpp> -[env:rak3x72_sensor] +[env:RAK_3x72_sensor] extends = rak3x72 build_flags = ${rak3x72.build_flags} -D ADVERT_NAME='"RAK3x72 Sensor"' @@ -30,7 +31,7 @@ build_flags = ${rak3x72.build_flags} build_src_filter = ${rak3x72.build_src_filter} +<../examples/simple_sensor> -[env:rak3x72_companion_radio_usb] +[env:RAK_3x72_companion_radio_usb] extends = rak3x72 build_flags = ${rak3x72.build_flags} ; -D FORMAT_FS=true From 31a2e74ada0b644ad955b19b872ddf662849fbbc Mon Sep 17 00:00:00 2001 From: Thane Gill <me@thanegill.com> Date: Sat, 7 Feb 2026 16:56:20 -0800 Subject: [PATCH 482/546] Correct manufacturer name 'Elecrow ThinkNode M5' --- variants/thinknode_m5/ThinknodeM5Board.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/thinknode_m5/ThinknodeM5Board.cpp b/variants/thinknode_m5/ThinknodeM5Board.cpp index 5adc8c00..c4de538c 100644 --- a/variants/thinknode_m5/ThinknodeM5Board.cpp +++ b/variants/thinknode_m5/ThinknodeM5Board.cpp @@ -43,5 +43,5 @@ void ThinknodeM5Board::begin() { } const char* ThinknodeM5Board::getManufacturerName() const { - return "Elecrow ThinkNode M2"; + return "Elecrow ThinkNode M5"; } From 3ff1394dd231a1320a8cdc67e7b5ddbcb1ead31c Mon Sep 17 00:00:00 2001 From: Thane Gill <me@thanegill.com> Date: Sun, 8 Feb 2026 14:49:03 -0800 Subject: [PATCH 483/546] build.sh: add list and -l to list firmwares available to build. --- build.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/build.sh b/build.sh index b7f95dd7..2b7c4824 100755 --- a/build.sh +++ b/build.sh @@ -7,6 +7,7 @@ sh build.sh <command> [target] Commands: help|usage|-h|--help: Shows this message. + list|-l: List firmwares available to build. build-firmware <target>: Build the firmware for the given build target. build-firmwares: Build all firmwares for all targets. build-matching-firmwares <build-match-spec>: Build all firmwares for build targets containing the string given for <build-match-spec>. @@ -46,20 +47,24 @@ $ sh build.sh build-firmware RAK_4631_repeater EOF } +# get a list of pio env names that start with "env:" +get_pio_envs() { + pio project config | grep 'env:' | sed 's/env://' +} + # Catch cries for help before doing anything else. case $1 in help|usage|-h|--help) global_usage exit 1 ;; + list|-l) + get_pio_envs + exit 0 + ;; esac -# get a list of pio env names that start with "env:" -get_pio_envs() { - echo $(pio project config | grep 'env:' | sed 's/env://') -} - # $1 should be the string to find (case insensitive) get_pio_envs_containing_string() { shopt -s nocasematch From 810fd561d232e4a2d344ffdc7346ce2d370ea177 Mon Sep 17 00:00:00 2001 From: Snayler <11491485+Snayler@users.noreply.github.com> Date: Mon, 9 Feb 2026 23:20:29 +0000 Subject: [PATCH 484/546] Enable TX LED for LilyGo LoRa32 V2.1_1.6 Working on my device, green TX LED starts blinking every time I transmit --- variants/lilygo_tlora_v2_1/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index f27f57fd..c28f9001 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -18,7 +18,7 @@ build_flags = -D P_LORA_SCLK=5 ; SPI clock -D P_LORA_MISO=19 ; SPI MISO -D P_LORA_MOSI=27 ; SPI MOSI - -D P_LORA_TX_LED=2 ; LED pin for TX indication + -D P_LORA_TX_LED=25 ; LED pin for TX indication -D PIN_BOARD_SDA=21 -D PIN_BOARD_SCL=22 -D PIN_VBAT_READ=35 ; Battery voltage reading (analog pin) @@ -191,4 +191,4 @@ build_flags = ; -D CORE_DEBUG_LEVEL=3 lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} From bafa2ccd2220fc194f9df64d26521458b711394a Mon Sep 17 00:00:00 2001 From: liamcottle <liam@liamcottle.com> Date: Tue, 10 Feb 2026 17:01:30 +1300 Subject: [PATCH 485/546] fix estimated timeout for multi byte path traces --- examples/companion_radio/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 96716091..a9ac1cf0 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1568,7 +1568,7 @@ void MyMesh::handleCmdFrame(size_t len) { sendDirect(pkt, &cmd_frame[10], path_len); uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2); - uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len); + uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len >> path_sz); out_frame[0] = RESP_CODE_SENT; out_frame[1] = 0; From f720338c03b25a01a9c14ec0dd293243f290cc4c Mon Sep 17 00:00:00 2001 From: dylan <1577856435@qq.com> Date: Wed, 11 Feb 2026 14:12:48 +0800 Subject: [PATCH 486/546] Fix WioTrackerL1 BLE companion: route sensors to Grove I2C bus (Wire1) Sensors connected via the Grove I2C connector (D18/D17) were not detected because the firmware scanned the OLED I2C bus (Wire, D14/D15) by default. Adding ENV_PIN_SDA/SCL flags directs EnvironmentSensorManager to use Wire1, matching the physical Grove connector pinout. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --- variants/wio-tracker-l1/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 75651d69..da760b51 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -96,6 +96,8 @@ build_flags = ${WioTrackerL1.build_flags} -D PIN_BUZZER=12 -D QSPIFLASH=1 -D ADVERT_NAME='"@@MAC"' + -D ENV_PIN_SDA=PIN_WIRE1_SDA + -D ENV_PIN_SCL=PIN_WIRE1_SCL ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${WioTrackerL1.build_src_filter} From beff18c53bf98f63dd72825f14001962f2cb6a6b Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Wed, 11 Feb 2026 09:34:41 +0100 Subject: [PATCH 487/546] fix usb and build for rak 3112 --- variants/rak3112/platformio.ini | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index bc43ee04..d030e749 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -7,6 +7,7 @@ build_flags = -I variants/rak3112 -D RAK_3112=1 -D ESP32_CPU_FREQ=80 + -D ARDUINO_USB_CDC_ON_BOOT=1 -D P_LORA_DIO_1=47 -D P_LORA_NSS=7 -D P_LORA_RESET=8 @@ -131,14 +132,14 @@ lib_deps = extends = rak3112 build_flags = ${rak3112.build_flags} - -I examples/companion_radio/ui-new + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 ; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 build_src_filter = ${rak3112.build_src_filter} +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -147,7 +148,7 @@ lib_deps = extends = rak3112 build_flags = ${rak3112.build_flags} - -I examples/companion_radio/ui-new + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 ; dynamic, random PIN @@ -159,7 +160,7 @@ build_flags = build_src_filter = ${rak3112.build_src_filter} +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -168,7 +169,7 @@ lib_deps = extends = rak3112 build_flags = ${rak3112.build_flags} - -I examples/companion_radio/ui-new + -I examples/companion_radio/ui-orig -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 @@ -180,7 +181,7 @@ build_flags = build_src_filter = ${rak3112.build_src_filter} +<helpers/esp32/*.cpp> +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-new/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${rak3112.lib_deps} densaugeo/base64 @ ~1.4.0 From fb025fb67e7d23b6eaaa3118b89d19f9e5c36b2a Mon Sep 17 00:00:00 2001 From: Leah <leah@leahdevs.xyz> Date: Wed, 11 Feb 2026 09:51:28 +0100 Subject: [PATCH 488/546] Add muted icon to show when buzzer is muted --- examples/companion_radio/ui-new/UITask.cpp | 8 ++++++++ examples/companion_radio/ui-new/UITask.h | 8 ++++++++ examples/companion_radio/ui-new/icons.h | 4 ++++ 3 files changed, 20 insertions(+) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index ae2d9375..265532be 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -131,6 +131,14 @@ class HomeScreen : public UIScreen { // fill the battery based on the percentage int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100; display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4); + + // show muted icon if buzzer is muted +#ifdef PIN_BUZZER + if (_task->isBuzzerQuiet()) { + display.setColor(DisplayDriver::RED); + display.drawXbm(iconX - 9, iconY + 1, muted_icon, 8, 8); + } +#endif } CayenneLPP sensors_lpp; diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 02c3cafb..a77ad6e7 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -78,6 +78,14 @@ public: bool hasDisplay() const { return _display != NULL; } bool isButtonPressed() const; + bool isBuzzerQuiet() { +#ifdef PIN_BUZZER + return buzzer.isQuiet(); +#else + return true; +#endif + } + void toggleBuzzer(); bool getGPSState(); void toggleGPS(); diff --git a/examples/companion_radio/ui-new/icons.h b/examples/companion_radio/ui-new/icons.h index 5220f409..cbe23790 100644 --- a/examples/companion_radio/ui-new/icons.h +++ b/examples/companion_radio/ui-new/icons.h @@ -115,4 +115,8 @@ static const uint8_t advert_icon[] = { 0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30, 0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static const uint8_t muted_icon[] = { + 0x20, 0x6a, 0xea, 0xe4, 0xe4, 0xea, 0x6a, 0x20 }; \ No newline at end of file From 77675ab4966a6efaf40e046585b4cd682f0aed11 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 6 Feb 2026 14:59:49 +1100 Subject: [PATCH 489/546] add -D ESP32_PLATFORM to esp32_base --- platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/platformio.ini b/platformio.ini index 69883271..c47e757e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -59,6 +59,7 @@ platform = platformio/espressif32@6.11.0 monitor_filters = esp32_exception_decoder extra_scripts = merge-bin.py build_flags = ${arduino_base.build_flags} + -D ESP32_PLATFORM ; -D ESP32_CPU_FREQ=80 ; change it to your need build_src_filter = ${arduino_base.build_src_filter} From 5df139f3d634a9666e3b2d4d9fb40116ac3e0960 Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Fri, 13 Feb 2026 12:43:04 +1100 Subject: [PATCH 490/546] update build.sh to support RP2040 and STM32 --- build.sh | 55 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 14 deletions(-) diff --git a/build.sh b/build.sh index 2b7c4824..313c4c47 100755 --- a/build.sh +++ b/build.sh @@ -64,6 +64,8 @@ case $1 in ;; esac +# cache project config json for use in get_platform_for_env() +PIO_CONFIG_JSON=$(pio project config --json-output) # $1 should be the string to find (case insensitive) get_pio_envs_containing_string() { @@ -87,6 +89,25 @@ get_pio_envs_ending_with_string() { done } +# get platform flag for a given environment +# $1 should be the environment name +get_platform_for_env() { + local env_name=$1 + echo "$PIO_CONFIG_JSON" | python3 -c " +import sys, json, re +data = json.load(sys.stdin) +for section, options in data: + if section == 'env:$env_name': + for key, value in options: + if key == 'build_flags': + for flag in value: + match = re.search(r'(ESP32_PLATFORM|NRF52_PLATFORM|STM32_PLATFORM|RP2040_PLATFORM)', flag) + if match: + print(match.group(1)) + sys.exit(0) +" +} + # disable all debug logging flags if DISABLE_DEBUG=1 is set disable_debug_flags() { if [ "$DISABLE_DEBUG" == "1" ]; then @@ -96,6 +117,8 @@ disable_debug_flags() { # build firmware for the provided pio env in $1 build_firmware() { + # get env platform for post build actions + ENV_PLATFORM=($(get_platform_for_env $1)) # get git commit sha COMMIT_HASH=$(git rev-parse --short HEAD) @@ -126,27 +149,31 @@ build_firmware() { # build firmware target pio run -e $1 - # build merge-bin for esp32 fresh install - if [ -f .pio/build/$1/firmware.bin ]; then + # build merge-bin for esp32 fresh install, copy .bins to out folder (e.g: Heltec_v3_room_server-v1.0.0-SHA.bin) + if [ "$ENV_PLATFORM" == "ESP32_PLATFORM" ]; then pio run -t mergebin -e $1 + cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true + cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true fi - # build .uf2 for nrf52 boards - if [[ -f .pio/build/$1/firmware.zip && -f .pio/build/$1/firmware.hex ]]; then + # build .uf2 for nrf52 boards, copy .uf2 and .zip to out folder (e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2) + if [ "$ENV_PLATFORM" == "NRF52_PLATFORM" ]; then python3 bin/uf2conv/uf2conv.py .pio/build/$1/firmware.hex -c -o .pio/build/$1/firmware.uf2 -f 0xADA52840 + cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true + cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true fi - # copy .bin, .uf2, and .zip to out folder - # e.g: Heltec_v3_room_server-v1.0.0-SHA.bin - # e.g: RAK_4631_Repeater-v1.0.0-SHA.uf2 + # for stm32, copy .bin and .hex to out folder + if [ "$ENV_PLATFORM" == "STM32_PLATFORM" ]; then + cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true + cp .pio/build/$1/firmware.hex out/${FIRMWARE_FILENAME}.hex 2>/dev/null || true + fi - # copy .bin for esp32 boards - cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true - cp .pio/build/$1/firmware-merged.bin out/${FIRMWARE_FILENAME}-merged.bin 2>/dev/null || true - - # copy .zip and .uf2 of nrf52 boards - cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true - cp .pio/build/$1/firmware.zip out/${FIRMWARE_FILENAME}.zip 2>/dev/null || true + # for rp2040, copy .bin and .uf2 to out folder + if [ "$ENV_PLATFORM" == "RP2040_PLATFORM" ]; then + cp .pio/build/$1/firmware.bin out/${FIRMWARE_FILENAME}.bin 2>/dev/null || true + cp .pio/build/$1/firmware.uf2 out/${FIRMWARE_FILENAME}.uf2 2>/dev/null || true + fi } From 564a19d125a332bfe8cf20be8d66c792af88c2b2 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 14 Feb 2026 15:50:06 +1100 Subject: [PATCH 491/546] * companion client repeat mode support --- examples/companion_radio/DataStore.cpp | 4 +-- examples/companion_radio/MyMesh.cpp | 42 +++++++++++++++++++++++++- examples/companion_radio/MyMesh.h | 4 ++- examples/companion_radio/NodePrefs.h | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index c0f2c021..1239ea3d 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -212,7 +212,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.read((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.read((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.read(pad, 1); // 62 + file.read((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62 file.read((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.read((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.read((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 @@ -247,7 +247,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.freq, sizeof(_prefs.freq)); // 56 file.write((uint8_t *)&_prefs.sf, sizeof(_prefs.sf)); // 60 file.write((uint8_t *)&_prefs.cr, sizeof(_prefs.cr)); // 61 - file.write(pad, 1); // 62 + file.write((uint8_t *)&_prefs.client_repeat, sizeof(_prefs.client_repeat)); // 62 file.write((uint8_t *)&_prefs.manual_add_contacts, sizeof(_prefs.manual_add_contacts)); // 63 file.write((uint8_t *)&_prefs.bw, sizeof(_prefs.bw)); // 64 file.write((uint8_t *)&_prefs.tx_power_dbm, sizeof(_prefs.tx_power_dbm)); // 68 diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index a9ac1cf0..03a55cd8 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -56,6 +56,7 @@ #define CMD_SEND_ANON_REQ 57 #define CMD_SET_AUTOADD_CONFIG 58 #define CMD_GET_AUTOADD_CONFIG 59 +#define CMD_GET_ALLOWED_REPEAT_FREQ 60 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -88,6 +89,7 @@ #define RESP_CODE_TUNING_PARAMS 23 #define RESP_CODE_STATS 24 // v8+, second byte is stats type #define RESP_CODE_AUTOADD_CONFIG 25 +#define RESP_ALLOWED_REPEAT_FREQ 26 #define SEND_TIMEOUT_BASE_MILLIS 500 #define FLOOD_SEND_TIMEOUT_FACTOR 16.0f @@ -455,6 +457,10 @@ bool MyMesh::filterRecvFloodPacket(mesh::Packet* packet) { return false; } +bool MyMesh::allowPacketForward(const mesh::Packet* packet) { + return _prefs.client_repeat != 0; +} + void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: dynamic send_scope, depending on recipient and current 'home' Region if (send_scope.isNull()) { @@ -881,6 +887,24 @@ uint32_t MyMesh::getBLEPin() { return _active_ble_pin; } +struct FreqRange { + uint32_t lower_freq, upper_freq; +}; + +static FreqRange repeat_freq_ranges[] = { + { 433000, 433000 }, + { 869000, 869000 }, + { 918000, 918000 } +}; + +bool MyMesh::isValidClientRepeatFreq(uint32_t f) const { + for (int i = 0; i < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]); i++) { + auto r = &repeat_freq_ranges[i]; + if (f >= r->lower_freq && f <= r->upper_freq) return true; + } + return false; +} + void MyMesh::startInterface(BaseSerialInterface &serial) { _serial = &serial; serial.enable(); @@ -1208,13 +1232,20 @@ void MyMesh::handleCmdFrame(size_t len) { i += 4; uint8_t sf = cmd_frame[i++]; uint8_t cr = cmd_frame[i++]; + uint8_t repeat = 0; // default - false + if (len > i) { + repeat = cmd_frame[i++]; // FIRMWARE_VER_CODE 9+ + } - if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && + if (repeat && !isValidClientRepeatFreq(freq)) { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } else if (freq >= 300000 && freq <= 2500000 && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7000 && bw <= 500000) { _prefs.sf = sf; _prefs.cr = cr; _prefs.freq = (float)freq / 1000.0; _prefs.bw = (float)bw / 1000.0; + _prefs.client_repeat = repeat; savePrefs(); radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); @@ -1741,6 +1772,15 @@ void MyMesh::handleCmdFrame(size_t len) { out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; out_frame[i++] = _prefs.autoadd_config; _serial->writeFrame(out_frame, i); + } else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) { + int i = 0; + out_frame[i++] = RESP_ALLOWED_REPEAT_FREQ; + for (int k = 0; k < sizeof(repeat_freq_ranges)/sizeof(repeat_freq_ranges[0]) && i + 8 < sizeof(out_frame); k++) { + auto r = &repeat_freq_ranges[k]; + memcpy(&out_frame[i], &r->lower_freq, 4); i += 4; + memcpy(&out_frame[i], &r->upper_freq, 4); i += 4; + } + _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_UNSUPPORTED_CMD); MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 95265a19..ff549771 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,7 +5,7 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 8 +#define FIRMWARE_VER_CODE 9 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "29 Jan 2026" @@ -108,6 +108,7 @@ protected: int calcRxDelay(float score, uint32_t air_time) const override; uint8_t getExtraAckTransmitCount() const override; bool filterRecvFloodPacket(mesh::Packet* packet) override; + bool allowPacketForward(const mesh::Packet* packet) override; void sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis=0) override; void sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis=0) override; @@ -176,6 +177,7 @@ private: void checkCLIRescueCmd(); void checkSerialInterface(); + bool isValidClientRepeatFreq(uint32_t f) const; // helpers, short-cuts void saveChannels() { _store->saveChannels(this); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index d7ddd92a..f2a52f41 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -28,4 +28,5 @@ struct NodePrefs { // persisted to file uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval; // GPS read interval in seconds uint8_t autoadd_config; // bitmask for auto-add contacts config + uint8_t client_repeat; }; \ No newline at end of file From 0abac357445ff790e7b9b58d1987a14d24e13728 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sat, 14 Feb 2026 16:45:41 +1100 Subject: [PATCH 492/546] * client_repeat state now in _DEVICE_INFO response --- examples/companion_radio/MyMesh.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 03a55cd8..87d3091a 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -928,6 +928,7 @@ void MyMesh::handleCmdFrame(size_t len) { i += 40; StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20); i += 20; + out_frame[i++] = _prefs.client_repeat; // v9+ _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_APP_START && len >= 8) { // sent when app establishes connection, respond with node ID From e2571accbec7a24cd73e71bdd01c2ca1f091f6a8 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Sun, 15 Feb 2026 17:24:37 +1100 Subject: [PATCH 493/546] * ver 1.13.0 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index ff549771..1c5813eb 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 9 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "29 Jan 2026" +#define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.12.0" +#define FIRMWARE_VERSION "v1.13.0" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 7a51b4a9..8388e29c 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 "29 Jan 2026" + #define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0" + #define FIRMWARE_VERSION "v1.13.0" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index b4529e77..d21e225f 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -26,11 +26,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "29 Jan 2026" + #define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0" + #define FIRMWARE_VERSION "v1.13.0" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 4bc0d784..7131db75 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -33,11 +33,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "29 Jan 2026" + #define FIRMWARE_BUILD_DATE "15 Feb 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.12.0" + #define FIRMWARE_VERSION "v1.13.0" #endif #define FIRMWARE_ROLE "sensor" From cafc212bb2171b316a34f47e229d4dc42dfbbb65 Mon Sep 17 00:00:00 2001 From: recrof <recrof@gmail.com> Date: Sun, 15 Feb 2026 11:25:27 +0100 Subject: [PATCH 494/546] fix M5Stack Unit M6L build errors --- src/helpers/esp32/SerialBLEInterface.cpp | 1 + variants/m5stack_unit_c6l/platformio.ini | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/helpers/esp32/SerialBLEInterface.cpp b/src/helpers/esp32/SerialBLEInterface.cpp index eccfeca6..dcfa0e1e 100644 --- a/src/helpers/esp32/SerialBLEInterface.cpp +++ b/src/helpers/esp32/SerialBLEInterface.cpp @@ -1,4 +1,5 @@ #include "SerialBLEInterface.h" +#include "esp_mac.h" // See the following for generating UUIDs: // https://www.uuidgenerator.net/ diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index bbfdb4a1..a2b8b087 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -5,7 +5,7 @@ board_build.partitions = min_spiffs.csv ; get around 4mb flash limit build_flags = ${esp32c6_base.build_flags} ${sensor_base.build_flags} - -I variants/M5Stack_Unit_C6L + -I variants/m5stack_unit_c6l -D P_LORA_TX_LED=15 -D P_LORA_SCLK=20 -D P_LORA_MISO=22 From e8785dd9b0fd4ceace644f19d68d42ff8c8a8cbc Mon Sep 17 00:00:00 2001 From: realtag <you@example.com> Date: Mon, 16 Feb 2026 22:35:20 +0000 Subject: [PATCH 495/546] discover sends a single repeater discovery request and populates the neighbor list; self is excluded --- examples/simple_repeater/MyMesh.cpp | 40 +++++++++++++++++++++++++++++ examples/simple_repeater/MyMesh.h | 1 + 2 files changed, 41 insertions(+) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 65e0cee5..e84ce08e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -738,6 +738,37 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { sendZeroHop(resp, getRetransmitDelay(resp)*4); // apply random delay (widened x4), as multiple nodes can respond to this } } + } else if (type == CTL_TYPE_NODE_DISCOVER_RESP && packet->payload_len >= 6) { + uint8_t node_type = packet->payload[0] & 0x0F; + if (node_type != ADV_TYPE_REPEATER) { + return; + } + if (packet->payload_len < 6 + PUB_KEY_SIZE) { + MESH_DEBUG_PRINTLN("onControlDataRecv: DISCOVER_RESP pubkey too short: %d", (uint32_t)packet->payload_len); + return; + } + + mesh::Identity id(&packet->payload[6]); + if (id.matches(self_id)) { + return; + } + putNeighbour(id, rtc_clock.getCurrentTime(), packet->getSNR()); + } +} + +void MyMesh::sendNodeDiscoverReq() { + if (_prefs.disable_fwd) return; + + uint8_t data[10]; + data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0 + data[1] = (1 << ADV_TYPE_REPEATER); + getRNG()->random(&data[2], 4); // tag + uint32_t since = 0; + memcpy(&data[6], &since, 4); + + auto pkt = createControlData(data, sizeof(data)); + if (pkt) { + sendZeroHop(pkt); } } @@ -1168,6 +1199,15 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - ??"); } + } else if (memcmp(command, "discover", 8) == 0) { + const char* sub = command + 8; + while (*sub == ' ') sub++; + if (*sub != 0) { + strcpy(reply, "Err - discover has no options"); + } else { + sendNodeDiscoverReq(); + strcpy(reply, "OK - Discover sent"); + } } else{ _cli.handleCommand(sender_timestamp, command, reply); // common CLI commands } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 8388e29c..6cded9cf 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -116,6 +116,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #endif void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr); + void sendNodeDiscoverReq(); uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood); uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data); From 87c78a98bdef070b60f694eb8c4f43fb6bd57d83 Mon Sep 17 00:00:00 2001 From: realtag <you@example.com> Date: Tue, 17 Feb 2026 01:04:14 +0000 Subject: [PATCH 496/546] discover.neighbors sends a tagged repeater discovery request and only accepts matching repeater responses --- examples/simple_repeater/MyMesh.cpp | 21 ++++++++++++++++++--- examples/simple_repeater/MyMesh.h | 2 ++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e84ce08e..aec4ff3e 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -748,6 +748,16 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { return; } + if (pending_discover_tag == 0 || millisHasNowPassed(pending_discover_until)) { + pending_discover_tag = 0; + return; + } + uint32_t tag; + memcpy(&tag, &packet->payload[2], 4); + if (tag != pending_discover_tag) { + return; + } + mesh::Identity id(&packet->payload[6]); if (id.matches(self_id)) { return; @@ -763,6 +773,8 @@ void MyMesh::sendNodeDiscoverReq() { data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0 data[1] = (1 << ADV_TYPE_REPEATER); getRNG()->random(&data[2], 4); // tag + memcpy(&pending_discover_tag, &data[2], 4); + pending_discover_until = futureMillis(30000); uint32_t since = 0; memcpy(&data[6], &since, 4); @@ -832,6 +844,9 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.advert_loc_policy = ADVERT_LOC_PREFS; _prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier + + pending_discover_tag = 0; + pending_discover_until = 0; } void MyMesh::begin(FILESYSTEM *fs) { @@ -1199,11 +1214,11 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply } else { strcpy(reply, "Err - ??"); } - } else if (memcmp(command, "discover", 8) == 0) { - const char* sub = command + 8; + } else if (memcmp(command, "discover.neighbors", 18) == 0) { + const char* sub = command + 18; while (*sub == ' ') sub++; if (*sub != 0) { - strcpy(reply, "Err - discover has no options"); + strcpy(reply, "Err - discover.neighbors has no options"); } else { sendNodeDiscoverReq(); strcpy(reply, "OK - Discover sent"); diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 6cded9cf..f0e7cc10 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -97,6 +97,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { RegionEntry* load_stack[8]; RegionEntry* recv_pkt_region; RateLimiter discover_limiter, anon_limiter; + uint32_t pending_discover_tag; + unsigned long pending_discover_until; bool region_load_active; unsigned long dirty_contacts_expiry; #if MAX_NEIGHBOURS From bf9c6cb50f91de89dfbc7dffc24653f68816eda7 Mon Sep 17 00:00:00 2001 From: realtag <you@example.com> Date: Tue, 17 Feb 2026 01:22:17 +0000 Subject: [PATCH 497/546] Increased the timeout timer to 60 seconds, up from 30 seconds. --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index aec4ff3e..e2bf0330 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -774,7 +774,7 @@ void MyMesh::sendNodeDiscoverReq() { data[1] = (1 << ADV_TYPE_REPEATER); getRNG()->random(&data[2], 4); // tag memcpy(&pending_discover_tag, &data[2], 4); - pending_discover_until = futureMillis(30000); + pending_discover_until = futureMillis(60000); uint32_t since = 0; memcpy(&data[6], &since, 4); From 0770618ee2329efaead80bfb78986ee459fd2db4 Mon Sep 17 00:00:00 2001 From: realtag <you@example.com> Date: Tue, 17 Feb 2026 01:39:04 +0000 Subject: [PATCH 498/546] Allow repeater discovery even if repeater mode is disabled on the requesting repeater. --- examples/simple_repeater/MyMesh.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index e2bf0330..20be1010 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -767,8 +767,6 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) { } void MyMesh::sendNodeDiscoverReq() { - if (_prefs.disable_fwd) return; - uint8_t data[10]; data[0] = CTL_TYPE_NODE_DISCOVER_REQ; // prefix_only=0 data[1] = (1 << ADV_TYPE_REPEATER); From 3e53df5082ec9da139c2ecd1dfd067ceadeff63a Mon Sep 17 00:00:00 2001 From: 3DPGG <3dpgg@protonmail.com> Date: Mon, 16 Feb 2026 17:41:52 -0800 Subject: [PATCH 499/546] Fix LilyGo_TLora_V2_1_1_6_terminal_chat build This change addresses two issues. The first is that the LilyGo_TLora_V2_1_1_6_terminal_chat build would try to compile simple_repeater/MyMesh.cpp. All other examples of terminal chat targets are instead building simple_secure_chat/main.cpp . This change would align this build to the rest of the builds. The second issue, found during the course of investigating the first, stems from simple_repeater/MyMesh.cpp using the MAX_NEIGHBOURS #define to control whether the neighbor list is kept. Repeaters that keep this list must define this value, and if the value is not defined, then all neighbor-related functionality is compiled out. However, the code that replies to REQ_TYPE_GET_NEIGHBOURS did not properly check for this #define, and thus any target that compiles simple_repeater/MyMesh.cpp without defining MAX_NEIGHBOURS would get an undefined variable compilation error. As a practical matter though, there are no targets that compile simple_repeater/MyMesh.cpp AND do not define MAX_NEIGHBOURS, except this build due to the first issue. As a result, the second issue is addressed only as a matter of completeness. The expected behavior with this change is that such a repeater would send a valid reply indicating zero known neighbors. --- examples/simple_repeater/MyMesh.cpp | 4 ++++ variants/lilygo_tlora_v2_1/platformio.ini | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 65e0cee5..edbc2c42 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -292,6 +292,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t // create copy of neighbours list, skipping empty entries so we can sort it separately from main list int16_t neighbours_count = 0; +#if MAX_NEIGHBOURS NeighbourInfo* sorted_neighbours[MAX_NEIGHBOURS]; for (int i = 0; i < MAX_NEIGHBOURS; i++) { auto neighbour = &neighbours[i]; @@ -327,6 +328,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t return a->snr < b->snr; // asc }); } +#endif // build results buffer int results_count = 0; @@ -341,6 +343,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t break; } +#if MAX_NEIGHBOURS // add next neighbour to results auto neighbour = sorted_neighbours[index + offset]; uint32_t heard_seconds_ago = getRTCClock()->getCurrentTime() - neighbour->heard_timestamp; @@ -348,6 +351,7 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t memcpy(&results_buffer[results_offset], &heard_seconds_ago, 4); results_offset += 4; memcpy(&results_buffer[results_offset], &neighbour->snr, 1); results_offset += 1; results_count++; +#endif } diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index c28f9001..7e1330e6 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -65,7 +65,7 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} - +<../examples/simple_repeater> + +<../examples/simple_secure_chat/main.cpp> lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} densaugeo/base64 @ ~1.4.0 From 5de3e1bf32fd1a9f1d1966c92f94b5414ef44b84 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 17 Feb 2026 20:10:13 +1100 Subject: [PATCH 500/546] * repeater: slight increase to default direct.txdelay --- examples/simple_repeater/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index edbc2c42..692fcafe 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -775,7 +775,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc _prefs.airtime_factor = 1.0; // one half _prefs.rx_delay_base = 0.0f; // turn off by default, was 10.0; _prefs.tx_delay_factor = 0.5f; // was 0.25f - _prefs.direct_tx_delay_factor = 0.2f; // was zero + _prefs.direct_tx_delay_factor = 0.3f; // was 0.2 StrHelper::strncpy(_prefs.node_name, ADVERT_NAME, sizeof(_prefs.node_name)); _prefs.node_lat = ADVERT_LAT; _prefs.node_lon = ADVERT_LON; From 2e0029812883c14485833064ec2e8ed401dc17c9 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 17 Feb 2026 20:25:56 +1100 Subject: [PATCH 501/546] * companion: retransmit delays now hard-coded (only for client repeat mode) --- examples/companion_radio/MyMesh.cpp | 9 +++++++++ examples/companion_radio/MyMesh.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 87d3091a..99b14952 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -257,6 +257,15 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { return (int)((pow(_prefs.rx_delay_base, 0.85f - score) - 1.0) * air_time); } +uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.5f); + return getRNG()->nextInt(0, 5*t + 1); +} +uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { + uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.2f); + return getRNG()->nextInt(0, 5*t + 1); +} + uint8_t MyMesh::getExtraAckTransmitCount() const { return _prefs.multi_acks; } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 1c5813eb..e3c10985 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -106,6 +106,8 @@ protected: float getAirtimeBudgetFactor() const override; int getInterferenceThreshold() const override; int calcRxDelay(float score, uint32_t air_time) const override; + uint32_t getRetransmitDelay(const mesh::Packet *packet) override; + uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override; uint8_t getExtraAckTransmitCount() const override; bool filterRecvFloodPacket(mesh::Packet* packet) override; bool allowPacketForward(const mesh::Packet* packet) override; From ffc9815e9a26a01245f6032fa341d0ba88d8b41d Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Tue, 17 Feb 2026 23:54:33 +0100 Subject: [PATCH 502/546] Fix packet pool leak when rx queue is full MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PacketQueue::add() silently dropped packets when the queue was at capacity. The packet pointer was lost — never enqueued, never returned to the unused pool. Each occurrence permanently shrank the 32-packet pool until allocNew() returned NULL and the node went deaf. Return bool from add() and free the packet back to the pool on failure. --- src/helpers/StaticPoolPacketManager.cpp | 16 +++++++++++----- src/helpers/StaticPoolPacketManager.h | 2 +- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 4f28eac6..125efb75 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -55,15 +55,15 @@ mesh::Packet* PacketQueue::removeByIdx(int i) { return item; } -void PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { +bool PacketQueue::add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { if (_num == _size) { - // TODO: log "FATAL: queue is full!" - return; + return false; } _table[_num] = packet; _pri_table[_num] = priority; _schedule_table[_num] = scheduled_for; _num++; + return true; } StaticPoolPacketManager::StaticPoolPacketManager(int pool_size): unused(pool_size), send_queue(pool_size), rx_queue(pool_size) { @@ -82,7 +82,10 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) { } void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) { - send_queue.add(packet, priority, scheduled_for); + if (!send_queue.add(packet, priority, scheduled_for)) { + MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet"); + free(packet); + } } mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) { @@ -106,7 +109,10 @@ mesh::Packet* StaticPoolPacketManager::removeOutboundByIdx(int i) { } void StaticPoolPacketManager::queueInbound(mesh::Packet* packet, uint32_t scheduled_for) { - rx_queue.add(packet, 0, scheduled_for); + if (!rx_queue.add(packet, 0, scheduled_for)) { + MESH_DEBUG_PRINTLN("queueInbound: rx queue full, dropping packet"); + free(packet); + } } mesh::Packet* StaticPoolPacketManager::getNextInbound(uint32_t now) { return rx_queue.get(now); diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index bbf4b193..52c299db 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -11,7 +11,7 @@ class PacketQueue { public: PacketQueue(int max_entries); mesh::Packet* get(uint32_t now); - void add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); + bool add(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for); int count() const { return _num; } int countBefore(uint32_t now) const; mesh::Packet* itemAt(int i) const { return _table[i]; } From 1500a5a9cbfe2df8bbd06ba08e5e58bd5f319f5e Mon Sep 17 00:00:00 2001 From: taco <taco@sly.nu> Date: Wed, 18 Feb 2026 15:35:20 +1100 Subject: [PATCH 503/546] add get bootloader.ver command for nrf52 --- src/MeshCore.h | 1 + src/helpers/CommonCLI.cpp | 11 +++++++++++ src/helpers/NRF52Board.cpp | 19 +++++++++++++++++++ src/helpers/NRF52Board.h | 1 + 4 files changed, 32 insertions(+) diff --git a/src/MeshCore.h b/src/MeshCore.h index f194cdeb..70cd0f06 100644 --- a/src/MeshCore.h +++ b/src/MeshCore.h @@ -55,6 +55,7 @@ public: virtual uint32_t getGpio() { return 0; } virtual void setGpio(uint32_t values) {} virtual uint8_t getStartupReason() const = 0; + virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; } virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported // Power management interface (boards with power management override these) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 6dcf7018..263eb665 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -362,6 +362,17 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch } else if (memcmp(config, "bridge.secret", 13) == 0) { sprintf(reply, "> %s", _prefs->bridge_secret); #endif + } else if (memcmp(config, "bootloader.ver", 14) == 0) { + #ifdef NRF52_PLATFORM + char ver[32]; + if (_board->getBootloaderVersion(ver, sizeof(ver))) { + sprintf(reply, "> %s", ver); + } else { + strcpy(reply, "> unknown"); + } + #else + strcpy(reply, "ERROR: unsupported"); + #endif } else if (memcmp(config, "adc.multiplier", 14) == 0) { float adc_mult = _board->getAdcMultiplier(); if (adc_mult == 0.0f) { diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index 1db858f5..2c8753d4 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -297,6 +297,25 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } +bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) { + static const char BOOTLOADER_MARKER[] = "UF2 Bootloader "; + const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B + + for (uint32_t i = 0; i < 0x3000 - (sizeof(BOOTLOADER_MARKER) - 1); i++) { + if (memcmp(&flash[i], BOOTLOADER_MARKER, sizeof(BOOTLOADER_MARKER) - 1) == 0) { + const char* ver = (const char*)&flash[i + sizeof(BOOTLOADER_MARKER) - 1]; + size_t len = 0; + while (len < max_len - 1 && ver[len] != '\0' && ver[len] != ' ' && ver[len] != '\n' && ver[len] != '\r') { + out[len] = ver[len]; + len++; + } + out[len] = '\0'; + return len > 0; // bootloader string is non-empty + } + } + return false; +} + bool NRF52Board::startOTAUpdate(const char *id, char reply[]) { // Config the peripheral connection with maximum bandwidth // more SRAM required by SoftDevice diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index 0332af07..96f67dc9 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -50,6 +50,7 @@ public: virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual bool getBootloaderVersion(char* version, size_t max_len) override; virtual bool startOTAUpdate(const char *id, char reply[]) override; virtual void sleep(uint32_t secs) override; From 063f5056f23b7a3999016f6b60028bd724c3837b Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Mon, 2 Feb 2026 11:21:00 +0700 Subject: [PATCH 504/546] Fixed RefCountedDigitalPin.h to release claim correctly. Ensure no negative claims number. --- src/helpers/RefCountedDigitalPin.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 753f6c30..4cf53cda 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -20,10 +20,12 @@ public: digitalWrite(_pin, _active); } } + void release() { - _claims--; if (_claims == 0) { digitalWrite(_pin, !_active); + } else { + _claims--; } } }; From 39fb2902ec5653971a62fb308b9d2e56e77f7480 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Thu, 5 Feb 2026 22:42:02 +0700 Subject: [PATCH 505/546] Avoid negative _claims --- src/helpers/RefCountedDigitalPin.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/helpers/RefCountedDigitalPin.h b/src/helpers/RefCountedDigitalPin.h index 4cf53cda..f30c4c58 100644 --- a/src/helpers/RefCountedDigitalPin.h +++ b/src/helpers/RefCountedDigitalPin.h @@ -22,10 +22,11 @@ public: } void release() { + if (_claims == 0) return; // avoid negative _claims + + _claims--; if (_claims == 0) { digitalWrite(_pin, !_active); - } else { - _claims--; } } }; From f6603fe7a5edf8b8197e45b7e909c6e781507511 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Thu, 5 Feb 2026 23:26:08 +0700 Subject: [PATCH 506/546] Set back PIN_VEXT_EN_ACTIVE=HIGH --- variants/heltec_v4/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c5011e0e..fdddcd5d 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -22,7 +22,7 @@ build_flags = -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 - -D PIN_VEXT_EN_ACTIVE=LOW + -D PIN_VEXT_EN_ACTIVE=HIGH -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX From 44b80d00c202a9783773086ef93f64bc0ce85457 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Thu, 5 Feb 2026 23:27:10 +0700 Subject: [PATCH 507/546] Disabled periph_power for Heltec v4's display --- variants/heltec_v4/target.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index f971cc60..54fc05e8 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -24,7 +24,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #endif #ifdef DISPLAY_CLASS - DISPLAY_CLASS display(&(board.periph_power)); + DISPLAY_CLASS display(NULL); MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #endif From 13d0dff9182bf7931b9b6c87b67d7e5120301099 Mon Sep 17 00:00:00 2001 From: Kevin Le <vinhdat82@gmail.com> Date: Wed, 18 Feb 2026 22:29:33 +0700 Subject: [PATCH 508/546] Reverted to use GPIO 17, 18 as I2C for Heltec v4 repeater --- variants/heltec_v4/platformio.ini | 2 -- 1 file changed, 2 deletions(-) diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index fdddcd5d..71ffc2e6 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -54,8 +54,6 @@ build_flags = -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_OLED_RESET=21 - -D ENV_PIN_SDA=4 - -D ENV_PIN_SCL=3 build_src_filter= ${Heltec_lora32_v4.build_src_filter} lib_deps = ${Heltec_lora32_v4.lib_deps} From 3e76161e9cacf739b5a575d3d897a13ce7c0c2e1 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 19 Feb 2026 14:37:51 +1100 Subject: [PATCH 509/546] * refactor of Contact/Client out_path_len (stored in files), from signed to unsigned byte (+2 squashed commits) Squashed commits: [f326e25] * misc [fa5152e] * new 'path mode' parsing in Dispatcher --- examples/companion_radio/MyMesh.cpp | 15 ++--- examples/simple_repeater/MyMesh.cpp | 61 ++++++++++-------- examples/simple_repeater/MyMesh.h | 1 + examples/simple_room_server/MyMesh.cpp | 43 ++++++------- examples/simple_secure_chat/main.cpp | 5 +- examples/simple_sensor/SensorMesh.cpp | 34 +++++----- examples/simple_sensor/SensorMesh.h | 2 +- src/Dispatcher.cpp | 86 ++++++++++++++------------ src/Dispatcher.h | 1 + src/Identity.h | 4 ++ src/Mesh.cpp | 74 +++++++++++----------- src/Mesh.h | 4 +- src/Packet.cpp | 13 +++- src/Packet.h | 8 +++ src/helpers/BaseChatMesh.cpp | 28 ++++----- src/helpers/ClientACL.cpp | 2 +- src/helpers/ClientACL.h | 4 +- src/helpers/ContactInfo.h | 4 +- 18 files changed, 222 insertions(+), 167 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 99b14952..f0f8e5fc 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -258,11 +258,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.5f); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.5f); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * 0.2f); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * 0.2f); return getRNG()->nextInt(0, 5*t + 1); } @@ -678,7 +678,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, } } -bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { +bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _in_path_len, uint8_t* out_path, uint8_t _out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 4) { uint32_t tag; memcpy(&tag, extra, 4); @@ -785,9 +785,10 @@ uint32_t MyMesh::calcFloodTimeoutMillisFor(uint32_t pkt_airtime_millis) const { return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } uint32_t MyMesh::calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const { + uint8_t path_hash_count = path_len & 63; return SEND_TIMEOUT_BASE_MILLIS + ((pkt_airtime_millis * DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * - (path_len + 1)); + (path_hash_count + 1)); } void MyMesh::onSendTimeout() {} @@ -1115,7 +1116,7 @@ void MyMesh::handleCmdFrame(size_t len) { } if (pkt) { if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) - sendFlood(pkt); + sendFlood(pkt); // TODO: which path_hash_size to use?? } else { sendZeroHop(pkt); } @@ -1127,7 +1128,7 @@ void MyMesh::handleCmdFrame(size_t len) { uint8_t *pub_key = &cmd_frame[1]; ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE); if (recipient) { - recipient->out_path_len = -1; + recipient->out_path_len = OUT_PATH_UNKNOWN; // recipient->lastmod = ?? shouldn't be needed, app already has this version of contact dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); writeOKFrame(); @@ -1449,7 +1450,7 @@ void MyMesh::handleCmdFrame(size_t len) { memset(&req_data[2], 0, 3); // reserved getRNG()->random(&req_data[5], 4); // random blob to help make packet-hash unique auto save = recipient->out_path_len; // temporarily force sendRequest() to flood - recipient->out_path_len = -1; + recipient->out_path_len = OUT_PATH_UNKNOWN; int result = sendRequest(*recipient, req_data, sizeof(req_data), tag, est_timeout); recipient->out_path_len = save; if (result == MSG_SEND_FAILED) { diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index b6d855f6..7328f703 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -129,7 +129,7 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr } if (is_flood) { - client->out_path_len = -1; // need to rediscover out_path + client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -147,9 +147,12 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data++ & 0x3F; - memcpy(reply_path, data, reply_path_len); - // data += reply_path_len; + reply_path_len = *data & 63; + reply_path_hash_size = (*data >> 6) + 1; + data++; + + memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -163,9 +166,12 @@ uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t send uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data++ & 0x3F; - memcpy(reply_path, data, reply_path_len); - // data += reply_path_len; + reply_path_len = *data & 63; + reply_path_hash_size = (*data >> 6) + 1; + data++; + + memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -180,9 +186,12 @@ uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) { if (anon_limiter.allow(rtc_clock.getCurrentTime())) { // request data has: {reply-path-len}{reply-path} - reply_path_len = *data++ & 0x3F; - memcpy(reply_path, data, reply_path_len); - // data += reply_path_len; + reply_path_len = *data & 63; + reply_path_hash_size = (*data >> 6) + 1; + data++; + + memcpy(reply_path, data, ((uint8_t)reply_path_len) * reply_path_hash_size); + // data += (uint8_t)reply_path_len * reply_path_hash_size; memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag uint32_t now = getRTCClock()->getCurrentTime(); @@ -389,7 +398,7 @@ File MyMesh::openAppend(const char *fname) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; if (packet->isRouteFlood() && recv_pkt_region == NULL) { MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); return false; @@ -484,11 +493,11 @@ int MyMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } @@ -538,13 +547,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else if (reply_path_len < 0) { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY); + uint8_t path_len = ((reply_path_hash_size - 1) << 6) | (reply_path_len & 63); + if (reply) sendDirect(reply, reply_path, path_len, SERVER_RESPONSE_DELAY); } } } @@ -613,15 +623,15 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -651,8 +661,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, mesh::Packet *ack = createAck(ack_hash); if (ack) { - if (client->out_path_len < 0) { - sendFlood(ack, TXT_ACK_DELAY); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize()); } else { sendDirect(ack, client->out_path, client->out_path_len, TXT_ACK_DELAY); } @@ -679,8 +689,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } else { sendDirect(reply, client->out_path, client->out_path_len, CLI_REPLY_DELAY_MILLIS); } @@ -701,7 +711,8 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); auto client = acl.getClientByIdx(i); - memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + // store a copy of path, for sendDirect() + client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); @@ -906,7 +917,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use?? } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index f0e7cc10..591f6366 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -92,6 +92,7 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { uint8_t reply_data[MAX_PACKET_PAYLOAD]; uint8_t reply_path[MAX_PATH_SIZE]; int8_t reply_path_len; + uint8_t reply_path_hash_size; TransportKeyStore key_store; RegionMap region_map, temp_map; RegionEntry* load_stack[8]; diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 598b14de..0b1d3a1b 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -73,13 +73,14 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply); // TODO: which path_hash_size to use? client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); - client->extra.room.ack_timeout = - futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (client->out_path_len + 1)); + + uint8_t path_hash_count = client->out_path_len & 63; + client->extra.room.ack_timeout = futureMillis(PUSH_TIMEOUT_BASE + PUSH_ACK_TIMEOUT_FACTOR * (path_hash_count + 1)); } _num_post_pushes++; // stats } else { @@ -264,17 +265,17 @@ const char *MyMesh::getLogDateTime() { } uint32_t MyMesh::getRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 5*t + 1); } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; return true; } @@ -333,7 +334,7 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m } if (packet->isRouteFlood()) { - client->out_path_len = -1; // need to rediscover out_path + client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -353,14 +354,14 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(sender, client->shared_secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, 13); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, client->shared_secret, reply_data, 13); if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -448,9 +449,9 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, uint32_t delay_millis; if (send_ack) { - if (client->out_path_len < 0) { + if (client->out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet *ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); + if (ack) sendFlood(ack, TXT_ACK_DELAY, packet->getPathHashSize()); delay_millis = TXT_ACK_DELAY + REPLY_DELAY_MILLIS; } else { uint32_t d = TXT_ACK_DELAY; @@ -482,8 +483,8 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, secret, temp, 5 + text_len); if (reply) { - if (client->out_path_len < 0) { - sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY); + if (client->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply, delay_millis + SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { sendDirect(reply, client->out_path, client->out_path_len, delay_millis + SERVER_RESPONSE_DELAY); } @@ -521,7 +522,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // if client sends too quickly, evict() // RULE: only send keep_alive response DIRECT! - if (client->out_path_len >= 0) { + if (client->out_path_len != OUT_PATH_UNKNOWN) { uint32_t ack_hash; // calc ACK to prove to sender that we got request mesh::Utils::sha256((uint8_t *)&ack_hash, 4, data, 9, client->id.pub_key, PUB_KEY_SIZE); @@ -538,14 +539,14 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx, // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet *path = createPathReturn(client->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet *reply = createDatagram(PAYLOAD_TYPE_RESPONSE, client->id, secret, reply_data, reply_len); if (reply) { - if (client->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (client->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -563,7 +564,7 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t if (i >= 0 && i < acl.getNumClients()) { // get from our known_clients table (sender SHOULD already be known in this context) MESH_DEBUG_PRINTLN("PATH to client, path_len=%d", (uint32_t)path_len); auto client = acl.getClientByIdx(i); - memcpy(client->out_path, path, client->out_path_len = path_len); // store a copy of path, for sendDirect() + client->out_path_len = mesh::Packet::copyPath(client->out_path, path, path_len); // store a copy of path, for sendDirect() client->last_activity = getRTCClock()->getCurrentTime(); } else { MESH_DEBUG_PRINTLN("onPeerPathRecv: invalid peer idx: %d", i); @@ -679,7 +680,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use? } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_secure_chat/main.cpp b/examples/simple_secure_chat/main.cpp index a389ec74..c1ed710a 100644 --- a/examples/simple_secure_chat/main.cpp +++ b/examples/simple_secure_chat/main.cpp @@ -213,7 +213,7 @@ protected: } void onContactPathUpdated(const ContactInfo& contact) override { - Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (int32_t) contact.out_path_len); + Serial.printf("PATH to: %s, path_len=%d\n", contact.name, (uint32_t) contact.out_path_len); saveContacts(); } @@ -266,8 +266,9 @@ protected: return SEND_TIMEOUT_BASE_MILLIS + (FLOOD_SEND_TIMEOUT_FACTOR * pkt_airtime_millis); } uint32_t calcDirectTimeoutMillisFor(uint32_t pkt_airtime_millis, uint8_t path_len) const override { + uint8_t path_hash_count = path_len & 63; return SEND_TIMEOUT_BASE_MILLIS + - ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_len + 1)); + ( (pkt_airtime_millis*DIRECT_SEND_PERHOP_FACTOR + DIRECT_SEND_PERHOP_EXTRA_MILLIS) * (path_hash_count + 1)); } void onSendTimeout() override { diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index f05fb245..fc9ef310 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -258,7 +258,7 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, c->id, c->shared_secret, data, 5 + text_len); if (pkt) { - if (c->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(pkt, c->out_path, c->out_path_len); } else { sendFlood(pkt); @@ -302,7 +302,7 @@ float SensorMesh::getAirtimeBudgetFactor() const { bool SensorMesh::allowPacketForward(const mesh::Packet* packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood() && packet->path_len >= _prefs.flood_max) return false; + if (packet->isRouteFlood() && packet->getPathHashCount() >= _prefs.flood_max) return false; return true; } @@ -312,11 +312,11 @@ int SensorMesh::calcRxDelay(float score, uint32_t air_time) const { } uint32_t SensorMesh::getRetransmitDelay(const mesh::Packet* packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.tx_delay_factor); return getRNG()->nextInt(0, 6)*t; } uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) { - uint32_t t = (_radio->getEstAirtimeFor(packet->path_len + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); + uint32_t t = (_radio->getEstAirtimeFor(packet->getPathByteLen() + packet->payload_len + 2) * _prefs.direct_tx_delay_factor); return getRNG()->nextInt(0, 6)*t; } int SensorMesh::getInterferenceThreshold() const { @@ -360,7 +360,7 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* } if (is_flood) { - client->out_path_len = -1; // need to rediscover out_path + client->out_path_len = OUT_PATH_UNKNOWN; // need to rediscover out_path } uint32_t now = getRTCClock()->getCurrentTimeUnique(); @@ -468,10 +468,10 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY); + if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -496,10 +496,10 @@ void SensorMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) { } } -void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash) { - if (dest.out_path_len < 0) { +void SensorMesh::sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size) { + if (dest.out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet* ack = createAck(ack_hash); - if (ack) sendFlood(ack, TXT_ACK_DELAY); + if (ack) sendFlood(ack, TXT_ACK_DELAY, path_hash_size); } else { uint32_t d = TXT_ACK_DELAY; if (getExtraAckTransmitCount() > 0) { @@ -537,14 +537,14 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); - if (path) sendFlood(path, SERVER_RESPONSE_DELAY); + if (path) sendFlood(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from->id, secret, reply_data, reply_len); if (reply) { - if (from->out_path_len >= 0) { // we have an out_path, so send DIRECT + if (from->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, from->out_path, from->out_path_len, SERVER_RESPONSE_DELAY); } else { - sendFlood(reply, SERVER_RESPONSE_DELAY); + sendFlood(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } @@ -569,7 +569,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); if (path) sendFlood(path, TXT_ACK_DELAY); } else { - sendAckTo(*from, ack_hash); + sendAckTo(*from, ack_hash, packet->getPathHashSize()); } } } else if (flags == TXT_TYPE_CLI_DATA) { @@ -596,8 +596,8 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, from->id, secret, temp, 5 + text_len); if (reply) { - if (from->out_path_len < 0) { - sendFlood(reply, CLI_REPLY_DELAY_MILLIS); + if (from->out_path_len == OUT_PATH_UNKNOWN) { + sendFlood(reply, CLI_REPLY_DELAY_MILLIS, packet->getPathHashSize()); } else { sendDirect(reply, from->out_path, from->out_path_len, CLI_REPLY_DELAY_MILLIS); } @@ -666,7 +666,7 @@ bool SensorMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint MESH_DEBUG_PRINTLN("PATH to contact, path_len=%d", (uint32_t) path_len); // NOTE: for this impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from->out_path, path, from->out_path_len = path_len); // store a copy of path, for sendDirect() + from->out_path_len = mesh::Packet::copyPath(from->out_path, path, path_len); // store a copy of path, for sendDirect() from->last_activity = getRTCClock()->getCurrentTime(); // REVISIT: maybe make ALL out_paths non-persisted to minimise flash writes?? diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 7131db75..b15a400a 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -128,7 +128,7 @@ protected: void onControlDataRecv(mesh::Packet* packet) override; void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override; virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len); - void sendAckTo(const ClientInfo& dest, uint32_t ack_hash); + void sendAckTo(const ClientInfo& dest, uint32_t ack_hash, uint8_t path_hash_size=1); private: FILESYSTEM* _fs; unsigned long next_local_advert, next_flood_advert; diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 0a154985..12889fb8 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -108,6 +108,48 @@ void Dispatcher::loop() { checkSend(); } +bool Dispatcher::tryParsePacket(Packet* pkt, const uint8_t* raw, int len) { + int i = 0; + + pkt->header = raw[i++]; + if (pkt->getPayloadVer() > PAYLOAD_VER_1) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported packet version", getLogDateTime()); + return false; + } + + if (pkt->hasTransportCodes()) { + memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2; + memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2; + } else { + pkt->transport_codes[0] = pkt->transport_codes[1] = 0; + } + + pkt->path_len = raw[i++]; + uint8_t path_mode = pkt->path_len >> 6; // upper 2 bits (legacy firmware: 00) + if (path_mode == 3) { // Reserved for future + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported path mode: 3", getLogDateTime()); + return false; + } + + uint8_t path_byte_len = (pkt->path_len & 63) * pkt->getPathHashSize(); + if (path_byte_len > MAX_PATH_SIZE || i + path_byte_len > len) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len); + return false; + } + + memcpy(pkt->path, &raw[i], path_byte_len); i += path_byte_len; + + pkt->payload_len = len - i; // payload is remainder + if (pkt->payload_len > sizeof(pkt->payload)) { + MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len); + return false; + } + + memcpy(pkt->payload, &raw[i], pkt->payload_len); + + return true; // success +} + void Dispatcher::checkRecv() { Packet* pkt; float score; @@ -122,45 +164,14 @@ void Dispatcher::checkRecv() { if (pkt == NULL) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): WARNING: received data, no unused packets available!", getLogDateTime()); } else { - int i = 0; -#ifdef NODE_ID - uint8_t sender_id = raw[i++]; - if (sender_id == NODE_ID - 1 || sender_id == NODE_ID + 1) { // simulate that NODE_ID can only hear NODE_ID-1 or NODE_ID+1, eg. 3 can't hear 1 + if (tryParsePacket(pkt, raw, len)) { + pkt->_snr = _radio->getLastSNR() * 4.0f; + score = _radio->packetScore(_radio->getLastSNR(), len); + air_time = _radio->getEstAirtimeFor(len); + rx_air_time += air_time; } else { - _mgr->free(pkt); // put back into pool - return; - } -#endif - - pkt->header = raw[i++]; - if (pkt->hasTransportCodes()) { - memcpy(&pkt->transport_codes[0], &raw[i], 2); i += 2; - memcpy(&pkt->transport_codes[1], &raw[i], 2); i += 2; - } else { - pkt->transport_codes[0] = pkt->transport_codes[1] = 0; - } - pkt->path_len = raw[i++]; - - if (pkt->path_len > MAX_PATH_SIZE || i + pkt->path_len > len) { - MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): partial or corrupt packet received, len=%d", getLogDateTime(), len); _mgr->free(pkt); // put back into pool pkt = NULL; - } else { - memcpy(pkt->path, &raw[i], pkt->path_len); i += pkt->path_len; - - pkt->payload_len = len - i; // payload is remainder - if (pkt->payload_len > sizeof(pkt->payload)) { - MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): packet payload too big, payload_len=%d", getLogDateTime(), (uint32_t)pkt->payload_len); - _mgr->free(pkt); // put back into pool - pkt = NULL; - } else { - memcpy(pkt->payload, &raw[i], pkt->payload_len); - - pkt->_snr = _radio->getLastSNR() * 4.0f; - score = _radio->packetScore(_radio->getLastSNR(), len); - air_time = _radio->getEstAirtimeFor(len); - rx_air_time += air_time; - } } } } else { @@ -249,9 +260,6 @@ void Dispatcher::checkSend() { int len = 0; uint8_t raw[MAX_TRANS_UNIT]; -#ifdef NODE_ID - raw[len++] = NODE_ID; -#endif raw[len++] = outbound->header; if (outbound->hasTransportCodes()) { memcpy(&raw[len], &outbound->transport_codes[0], 2); len += 2; diff --git a/src/Dispatcher.h b/src/Dispatcher.h index 25a41d82..0a448c40 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -184,6 +184,7 @@ public: unsigned long futureMillis(int millis_from_now) const; private: + bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len); void checkRecv(); void checkSend(); }; diff --git a/src/Identity.h b/src/Identity.h index c3ffcd75..008f7b5b 100644 --- a/src/Identity.h +++ b/src/Identity.h @@ -20,6 +20,10 @@ public: memcpy(dest, pub_key, PATH_HASH_SIZE); // hash is just prefix of pub_key return PATH_HASH_SIZE; } + int copyHashTo(uint8_t* dest, uint8_t len) const { + memcpy(dest, pub_key, len); // hash is just prefix of pub_key + return len; + } bool isHashMatch(const uint8_t* hash) const { return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0; } diff --git a/src/Mesh.cpp b/src/Mesh.cpp index 0548c907..57fee140 100644 --- a/src/Mesh.cpp +++ b/src/Mesh.cpp @@ -39,11 +39,6 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int } DispatcherAction Mesh::onRecvPacket(Packet* pkt) { - if (pkt->getPayloadVer() > PAYLOAD_VER_1) { // not supported in this firmware version - MESH_DEBUG_PRINTLN("%s Mesh::onRecvPacket(): unsupported packet version", getLogDateTime()); - return ACTION_RELEASE; - } - if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) { if (pkt->path_len < MAX_PATH_SIZE) { uint8_t i = 0; @@ -70,14 +65,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_CONTROL && (pkt->payload[0] & 0x80) != 0) { - if (pkt->path_len == 0) { + if (pkt->getPathHashCount() == 0) { onControlDataRecv(pkt); } // just zero-hop control packets allowed (for this subset of payloads) return ACTION_RELEASE; } - if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) { + if (pkt->isRouteDirect() && pkt->getPathHashCount() > 0) { // check for 'early received' ACK if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { int i = 0; @@ -88,7 +83,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { } } - if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) { + if (self_id.isHashMatch(pkt->path, pkt->getPathHashSize()) && allowPacketForward(pkt)) { if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) { return forwardMultipartDirect(pkt); } else if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) { @@ -158,7 +153,9 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (pkt->getPayloadType() == PAYLOAD_TYPE_PATH) { int k = 0; uint8_t path_len = data[k++]; - uint8_t* path = &data[k]; k += path_len; + uint8_t hash_size = (path_len >> 6) + 1; + uint8_t hash_count = path_len & 63; + uint8_t* path = &data[k]; k += hash_size*hash_count; uint8_t extra_type = data[k++] & 0x0F; // upper 4 bits reserved for future use uint8_t* extra = &data[k]; uint8_t extra_len = len - k; // remainder of packet (may be padded with zeroes!) @@ -293,8 +290,7 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK Packet tmp; tmp.header = pkt->header; - tmp.path_len = pkt->path_len; - memcpy(tmp.path, pkt->path, pkt->path_len); + tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len); tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); @@ -321,27 +317,25 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) { void Mesh::removeSelfFromPath(Packet* pkt) { // remove our hash from 'path' - pkt->path_len -= PATH_HASH_SIZE; -#if 0 - memcpy(pkt->path, &pkt->path[PATH_HASH_SIZE], pkt->path_len); -#elif PATH_HASH_SIZE == 1 - for (int k = 0; k < pkt->path_len; k++) { // shuffle bytes by 1 - pkt->path[k] = pkt->path[k + 1]; + pkt->setPathHashCount(pkt->getPathHashCount() - 1); // decrement the count + + uint8_t sz = pkt->getPathHashSize(); + for (int k = 0; k < pkt->getPathHashCount()*sz; k += sz) { // shuffle path by 1 'entry' + memcpy(&pkt->path[k], &pkt->path[k + sz], sz); } -#else - #error "need path remove impl" -#endif } DispatcherAction Mesh::routeRecvPacket(Packet* packet) { + uint8_t n = packet->getPathHashCount(); if (packet->isRouteFlood() && !packet->isMarkedDoNotRetransmit() - && packet->path_len + PATH_HASH_SIZE <= MAX_PATH_SIZE && allowPacketForward(packet)) { + && (n + 1)*packet->getPathHashSize() <= MAX_PATH_SIZE && allowPacketForward(packet)) { // append this node's hash to 'path' - packet->path_len += self_id.copyHashTo(&packet->path[packet->path_len]); + self_id.copyHashTo(&packet->path[n * packet->getPathHashSize()], packet->getPathHashSize()); + packet->setPathHashCount(n + 1); uint32_t d = getRetransmitDelay(packet); // as this propagates outwards, give it lower and lower priority - return ACTION_RETRANSMIT_DELAYED(packet->path_len, d); // give priority to closer sources, than ones further away + return ACTION_RETRANSMIT_DELAYED(packet->getPathHashCount(), d); // give priority to closer sources, than ones further away } return ACTION_RELEASE; } @@ -353,8 +347,7 @@ DispatcherAction Mesh::forwardMultipartDirect(Packet* pkt) { if (type == PAYLOAD_TYPE_ACK && pkt->payload_len >= 5) { // a multipart ACK Packet tmp; tmp.header = pkt->header; - tmp.path_len = pkt->path_len; - memcpy(tmp.path, pkt->path, pkt->path_len); + tmp.path_len = Packet::copyPath(tmp.path, pkt->path, pkt->path_len); tmp.payload_len = pkt->payload_len - 1; memcpy(tmp.payload, &pkt->payload[1], tmp.payload_len); @@ -376,7 +369,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { delay_millis += getDirectRetransmitDelay(packet) + 300; auto a1 = createMultiAck(crc, extra); if (a1) { - memcpy(a1->path, packet->path, a1->path_len = packet->path_len); + a1->path_len = Packet::copyPath(a1->path, packet->path, packet->path_len); a1->header &= ~PH_ROUTE_MASK; a1->header |= ROUTE_TYPE_DIRECT; sendPacket(a1, 0, delay_millis); @@ -386,7 +379,7 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) { auto a2 = createAck(crc); if (a2) { - memcpy(a2->path, packet->path, a2->path_len = packet->path_len); + a2->path_len = Packet::copyPath(a2->path, packet->path, packet->path_len); a2->header &= ~PH_ROUTE_MASK; a2->header |= ROUTE_TYPE_DIRECT; sendPacket(a2, 0, delay_millis); @@ -439,7 +432,10 @@ Packet* Mesh::createPathReturn(const Identity& dest, const uint8_t* secret, cons } Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len) { - if (path_len + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!! + uint8_t path_hash_size = (path_len >> 6) + 1; + uint8_t path_hash_count = path_len & 63; + + if (path_hash_count*path_hash_size + extra_len + 5 > MAX_COMBINED_PATH) return NULL; // too long!! Packet* packet = obtainNewPacket(); if (packet == NULL) { @@ -457,7 +453,7 @@ Packet* Mesh::createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, uint8_t data[MAX_PACKET_PAYLOAD]; data[data_len++] = path_len; - memcpy(&data[data_len], path, path_len); data_len += path_len; + memcpy(&data[data_len], path, path_hash_count*path_hash_size); data_len += path_hash_count*path_hash_size; if (extra_len > 0) { data[data_len++] = extra_type; memcpy(&data[data_len], extra, extra_len); data_len += extra_len; @@ -624,15 +620,19 @@ Packet* Mesh::createControlData(const uint8_t* data, size_t len) { return packet; } -void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { +void Mesh::sendFlood(Packet* packet, uint32_t delay_millis, uint8_t path_hash_size) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); return; } + if (path_hash_size == 0 || path_hash_size > 3) { + MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): invalid path_hash_size", getLogDateTime()); + return; + } packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_FLOOD; - packet->path_len = 0; + packet->setPathHashSizeAndCount(path_hash_size, 0); _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us @@ -647,17 +647,21 @@ void Mesh::sendFlood(Packet* packet, uint32_t delay_millis) { sendPacket(packet, pri, delay_millis); } -void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis) { +void Mesh::sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis, uint8_t path_hash_size) { if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): TRACE type not suspported", getLogDateTime()); return; } + if (path_hash_size == 0 || path_hash_size > 3) { + MESH_DEBUG_PRINTLN("%s Mesh::sendFlood(): invalid path_hash_size", getLogDateTime()); + return; + } packet->header &= ~PH_ROUTE_MASK; packet->header |= ROUTE_TYPE_TRANSPORT_FLOOD; packet->transport_codes[0] = transport_codes[0]; packet->transport_codes[1] = transport_codes[1]; - packet->path_len = 0; + packet->setPathHashSizeAndCount(path_hash_size, 0); _tables->hasSeen(packet); // mark this packet as already sent in case it is rebroadcast back to us @@ -679,13 +683,13 @@ void Mesh::sendDirect(Packet* packet, const uint8_t* path, uint8_t path_len, uin uint8_t pri; if (packet->getPayloadType() == PAYLOAD_TYPE_TRACE) { // TRACE packets are different // for TRACE packets, path is appended to end of PAYLOAD. (path is used for SNR's) - memcpy(&packet->payload[packet->payload_len], path, path_len); + memcpy(&packet->payload[packet->payload_len], path, path_len); // NOTE: path_len here can be > 64, and NOT in the new scheme packet->payload_len += path_len; packet->path_len = 0; pri = 5; // maybe make this configurable } else { - memcpy(packet->path, path, packet->path_len = path_len); + packet->path_len = Packet::copyPath(packet->path, path, path_len); if (packet->getPayloadType() == PAYLOAD_TYPE_PATH) { pri = 1; // slightly less priority } else { diff --git a/src/Mesh.h b/src/Mesh.h index 00f7ed00..f9f87863 100644 --- a/src/Mesh.h +++ b/src/Mesh.h @@ -196,13 +196,13 @@ public: /** * \brief send a locally-generated Packet with flood routing */ - void sendFlood(Packet* packet, uint32_t delay_millis=0); + void sendFlood(Packet* packet, uint32_t delay_millis=0, uint8_t path_hash_size=1); /** * \brief send a locally-generated Packet with flood routing * \param transport_codes array of 2 codes to attach to packet */ - void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0); + void sendFlood(Packet* packet, uint16_t* transport_codes, uint32_t delay_millis=0, uint8_t path_hash_size=1); /** * \brief send a locally-generated Packet with Direct routing diff --git a/src/Packet.cpp b/src/Packet.cpp index 2d54ca45..0a75c2b3 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -10,8 +10,19 @@ Packet::Packet() { payload_len = 0; } +uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { + uint8_t hash_count = path_len & 63; + uint8_t hash_size = (path_len >> 6) + 1; + if (hash_count*hash_size > MAX_PATH_SIZE) { + MESH_DEBUG_PRINTLN("Packet::copyPath, invalid path_len=%d", (uint32_t)path_len); + return 0; // Error + } + memcpy(dest, src, hash_count*hash_size); + return path_len; +} + int Packet::getRawLength() const { - return 2 + path_len + payload_len + (hasTransportCodes() ? 4 : 0); + return 2 + getPathByteLen() + payload_len + (hasTransportCodes() ? 4 : 0); } void Packet::calculatePacketHash(uint8_t* hash) const { diff --git a/src/Packet.h b/src/Packet.h index 42d73f41..b325fc1c 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -76,6 +76,14 @@ public: */ uint8_t getPayloadVer() const { return (header >> PH_VER_SHIFT) & PH_VER_MASK; } + uint8_t getPathHashSize() const { return (path_len >> 6) + 1; } + uint8_t getPathHashCount() const { return path_len & 63; } + uint8_t getPathByteLen() const { return getPathHashCount() * getPathHashSize(); } + void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; } + void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); } + + static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); + void markDoNotRetransmit() { header = 0xFF; } bool isMarkedDoNotRetransmit() const { return header == 0xFF; } diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 6de7469d..5ec678c7 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -39,7 +39,7 @@ mesh::Packet* BaseChatMesh::createSelfAdvert(const char* name, double lat, doubl } void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) { - if (dest.out_path_len < 0) { + if (dest.out_path_len == OUT_PATH_UNKNOWN) { mesh::Packet* ack = createAck(ack_hash); if (ack) sendFloodScoped(dest, ack, TXT_ACK_DELAY); } else { @@ -92,7 +92,7 @@ ContactInfo* BaseChatMesh::allocateContactSlot() { void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) { memset(&ci, 0, sizeof(ci)); ci.id = id; - ci.out_path_len = -1; // initially out_path is unknown + ci.out_path_len = OUT_PATH_UNKNOWN; StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name)); ci.type = parser.getType(); if (parser.hasLatLon()) { @@ -263,7 +263,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } else { mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, from.id, secret, temp_buf, reply_len); if (reply) { - if (from.out_path_len >= 0) { // we have an out_path, so send DIRECT + if (from.out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(reply, from.out_path, from.out_path_len, SERVER_RESPONSE_DELAY); } else { sendFloodScoped(from, reply, SERVER_RESPONSE_DELAY); @@ -273,7 +273,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender } } else if (type == PAYLOAD_TYPE_RESPONSE && len > 0) { onContactResponse(from, data, len); - if (packet->isRouteFlood() && from.out_path_len >= 0) { + if (packet->isRouteFlood() && from.out_path_len != OUT_PATH_UNKNOWN) { // we have direct path, but other node is still sending flood response, so maybe they didn't receive reciprocal path properly(?) handleReturnPathRetry(from, packet->path, packet->path_len); } @@ -295,7 +295,7 @@ bool BaseChatMesh::onPeerPathRecv(mesh::Packet* packet, int sender_idx, const ui bool BaseChatMesh::onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { // NOTE: default impl, we just replace the current 'out_path' regardless, whenever sender sends us a new out_path. // FUTURE: could store multiple out_paths per contact, and try to find which is the 'best'(?) - memcpy(from.out_path, out_path, from.out_path_len = out_path_len); // store a copy of path, for sendDirect() + from.out_path_len = mesh::Packet::copyPath(from.out_path, out_path, out_path_len); // store a copy of path, for sendDirect() from.lastmod = getRTCClock()->getCurrentTime(); onContactPathUpdated(from); @@ -317,7 +317,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) { txt_send_timeout = 0; // matched one we're waiting for, cancel timeout timer packet->markDoNotRetransmit(); // ACK was for this node, so don't retransmit - if (packet->isRouteFlood() && from->out_path_len >= 0) { + if (packet->isRouteFlood() && from->out_path_len != OUT_PATH_UNKNOWN) { // we have direct path, but other node is still sending flood, so maybe they didn't receive reciprocal path properly(?) handleReturnPathRetry(*from, packet->path, packet->path_len); } @@ -386,7 +386,7 @@ int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; @@ -412,7 +412,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); int rc; - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); txt_send_timeout = futureMillis(est_timeout = calcFloodTimeoutMillisFor(t)); rc = MSG_SEND_SENT_FLOOD; @@ -500,7 +500,7 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password, } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -525,7 +525,7 @@ int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -552,7 +552,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_ } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -579,7 +579,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u } if (pkt) { uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength()); - if (recipient.out_path_len < 0) { + if (recipient.out_path_len == OUT_PATH_UNKNOWN) { sendFloodScoped(recipient, pkt); est_timeout = calcFloodTimeoutMillisFor(t); return MSG_SEND_SENT_FLOOD; @@ -683,7 +683,7 @@ void BaseChatMesh::checkConnections() { MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact not found!"); continue; } - if (contact->out_path_len < 0) { + if (contact->out_path_len == OUT_PATH_UNKNOWN) { MESH_DEBUG_PRINTLN("checkConnections(): Keep_alive contact, no out_path!"); continue; } @@ -710,7 +710,7 @@ void BaseChatMesh::checkConnections() { } void BaseChatMesh::resetPathTo(ContactInfo& recipient) { - recipient.out_path_len = -1; + recipient.out_path_len = OUT_PATH_UNKNOWN; } static ContactInfo* table; // pass via global :-( diff --git a/src/helpers/ClientACL.cpp b/src/helpers/ClientACL.cpp index 55b70ca5..12823827 100644 --- a/src/helpers/ClientACL.cpp +++ b/src/helpers/ClientACL.cpp @@ -114,7 +114,7 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) { memset(c, 0, sizeof(*c)); c->permissions = init_perms; c->id = id; - c->out_path_len = -1; // initially out_path is unknown + c->out_path_len = OUT_PATH_UNKNOWN; return c; } diff --git a/src/helpers/ClientACL.h b/src/helpers/ClientACL.h index dfbc3fce..b758f706 100644 --- a/src/helpers/ClientACL.h +++ b/src/helpers/ClientACL.h @@ -10,10 +10,12 @@ #define PERM_ACL_READ_WRITE 2 #define PERM_ACL_ADMIN 3 +#define OUT_PATH_UNKNOWN 0xFF + struct ClientInfo { mesh::Identity id; uint8_t permissions; - int8_t out_path_len; + uint8_t out_path_len; uint8_t out_path[MAX_PATH_SIZE]; uint8_t shared_secret[PUB_KEY_SIZE]; uint32_t last_timestamp; // by THEIR clock (transient) diff --git a/src/helpers/ContactInfo.h b/src/helpers/ContactInfo.h index eff07741..ede977ca 100644 --- a/src/helpers/ContactInfo.h +++ b/src/helpers/ContactInfo.h @@ -3,12 +3,14 @@ #include <Arduino.h> #include <Mesh.h> +#define OUT_PATH_UNKNOWN 0xFF + struct ContactInfo { mesh::Identity id; char name[32]; uint8_t type; // on of ADV_TYPE_* uint8_t flags; - int8_t out_path_len; + uint8_t out_path_len; mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated uint8_t out_path[MAX_PATH_SIZE]; uint32_t last_advert_timestamp; // by THEIR clock From 3dc14976a03d46f855ce21faddc64866471aa702 Mon Sep 17 00:00:00 2001 From: Sander van Grieken <sander@outrightsolutions.nl> Date: Sun, 22 Feb 2026 14:46:45 +0100 Subject: [PATCH 510/546] add companion usb build target for Heltec Wireless Tracker --- variants/heltec_tracker/platformio.ini | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 797eafdc..dba05dcf 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -43,6 +43,31 @@ lib_deps = stevemarple/MicroNMEA @ ^2.0.6 adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 +[env:Heltec_Wireless_Tracker_companion_radio_usb] +extends = Heltec_tracker_base +build_flags = + ${Heltec_tracker_base.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D DISPLAY_ROTATION=1 + -D DISPLAY_CLASS=ST7735Display + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; -D BLE_PIN_CODE=123456 ; HWT will use display for pin +; -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Heltec_tracker_base.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> + +<helpers/ui/ST7735Display.cpp> +lib_deps = + ${Heltec_tracker_base.lib_deps} + densaugeo/base64 @ ~1.4.0 + [env:Heltec_Wireless_Tracker_companion_radio_ble] extends = Heltec_tracker_base build_flags = From 011edd3c999ca6c528016d67f3e68ac46830131c Mon Sep 17 00:00:00 2001 From: Daniel Novak <daniel@danielnovak.sk> Date: Sun, 22 Feb 2026 18:01:30 +0100 Subject: [PATCH 511/546] Fix millis() wraparound in PacketQueue time comparisons PacketQueue::countBefore() and PacketQueue::get() use unsigned comparison (_schedule_table[j] > now) to check if a packet is scheduled for the future. This breaks when millis() wraps around after ~49.7 days: packets scheduled just before the wrap appear to be in the far future and get stuck in the queue. Use signed subtraction instead, matching the approach already used by Dispatcher::millisHasNowPassed(). This correctly handles the wraparound for time differences up to ~24.8 days in either direction, well beyond the maximum queue delay of 32 seconds. --- src/helpers/StaticPoolPacketManager.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/helpers/StaticPoolPacketManager.cpp b/src/helpers/StaticPoolPacketManager.cpp index 125efb75..67d63979 100644 --- a/src/helpers/StaticPoolPacketManager.cpp +++ b/src/helpers/StaticPoolPacketManager.cpp @@ -11,7 +11,7 @@ PacketQueue::PacketQueue(int max_entries) { int PacketQueue::countBefore(uint32_t now) const { int n = 0; for (int j = 0; j < _num; j++) { - if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now n++; } return n; @@ -21,7 +21,7 @@ mesh::Packet* PacketQueue::get(uint32_t now) { uint8_t min_pri = 0xFF; int best_idx = -1; for (int j = 0; j < _num; j++) { - if (_schedule_table[j] > now) continue; // scheduled for future... ignore for now + if ((int32_t)(_schedule_table[j] - now) > 0) continue; // scheduled for future... ignore for now if (_pri_table[j] < min_pri) { // select most important priority amongst non-future entries min_pri = _pri_table[j]; best_idx = j; From 5a885bffe4ad6e9e33771845a7e43e3a0db6d6d9 Mon Sep 17 00:00:00 2001 From: Sam Koucha <23366921+ElectroMW@users.noreply.github.com> Date: Sun, 22 Feb 2026 18:14:39 +0000 Subject: [PATCH 512/546] Make full use of board's 8MB Flash and add companion WiFI target --- boards/t_beams3_supreme.json | 2 +- .../platformio.ini | 28 ++++++++++++++++++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/boards/t_beams3_supreme.json b/boards/t_beams3_supreme.json index 6a725247..3eb9c016 100644 --- a/boards/t_beams3_supreme.json +++ b/boards/t_beams3_supreme.json @@ -41,7 +41,7 @@ "name": "LilyGo T-Beam supreme (8MB Flash 8MB PSRAM)", "upload": { "flash_size": "8MB", - "maximum_ram_size": 327680, + "maximum_ram_size": 8388608, "maximum_size": 8388608, "require_upload_port": true, "speed": 460800 diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 2d2a095a..04e4b0bf 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -26,7 +26,9 @@ build_src_filter = ${esp32_base.build_src_filter} +<helpers/ui/SH1106Display.cpp> +<helpers/esp32/TBeamBoard.cpp> +<helpers/sensors> -board_build.partitions = min_spiffs.csv ; get around 4mb flash limit +board_build.partitions = default_8MB.csv +board_upload.flash_size = 8MB +board_upload.maximum_size = 8388608 lib_deps = ${esp32_base.lib_deps} lewisxhe/XPowersLib @ ^0.2.7 @@ -131,3 +133,27 @@ build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} lib_deps = ${T_Beam_S3_Supreme_SX1262.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:T_Beam_S3_Supreme_SX1262_companion_radio_wifi] +extends = T_Beam_S3_Supreme_SX1262 +build_flags = + ${T_Beam_S3_Supreme_SX1262.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D WIFI_SSID='"Three_CA7C65"' + -D WIFI_PWD='"8hC45a66265eA3w"' +; -D WIFI_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=8 +; -D MESH_DEBUG=1 +; -D ARDUHAL_LOG_LEVEL=4 +; -D CORE_DEBUG_LEVEL=4 +build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + +<helpers/esp32/*.cpp> + +<helpers/ui/MomentaryButton.cpp> + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${T_Beam_S3_Supreme_SX1262.lib_deps} + densaugeo/base64 @ ~1.4.0 From a66773bac096252eb557aba1ed8ddafc955042fa Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 23 Feb 2026 14:25:19 +1100 Subject: [PATCH 513/546] * CommonCLI: added "get/set path.hash.mode " --- examples/companion_radio/MyMesh.cpp | 10 ++++------ examples/simple_repeater/MyMesh.cpp | 2 +- examples/simple_room_server/MyMesh.cpp | 2 +- examples/simple_sensor/SensorMesh.cpp | 10 ++++++---- src/Packet.cpp | 19 ++++++++++++++++--- src/Packet.h | 4 +++- src/helpers/CommonCLI.cpp | 19 +++++++++++++++++-- src/helpers/CommonCLI.h | 1 + 8 files changed, 49 insertions(+), 18 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f0f8e5fc..26beeab9 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -678,7 +678,7 @@ void MyMesh::onContactResponse(const ContactInfo &contact, const uint8_t *data, } } -bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _in_path_len, uint8_t* out_path, uint8_t _out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { +bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) { if (extra_type == PAYLOAD_TYPE_RESPONSE && extra_len > 4) { uint32_t tag; memcpy(&tag, extra, 4); @@ -686,7 +686,7 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _ if (tag == pending_discovery) { // check for matching response tag) pending_discovery = 0; - if (in_path_len > MAX_PATH_SIZE || out_path_len > MAX_PATH_SIZE) { + if (!mesh::Packet::isValidPathLen(in_path_len) || !mesh::Packet::isValidPathLen(out_path_len)) { MESH_DEBUG_PRINTLN("onContactPathRecv, invalid path sizes: %d, %d", in_path_len, out_path_len); } else { int i = 0; @@ -695,11 +695,9 @@ bool MyMesh::onContactPathRecv(ContactInfo& contact, uint8_t* in_path, uint8_t _ memcpy(&out_frame[i], contact.id.pub_key, 6); i += 6; // pub_key_prefix out_frame[i++] = out_path_len; - memcpy(&out_frame[i], out_path, out_path_len); - i += out_path_len; + i += mesh::Packet::writePath(&out_frame[i], out_path, out_path_len); out_frame[i++] = in_path_len; - memcpy(&out_frame[i], in_path, in_path_len); - i += in_path_len; + i += mesh::Packet::writePath(&out_frame[i], in_path, in_path_len); // NOTE: telemetry data in 'extra' is discarded at present _serial->writeFrame(out_frame, i); diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 7328f703..81c1dcb4 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -917,7 +917,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use?? + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0b1d3a1b..06d9cc96 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -680,7 +680,7 @@ void MyMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet *pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); // TODO: which path_hash_size to use? + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt, delay_millis); } diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index fc9ef310..68fea474 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -261,7 +261,8 @@ void SensorMesh::sendAlert(const ClientInfo* c, Trigger* t) { if (c->out_path_len != OUT_PATH_UNKNOWN) { // we have an out_path, so send DIRECT sendDirect(pkt, c->out_path, c->out_path_len); } else { - sendFlood(pkt); + unsigned long delay_millis = 0; + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } } t->send_expiry = futureMillis(ALERT_ACK_EXPIRY_MILLIS); @@ -567,7 +568,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the ACK mesh::Packet* path = createPathReturn(from->id, secret, packet->path, packet->path_len, PAYLOAD_TYPE_ACK, (uint8_t *) &ack_hash, 4); - if (path) sendFlood(path, TXT_ACK_DELAY); + if (path) sendFlood(path, TXT_ACK_DELAY, packet->getPathHashSize()); } else { sendAckTo(*from, ack_hash, packet->getPathHashSize()); } @@ -791,7 +792,7 @@ void SensorMesh::sendSelfAdvertisement(int delay_millis, bool flood) { mesh::Packet* pkt = createSelfAdvert(); if (pkt) { if (flood) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt, delay_millis); } @@ -868,7 +869,8 @@ void SensorMesh::loop() { if (next_flood_advert && millisHasNowPassed(next_flood_advert)) { mesh::Packet* pkt = createSelfAdvert(); - if (pkt) sendFlood(pkt); + unsigned long delay_millis = 0; + if (pkt) sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); updateFloodAdvertTimer(); // schedule next flood advert updateAdvertTimer(); // also schedule local advert (so they don't overlap) diff --git a/src/Packet.cpp b/src/Packet.cpp index 0a75c2b3..9fd06395 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -10,14 +10,27 @@ Packet::Packet() { payload_len = 0; } -uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { +bool Packet::isValidPathLen(uint8_t path_len) { uint8_t hash_count = path_len & 63; uint8_t hash_size = (path_len >> 6) + 1; - if (hash_count*hash_size > MAX_PATH_SIZE) { + if (hash_size == 4) return false; // Reserved for future + return hash_count*hash_size <= MAX_PATH_SIZE; +} + +size_t Packet::writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { + uint8_t hash_count = path_len & 63; + uint8_t hash_size = (path_len >> 6) + 1; + size_t len = hash_count*hash_size; + if (len > MAX_PATH_SIZE) { MESH_DEBUG_PRINTLN("Packet::copyPath, invalid path_len=%d", (uint32_t)path_len); return 0; // Error } - memcpy(dest, src, hash_count*hash_size); + memcpy(dest, src, len); + return len; +} + +uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { + if (writePath(dest, src, path_len) == 0) return 0; return path_len; } diff --git a/src/Packet.h b/src/Packet.h index b325fc1c..78619546 100644 --- a/src/Packet.h +++ b/src/Packet.h @@ -82,7 +82,9 @@ public: void setPathHashCount(uint8_t n) { path_len &= ~63; path_len |= n; } void setPathHashSizeAndCount(uint8_t sz, uint8_t n) { path_len = ((sz - 1) << 6) | (n & 63); } - static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); + static uint8_t copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns path_len + static size_t writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len); // returns byte length written + static bool isValidPathLen(uint8_t path_len); void markDoNotRetransmit() { header = 0xFF; } bool isMarkedDoNotRetransmit() const { return header == 0xFF; } diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 263eb665..f2f961b9 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -63,7 +63,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { file.read((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.read((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.read((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.read(pad, 3); // 121 + file.read((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 + file.read(pad, 2); // 122 file.read((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.read((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.read((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 @@ -95,6 +96,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, -9, 30); _prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1); _prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f); + _prefs->path_hash_mode = constrain(_prefs->path_hash_mode, 0, 2); // NOTE: mode 3 reserved for future // sanitise bad bridge pref values _prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1); @@ -147,7 +149,8 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) { file.write((uint8_t *)&_prefs->multi_acks, sizeof(_prefs->multi_acks)); // 115 file.write((uint8_t *)&_prefs->bw, sizeof(_prefs->bw)); // 116 file.write((uint8_t *)&_prefs->agc_reset_interval, sizeof(_prefs->agc_reset_interval)); // 120 - file.write(pad, 3); // 121 + file.write((uint8_t *)&_prefs->path_hash_mode, sizeof(_prefs->path_hash_mode)); // 121 + file.write(pad, 2); // 122 file.write((uint8_t *)&_prefs->flood_max, sizeof(_prefs->flood_max)); // 124 file.write((uint8_t *)&_prefs->flood_advert_interval, sizeof(_prefs->flood_advert_interval)); // 125 file.write((uint8_t *)&_prefs->interference_threshold, sizeof(_prefs->interference_threshold)); // 126 @@ -325,6 +328,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch sp++; } *reply = 0; // set null terminator + } else if (memcmp(config, "path.hash.mode", 14) == 0) { + sprintf(reply, "> %d", (uint32_t)_prefs->path_hash_mode); } else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) { sprintf(reply, "> %d", (int32_t) _prefs->tx_power_dbm); } else if (memcmp(config, "freq", 4) == 0) { @@ -556,6 +561,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch *dp = 0; savePrefs(); strcpy(reply, "OK"); + } else if (memcmp(config, "path.hash.mode ", 15) == 0) { + config += 15; + uint8_t mode = atoi(config); + if (mode < 3) { + _prefs->path_hash_mode = mode; + savePrefs(); + strcpy(reply, "OK"); + } else { + strcpy(reply, "Error, must be 0,1, or 2"); + } } else if (memcmp(config, "tx ", 3) == 0) { _prefs->tx_power_dbm = atoi(&config[3]); savePrefs(); diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 146e1c6e..1e454ec2 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -52,6 +52,7 @@ struct NodePrefs { // persisted to file uint32_t discovery_mod_timestamp; float adc_multiplier; char owner_info[120]; + uint8_t path_hash_mode; // which path mode to use when sending }; class CommonCLICallbacks { From e52d57c06528a533d7734f886c8016489373e8cb Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 23 Feb 2026 18:26:56 +1100 Subject: [PATCH 514/546] * companion: new pref: path_hash_mode (0..2) * companion: new field in CMD_SET_OTHER_PARAMS, path_hash_mode * companion: CMD_SEND_SELF_ADVERT, cmd_frame[1] now holds the path hash size (0 = zero hop, 1..3 = flood path hash size) --- examples/companion_radio/DataStore.cpp | 6 ++++-- examples/companion_radio/MyMesh.cpp | 17 +++++++++++------ examples/companion_radio/NodePrefs.h | 1 + examples/simple_room_server/MyMesh.cpp | 3 ++- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 1239ea3d..fba64e8c 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -222,7 +222,8 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.read((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 - file.read(pad, 2); // 78 + file.read((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78 + file.read(pad, 1); // 79 file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 @@ -257,7 +258,8 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.rx_delay_base, sizeof(_prefs.rx_delay_base)); // 72 file.write((uint8_t *)&_prefs.advert_loc_policy, sizeof(_prefs.advert_loc_policy)); // 76 file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77 - file.write(pad, 2); // 78 + file.write((uint8_t *)&_prefs.path_hash_mode, sizeof(_prefs.path_hash_mode)); // 78 + file.write(pad, 1); // 79 file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80 file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84 file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 26beeab9..09968ecd 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -473,23 +473,23 @@ bool MyMesh::allowPacketForward(const mesh::Packet* packet) { void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: dynamic send_scope, depending on recipient and current 'home' Region if (send_scope.isNull()) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { uint16_t codes[2]; codes[0] = send_scope.calcTransportCode(pkt); codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? - sendFlood(pkt, codes, delay_millis); + sendFlood(pkt, codes, delay_millis, _prefs.path_hash_mode + 1); } } void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { // TODO: have per-channel send_scope if (send_scope.isNull()) { - sendFlood(pkt, delay_millis); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { uint16_t codes[2]; codes[0] = send_scope.calcTransportCode(pkt); codes[1] = 0; // REVISIT: set to 'home' Region, for sender/return region? - sendFlood(pkt, codes, delay_millis); + sendFlood(pkt, codes, delay_millis, _prefs.path_hash_mode + 1); } } @@ -937,6 +937,7 @@ void MyMesh::handleCmdFrame(size_t len) { StrHelper::strzcpy((char *)&out_frame[i], FIRMWARE_VERSION, 20); i += 20; out_frame[i++] = _prefs.client_repeat; // v9+ + out_frame[i++] = _prefs.path_hash_mode; // v10+ _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_APP_START && len >= 8) { // sent when app establishes connection, respond with node ID @@ -1113,8 +1114,9 @@ void MyMesh::handleCmdFrame(size_t len) { pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon); } if (pkt) { - if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) - sendFlood(pkt); // TODO: which path_hash_size to use?? + if (len >= 2 && cmd_frame[1] >= 1 && cmd_frame[1] <= 3) { // optional param (1..3 = flood, 0 = zero hop) + unsigned long delay_millis = 0; + sendFlood(pkt, delay_millis, cmd_frame[1]); } else { sendZeroHop(pkt); } @@ -1306,6 +1308,9 @@ void MyMesh::handleCmdFrame(size_t len) { _prefs.advert_loc_policy = cmd_frame[3]; if (len >= 5) { _prefs.multi_acks = cmd_frame[4]; + if (len >= 6) { + _prefs.path_hash_mode = cmd_frame[5]; + } } } } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index f2a52f41..ec60c94a 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -29,4 +29,5 @@ struct NodePrefs { // persisted to file uint32_t gps_interval; // GPS read interval in seconds uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; + uint8_t path_hash_mode; // which path mode to use when sending }; \ No newline at end of file diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 06d9cc96..3d2b5794 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -74,7 +74,8 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { auto reply = createDatagram(PAYLOAD_TYPE_TXT_MSG, client->id, client->shared_secret, reply_data, len); if (reply) { if (client->out_path_len == OUT_PATH_UNKNOWN) { - sendFlood(reply); // TODO: which path_hash_size to use? + unsigned long delay_millis = 0; + sendFlood(reply, delay_millis, _prefs.path_hash_mode); client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); From 5b0884ad2d31ede8eae4f08591c69ad186109251 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 23 Feb 2026 21:08:22 +1100 Subject: [PATCH 515/546] * added CMD_SET_PATH_HASH_MODE --- examples/companion_radio/MyMesh.cpp | 12 +++++++++--- examples/companion_radio/MyMesh.h | 2 +- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 09968ecd..e928e7e0 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -57,6 +57,7 @@ #define CMD_SET_AUTOADD_CONFIG 58 #define CMD_GET_AUTOADD_CONFIG 59 #define CMD_GET_ALLOWED_REPEAT_FREQ 60 +#define CMD_SET_PATH_HASH_MODE 61 // Stats sub-types for CMD_GET_STATS #define STATS_TYPE_CORE 0 @@ -1308,14 +1309,19 @@ void MyMesh::handleCmdFrame(size_t len) { _prefs.advert_loc_policy = cmd_frame[3]; if (len >= 5) { _prefs.multi_acks = cmd_frame[4]; - if (len >= 6) { - _prefs.path_hash_mode = cmd_frame[5]; - } } } } savePrefs(); writeOKFrame(); + } else if (cmd_frame[0] == CMD_SET_PATH_HASH_MODE && cmd_frame[1] == 0 && len >= 3) { + if (cmd_frame[2] >= 3) { + writeErrFrame(ERR_CODE_ILLEGAL_ARG); + } else { + _prefs.path_hash_mode = cmd_frame[2]; + savePrefs(); + writeOKFrame(); + } } else if (cmd_frame[0] == CMD_REBOOT && memcmp(&cmd_frame[1], "reboot", 6) == 0) { if (dirty_contacts_expiry) { // is there are pending dirty contacts write needed? saveContacts(); diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index e3c10985..87e6cf33 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -5,7 +5,7 @@ #include "AbstractUITask.h" /*------------ Frame Protocol --------------*/ -#define FIRMWARE_VER_CODE 9 +#define FIRMWARE_VER_CODE 10 #ifndef FIRMWARE_BUILD_DATE #define FIRMWARE_BUILD_DATE "15 Feb 2026" From 45564bad9b6522a032e40d98cdcf9e83dacae70c Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Mon, 23 Feb 2026 23:51:30 +1100 Subject: [PATCH 516/546] * Dispatcher bug fixes --- src/Dispatcher.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Dispatcher.cpp b/src/Dispatcher.cpp index 12889fb8..35eca0a9 100644 --- a/src/Dispatcher.cpp +++ b/src/Dispatcher.cpp @@ -68,7 +68,7 @@ void Dispatcher::loop() { next_tx_time = futureMillis(t * getAirtimeBudgetFactor()); _radio->onSendFinished(); - logTx(outbound, 2 + outbound->path_len + outbound->payload_len); + logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); if (outbound->isRouteFlood()) { n_sent_flood++; } else { @@ -80,7 +80,7 @@ void Dispatcher::loop() { MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime()); _radio->onSendFinished(); - logTxFail(outbound, 2 + outbound->path_len + outbound->payload_len); + logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len); releasePacket(outbound); // return to pool outbound = NULL; @@ -266,7 +266,7 @@ void Dispatcher::checkSend() { memcpy(&raw[len], &outbound->transport_codes[1], 2); len += 2; } raw[len++] = outbound->path_len; - memcpy(&raw[len], outbound->path, outbound->path_len); len += outbound->path_len; + len += Packet::writePath(&raw[len], outbound->path, outbound->path_len); if (len + outbound->payload_len > MAX_TRANS_UNIT) { MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", getLogDateTime(), len + outbound->payload_len); @@ -320,7 +320,7 @@ void Dispatcher::releasePacket(Packet* packet) { } void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) { - if (packet->path_len > MAX_PATH_SIZE || packet->payload_len > MAX_PACKET_PAYLOAD) { + if (!Packet::isValidPathLen(packet->path_len) || packet->payload_len > MAX_PACKET_PAYLOAD) { MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", getLogDateTime(), (uint32_t) packet->path_len, (uint32_t) packet->payload_len); _mgr->free(packet); } else { From 213d085012ab59c24f462fdf80d64a7a20b9903f Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 24 Feb 2026 00:08:13 +1100 Subject: [PATCH 517/546] * revert CMD_SEND_SELF_ADVERT, use _prefs.path_hash_mode --- examples/companion_radio/MyMesh.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index e928e7e0..f23e50b6 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1115,9 +1115,9 @@ void MyMesh::handleCmdFrame(size_t len) { pkt = createSelfAdvert(_prefs.node_name, sensors.node_lat, sensors.node_lon); } if (pkt) { - if (len >= 2 && cmd_frame[1] >= 1 && cmd_frame[1] <= 3) { // optional param (1..3 = flood, 0 = zero hop) + if (len >= 2 && cmd_frame[1] == 1) { // optional param (1 = flood, 0 = zero hop) unsigned long delay_millis = 0; - sendFlood(pkt, delay_millis, cmd_frame[1]); + sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); } else { sendZeroHop(pkt); } From 9d5c4865c346b949a9b172a344b4d80d8c97b074 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 24 Feb 2026 01:08:11 +1100 Subject: [PATCH 518/546] * room server fix --- examples/simple_room_server/MyMesh.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 3d2b5794..5451505a 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -75,7 +75,7 @@ void MyMesh::pushPostToClient(ClientInfo *client, PostInfo &post) { if (reply) { if (client->out_path_len == OUT_PATH_UNKNOWN) { unsigned long delay_millis = 0; - sendFlood(reply, delay_millis, _prefs.path_hash_mode); + sendFlood(reply, delay_millis, _prefs.path_hash_mode + 1); client->extra.room.ack_timeout = futureMillis(PUSH_ACK_TIMEOUT_FLOOD); } else { sendDirect(reply, client->out_path, client->out_path_len); From 9f4eeeecebc10851180d3d336445090d6b8d570c Mon Sep 17 00:00:00 2001 From: callum5892 <callumdolan@gmail.com> Date: Mon, 23 Feb 2026 17:31:18 +0000 Subject: [PATCH 519/546] Added build flags for M5Stack Unit C6L Enabled USB-CDC on boot for M5Stack_Unit_C6L_companion_radio_usb to fix serial connection issues --- variants/m5stack_unit_c6l/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index a2b8b087..1dd6749a 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -94,6 +94,8 @@ build_flags = ${M5Stack_Unit_C6L.build_flags} -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D ARDUINO_USB_MODE=1 build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} +<helpers/esp32/*.cpp> -<helpers/esp32/ESPNOWRadio.cpp> From b14879ce2d1cfef2710e90ed35161a8d15aa5471 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 24 Feb 2026 14:23:59 +1100 Subject: [PATCH 520/546] * CMD_GET_ADVERT_PATH bug fix --- examples/companion_radio/MyMesh.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f23e50b6..c96f7e01 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -350,7 +350,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path } // add inbound-path to mem cache - if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid + if (path && mesh::Packet::isValidPathLen(path_len)) { // check path is valid AdvertPath* p = advert_paths; uint32_t oldest = 0xFFFFFFFF; for (int i = 0; i < ADVERT_PATH_TABLE_SIZE; i++) { // check if already in table, otherwise evict oldest @@ -367,8 +367,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix)); strcpy(p->name, contact.name); p->recv_timestamp = getRTCClock()->getCurrentTime(); - p->path_len = path_len; - memcpy(p->path, path, p->path_len); + p->path_len = mesh::Packet::copyPath(p->path, path, path_len); } if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[] @@ -1696,11 +1695,12 @@ void MyMesh::handleCmdFrame(size_t len) { } } if (found) { - out_frame[0] = RESP_CODE_ADVERT_PATH; - memcpy(&out_frame[1], &found->recv_timestamp, 4); - out_frame[5] = found->path_len; - memcpy(&out_frame[6], found->path, found->path_len); - _serial->writeFrame(out_frame, 6 + found->path_len); + int i = 0; + out_frame[i++] = RESP_CODE_ADVERT_PATH; + memcpy(&out_frame[i], &found->recv_timestamp, 4); i += 4; + out_frame[i++] = found->path_len; + i += mesh::Packet::writePath(&out_frame[i], found->path, found->path_len); + _serial->writeFrame(out_frame, i); } else { writeErrFrame(ERR_CODE_NOT_FOUND); } From b777a7c635e2c3ed329f82d45cf27919f949f7f7 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky <recrof@gmail.com> Date: Tue, 24 Feb 2026 11:28:23 +0100 Subject: [PATCH 521/546] Update default preset to EU/UK (Narrow) --- platformio.ini | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platformio.ini b/platformio.ini index c47e757e..ba601c26 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,9 +24,9 @@ lib_deps = melopero/Melopero RV3028 @ ^1.1.0 electroniccats/CayenneLPP @ 1.6.1 build_flags = -w -DNDEBUG -DRADIOLIB_STATIC_ONLY=1 -DRADIOLIB_GODMODE=1 - -D LORA_FREQ=869.525 - -D LORA_BW=250 - -D LORA_SF=11 + -D LORA_FREQ=869.618 + -D LORA_BW=62.5 + -D LORA_SF=8 -D ENABLE_ADVERT_ON_BOOT=1 -D ENABLE_PRIVATE_KEY_IMPORT=1 ; NOTE: comment these out for more secure firmware -D ENABLE_PRIVATE_KEY_EXPORT=1 From f4748a7f9d4c5f1440c47c0bd52074f4e01e97b9 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Tue, 24 Feb 2026 21:30:04 +1100 Subject: [PATCH 522/546] * misc --- variants/lilygo_tbeam_supreme_SX1262/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index 04e4b0bf..1ac622db 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -142,8 +142,8 @@ build_flags = -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D OFFLINE_QUEUE_SIZE=256 - -D WIFI_SSID='"Three_CA7C65"' - -D WIFI_PWD='"8hC45a66265eA3w"' + -D WIFI_SSID='"WIFI_SSID"' + -D WIFI_PWD='"Password"' ; -D WIFI_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=8 ; -D MESH_DEBUG=1 From 15cce12efd47c41fe67575a7c36d2e9b9ec4f1ba Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Wed, 25 Feb 2026 02:43:48 +0100 Subject: [PATCH 523/546] Add basic sanity test github PR workflow Build a few generic variants to verify at least those compile. Can't hurt. --- .github/workflows/pr-build-check.yml | 43 ++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/pr-build-check.yml diff --git a/.github/workflows/pr-build-check.yml b/.github/workflows/pr-build-check.yml new file mode 100644 index 00000000..5ba677cd --- /dev/null +++ b/.github/workflows/pr-build-check.yml @@ -0,0 +1,43 @@ +name: PR Build Check + +on: + pull_request: + branches: [main, dev] + paths: + - 'src/**' + - 'examples/**' + - 'variants/**' + - 'platformio.ini' + - '.github/workflows/pr-build-check.yml' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + environment: + # ESP32-S3 (most common platform) + - Heltec_v3_companion_radio_ble + - Heltec_v3_repeater + - Heltec_v3_room_server + # nRF52 + - RAK_4631_companion_radio_ble + - RAK_4631_repeater + - RAK_4631_room_server + # RP2040 + - PicoW_repeater + # STM32 + - wio-e5-mini_repeater + # ESP32-C6 + - LilyGo_Tlora_C6_repeater_ + + steps: + - name: Clone Repo + uses: actions/checkout@v4 + + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + + - name: Build ${{ matrix.environment }} + run: pio run -e ${{ matrix.environment }} From 8737c64fdb47d0813d8bd5b63b45ae9607dd9e45 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Wed, 25 Feb 2026 17:10:31 +1100 Subject: [PATCH 524/546] * Packet::copyPath() fix --- src/Packet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Packet.cpp b/src/Packet.cpp index 9fd06395..934020b3 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -30,7 +30,7 @@ size_t Packet::writePath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { } uint8_t Packet::copyPath(uint8_t* dest, const uint8_t* src, uint8_t path_len) { - if (writePath(dest, src, path_len) == 0) return 0; + writePath(dest, src, path_len); return path_len; } From b67decfba06bbcafbb6909157b46a37c09106930 Mon Sep 17 00:00:00 2001 From: Scott Powell <sqij@protonmail.com> Date: Thu, 26 Feb 2026 15:36:21 +1100 Subject: [PATCH 525/546] * bug fix: Packet::writeTo(), Packet::readFrom() --- src/Packet.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Packet.cpp b/src/Packet.cpp index 934020b3..aad3e2f4 100644 --- a/src/Packet.cpp +++ b/src/Packet.cpp @@ -57,7 +57,7 @@ uint8_t Packet::writeTo(uint8_t dest[]) const { memcpy(&dest[i], &transport_codes[1], 2); i += 2; } dest[i++] = path_len; - memcpy(&dest[i], path, path_len); i += path_len; + i += writePath(&dest[i], path, path_len); memcpy(&dest[i], payload, payload_len); i += payload_len; return i; } @@ -72,8 +72,11 @@ bool Packet::readFrom(const uint8_t src[], uint8_t len) { transport_codes[0] = transport_codes[1] = 0; } path_len = src[i++]; - if (path_len > sizeof(path)) return false; // bad encoding - memcpy(path, &src[i], path_len); i += path_len; + if (!isValidPathLen(path_len)) return false; // bad encoding + + uint8_t bl = getPathByteLen(); + memcpy(path, &src[i], bl); i += bl; + if (i >= len) return false; // bad encoding payload_len = len - i; if (payload_len > sizeof(payload)) return false; // bad encoding From 8ad17d1022242ac066021778e5ca4b79c839225f Mon Sep 17 00:00:00 2001 From: enricolorenzoni59 <enricolorenzoni59@gmail.com> Date: Sat, 28 Feb 2026 09:07:30 +0000 Subject: [PATCH 526/546] `gps sync` reply: fill buffer with text --- src/helpers/CommonCLI.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index f2f961b9..e20bbb1c 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -717,6 +717,9 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { l->syncTime(); + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps provider not found"); } } else if (memcmp(command, "gps setloc", 10) == 0) { _prefs->node_lat = _sensors->node_lat; From 329e40819720981b2c208831ab38ac907a849970 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Fri, 6 Feb 2026 01:57:27 +0100 Subject: [PATCH 527/546] Hold GC1109 PA_POWER during deep sleep for LNA RX wake The GC1109 FEM needs its VFEM_Ctrl pin held HIGH during deep sleep to keep the LNA active, enabling proper RX sensitivity for wake-on-packet. Without this, the LNA is unpowered during sleep and RX wake sensitivity is degraded by ~17dB. Release RTC holds in begin() after configuring GPIO registers (not before) to ensure glitch-free pin transitions on wake. Trade-off: ~6.5mA additional sleep current for significantly improved wake-on-packet range. --- variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp | 11 +++++++++-- variants/heltec_tracker_v2/platformio.ini | 10 +++++----- variants/heltec_v4/HeltecV4Board.cpp | 11 +++++++++-- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 4975d5cd..1b694c11 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -6,12 +6,17 @@ void HeltecTrackerV2Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); @@ -48,7 +53,9 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 25d16f2f..af41b4f5 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -17,11 +17,11 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=4 - -D P_LORA_PA_TX_EN=46 ;enable tx - -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. - -D MAX_LORA_TX_POWER=22 ;Max SX1262 output + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - GC1109 LDO power enable + -D P_LORA_PA_EN=4 ; CSD - GC1109 chip enable (HIGH=on) + -D P_LORA_PA_TX_EN=46 ; CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) + -D LORA_TX_POWER=10 ; 10dBm + ~11dB GC1109 gain = ~21dBm output + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -> ~28dBm at antenna -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 92f93437..626f2577 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -7,12 +7,17 @@ void HeltecV4Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // Set up digital GPIO registers before releasing RTC hold. The hold latches + // the pad state including function select, so register writes accumulate + // without affecting the pad. On hold release, all changes apply atomically + // (IO MUX switches to digital GPIO with output already HIGH — no glitch). pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN,HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); @@ -50,7 +55,9 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet From 2bb6f636a4ae18da2f793501effe9265cd9654cd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Sun, 8 Feb 2026 16:36:13 +0100 Subject: [PATCH 528/546] Add 1ms delay after powering PA (cold-boot) --- variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp | 7 +++++-- variants/heltec_v4/HeltecV4Board.cpp | 6 ++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 1b694c11..bd7f680e 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -20,9 +20,12 @@ void HeltecTrackerV2Board::begin() { pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); - periph_power.begin(); - esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } + + periph_power.begin(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 626f2577..8186f2d4 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -21,10 +21,12 @@ void HeltecV4Board::begin() { pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN,LOW); + esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); // GC1109 startup time after cold power-on + } periph_power.begin(); - - esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) From d9e67222f59391a4352bd406ed21bed69ae0dc22 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Wed, 25 Feb 2026 09:11:23 +0100 Subject: [PATCH 529/546] prefs is 5 char length :nerd: --- src/helpers/CommonCLI.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index e20bbb1c..fd631273 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -749,7 +749,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch _prefs->advert_loc_policy = ADVERT_LOC_SHARE; savePrefs(); strcpy(reply, "ok"); - } else if (memcmp(command+11, "prefs", 4) == 0) { + } else if (memcmp(command+11, "prefs", 5) == 0) { _prefs->advert_loc_policy = ADVERT_LOC_PREFS; savePrefs(); strcpy(reply, "ok"); From 70f1ad4aebf22a70a663f310487bbb8eb167ddf8 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Wed, 25 Feb 2026 00:26:38 +0100 Subject: [PATCH 530/546] Fix RAK3401 SKY66122-11 FEM control: enable CSD/CPS for proper PA and LNA operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RAK13302 1W module uses a Skyworks SKY66122-11 front-end module with three digital control pins (CSD, CTX, CPS) that must be actively driven by the host MCU. The previous code only managed CTX (GPIO 31) — toggling it for TX/RX — but never initialized CSD (GPIO 24) or CPS (GPIO 21), leaving them floating with no pull-up/pull-down resistors on the PCB. With floating CSD and CPS, the SKY66122 was in an undefined operating mode: - The 30 dB TX PA may not have been reliably engaging - The 16 dB RX LNA was never reliably active, degrading receive sensitivity --- variants/rak3401/RAK3401Board.cpp | 33 ++++++++++++++++++++++++++----- variants/rak3401/RAK3401Board.h | 9 +++++---- variants/rak3401/variant.h | 11 ++++++++--- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index b9431c92..e2a9f318 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -23,10 +23,33 @@ void RAK3401Board::begin() { pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); -#ifdef P_LORA_PA_EN - // Initialize RAK13302 1W LoRa transceiver module PA control pin + // Initialize SKY66122-11 FEM on the RAK13302 module. + // CSD (P0.24) and CPS (P0.21) must be HIGH for both TX and RX modes. + // CTX (P0.31) selects TX(HIGH) vs RX(LOW) and also enables the 5V boost + // converter that powers the PA section (VCC1/VCC2). + // The LNA section (VSUP1/VCC0) runs on 3.3V and works with boost off. + pinMode(P_LORA_PA_CSD, OUTPUT); + digitalWrite(P_LORA_PA_CSD, HIGH); // CSD=1: enable FEM + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); // CPS=1: enable TX/RX paths + pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled - delay(10); // Allow PA module to initialize + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off + + delay(1); // SKY66122 turn-on settling time +} + +#ifdef NRF52_POWER_MANAGEMENT +void RAK3401Board::initiateShutdown(uint8_t reason) { + // Put SKY66122 in guaranteed <1 uA shutdown (Mode 4: CSD=0, CTX=0, CPS=0) + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0, boost off + digitalWrite(SX126X_POWER_EN, LOW); // CPS=0 + digitalWrite(P_LORA_PA_CSD, LOW); // CSD=0 + + // Disable 3V3 switched peripherals + digitalWrite(PIN_3V3_EN, LOW); + + enterSystemOff(reason); +} #endif -} \ No newline at end of file diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 20edf906..8ca5b52e 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -38,13 +38,14 @@ public: return "RAK 3401"; } -#ifdef P_LORA_PA_EN + // SKY66122 FEM TX/RX switching via CTX pin. + // CTX=HIGH: TX mode + 5V boost ON (PA powered from VCC1/VCC2) + // CTX=LOW: RX mode + 5V boost OFF (LNA powered from VSUP1 at 3.3V) void onBeforeTransmit() override { - digitalWrite(P_LORA_PA_EN, HIGH); // Enable PA before transmission + digitalWrite(P_LORA_PA_EN, HIGH); // CTX=1: TX mode, boost on } void onAfterTransmit() override { - digitalWrite(P_LORA_PA_EN, LOW); // Disable PA after transmission to save power + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off } -#endif }; diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index 56fe0816..f2ef4ace 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -147,8 +147,14 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_BUSY (9) #define SX126X_RESET (4) -#define SX126X_POWER_EN (21) -// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3 +// SKY66122-11 FEM control pins (active HIGH, active LOW = shutdown <1uA) +// CSD+CPS must be HIGH for TX and RX; CTX selects TX(HIGH) vs RX(LOW) +// CTX also enables the 5V boost converter for the PA during TX +#define P_LORA_PA_CSD (24) // P0.24 -> SKY66122 CSD (pin 11) - FEM enable +#define SX126X_POWER_EN (21) // P0.21 -> SKY66122 CPS (pin 1) - path select +#define P_LORA_PA_EN (31) // P0.31 -> SKY66122 CTX (pin 2) - TX/RX + boost EN + +// DIO2 has a NC 0R footprint (R25) to CTX; not connected by default #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 @@ -159,7 +165,6 @@ static const uint8_t AREF = PIN_AREF; #define P_LORA_DIO_1 SX126X_DIO1 #define P_LORA_BUSY SX126X_BUSY #define P_LORA_RESET SX126X_RESET -#define P_LORA_PA_EN 31 // enables 3.3V periphery like GPS or IO Module // Do not toggle this for GPS power savings From ac2aa03b0903200f81a0e6981d56e194a4ce8ae7 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Wed, 25 Feb 2026 01:18:16 +0100 Subject: [PATCH 531/546] Add SX126X_REGISTER_PATCH for RAK3401 --- variants/rak3401/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 7467ceb9..ecea0317 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -11,6 +11,7 @@ build_flags = ${nrf52_base.build_flags} -D LORA_TX_POWER=22 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX with SKY66122 FEM build_src_filter = ${nrf52_base.build_src_filter} +<../variants/rak3401> +<helpers/sensors> From 5a5568ed56ac25d4de6e10e9d2cde07d6bf686c1 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Thu, 26 Feb 2026 08:57:56 +0100 Subject: [PATCH 532/546] Drive CTX low first --- variants/rak3401/RAK3401Board.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index e2a9f318..4c18c6dd 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -28,15 +28,18 @@ void RAK3401Board::begin() { // CTX (P0.31) selects TX(HIGH) vs RX(LOW) and also enables the 5V boost // converter that powers the PA section (VCC1/VCC2). // The LNA section (VSUP1/VCC0) runs on 3.3V and works with boost off. + // + // Drive CTX LOW first to prevent transient TX mode (Mode 2) while CSD/CPS + // are being enabled — the RAK13302 has no pull-downs on these pins. + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off + pinMode(P_LORA_PA_CSD, OUTPUT); digitalWrite(P_LORA_PA_CSD, HIGH); // CSD=1: enable FEM pinMode(SX126X_POWER_EN, OUTPUT); digitalWrite(SX126X_POWER_EN, HIGH); // CPS=1: enable TX/RX paths - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off - delay(1); // SKY66122 turn-on settling time } From 49d831350171f0ee377a781cd646382b7988ab86 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Fri, 27 Feb 2026 11:30:46 +0100 Subject: [PATCH 533/546] Fix pin mapping & TX switch (it's DIO2) --- variants/rak3401/RAK3401Board.cpp | 34 +++++++++++-------------------- variants/rak3401/RAK3401Board.h | 12 ++--------- variants/rak3401/variant.h | 15 +++++++------- 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/variants/rak3401/RAK3401Board.cpp b/variants/rak3401/RAK3401Board.cpp index 4c18c6dd..33e1de42 100644 --- a/variants/rak3401/RAK3401Board.cpp +++ b/variants/rak3401/RAK3401Board.cpp @@ -20,37 +20,27 @@ void RAK3401Board::begin() { Wire.begin(); + // PIN_3V3_EN (WB_IO2, P0.34) controls the 3V3_S switched peripheral rail + // AND the 5V boost regulator (U5) on the RAK13302 that powers the SKY66122 PA. + // Must stay HIGH during radio operation — do not toggle for power saving. pinMode(PIN_3V3_EN, OUTPUT); digitalWrite(PIN_3V3_EN, HIGH); - // Initialize SKY66122-11 FEM on the RAK13302 module. - // CSD (P0.24) and CPS (P0.21) must be HIGH for both TX and RX modes. - // CTX (P0.31) selects TX(HIGH) vs RX(LOW) and also enables the 5V boost - // converter that powers the PA section (VCC1/VCC2). - // The LNA section (VSUP1/VCC0) runs on 3.3V and works with boost off. - // - // Drive CTX LOW first to prevent transient TX mode (Mode 2) while CSD/CPS - // are being enabled — the RAK13302 has no pull-downs on these pins. - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off - - pinMode(P_LORA_PA_CSD, OUTPUT); - digitalWrite(P_LORA_PA_CSD, HIGH); // CSD=1: enable FEM - + // Enable SKY66122-11 FEM on the RAK13302 module. + // CSD and CPS are tied together on the RAK13302 PCB, routed to IO3 (P0.21). + // HIGH = FEM active (LNA for RX, PA path available for TX). + // TX/RX switching (CTX) is handled by SX1262 DIO2 via SetDIO2AsRfSwitchCtrl. pinMode(SX126X_POWER_EN, OUTPUT); - digitalWrite(SX126X_POWER_EN, HIGH); // CPS=1: enable TX/RX paths - - delay(1); // SKY66122 turn-on settling time + digitalWrite(SX126X_POWER_EN, HIGH); + delay(1); // SKY66122 turn-on settling time (tON = 3us typ) } #ifdef NRF52_POWER_MANAGEMENT void RAK3401Board::initiateShutdown(uint8_t reason) { - // Put SKY66122 in guaranteed <1 uA shutdown (Mode 4: CSD=0, CTX=0, CPS=0) - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0, boost off - digitalWrite(SX126X_POWER_EN, LOW); // CPS=0 - digitalWrite(P_LORA_PA_CSD, LOW); // CSD=0 + // Disable SKY66122 FEM (CSD+CPS LOW = shutdown, <1 uA) + digitalWrite(SX126X_POWER_EN, LOW); - // Disable 3V3 switched peripherals + // Disable 3V3 switched peripherals and 5V boost digitalWrite(PIN_3V3_EN, LOW); enterSystemOff(reason); diff --git a/variants/rak3401/RAK3401Board.h b/variants/rak3401/RAK3401Board.h index 8ca5b52e..3a080d5e 100644 --- a/variants/rak3401/RAK3401Board.h +++ b/variants/rak3401/RAK3401Board.h @@ -38,14 +38,6 @@ public: return "RAK 3401"; } - // SKY66122 FEM TX/RX switching via CTX pin. - // CTX=HIGH: TX mode + 5V boost ON (PA powered from VCC1/VCC2) - // CTX=LOW: RX mode + 5V boost OFF (LNA powered from VSUP1 at 3.3V) - void onBeforeTransmit() override { - digitalWrite(P_LORA_PA_EN, HIGH); // CTX=1: TX mode, boost on - } - - void onAfterTransmit() override { - digitalWrite(P_LORA_PA_EN, LOW); // CTX=0: RX mode, boost off - } + // TX/RX switching is handled by SX1262 DIO2 -> SKY66122 CTX (hardware-timed). + // No onBeforeTransmit/onAfterTransmit overrides needed. }; diff --git a/variants/rak3401/variant.h b/variants/rak3401/variant.h index f2ef4ace..268aec53 100644 --- a/variants/rak3401/variant.h +++ b/variants/rak3401/variant.h @@ -147,14 +147,15 @@ static const uint8_t AREF = PIN_AREF; #define SX126X_BUSY (9) #define SX126X_RESET (4) -// SKY66122-11 FEM control pins (active HIGH, active LOW = shutdown <1uA) -// CSD+CPS must be HIGH for TX and RX; CTX selects TX(HIGH) vs RX(LOW) -// CTX also enables the 5V boost converter for the PA during TX -#define P_LORA_PA_CSD (24) // P0.24 -> SKY66122 CSD (pin 11) - FEM enable -#define SX126X_POWER_EN (21) // P0.21 -> SKY66122 CPS (pin 1) - path select -#define P_LORA_PA_EN (31) // P0.31 -> SKY66122 CTX (pin 2) - TX/RX + boost EN +// SKY66122-11 FEM control on the RAK13302 module: +// CSD + CPS are tied together on the PCB, routed to WisBlock IO3 (P0.21). +// Setting IO3 HIGH enables the FEM (LNA for RX, PA path for TX). +// CTX is connected to SX1262 DIO2 — the radio handles TX/RX switching +// in hardware via SetDIO2AsRfSwitchCtrl (microsecond-accurate, no GPIO needed). +// The 5V boost for the PA is enabled by WB_IO2 (P0.34 = PIN_3V3_EN). +#define SX126X_POWER_EN (21) // P0.21 = IO3 -> SKY66122 CSD+CPS (FEM enable) -// DIO2 has a NC 0R footprint (R25) to CTX; not connected by default +// CTX is driven by SX1262 DIO2, not a GPIO #define SX126X_DIO2_AS_RF_SWITCH #define SX126X_DIO3_TCXO_VOLTAGE 1.8 From f81ec4b14ca7126e17d0005bf0c64adc8d87c821 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Thu, 19 Feb 2026 14:44:25 +0100 Subject: [PATCH 534/546] fix agc reset --- src/helpers/radiolib/RadioLibWrappers.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index cf3e1266..a4b4c3ba 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -57,8 +57,11 @@ void RadioLibWrapper::resetAGC() { // make sure we're not mid-receive of packet! if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return; - // NOTE: according to higher powers, just issuing RadioLib's startReceive() will reset the AGC. - // revisit this if a better impl is discovered. + // Warm sleep powers down the entire analog frontend (including AGC), forcing a + // fresh gain calibration on the next startReceive(). A plain standby->startReceive + // cycle does NOT reset the AGC — the analog state can persist across STDBY_RC. + // The ~1-2 ms sleep gap is negligible vs the preamble budget (131 ms at SF11/BW250). + _radio->sleep(); state = STATE_IDLE; // trigger a startReceive() } From a2dc2eb50cda605be7ef619f358024162fb48009 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Thu, 19 Feb 2026 16:16:21 +0100 Subject: [PATCH 535/546] when doing AGC reset, call Calibrate(0x7F) 1. warm sleep 2. wake to stdby 3. Calibrate(0x7F) to reset all internal blocks 4. re-apply DIO2 RF / boosted gain & register patch to make sure everything is as it was --- src/helpers/radiolib/CustomLLCC68Wrapper.h | 26 +++++++++++++++++ src/helpers/radiolib/CustomSTM32WLxWrapper.h | 26 +++++++++++++++++ src/helpers/radiolib/CustomSX1262Wrapper.h | 30 ++++++++++++++++++++ src/helpers/radiolib/CustomSX1268Wrapper.h | 26 +++++++++++++++++ src/helpers/radiolib/RadioLibWrappers.cpp | 10 +++---- src/helpers/radiolib/RadioLibWrappers.h | 1 + 6 files changed, 114 insertions(+), 5 deletions(-) diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index f7dd7a9f..826c8ed2 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -19,4 +19,30 @@ public: int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { + auto* radio = (CustomLLCC68 *)_radio; + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index 9e2d0441..ed65b188 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -20,4 +20,30 @@ public: int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { + auto* radio = (CustomSTM32WLx *)_radio; + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 1afee5e8..505b4996 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -22,4 +22,34 @@ public: virtual void powerOff() override { ((CustomSX1262 *)_radio)->sleep(false); } + + void doResetAGC() override { + auto* radio = (CustomSX1262 *)_radio; + // Warm sleep powers down analog frontend (resets AGC gain state) + radio->sleep(true); + // Wake to STDBY_RC for calibration + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + // Recalibrate all blocks (ADC, PLL, image, oscillators) + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } + // Re-apply RX settings that calibration may reset +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 5d7106b4..c87ee977 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -19,4 +19,30 @@ public: int sf = ((CustomSX1268 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + + void doResetAGC() override { + auto* radio = (CustomSX1268 *)_radio; + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif + } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index a4b4c3ba..53a4b0a2 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -53,15 +53,15 @@ void RadioLibWrapper::triggerNoiseFloorCalibrate(int threshold) { } } +void RadioLibWrapper::doResetAGC() { + _radio->sleep(); // warm sleep to reset analog frontend +} + void RadioLibWrapper::resetAGC() { // make sure we're not mid-receive of packet! if ((state & STATE_INT_READY) != 0 || isReceivingPacket()) return; - // Warm sleep powers down the entire analog frontend (including AGC), forcing a - // fresh gain calibration on the next startReceive(). A plain standby->startReceive - // cycle does NOT reset the AGC — the analog state can persist across STDBY_RC. - // The ~1-2 ms sleep gap is negligible vs the preamble budget (131 ms at SF11/BW250). - _radio->sleep(); + doResetAGC(); state = STATE_IDLE; // trigger a startReceive() } diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 9ac1bbae..b338b03a 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -16,6 +16,7 @@ protected: void startRecv(); float packetScoreInt(float snr, int sf, int packet_len); virtual bool isReceivingPacket() =0; + virtual void doResetAGC(); public: RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } From 9106ab46e1a05103c244ab6dd981d5cfdd3fbccd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Thu, 19 Feb 2026 16:52:57 +0100 Subject: [PATCH 536/546] reset noise_floor sampling after agc reset --- src/helpers/radiolib/RadioLibWrappers.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 53a4b0a2..2216ca8f 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -63,6 +63,14 @@ void RadioLibWrapper::resetAGC() { doResetAGC(); state = STATE_IDLE; // trigger a startReceive() + + // Reset noise floor sampling so it reconverges from scratch. + // Without this, a stuck _noise_floor of -120 makes the sampling threshold + // too low (-106) to accept normal samples (~-105), self-reinforcing the + // stuck value even after the receiver has recovered. + _noise_floor = 0; + _num_floor_samples = 0; + _floor_sample_sum = 0; } void RadioLibWrapper::loop() { From b2032e11b66048ffd45b7d0280255df5bb58783e Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Thu, 19 Feb 2026 17:34:48 +0100 Subject: [PATCH 537/546] make it more dry --- src/helpers/radiolib/CustomLLCC68Wrapper.h | 27 ++-------------- src/helpers/radiolib/CustomSTM32WLxWrapper.h | 27 ++-------------- src/helpers/radiolib/CustomSX1262Wrapper.h | 31 ++---------------- src/helpers/radiolib/CustomSX1268Wrapper.h | 27 ++-------------- src/helpers/radiolib/SX126xReset.h | 33 ++++++++++++++++++++ 5 files changed, 41 insertions(+), 104 deletions(-) create mode 100644 src/helpers/radiolib/SX126xReset.h diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 826c8ed2..9e783a95 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -2,6 +2,7 @@ #include "CustomLLCC68.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomLLCC68Wrapper : public RadioLibWrapper { public: @@ -20,29 +21,5 @@ public: return packetScoreInt(snr, sf, packet_len); } - void doResetAGC() override { - auto* radio = (CustomLLCC68 *)_radio; - radio->sleep(true); - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index ed65b188..e3e52029 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -2,6 +2,7 @@ #include "CustomSTM32WLx.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" #include <math.h> class CustomSTM32WLxWrapper : public RadioLibWrapper { @@ -21,29 +22,5 @@ public: return packetScoreInt(snr, sf, packet_len); } - void doResetAGC() override { - auto* radio = (CustomSTM32WLx *)_radio; - radio->sleep(true); - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 505b4996..5856720b 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -2,6 +2,7 @@ #include "CustomSX1262.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomSX1262Wrapper : public RadioLibWrapper { public: @@ -23,33 +24,5 @@ public: ((CustomSX1262 *)_radio)->sleep(false); } - void doResetAGC() override { - auto* radio = (CustomSX1262 *)_radio; - // Warm sleep powers down analog frontend (resets AGC gain state) - radio->sleep(true); - // Wake to STDBY_RC for calibration - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - // Recalibrate all blocks (ADC, PLL, image, oscillators) - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } - // Re-apply RX settings that calibration may reset -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index c87ee977..5149fc43 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -2,6 +2,7 @@ #include "CustomSX1268.h" #include "RadioLibWrappers.h" +#include "SX126xReset.h" class CustomSX1268Wrapper : public RadioLibWrapper { public: @@ -20,29 +21,5 @@ public: return packetScoreInt(snr, sf, packet_len); } - void doResetAGC() override { - auto* radio = (CustomSX1268 *)_radio; - radio->sleep(true); - radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); - uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; - radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); - radio->mod->hal->delay(5); - uint32_t start = millis(); - while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { - if (millis() - start > 50) break; - radio->mod->hal->yield(); - } -#ifdef SX126X_DIO2_AS_RF_SWITCH - radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); -#endif -#ifdef SX126X_RX_BOOSTED_GAIN - radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); -#endif -#ifdef SX126X_REGISTER_PATCH - uint8_t r_data = 0; - radio->readRegister(0x8B5, &r_data, 1); - r_data |= 0x01; - radio->writeRegister(0x8B5, &r_data, 1); -#endif - } + void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h new file mode 100644 index 00000000..ba08ef8d --- /dev/null +++ b/src/helpers/radiolib/SX126xReset.h @@ -0,0 +1,33 @@ +#pragma once + +#include <RadioLib.h> + +// Full receiver reset for all SX126x-family chips (SX1262, SX1268, LLCC68, STM32WLx). +// Warm sleep powers down analog, Calibrate(0x7F) refreshes ADC/PLL/image calibration, +// then re-applies RX settings that calibration may reset. +inline void sx126xResetAGC(SX126x* radio) { + radio->sleep(true); + radio->standby(RADIOLIB_SX126X_STANDBY_RC, true); + + uint8_t calData = RADIOLIB_SX126X_CALIBRATE_ALL; + radio->mod->SPIwriteStream(RADIOLIB_SX126X_CMD_CALIBRATE, &calData, 1, true, false); + radio->mod->hal->delay(5); + uint32_t start = millis(); + while (radio->mod->hal->digitalRead(radio->mod->getGpio())) { + if (millis() - start > 50) break; + radio->mod->hal->yield(); + } + +#ifdef SX126X_DIO2_AS_RF_SWITCH + radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); +#endif +#ifdef SX126X_RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(SX126X_RX_BOOSTED_GAIN); +#endif +#ifdef SX126X_REGISTER_PATCH + uint8_t r_data = 0; + radio->readRegister(0x8B5, &r_data, 1); + r_data |= 0x01; + radio->writeRegister(0x8B5, &r_data, 1); +#endif +} From f54948e06db394c74269b064e0d92dfc193c0f64 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Sat, 21 Feb 2026 15:33:38 +0100 Subject: [PATCH 538/546] Also implement LR11x10 AGC reset Similar to SX126x but simpler. --- src/helpers/radiolib/CustomLR1110Wrapper.h | 4 +++- src/helpers/radiolib/LR11x0Reset.h | 17 +++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/helpers/radiolib/LR11x0Reset.h diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index 947bb51d..be4a6cde 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -2,11 +2,13 @@ #include "CustomLR1110.h" #include "RadioLibWrappers.h" +#include "LR11x0Reset.h" class CustomLR1110Wrapper : public RadioLibWrapper { public: CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - bool isReceivingPacket() override { + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio); } + bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } float getCurrentRSSI() override { diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h new file mode 100644 index 00000000..539ed44e --- /dev/null +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -0,0 +1,17 @@ +#pragma once + +#include <RadioLib.h> + +// Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). +// Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, +// then re-applies RX settings that calibration may reset. +inline void lr11x0ResetAGC(LR11x0* radio) { + radio->sleep(true, 0); + radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); + + radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); + +#ifdef RX_BOOSTED_GAIN + radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); +#endif +} From 85f764a114c299644587d40cb2ec08a15f4cf4d1 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Sat, 21 Feb 2026 17:34:28 +0100 Subject: [PATCH 539/546] Calibrate configured frequency for AGC reset --- src/helpers/radiolib/CustomLR1110.h | 2 ++ src/helpers/radiolib/CustomLR1110Wrapper.h | 2 +- src/helpers/radiolib/LR11x0Reset.h | 6 +++++- src/helpers/radiolib/SX126xReset.h | 4 ++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index e4332013..b1f68080 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -20,6 +20,8 @@ class CustomLR1110 : public LR1110 { return len; } + float getFreqMHz() const { return freqMHz; } + bool isReceiving() { uint16_t irq = getIrqStatus(); bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index be4a6cde..a1e0a493 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -7,7 +7,7 @@ class CustomLR1110Wrapper : public RadioLibWrapper { public: CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } - void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio); } + void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); } bool isReceivingPacket() override { return ((CustomLR1110 *)_radio)->isReceiving(); } diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h index 539ed44e..47cca627 100644 --- a/src/helpers/radiolib/LR11x0Reset.h +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -5,12 +5,16 @@ // Full receiver reset for LR11x0-family chips (LR1110, LR1120, LR1121). // Warm sleep powers down analog, calibrate(0x3F) refreshes all calibration blocks, // then re-applies RX settings that calibration may reset. -inline void lr11x0ResetAGC(LR11x0* radio) { +inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { radio->sleep(true, 0); radio->standby(RADIOLIB_LR11X0_STANDBY_RC, true); radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); + // calibrate(0x3F) defaults image calibration to an unknown band. + // Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default). + radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); + #ifdef RX_BOOSTED_GAIN radio->setRxBoostedGainMode(RX_BOOSTED_GAIN); #endif diff --git a/src/helpers/radiolib/SX126xReset.h b/src/helpers/radiolib/SX126xReset.h index ba08ef8d..39ddb73e 100644 --- a/src/helpers/radiolib/SX126xReset.h +++ b/src/helpers/radiolib/SX126xReset.h @@ -18,6 +18,10 @@ inline void sx126xResetAGC(SX126x* radio) { radio->mod->hal->yield(); } + // Calibrate(0x7F) defaults image calibration to 902-928MHz band. + // Re-calibrate for the actual operating frequency. + radio->calibrateImage(radio->freqMHz); + #ifdef SX126X_DIO2_AS_RF_SWITCH radio->setDio2AsRfSwitch(SX126X_DIO2_AS_RF_SWITCH); #endif From 9bae9d0ed2a6de07847ec3e4ae47c5346028ab9e Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <github@weebl.me> Date: Sat, 21 Feb 2026 17:42:33 +0100 Subject: [PATCH 540/546] fix comment, we know the band now after checking LR1110 user manual --- src/helpers/radiolib/LR11x0Reset.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/radiolib/LR11x0Reset.h b/src/helpers/radiolib/LR11x0Reset.h index 47cca627..d06ffc53 100644 --- a/src/helpers/radiolib/LR11x0Reset.h +++ b/src/helpers/radiolib/LR11x0Reset.h @@ -11,7 +11,7 @@ inline void lr11x0ResetAGC(LR11x0* radio, float freqMHz) { radio->calibrate(RADIOLIB_LR11X0_CALIBRATE_ALL); - // calibrate(0x3F) defaults image calibration to an unknown band. + // calibrate(0x3F) defaults image calibration to 902-928MHz band. // Re-calibrate for the actual operating frequency (band=4MHz matches RadioLib default). radio->calibrateImageRejection(freqMHz - 4.0f, freqMHz + 4.0f); From 59d9770ab97dce5cf47a142e7ff38b2bb686d0ac Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel.nieboer@onior.com> Date: Fri, 9 Jan 2026 05:06:17 +0100 Subject: [PATCH 541/546] Add GPS support Heltec Wireless Tracker v1.x Pin mapping verified against HTIT-Tracker V0.5 schematic: - GPIO35 (GPS_EN): N-ch MOSFET drives P-ch high-side switch, active HIGH - GPIO36 (GPS_RST): hardware reset, active LOW - GPIO33/34: UART TX/RX Delegates power management to MicroNMEALocationProvider begin()/stop() which independently controls GPS power via GPS_EN and shares VEXT with the display through RefCountedDigitalPin. --- variants/heltec_tracker/platformio.ini | 5 +++++ variants/heltec_tracker/target.cpp | 11 +++++------ variants/heltec_tracker/target.h | 1 + 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index dba05dcf..1dbda126 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -32,6 +32,11 @@ build_flags = -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) -D PIN_GPS_RX=33 -D PIN_GPS_TX=34 + -D PIN_GPS_EN=35 ; N-ch MOSFET Q2 drives P-ch high-side switch → active HIGH (default) + -D PIN_GPS_RESET=36 + -D PIN_GPS_RESET_ACTIVE=LOW + -D GPS_BAUD_RATE=115200 + -D ENV_INCLUDE_GPS=1 -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 25c2634b..f801bacb 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -16,7 +16,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1); +// GPS_EN (GPIO35) drives N-ch MOSFET → P-ch high-side switch; GPS_RESET (GPIO36) active LOW +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); HWTSensorManager sensors = HWTSensorManager(nmea); #ifdef DISPLAY_CLASS @@ -58,18 +59,16 @@ mesh::LocalIdentity radio_new_identity() { void HWTSensorManager::start_gps() { if (!gps_active) { - board.periph_power.claim(); - + _location->begin(); // Claims periph_power via RefCountedDigitalPin gps_active = true; - Serial1.println("$CFGSYS,h35155*68"); + Serial1.println("$CFGSYS,h35155*68"); // Configure GPS for all constellations } } void HWTSensorManager::stop_gps() { if (gps_active) { gps_active = false; - - board.periph_power.release(); + _location->stop(); // Releases periph_power via RefCountedDigitalPin } } diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 5296fb2c..29099f46 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -28,6 +28,7 @@ public: const char* getSettingName(int i) const override; const char* getSettingValue(int i) const override; bool setSettingValue(const char* name, const char* value) override; + LocationProvider* getLocationProvider() override { return _location; } }; extern HeltecV3Board board; From 8a9a0dca5f6c1cc554d3b7c001b38d5937bb281d Mon Sep 17 00:00:00 2001 From: Wessel Nieboer <wessel@weebl.me> Date: Mon, 9 Feb 2026 10:56:17 +0100 Subject: [PATCH 542/546] Fix GPS +8mA power leak when disabled (nRF52) On the T114, GPS_RESET (pin 38) is the same pin as PIN_3V3_EN. MicroNMEALocationProvider::begin() sets pin 38 HIGH (powering the 3V3 rail) but stop() never set it back LOW, leaving the GPS module powered even when disabled. Assert reset pin in stop() to mirror begin(), and guard _location->loop() behind gps_active check. Fixes meshcore-dev/MeshCore#1628 --- src/helpers/sensors/EnvironmentSensorManager.cpp | 4 +++- src/helpers/sensors/MicroNMEALocationProvider.h | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index a75d378c..f7b08508 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -707,7 +707,9 @@ void EnvironmentSensorManager::loop() { static long next_gps_update = 0; #if ENV_INCLUDE_GPS - _location->loop(); + if (gps_active) { + _location->loop(); + } if (millis() > next_gps_update) { if(gps_active){ diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 574570a3..1de75327 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -79,7 +79,10 @@ public : if (_pin_en != -1) { digitalWrite(_pin_en, !PIN_GPS_EN_ACTIVE); } - if (_peripher_power) _peripher_power->release(); + if (_pin_reset != -1) { + digitalWrite(_pin_reset, GPS_RESET_FORCE); + } + if (_peripher_power) _peripher_power->release(); } bool isEnabled() override { From 00566741f65fd90ab3f563cb198fcc142b909b59 Mon Sep 17 00:00:00 2001 From: Wouter Bijen <wbijen@gmail.com> Date: Mon, 2 Mar 2026 20:41:41 +0100 Subject: [PATCH 543/546] Add configurable max hops filter for auto-add contacts Filter auto-add of new contacts by hop count (issues #1533, #1546). Setting is configurable from the companion app via extended CMD_SET/GET_AUTOADD_CONFIG protocol (0 = no limit, 1-63 = max hops). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --- examples/companion_radio/DataStore.cpp | 2 ++ examples/companion_radio/MyMesh.cpp | 10 +++++++++- examples/companion_radio/MyMesh.h | 1 + examples/companion_radio/NodePrefs.h | 1 + src/helpers/BaseChatMesh.cpp | 9 +++++++++ src/helpers/BaseChatMesh.h | 1 + 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index fba64e8c..d9ebacb4 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -229,6 +229,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 + file.read((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 file.close(); } @@ -265,6 +266,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_ file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85 file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86 file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87 + file.write((uint8_t *)&_prefs.autoadd_max_hops, sizeof(_prefs.autoadd_max_hops)); // 88 file.close(); } diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c96f7e01..7477ce8e 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -318,6 +318,10 @@ bool MyMesh::shouldOverwriteWhenFull() const { return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0; } +uint8_t MyMesh::getAutoAddMaxHops() const { + return _prefs.autoadd_max_hops; +} + void MyMesh::onContactOverwrite(const uint8_t* pub_key) { _store->deleteBlobByKey(pub_key, PUB_KEY_SIZE); // delete from storage if (_serial->isConnected()) { @@ -1785,12 +1789,16 @@ void MyMesh::handleCmdFrame(size_t len) { } } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; + if (len >= 3) { + _prefs.autoadd_max_hops = cmd_frame[2]; + } savePrefs(); - writeOKFrame(); + writeOKFrame(); } else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) { int i = 0; out_frame[i++] = RESP_CODE_AUTOADD_CONFIG; out_frame[i++] = _prefs.autoadd_config; + out_frame[i++] = _prefs.autoadd_max_hops; _serial->writeFrame(out_frame, i); } else if (cmd_frame[0] == CMD_GET_ALLOWED_REPEAT_FREQ) { int i = 0; diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 87e6cf33..fe2c19bf 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -119,6 +119,7 @@ protected: bool isAutoAddEnabled() const override; bool shouldAutoAddContactType(uint8_t type) const override; bool shouldOverwriteWhenFull() const override; + uint8_t getAutoAddMaxHops() const override; void onContactsFull() override; void onContactOverwrite(const uint8_t* pub_key) override; bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override; diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index ec60c94a..3fd96660 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,4 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending + uint8_t autoadd_max_hops; // 0 = no limit, 1-63 = max hops for auto-add }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 5ec678c7..279e361c 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,6 +141,15 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } + // check hop limit for new contacts (0 = no limit) + uint8_t max_hops = getAutoAddMaxHops(); + if (max_hops > 0 && packet->getPathHashCount() > max_hops) { + ContactInfo ci; + populateContactFromAdvert(ci, id, parser, timestamp); + onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know + return; + } + from = allocateContactSlot(); if (from == NULL) { ContactInfo ci; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index fd391b98..ad14cc1f 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,6 +98,7 @@ protected: virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1-63 = max hops for auto-add virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; From c016db86d5e18d1cbd4a90e1b1391b532d90feea Mon Sep 17 00:00:00 2001 From: Wouter Bijen <wbijen@gmail.com> Date: Tue, 3 Mar 2026 08:37:22 +0100 Subject: [PATCH 544/546] Address PR review: subtract-1 encoding and clamp max_hops - Change > to >= so stored value 1 means direct/0-hop only (liamcottle) - Clamp max_hops to 63 on write since getPathHashCount() caps at 63 (robekl) - Update comments to reflect encoding: 0=no limit, 1=direct only, N=up to N-1 hops Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --- examples/companion_radio/MyMesh.cpp | 2 +- examples/companion_radio/NodePrefs.h | 2 +- src/helpers/BaseChatMesh.cpp | 4 ++-- src/helpers/BaseChatMesh.h | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 7477ce8e..6ec24ab1 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1790,7 +1790,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; if (len >= 3) { - _prefs.autoadd_max_hops = cmd_frame[2]; + _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)63); } savePrefs(); writeOKFrame(); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 3fd96660..0a59a6dc 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,5 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1-63 = max hops for auto-add + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct only, N = up to N-1 hops (max 63) }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 279e361c..84c6ae4a 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,9 +141,9 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } - // check hop limit for new contacts (0 = no limit) + // check hop limit for new contacts (0 = no limit, 1 = direct only, N = up to N-1 hops) uint8_t max_hops = getAutoAddMaxHops(); - if (max_hops > 0 && packet->getPathHashCount() > max_hops) { + if (max_hops > 0 && packet->getPathHashCount() >= max_hops) { ContactInfo ci; populateContactFromAdvert(ci, id, parser, timestamp); onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index ad14cc1f..0dd88739 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,7 +98,7 @@ protected: virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } - virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1-63 = max hops for auto-add + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct only, N = up to N-1 hops virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; From 2cb08775c010bd1474a0b57d942812965406beda Mon Sep 17 00:00:00 2001 From: Wouter Bijen <wbijen@gmail.com> Date: Tue, 3 Mar 2026 08:40:17 +0100 Subject: [PATCH 545/546] Clarify comment wording: 1 = direct (0 hops) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --- examples/companion_radio/NodePrefs.h | 2 +- src/helpers/BaseChatMesh.cpp | 2 +- src/helpers/BaseChatMesh.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 0a59a6dc..0c887802 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,5 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct only, N = up to N-1 hops (max 63) + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 63) }; \ No newline at end of file diff --git a/src/helpers/BaseChatMesh.cpp b/src/helpers/BaseChatMesh.cpp index 84c6ae4a..33d7edbe 100644 --- a/src/helpers/BaseChatMesh.cpp +++ b/src/helpers/BaseChatMesh.cpp @@ -141,7 +141,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, return; } - // check hop limit for new contacts (0 = no limit, 1 = direct only, N = up to N-1 hops) + // check hop limit for new contacts (0 = no limit, 1 = direct (0 hops), N = up to N-1 hops) uint8_t max_hops = getAutoAddMaxHops(); if (max_hops > 0 && packet->getPathHashCount() >= max_hops) { ContactInfo ci; diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index 0dd88739..ab90d581 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -98,7 +98,7 @@ protected: virtual bool shouldAutoAddContactType(uint8_t type) const { return true; } virtual void onContactsFull() {}; virtual bool shouldOverwriteWhenFull() const { return false; } - virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct only, N = up to N-1 hops + virtual uint8_t getAutoAddMaxHops() const { return 0; } // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops virtual void onContactOverwrite(const uint8_t* pub_key) {}; virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0; virtual ContactInfo* processAck(const uint8_t *data) = 0; From 1d190ad9440d54b01afee1d2c763c43a2459170c Mon Sep 17 00:00:00 2001 From: Wouter Bijen <wbijen@gmail.com> Date: Tue, 3 Mar 2026 09:05:53 +0100 Subject: [PATCH 546/546] Clamp max_hops to 64 to cover full protocol hop range (0-63) --- examples/companion_radio/MyMesh.cpp | 2 +- examples/companion_radio/NodePrefs.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 6ec24ab1..1f71a9bc 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1790,7 +1790,7 @@ void MyMesh::handleCmdFrame(size_t len) { } else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) { _prefs.autoadd_config = cmd_frame[1]; if (len >= 3) { - _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)63); + _prefs.autoadd_max_hops = min(cmd_frame[2], (uint8_t)64); } savePrefs(); writeOKFrame(); diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 0c887802..090209c1 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -30,5 +30,5 @@ struct NodePrefs { // persisted to file uint8_t autoadd_config; // bitmask for auto-add contacts config uint8_t client_repeat; uint8_t path_hash_mode; // which path mode to use when sending - uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 63) + uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64) }; \ No newline at end of file