From 0bc0fc2c8124e14eef575dffffaa13a2c3e2ee5a Mon Sep 17 00:00:00 2001 From: ssp97 Date: Mon, 26 Jan 2026 02:09:50 +0800 Subject: [PATCH 01/16] fix: avoid redundant redefinition of SX126X_DIO3_TCXO_VOLTAGE --- src/helpers/radiolib/CustomLLCC68.h | 3 +-- src/helpers/radiolib/CustomSX1262.h | 3 +-- src/helpers/radiolib/CustomSX1268.h | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/helpers/radiolib/CustomLLCC68.h b/src/helpers/radiolib/CustomLLCC68.h index c239cc41..4269fc73 100644 --- a/src/helpers/radiolib/CustomLLCC68.h +++ b/src/helpers/radiolib/CustomLLCC68.h @@ -45,8 +45,7 @@ class CustomLLCC68 : public LLCC68 { int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); // if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) { - #define SX126X_DIO3_TCXO_VOLTAGE (0.0f); - tcxo = SX126X_DIO3_TCXO_VOLTAGE; + tcxo = 0.0f; status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); } if (status != RADIOLIB_ERR_NONE) { diff --git a/src/helpers/radiolib/CustomSX1262.h b/src/helpers/radiolib/CustomSX1262.h index bfaea7c7..3dca771b 100644 --- a/src/helpers/radiolib/CustomSX1262.h +++ b/src/helpers/radiolib/CustomSX1262.h @@ -45,8 +45,7 @@ class CustomSX1262 : public SX1262 { int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); // if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) { - #define SX126X_DIO3_TCXO_VOLTAGE (0.0f); - tcxo = SX126X_DIO3_TCXO_VOLTAGE; + tcxo = 0.0f; status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); } if (status != RADIOLIB_ERR_NONE) { diff --git a/src/helpers/radiolib/CustomSX1268.h b/src/helpers/radiolib/CustomSX1268.h index 1e2e2620..f863829a 100644 --- a/src/helpers/radiolib/CustomSX1268.h +++ b/src/helpers/radiolib/CustomSX1268.h @@ -45,8 +45,7 @@ class CustomSX1268 : public SX1268 { int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); // if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) { - #define SX126X_DIO3_TCXO_VOLTAGE (0.0f); - tcxo = SX126X_DIO3_TCXO_VOLTAGE; + tcxo = 0.0f; status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo); } if (status != RADIOLIB_ERR_NONE) { From 557d714c3c8ac993032b7d0b2ff0229a05d7cef7 Mon Sep 17 00:00:00 2001 From: lincomatic Date: Wed, 25 Feb 2026 07:52:42 -0800 Subject: [PATCH 02/16] don't limit bridge_baud to 115200 --- src/helpers/CommonCLI.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 263eb665..389136c6 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -4,6 +4,10 @@ #include "AdvertDataHelpers.h" #include +#ifndef BRIDGE_MAX_BAUD +#define BRIDGE_MAX_BAUD 115200 +#endif + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -100,7 +104,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) { _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_baud = constrain(_prefs->bridge_baud, 9600, BRIDGE_MAX_BAUD); _prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14); _prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1); @@ -588,13 +592,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch #ifdef WITH_RS232_BRIDGE } else if (memcmp(config, "bridge.baud ", 12) == 0) { uint32_t baud = atoi(&config[12]); - if (baud >= 9600 && baud <= 115200) { + if (baud >= 9600 && baud <= BRIDGE_MAX_BAUD) { _prefs->bridge_baud = (uint32_t)baud; _callbacks->restartBridge(); savePrefs(); strcpy(reply, "OK"); } else { - strcpy(reply, "Error: baud rate must be between 9600-115200"); + sprintf(reply, "Error: baud rate must be between 9600-%d",BRIDGE_MAX_BAUD); } #endif #ifdef WITH_ESPNOW_BRIDGE From e323755990049215883c92b783b42299805b04ff Mon Sep 17 00:00:00 2001 From: Specter242 Date: Fri, 27 Feb 2026 22:12:12 -0500 Subject: [PATCH 03/16] sensecap solar: add poweroff command and board power/LED parity --- docs/nrf52_power_management.md | 2 +- src/helpers/CommonCLI.cpp | 4 +- .../sensecap_solar/SenseCapSolarBoard.cpp | 52 ++++++++++++++++++- variants/sensecap_solar/SenseCapSolarBoard.h | 24 +++++++++ variants/sensecap_solar/platformio.ini | 6 ++- variants/sensecap_solar/variant.h | 8 ++- 6 files changed, 89 insertions(+), 7 deletions(-) diff --git a/docs/nrf52_power_management.md b/docs/nrf52_power_management.md index ebe9bbbe..9c7416b3 100644 --- a/docs/nrf52_power_management.md +++ b/docs/nrf52_power_management.md @@ -42,7 +42,7 @@ Shutdown reason codes (stored in GPREGRET2): | 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 | +| SenseCAP Solar | Yes | Yes | Yes | | WIO Tracker L1 / L1 E-Ink | No | No | No | | WIO WM1110 | No | No | No | | Mesh Pocket | No | No | No | diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 6dcf7018..e72307a1 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -194,7 +194,9 @@ 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) { + if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) { + _board->powerOff(); // doesn't return + } else if (memcmp(command, "reboot", 6) == 0) { _board->reboot(); // doesn't return } else if (memcmp(command, "clkreboot", 9) == 0) { // Reset clock diff --git a/variants/sensecap_solar/SenseCapSolarBoard.cpp b/variants/sensecap_solar/SenseCapSolarBoard.cpp index c0883035..b9dd2503 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.cpp +++ b/variants/sensecap_solar/SenseCapSolarBoard.cpp @@ -3,8 +3,43 @@ #include "SenseCapSolarBoard.h" +#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 SenseCapSolarBoard::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 SenseCapSolarBoard::begin() { - NRF52Board::begin(); + NRF52BoardDCDC::begin(); + + pinMode(BATTERY_PIN, INPUT); + pinMode(VBAT_ENABLE, OUTPUT); + digitalWrite(VBAT_ENABLE, LOW); + analogReadResolution(12); + analogReference(AR_INTERNAL_3_0); + delay(50); + +#ifdef PIN_USER_BTN + pinMode(PIN_USER_BTN, INPUT_PULLUP); +#elif defined(PIN_BUTTON1) + pinMode(PIN_BUTTON1, INPUT_PULLUP); +#endif #if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL) Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL); @@ -12,10 +47,23 @@ void SenseCapSolarBoard::begin() { Wire.begin(); +#ifdef LED_GREEN + pinMode(LED_GREEN, OUTPUT); + digitalWrite(LED_GREEN, HIGH); +#endif +#ifdef LED_BLUE + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, LOW); +#endif + #ifdef P_LORA_TX_LED pinMode(P_LORA_TX_LED, OUTPUT); digitalWrite(P_LORA_TX_LED, LOW); #endif +#ifdef NRF52_POWER_MANAGEMENT + checkBootVoltage(&power_config); +#endif + delay(10); // give sx1262 some time to power up -} \ No newline at end of file +} diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index 67215b8e..839c1963 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -5,6 +5,11 @@ #include class SenseCapSolarBoard : public NRF52BoardDCDC { +protected: +#ifdef NRF52_POWER_MANAGEMENT + void initiateShutdown(uint8_t reason) override; +#endif + public: SenseCapSolarBoard() : NRF52Board("SENSECAP_SOLAR_OTA") {} void begin(); @@ -31,4 +36,23 @@ public: const char* getManufacturerName() const override { return "Seeed SenseCap Solar"; } + + void powerOff() override { + digitalWrite(LED_GREEN, LOW); + digitalWrite(LED_BLUE, LOW); + +#ifdef PIN_USER_BTN + while (digitalRead(PIN_USER_BTN) == LOW); + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); +#elif defined(PIN_BUTTON1) + while (digitalRead(PIN_BUTTON1) == LOW); + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_BUTTON1]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); +#endif + +#ifdef NRF52_POWER_MANAGEMENT + initiateShutdown(SHUTDOWN_REASON_USER); +#else + sd_power_system_off(); +#endif + } }; diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index d4fb7b44..e69b651f 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -10,13 +10,15 @@ build_flags = ${nrf52_base.build_flags} -I src/helpers/nrf52 -UENV_INCLUDE_GPS -D NRF52_PLATFORM=1 + -D NRF52_POWER_MANAGEMENT -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper - -D P_LORA_TX_LED=12 + -D P_LORA_TX_LED=11 -D P_LORA_DIO_1=1 -D P_LORA_RESET=2 -D P_LORA_BUSY=3 -D P_LORA_NSS=4 + -D PIN_USER_BTN=PIN_BUTTON1 -D LORA_TX_POWER=22 -D SX126X_RXEN=5 -D SX126X_TXEN=RADIOLIB_NC @@ -96,4 +98,4 @@ build_src_filter = ${SenseCap_Solar.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${SenseCap_Solar.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 diff --git a/variants/sensecap_solar/variant.h b/variants/sensecap_solar/variant.h index 76494f48..537c9024 100644 --- a/variants/sensecap_solar/variant.h +++ b/variants/sensecap_solar/variant.h @@ -32,6 +32,7 @@ // Buttons #define PIN_BUTTON1 (13) #define PIN_BUTTON2 (20) +#define PIN_USER_BTN PIN_BUTTON1 #define VBAT_ENABLE (19) // Output LOW to enable reading of the BAT voltage. @@ -41,6 +42,11 @@ #define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge #define ADC_RESOLUTION (12) +// nRF52 power management settings +#define PWRMGT_VOLTAGE_BOOTLOCK (3300) // Won't boot below this voltage (mV) +#define PWRMGT_LPCOMP_AIN (7) // AIN7 = P0.31 = BATTERY_PIN +#define PWRMGT_LPCOMP_REFSEL (2) // 3/8 VDD (~3.38-3.71V) + // Serial interfaces #define PIN_SERIAL1_RX (7) #define PIN_SERIAL1_TX (6) @@ -82,4 +88,4 @@ #define EXTERNAL_FLASH_DEVICES P25Q16H #define EXTERNAL_FLASH_USE_QSPI -#endif \ No newline at end of file +#endif From e08dcbdd03a77c2d6421b574427bf9c459c943a1 Mon Sep 17 00:00:00 2001 From: Specter242 Date: Fri, 27 Feb 2026 22:51:19 -0500 Subject: [PATCH 04/16] sensecap solar: stabilize wake pin and add button hold poweroff --- examples/simple_repeater/main.cpp | 20 ++++++++++++++++++++ variants/sensecap_solar/SenseCapSolarBoard.h | 6 ++++-- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index d226d1fa..e37078ce 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -23,6 +23,11 @@ static char command[160]; 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 +#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) +static unsigned long userBtnDownAt = 0; +#define USER_BTN_HOLD_OFF_MILLIS 1500 +#endif + void setup() { Serial.begin(115200); delay(1000); @@ -127,6 +132,21 @@ void loop() { command[0] = 0; // reset command buffer } +#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) + // Hold the user button to power off the SenseCAP Solar repeater. + int btnState = digitalRead(PIN_USER_BTN); + if (btnState == LOW) { + if (userBtnDownAt == 0) { + userBtnDownAt = millis(); + } else if ((unsigned long)(millis() - userBtnDownAt) >= USER_BTN_HOLD_OFF_MILLIS) { + Serial.println("Powering off..."); + board.powerOff(); // does not return + } + } else { + userBtnDownAt = 0; + } +#endif + the_mesh.loop(); sensors.loop(); #ifdef DISPLAY_CLASS diff --git a/variants/sensecap_solar/SenseCapSolarBoard.h b/variants/sensecap_solar/SenseCapSolarBoard.h index 839c1963..50447fba 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.h +++ b/variants/sensecap_solar/SenseCapSolarBoard.h @@ -43,10 +43,12 @@ public: #ifdef PIN_USER_BTN while (digitalRead(PIN_USER_BTN) == LOW); - nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); + // Keep pull-up enabled in system-off so the wake line doesn't float low. + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #elif defined(PIN_BUTTON1) while (digitalRead(PIN_BUTTON1) == LOW); - nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_BUTTON1]), NRF_GPIO_PIN_NOPULL, NRF_GPIO_PIN_SENSE_LOW); + // Keep pull-up enabled in system-off so the wake line doesn't float low. + nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_BUTTON1]), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); #endif #ifdef NRF52_POWER_MANAGEMENT From 992cc083e6b414e1452f2ebcca9b56c6a1ab40b7 Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Fri, 6 Mar 2026 15:22:00 -0800 Subject: [PATCH 05/16] Update cli_commands.md to include path.hash.mode and loop.detect Adding the new repeater cli commands introduced in 1.14 Ref: https://buymeacoffee.com/ripplebiz/path-diagnostics-improvements --- docs/cli_commands.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 1d3430db..8c48ced7 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -383,6 +383,43 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +### View or change this node's avert path hash size +**Usage:** +- `get path.hash.mode` +- `set path.hash.mode ` + +**Parameters:** +- `value`: Path hash size (0-2) + - `0`: 1 Byte hash size (256 count)[64 max flood] + - `1`: 2 Byte hash size (65,536 count)[32 max flood] + - `2`: 3 Byte hash size (16,777,216 count)[21 max flood] + +**Default:** `0` + +**Note:** the 'mode' is the low-level encoding (0..3) where 0 -> 1-byte (legacy), 1 -> 2-byte, 2 -> 3-byte for the ID/hash of the repeaters adverts. This feature was added in firmware 1.14 + +--- + +### View or change this node's loop detection +**Usage:** +- `get loop.detect` +- `set loop.detect ` + +**Parameters:** +- `state`: + - `off`: no loop detection is performed + - `minimal`: packets are dropped if repeaters ID/hash shows more times than 4(1-byte), 2(2-byte), 1(3-byte) + - `moderate`: packets are dropped if repeaters ID/hash shows more times than 2(1-byte), 1(2-byte), 1(3-byte) + - `strict`: packets are dropped if repeaters ID/hash shows more times than 1(1-byte), 1(2-byte), 1(3-byte) + +**Default:** `off` + +**Note:** When it is enabled, repeaters will now reject flood packets which look like they are in a loop. This has been happening recently in some meshes when there is just a single 'bad' repeater firmware out there (prob some forked or custom firmware). If the payload is messed with, then forwarded, the same packet ends up causing a packet storm, repeated up to the max 64 hops. This feature was added in firmware 1.14 + +**Example:** If preference is `loop.detect minimal`, and a 1-byte path size packet is received, the repeater will see if its own ID/hash is already in the path. If it's already encoded 4 times, it will reject the packet. If the packet uses 2-byte path size, and repeater's own ID/hash is already encoded 2 times, it rejects. If the packet uses 3-byte path size, and the repeater's own ID/hash is already encoded 1 time, it rejects. + +--- + #### View or change the retransmit delay factor for flood traffic **Usage:** - `get txdelay` From d2a6fda8d587a9bb7f8af09d74d1bc9bce5e3e64 Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Fri, 6 Mar 2026 16:30:45 -0800 Subject: [PATCH 06/16] Update cli_commands.md --- docs/cli_commands.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 8c48ced7..ff5f2366 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -398,6 +398,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Note:** the 'mode' is the low-level encoding (0..3) where 0 -> 1-byte (legacy), 1 -> 2-byte, 2 -> 3-byte for the ID/hash of the repeaters adverts. This feature was added in firmware 1.14 +**Temporary Note:** adverts with hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new. Consider your install base of firmware >=1.14 before implementing. + --- ### View or change this node's loop detection From 721c21f1e52867cc4ff39b70d1b523c7fac01128 Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:40:41 -0800 Subject: [PATCH 07/16] Apply suggestion from @weebl2000 Co-authored-by: Wessel --- 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 ff5f2366..e7cb85e3 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -383,7 +383,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -### View or change this node's avert path hash size +#### View or change this node's advert path hash size **Usage:** - `get path.hash.mode` - `set path.hash.mode ` From 0228d596e8aef95d32596b407f14874ac08df6fb Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:42:13 -0800 Subject: [PATCH 08/16] Apply suggestion from @weebl2000 Co-authored-by: Wessel --- 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 e7cb85e3..d4c97e84 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -402,7 +402,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -### View or change this node's loop detection +#### View or change this node's loop detection **Usage:** - `get loop.detect` - `set loop.detect ` From 4aaa557dafe18d757c5ef9a7acb60b48b6be09be Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:43:23 -0800 Subject: [PATCH 09/16] Apply suggestion from @weebl2000 Co-authored-by: Wessel --- docs/cli_commands.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index d4c97e84..9f84808c 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -410,9 +410,9 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Parameters:** - `state`: - `off`: no loop detection is performed - - `minimal`: packets are dropped if repeaters ID/hash shows more times than 4(1-byte), 2(2-byte), 1(3-byte) - - `moderate`: packets are dropped if repeaters ID/hash shows more times than 2(1-byte), 1(2-byte), 1(3-byte) - - `strict`: packets are dropped if repeaters ID/hash shows more times than 1(1-byte), 1(2-byte), 1(3-byte) + - `minimal`: packets are dropped if repeater's ID/hash appears 4 or more times (1-byte), 2 or more (2-byte), 1 or more (3-byte) + - `moderate`: packets are dropped if repeater's ID/hash appears 2 or more times (1-byte), 1 or more (2-byte), 1 or more (3-byte) + - `strict`: packets are dropped if repeater's ID/hash appears 1 or more times (1-byte), 1 or more (2-byte), 1 or more (3-byte) **Default:** `off` From fe32f16aa40a00dfed9d7315261680d84b9bf174 Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Sat, 7 Mar 2026 10:55:40 -0800 Subject: [PATCH 10/16] Update cli_commands.md R399 updates --- docs/cli_commands.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 9f84808c..1bcb33cc 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -390,13 +390,14 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Parameters:** - `value`: Path hash size (0-2) - - `0`: 1 Byte hash size (256 count)[64 max flood] - - `1`: 2 Byte hash size (65,536 count)[32 max flood] - - `2`: 3 Byte hash size (16,777,216 count)[21 max flood] + - `0`: 1 Byte hash size (256 unique ids)[64 max flood] + - `1`: 2 Byte hash size (65,536 unique ids)[32 max flood] + - `2`: 3 Byte hash size (16,777,216 unique ids)[21 max flood] + - `3`: DO NOT USE (Reserved) **Default:** `0` -**Note:** the 'mode' is the low-level encoding (0..3) where 0 -> 1-byte (legacy), 1 -> 2-byte, 2 -> 3-byte for the ID/hash of the repeaters adverts. This feature was added in firmware 1.14 +**Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used the for when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14 **Temporary Note:** adverts with hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new. Consider your install base of firmware >=1.14 before implementing. From bb454861c7b893c76813c3e29f0f19a871f67c71 Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:01:20 -0800 Subject: [PATCH 11/16] Update cli_commands.md R402 --- 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 1bcb33cc..78df127e 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -399,7 +399,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used the for when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14 -**Temporary Note:** adverts with hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new. Consider your install base of firmware >=1.14 before implementing. +**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are suppored. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. --- From 36db50a0d25af181de03fe2239cd9f8053c89930 Mon Sep 17 00:00:00 2001 From: AI7NC <77077873+AI7NC@users.noreply.github.com> Date: Sat, 7 Mar 2026 11:14:03 -0800 Subject: [PATCH 12/16] Update cli_commands.md R400 grammer Small grammar fix --- 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 78df127e..98238f0d 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -397,7 +397,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `0` -**Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used the for when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14 +**Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14 **Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are suppored. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. From 0d0b31eb52fa794f9e2b08764baf04b039159623 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Sun, 8 Mar 2026 21:31:08 -0600 Subject: [PATCH 13/16] docs: sync CLI and payload docs with implementation --- docs/cli_commands.md | 83 ++++++++++++++++++++++++++++++++++++-------- docs/faq.md | 10 +++--- docs/payloads.md | 35 ++++++++----------- 3 files changed, 90 insertions(+), 38 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 7ade9706..68e8fb9e 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -53,7 +53,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore - `time ` **Parameters:** -- `epoc_seconds`: Unix epoc time +- `epoch_seconds`: Unix epoch time --- @@ -63,6 +63,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +### Send a zero-hop advert +**Usage:** +- `advert.zerohop` + +--- + ### Start an Over-The-Air (OTA) firmware update **Usage:** - `start ota` @@ -136,7 +142,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- -### End capture of rx log to node sotrage +### End capture of rx log to node storage **Usage:** `log stop` --- @@ -200,7 +206,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **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.** +**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. Refer to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** --- @@ -230,6 +236,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Default:** `869.525` **Note:** Requires reboot to apply +**Serial Only:** `set freq ` ### System @@ -287,7 +294,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Serial Only:** - `get prv.key`: Yes -- `set prv.key`: No +- `set prv.key`: Yes **Note:** Requires reboot to take effect after setting @@ -295,16 +302,16 @@ This document provides an overview of CLI commands that can be sent to MeshCore #### Change this node's admin password **Usage:** -- `password ` +- `password ` **Parameters:** -- `password`: Admin password +- `new_password`: New admin password **Set by build flag:** `ADMIN_PASSWORD` **Default:** `password` -**Note:** Echoed back for confirmation +**Note:** Command reply echoes the updated password for confirmation. **Note:** Any node using this password will be added to the admin ACL list. @@ -354,13 +361,25 @@ This document provides an overview of CLI commands that can be sent to MeshCore --- +#### View this node's public key +**Usage:** `get public.key` + +--- + +#### View this node's configured role +**Usage:** `get role` + +--- + #### View or change this node's power saving flag (Repeater Only) **Usage:** -- `powersaving ` - `powersaving` +- `powersaving on` +- `powersaving off` **Parameters:** -- `state`: `on`|`off` +- `on`: enable power saving +- `off`: disable power saving **Default:** `on` @@ -769,7 +788,7 @@ region save - `gps advert ` **Parameters:** -- `policy`: `none`|`shared`|`prefs` +- `policy`: `none`|`share`|`prefs` - `none`: don't include location in adverts - `share`: share gps location (from SensorManager) - `prefs`: location stored in node's lat and lon settings @@ -803,6 +822,11 @@ region save ### Bridge (When bridge support is compiled in) +#### View the compiled bridge type +**Usage:** `get bridge.type` + +--- + #### View or change the bridge enabled flag **Usage:** - `get bridge.enabled` @@ -840,10 +864,10 @@ region save **Parameters:** - `source`: - - `rx`: bridges received packets - - `tx`: bridges transmitted packets + - `logRx`: bridges received packets + - `logTx`: bridges transmitted packets -**Default:** `tx` +**Default:** `logTx` --- @@ -875,8 +899,39 @@ region save - `set bridge.secret ` **Parameters:** -- `secret`: 16-character encryption secret +- `secret`: ESP-NOW bridge secret, up to 15 characters **Default:** Varies by board --- + +#### View the bootloader version (nRF52 only) +**Usage:** `get bootloader.ver` + +--- + +#### View power management support +**Usage:** `get pwrmgt.support` + +--- + +#### View the current power source +**Usage:** `get pwrmgt.source` + +**Note:** Returns an error on boards without power management support. + +--- + +#### View the boot reset and shutdown reasons +**Usage:** `get pwrmgt.bootreason` + +**Note:** Returns an error on boards without power management support. + +--- + +#### View the boot voltage +**Usage:** `get pwrmgt.bootmv` + +**Note:** Returns an error on boards without power management support. + +--- diff --git a/docs/faq.md b/docs/faq.md index 220b8971..530f9701 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -221,11 +221,11 @@ 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 sends a flood advert once every 3 hours by default. 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 12 hours by default. This interval can be configured using the following command: -`set advert.interval {minutes}` +`set flood.advert.interval {hours}` -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. +The separate `set advert.interval {minutes}` command controls the local zero-hop advert timer. ### 2.5. Q: Is there a hop limit? @@ -260,7 +260,9 @@ Repeater or room server can be administered with one of the options below: ### 3.2. Q: Do I need to set the location for a repeater? **A:** While not required, with location set for a repeater it will show up on the MeshCore map in the future. Set location with the following command: -`set lat set long ` +`set lat ` + +`set lon ` You can get the latitude and longitude from Google Maps by right-clicking the location you are at on the map. diff --git a/docs/payloads.md b/docs/payloads.md index 3648b655..15fec757 100644 --- a/docs/payloads.md +++ b/docs/payloads.md @@ -90,23 +90,17 @@ Returned path messages provide a description of the route a packet took from the ## Request -| Field | Size (bytes) | Description | -|--------------|-----------------|----------------------------| -| timestamp | 4 | send time (unix timestamp) | -| request type | 1 | see below | -| request data | rest of payload | depends on request type | +| Field | Size (bytes) | Description | +|--------------|-----------------|------------------------------------------| +| timestamp | 4 | sender time (unix timestamp) | +| request data | rest of payload | application-defined request payload body | -Request type +For the common chat/server helpers in `BaseChatMesh`, the current request type values are: | Value | Name | Description | |--------|----------------------|---------------------------------------| | `0x01` | get stats | get stats of repeater or room server | -| `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 | -| `0x06` | get neighbors | get repeater node's neighbors | -| `0x07` | get owner info | get repeater firmware-ver/name/owner info | +| `0x02` | keepalive | keep-alive request used for maintained connections | ### Get stats @@ -133,35 +127,36 @@ Gets information about the node, possibly including the following: ### Get telemetry data -Request data about sensors on the node, including battery level. +Not defined in `BaseChatMesh`. Sensor- and application-specific request payloads may be implemented by higher-level firmware. ### Get Telemetry -TODO +Not defined in `BaseChatMesh`. ### Get Min/Max/Ave (Sensor nodes) -TODO +Not defined in `BaseChatMesh`. ### Get Access List -TODO +Not defined in `BaseChatMesh`. ### Get Neighors -TODO +Not defined in `BaseChatMesh`. ### Get Owner Info -TODO +Not defined in `BaseChatMesh`. ## Response | Field | Size (bytes) | Description | |---------|-----------------|-------------| -| tag | 4 | TODO | -| content | rest of payload | TODO | +| content | rest of payload | application-defined response body | + +Response contents are opaque application data. There is no single generic response envelope beyond the encrypted payload wrapper shown above. ## Plain text message From 6677b409540843a1b4b00b11515d11f8d5357d78 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Sun, 8 Mar 2026 21:31:08 -0600 Subject: [PATCH 14/16] docs: sync companion and kiss protocol docs --- docs/companion_protocol.md | 176 ++++++++++++------------------------ docs/kiss_modem_protocol.md | 6 +- 2 files changed, 59 insertions(+), 123 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index 9d45b59e..11ba0ab2 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -1,6 +1,6 @@ # Companion Protocol -- **Last Updated**: 2026-01-03 +- **Last Updated**: 2026-03-08 - **Protocol Version**: Companion Firmware v1.12.0+ > NOTE: This document is still in development. Some information may be inaccurate. @@ -100,7 +100,7 @@ When writing commands to the RX characteristic, specify the write type: ### 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: +The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (50 bytes), you may need to: 1. **Request Larger MTU**: Request MTU of 512 bytes if supported - Android: `gatt.requestMtu(512)` @@ -167,16 +167,16 @@ The first byte indicates the packet type (see [Response Parsing](#response-parsi **Command Format**: ``` Byte 0: 0x01 -Byte 1: 0x03 -Bytes 2-10: "mccli" (ASCII, null-padded to 9 bytes) +Bytes 1-7: Reserved (currently ignored by firmware) +Bytes 8+: Application name (UTF-8, optional) ``` **Example** (hex): ``` -01 03 6d 63 63 6c 69 00 00 00 00 +01 00 00 00 00 00 00 00 6d 63 63 6c 69 ``` -**Response**: `PACKET_OK` (0x00) +**Response**: `PACKET_SELF_INFO` (0x05) --- @@ -216,8 +216,6 @@ Byte 1: Channel Index (0-7) **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 @@ -229,10 +227,10 @@ Byte 1: Channel Index (0-7) 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) +Bytes 34-49: Secret (16 bytes) ``` -**Total Length**: 66 bytes +**Total Length**: 50 bytes **Channel Index**: - Index 0: Reserved for public channels (no secret) @@ -243,16 +241,18 @@ Bytes 34-65: Secret (32 bytes) - Maximum 32 bytes - Padded with null bytes (0x00) if shorter -**Secret Field** (32 bytes): -- For **private channels**: 32-byte secret +**Secret Field** (16 bytes): +- For **private channels**: 16-byte secret - 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] + [16 bytes of secret] ``` +**Note**: The 32-byte secret variant is unsupported and returns `PACKET_ERROR`. + **Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure --- @@ -304,9 +304,9 @@ Byte 0: 0x0A --- -### 7. Get Battery +### 7. Get Battery and Storage -**Purpose**: Query device battery level. +**Purpose**: Query device battery voltage and storage usage. **Command Format**: ``` @@ -318,7 +318,7 @@ Byte 0: 0x14 14 ``` -**Response**: `PACKET_BATTERY` (0x0C) with battery percentage +**Response**: `PACKET_BATTERY` (0x0C) with battery millivolts and storage information --- @@ -346,7 +346,7 @@ Byte 0: 0x14 1. **Set Channel**: - Fetch all channel slots, and find one with empty name and all-zero secret - Generate or provide a 16-byte secret - - Send `CMD_SET_CHANNEL` with name and secret + - Send `CMD_SET_CHANNEL` with name and a 16-byte secret 2. **Get Channel**: - Send `CMD_GET_CHANNEL` with channel index - Parse `RESP_CODE_CHANNEL_INFO` response @@ -360,7 +360,7 @@ Byte 0: 0x14 ### Receiving Messages -Messages are received via the RX characteristic (notifications). The device sends: +Messages are received via the TX characteristic (notifications). The device sends: 1. **Channel Messages**: - `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format @@ -544,10 +544,10 @@ Byte 1: Error code (optional) 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) +Bytes 34-49: Secret (16 bytes) ``` -**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. +**Note**: The device returns the 16-byte channel secret in this response. **PACKET_DEVICE_INFO** (0x0D): ``` @@ -562,6 +562,8 @@ 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) +Byte 80: Client repeat enabled/preferred (firmware v9+) +Byte 81: Path hash mode (firmware v10+) ``` **Parsing Pseudocode**: @@ -587,9 +589,7 @@ def parse_device_info(data): **PACKET_BATTERY** (0x0C): ``` Byte 0: 0x0C -Bytes 1-2: Battery Level (16-bit little-endian, percentage 0-100) - -Optional (if data size > 3): +Bytes 1-2: Battery Voltage (16-bit little-endian, millivolts) Bytes 3-6: Used Storage (32-bit little-endian, KB) Bytes 7-10: Total Storage (32-bit little-endian, KB) ``` @@ -600,14 +600,12 @@ def parse_battery(data): if len(data) < 3: return None - level = int.from_bytes(data[1:3], 'little') - info = {'level': level} + mv = int.from_bytes(data[1:3], 'little') + info = {'battery_mv': mv} - 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 + if len(data) >= 11: + info['used_kb'] = int.from_bytes(data[3:7], 'little') + info['total_kb'] = int.from_bytes(data[7:11], 'little') return info ``` @@ -629,7 +627,7 @@ 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) +Bytes 58+: Device Name (UTF-8, variable length, no null terminator required) ``` **Parsing Pseudocode**: @@ -680,9 +678,9 @@ def parse_self_info(data): **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) +Byte 1: Route Flag (0 = direct, 1 = flood) +Bytes 2-5: Tag / Expected ACK (4 bytes, little-endian) +Bytes 6-9: Suggested Timeout (32-bit little-endian, milliseconds) ``` **PACKET_ACK** (0x82): @@ -710,89 +708,32 @@ Bytes 1-6: ACK Code (6 bytes, hex) **Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response. -### Partial Packet Handling +### Frame Handling -BLE notifications may arrive in chunks, especially for larger packets. Implement buffering: +BLE implementations enqueue and deliver one protocol frame per BLE write/notification at the firmware layer. -**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) -``` +- Apps should treat each characteristic write/notification as exactly one companion protocol frame +- Apps should still validate frame lengths before parsing +- Future transports or firmware revisions may differ, so avoid assuming fixed payload sizes for variable-length responses ### Response Handling 1. **Command-Response Pattern**: - - Send command via TX characteristic - - Wait for response via RX characteristic (notification) + - Send command via RX characteristic + - Wait for response via TX 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 + - Device may send messages at any time via TX characteristic - Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command - Parse incoming messages and route to appropriate handlers - - Buffer partial packets until complete + - Validate frame length before decoding 3. **Response Matching**: - Match responses to commands by expected packet type: - - `APP_START` → `PACKET_OK` + - `APP_START` → `PACKET_SELF_INFO` - `DEVICE_QUERY` → `PACKET_DEVICE_INFO` - `GET_CHANNEL` → `PACKET_CHANNEL_INFO` - `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR` @@ -825,16 +766,16 @@ device = scan_for_device("MeshCore") 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") +service = discover_service(gatt, "6E400001-B5A3-F393-E0A9-E50E24DCCA9E") +rx_char = discover_characteristic(service, "6E400002-B5A3-F393-E0A9-E50E24DCCA9E") +tx_char = discover_characteristic(service, "6E400003-B5A3-F393-E0A9-E50E24DCCA9E") -# 4. Enable notifications on RX characteristic -enable_notifications(rx_char, on_notification_received) +# 4. Enable notifications on TX characteristic +enable_notifications(tx_char, on_notification_received) # 5. Send AppStart command -send_command(tx_char, build_app_start()) -wait_for_response(PACKET_OK) +send_command(rx_char, build_app_start()) +wait_for_response(PACKET_SELF_INFO) ``` ### Creating a Private Channel @@ -844,21 +785,16 @@ wait_for_response(PACKET_OK) 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 +# 2. 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) +command = build_set_channel(channel_index, channel_name, secret_16_bytes) -# 4. Send command -send_command(tx_char, command) +# 3. Send command +send_command(rx_char, command) response = wait_for_response(PACKET_OK) -# 5. Store secret locally (device won't return it) +# 4. Store secret locally store_channel_secret(channel_index, secret_hex) ``` @@ -872,7 +808,7 @@ timestamp = int(time.time()) command = build_channel_message(channel_index, message, timestamp) # 2. Send command -send_command(tx_char, command) +send_command(rx_char, command) response = wait_for_response(PACKET_MSG_SENT) ``` @@ -887,7 +823,7 @@ def on_notification_received(data): handle_channel_message(message) elif packet_type == PACKET_MESSAGES_WAITING: # Poll for messages - send_command(tx_char, build_get_message()) + send_command(rx_char, build_get_message()) ``` --- diff --git a/docs/kiss_modem_protocol.md b/docs/kiss_modem_protocol.md index 6a08614f..8fbb57e4 100644 --- a/docs/kiss_modem_protocol.md +++ b/docs/kiss_modem_protocol.md @@ -190,7 +190,7 @@ All values little-endian. | Field | Size | Description | |-------|------|-------------| | MAC | 2 bytes | HMAC-SHA256 truncated to 2 bytes | -| Ciphertext | variable | AES-128-CBC encrypted data | +| Ciphertext | variable | AES-128 block-encrypted data with zero padding | ### Airtime (Airtime response) @@ -268,7 +268,7 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs. |-----------|-----------| | Identity / Signing / Verification | Ed25519 | | Key Exchange | X25519 (ECDH) | -| Encryption | AES-128-CBC + HMAC-SHA256 (MAC truncated to 2 bytes) | +| Encryption | AES-128 block encryption with zero padding + HMAC-SHA256 (MAC truncated to 2 bytes) | | Hashing | SHA-256 | ## Notes @@ -279,4 +279,4 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs. - 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 +- See [packet_format.md](./packet_format.md) for packet format From e3afbf975ebc69b9b9a2a82975b4e567c7be6f34 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Tue, 10 Mar 2026 14:31:58 +0100 Subject: [PATCH 15/16] Prevent auto-restarting BLE when disabling it on nRF52 If client is still connected, client would automatically reconnect immediately thus keeping BLE on fixes #1933 --- src/helpers/nrf52/SerialBLEInterface.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 5648707e..5a3017af 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -246,6 +246,7 @@ void SerialBLEInterface::enable() { clearBuffers(); _last_health_check = millis(); + Bluefruit.Advertising.restartOnDisconnect(true); Bluefruit.Advertising.start(0); } @@ -259,8 +260,9 @@ void SerialBLEInterface::disable() { _isEnabled = false; BLE_DEBUG_PRINTLN("SerialBLEInterface: disable"); - disconnect(); + Bluefruit.Advertising.restartOnDisconnect(false); Bluefruit.Advertising.stop(); + disconnect(); _last_health_check = 0; } From 095f97b3556e94dd8e32d58b2b6db20c73182478 Mon Sep 17 00:00:00 2001 From: Robert Ekl Date: Tue, 10 Mar 2026 22:12:55 -0500 Subject: [PATCH 16/16] set prv.key doc update --- 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 68e8fb9e..03c7e509 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -294,7 +294,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore **Serial Only:** - `get prv.key`: Yes -- `set prv.key`: Yes +- `set prv.key`: No **Note:** Requires reboot to take effect after setting