From 534a0c97a7f36fe2caffd1ac111e3f38f6d7fdd4 Mon Sep 17 00:00:00 2001 From: overkillfpv Date: Thu, 5 Mar 2026 21:39:14 +1100 Subject: [PATCH 01/48] Increased preamble to 32 for setPreambleLength(16); // overcomes weird issues with small and big pkts + _radio->setPreambleLength(LORA_SF <= 8 ? 32 : 16); // overcomes weird issues with small and big pkts } float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); } diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 2216ca8f39..9cc7318af5 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -26,6 +26,7 @@ void setFlag(void) { void RadioLibWrapper::begin() { _radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt + _radio->setPreambleLength(LORA_SF <= 8 ? 32 : 16); // longer preamble for lower SF improves reliability state = STATE_IDLE; if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep) From a61add2e6a7633f8035ff40bc9ed03f251959dea Mon Sep 17 00:00:00 2001 From: overkillfpv Date: Thu, 5 Mar 2026 22:14:22 +1100 Subject: [PATCH 02/48] Update preamble dynamically to match runtime SF changes via CLI --- src/helpers/radiolib/CustomLLCC68Wrapper.h | 1 + src/helpers/radiolib/CustomLR1110.h | 2 ++ src/helpers/radiolib/CustomLR1110Wrapper.h | 3 ++- src/helpers/radiolib/CustomSTM32WLxWrapper.h | 1 + src/helpers/radiolib/CustomSX1262Wrapper.h | 1 + src/helpers/radiolib/CustomSX1268Wrapper.h | 1 + src/helpers/radiolib/CustomSX1276Wrapper.h | 1 + src/helpers/radiolib/RadioLibWrappers.cpp | 3 ++- src/helpers/radiolib/RadioLibWrappers.h | 1 + 9 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/helpers/radiolib/CustomLLCC68Wrapper.h b/src/helpers/radiolib/CustomLLCC68Wrapper.h index 9e783a955b..e62acaffe9 100644 --- a/src/helpers/radiolib/CustomLLCC68Wrapper.h +++ b/src/helpers/radiolib/CustomLLCC68Wrapper.h @@ -20,6 +20,7 @@ class CustomLLCC68Wrapper : public RadioLibWrapper { int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomLR1110.h b/src/helpers/radiolib/CustomLR1110.h index b1f68080b5..8cfb0906d5 100644 --- a/src/helpers/radiolib/CustomLR1110.h +++ b/src/helpers/radiolib/CustomLR1110.h @@ -27,4 +27,6 @@ class CustomLR1110 : public LR1110 { bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); return detected; } + + uint8_t getSpreadingFactor() const { return spreadingFactor; } }; \ No newline at end of file diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index be3a9f8dd6..c5b0423396 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -19,10 +19,11 @@ class CustomLR1110Wrapper : public RadioLibWrapper { void onSendFinished() override { RadioLibWrapper::onSendFinished(); - _radio->setPreambleLength(LORA_SF <= 8 ? 32 : 16); // overcomes weird issues with small and big pkts + _radio->setPreambleLength(getSpreadingFactor() <= 8 ? 32 : 16); // overcomes weird issues with small and big pkts } float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); } float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); } + uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); } int16_t setRxBoostedGainMode(bool en) { return ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); }; }; diff --git a/src/helpers/radiolib/CustomSTM32WLxWrapper.h b/src/helpers/radiolib/CustomSTM32WLxWrapper.h index e3e5202949..cccdc0437c 100644 --- a/src/helpers/radiolib/CustomSTM32WLxWrapper.h +++ b/src/helpers/radiolib/CustomSTM32WLxWrapper.h @@ -21,6 +21,7 @@ class CustomSTM32WLxWrapper : public RadioLibWrapper { int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1262Wrapper.h b/src/helpers/radiolib/CustomSX1262Wrapper.h index 5856720b9e..ad140b6618 100644 --- a/src/helpers/radiolib/CustomSX1262Wrapper.h +++ b/src/helpers/radiolib/CustomSX1262Wrapper.h @@ -20,6 +20,7 @@ class CustomSX1262Wrapper : public RadioLibWrapper { int sf = ((CustomSX1262 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; } virtual void powerOff() override { ((CustomSX1262 *)_radio)->sleep(false); } diff --git a/src/helpers/radiolib/CustomSX1268Wrapper.h b/src/helpers/radiolib/CustomSX1268Wrapper.h index 5149fc431f..0c97752efe 100644 --- a/src/helpers/radiolib/CustomSX1268Wrapper.h +++ b/src/helpers/radiolib/CustomSX1268Wrapper.h @@ -20,6 +20,7 @@ class CustomSX1268Wrapper : public RadioLibWrapper { int sf = ((CustomSX1268 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } }; diff --git a/src/helpers/radiolib/CustomSX1276Wrapper.h b/src/helpers/radiolib/CustomSX1276Wrapper.h index 282579906a..fa613c8e78 100644 --- a/src/helpers/radiolib/CustomSX1276Wrapper.h +++ b/src/helpers/radiolib/CustomSX1276Wrapper.h @@ -19,4 +19,5 @@ class CustomSX1276Wrapper : public RadioLibWrapper { int sf = ((CustomSX1276 *)_radio)->spreadingFactor; return packetScoreInt(snr, sf, packet_len); } + uint8_t getSpreadingFactor() const override { return ((CustomSX1276 *)_radio)->spreadingFactor; } }; diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 9cc7318af5..9260e6df54 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -26,7 +26,7 @@ void setFlag(void) { void RadioLibWrapper::begin() { _radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt - _radio->setPreambleLength(LORA_SF <= 8 ? 32 : 16); // longer preamble for lower SF improves reliability + _radio->setPreambleLength(getSpreadingFactor() <= 8 ? 32 : 16); // longer preamble for lower SF improves reliability state = STATE_IDLE; if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep) @@ -143,6 +143,7 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); + _radio->setPreambleLength(getSpreadingFactor() <= 8 ? 32 : 16); // keep preamble in sync with current SF int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { state = STATE_TX_WAIT; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index b338b03a48..c9791a4b1e 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -38,6 +38,7 @@ class RadioLibWrapper : public mesh::Radio { } virtual float getCurrentRSSI() =0; + virtual uint8_t getSpreadingFactor() const { return LORA_SF; } int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; From 6fd8c28522c7c5143d2384a91a2116b4d0738102 Mon Sep 17 00:00:00 2001 From: overkillfpv Date: Sat, 7 Mar 2026 12:48:22 +1100 Subject: [PATCH 03/48] Cache preamble SF to avoid redundant setPreambleLength() on every TX --- src/helpers/radiolib/RadioLibWrappers.cpp | 9 +++++++-- src/helpers/radiolib/RadioLibWrappers.h | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 9260e6df54..883cc62315 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -26,7 +26,8 @@ void setFlag(void) { void RadioLibWrapper::begin() { _radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt - _radio->setPreambleLength(getSpreadingFactor() <= 8 ? 32 : 16); // longer preamble for lower SF improves reliability + _preamble_sf = getSpreadingFactor(); + _radio->setPreambleLength(_preamble_sf <= 8 ? 32 : 16); // longer preamble for lower SF improves reliability state = STATE_IDLE; if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep) @@ -143,7 +144,11 @@ uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); - _radio->setPreambleLength(getSpreadingFactor() <= 8 ? 32 : 16); // keep preamble in sync with current SF + uint8_t sf = getSpreadingFactor(); + if (sf != _preamble_sf) { + _preamble_sf = sf; + _radio->setPreambleLength(sf <= 8 ? 32 : 16); // update preamble when SF has changed + } int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { state = STATE_TX_WAIT; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index c9791a4b1e..0dbc88973c 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -11,6 +11,7 @@ class RadioLibWrapper : public mesh::Radio { int16_t _noise_floor, _threshold; uint16_t _num_floor_samples; int32_t _floor_sample_sum; + uint8_t _preamble_sf; void idle(); void startRecv(); @@ -19,7 +20,7 @@ class RadioLibWrapper : public mesh::Radio { virtual void doResetAGC(); public: - RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; } + RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; } void begin() override; virtual void powerOff() { _radio->sleep(); } From a434f9eff5d5f851edfb4a902155721a9d9ff99f Mon Sep 17 00:00:00 2001 From: overkillfpv Date: Sun, 8 Mar 2026 05:37:49 +1100 Subject: [PATCH 04/48] simplified the calls --- src/helpers/radiolib/CustomLR1110Wrapper.h | 2 +- src/helpers/radiolib/RadioLibWrappers.cpp | 4 ++-- src/helpers/radiolib/RadioLibWrappers.h | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/helpers/radiolib/CustomLR1110Wrapper.h b/src/helpers/radiolib/CustomLR1110Wrapper.h index c5b0423396..2f2cc24c99 100644 --- a/src/helpers/radiolib/CustomLR1110Wrapper.h +++ b/src/helpers/radiolib/CustomLR1110Wrapper.h @@ -19,7 +19,7 @@ class CustomLR1110Wrapper : public RadioLibWrapper { void onSendFinished() override { RadioLibWrapper::onSendFinished(); - _radio->setPreambleLength(getSpreadingFactor() <= 8 ? 32 : 16); // overcomes weird issues with small and big pkts + _radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts } float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); } diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 883cc62315..18251e435a 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -27,7 +27,7 @@ void setFlag(void) { void RadioLibWrapper::begin() { _radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt _preamble_sf = getSpreadingFactor(); - _radio->setPreambleLength(_preamble_sf <= 8 ? 32 : 16); // longer preamble for lower SF improves reliability + _radio->setPreambleLength(preambleLengthForSF(_preamble_sf)); // longer preamble for lower SF improves reliability state = STATE_IDLE; if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep) @@ -147,7 +147,7 @@ bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { uint8_t sf = getSpreadingFactor(); if (sf != _preamble_sf) { _preamble_sf = sf; - _radio->setPreambleLength(sf <= 8 ? 32 : 16); // update preamble when SF has changed + _radio->setPreambleLength(preambleLengthForSF(sf)); // update preamble when SF has changed } int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 0dbc88973c..8e266deefa 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -40,6 +40,7 @@ class RadioLibWrapper : public mesh::Radio { virtual float getCurrentRSSI() =0; virtual uint8_t getSpreadingFactor() const { return LORA_SF; } + static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; } int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; From 3843c00f54ba1d977accc7b037bb409296238833 Mon Sep 17 00:00:00 2001 From: overkillfpv Date: Tue, 24 Mar 2026 15:05:15 +1100 Subject: [PATCH 05/48] added the preamble update into get est airtime as a prevention for false airtime calcs. Left update in the startsendraw as a safety, but should not be used under normal circumstances --- src/helpers/radiolib/RadioLibWrappers.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index 18251e435a..cc1dc00143 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -139,6 +139,11 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { } uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { + uint8_t sf = getSpreadingFactor(); + if (sf != _preamble_sf) { + _preamble_sf = sf; + _radio->setPreambleLength(preambleLengthForSF(sf)); // sync preamble before airtime estimate + } return _radio->getTimeOnAir(len_bytes) / 1000; } From f0ec5d2ae788d3891625b4323117bffa2079d4ba Mon Sep 17 00:00:00 2001 From: overkillfpv Date: Sun, 29 Mar 2026 21:45:16 +1100 Subject: [PATCH 06/48] changed to set the preamble on radio settings change --- src/helpers/radiolib/RadioLibWrappers.cpp | 10 ---------- src/helpers/radiolib/RadioLibWrappers.h | 1 + variants/ebyte_eora_s3/target.cpp | 1 + variants/generic-e22/target.cpp | 1 + variants/heltec_ct62/target.cpp | 1 + variants/heltec_e213/target.cpp | 1 + variants/heltec_e290/target.cpp | 1 + variants/heltec_mesh_solar/target.cpp | 1 + variants/heltec_t114/target.cpp | 1 + variants/heltec_t190/target.cpp | 1 + variants/heltec_tracker/target.cpp | 1 + variants/heltec_tracker_v2/target.cpp | 1 + variants/heltec_v2/target.cpp | 1 + variants/heltec_v3/target.cpp | 1 + variants/heltec_v4/target.cpp | 1 + variants/heltec_wireless_paper/target.cpp | 1 + variants/ikoka_handheld_nrf/target.cpp | 1 + variants/ikoka_nano_nrf/target.cpp | 1 + variants/ikoka_stick_nrf/target.cpp | 1 + variants/keepteen_lt1/target.cpp | 1 + variants/lilygo_t3s3/target.cpp | 1 + variants/lilygo_t3s3_sx1276/target.cpp | 1 + variants/lilygo_tbeam_1w/target.cpp | 1 + variants/lilygo_tbeam_SX1262/target.cpp | 1 + variants/lilygo_tbeam_SX1276/target.cpp | 1 + variants/lilygo_tbeam_supreme_SX1262/target.cpp | 1 + variants/lilygo_tdeck/target.cpp | 1 + variants/lilygo_techo/target.cpp | 1 + variants/lilygo_techo_lite/target.cpp | 1 + variants/lilygo_tlora_c6/target.cpp | 1 + variants/lilygo_tlora_v2_1/target.cpp | 1 + variants/m5stack_unit_c6l/UnitC6LBoard.cpp | 1 + variants/mesh_pocket/target.cpp | 1 + variants/meshadventurer/target.cpp | 1 + variants/meshtiny/target.cpp | 1 + variants/minewsemi_me25ls01/target.cpp | 1 + variants/nano_g2_ultra/target.cpp | 1 + variants/nibble_screen_connect/target.cpp | 1 + variants/promicro/target.cpp | 1 + variants/rak11310/target.cpp | 1 + variants/rak3112/target.cpp | 1 + variants/rak3401/target.cpp | 1 + variants/rak3x72/target.cpp | 1 + variants/rak4631/target.cpp | 1 + variants/rak_wismesh_tag/target.cpp | 1 + variants/rpi_picow/target.cpp | 1 + variants/sensecap_solar/target.cpp | 1 + variants/station_g2/target.cpp | 1 + variants/t1000-e/target.cpp | 1 + variants/tenstar_c3/target.cpp | 1 + variants/thinknode_m1/target.cpp | 1 + variants/thinknode_m2/target.cpp | 1 + variants/thinknode_m3/target.cpp | 1 + variants/thinknode_m5/target.cpp | 1 + variants/thinknode_m6/target.cpp | 1 + variants/tiny_relay/target.cpp | 1 + variants/waveshare_rp2040_lora/target.cpp | 1 + variants/wio-e5-dev/target.cpp | 1 + variants/wio-e5-mini/target.cpp | 1 + variants/wio-tracker-l1/target.cpp | 1 + variants/wio_wm1110/target.cpp | 1 + variants/xiao_c3/target.cpp | 1 + variants/xiao_c6/XiaoC6Board.cpp | 1 + variants/xiao_nrf52/target.cpp | 1 + variants/xiao_rp2040/target.cpp | 1 + variants/xiao_s3_wio/target.cpp | 1 + 66 files changed, 65 insertions(+), 10 deletions(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.cpp b/src/helpers/radiolib/RadioLibWrappers.cpp index cc1dc00143..85f3a935db 100644 --- a/src/helpers/radiolib/RadioLibWrappers.cpp +++ b/src/helpers/radiolib/RadioLibWrappers.cpp @@ -139,21 +139,11 @@ int RadioLibWrapper::recvRaw(uint8_t* bytes, int sz) { } uint32_t RadioLibWrapper::getEstAirtimeFor(int len_bytes) { - uint8_t sf = getSpreadingFactor(); - if (sf != _preamble_sf) { - _preamble_sf = sf; - _radio->setPreambleLength(preambleLengthForSF(sf)); // sync preamble before airtime estimate - } return _radio->getTimeOnAir(len_bytes) / 1000; } bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) { _board->onBeforeTransmit(); - uint8_t sf = getSpreadingFactor(); - if (sf != _preamble_sf) { - _preamble_sf = sf; - _radio->setPreambleLength(preambleLengthForSF(sf)); // update preamble when SF has changed - } int err = _radio->startTransmit((uint8_t *) bytes, len); if (err == RADIOLIB_ERR_NONE) { state = STATE_TX_WAIT; diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 8e266deefa..66c8aa62da 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -41,6 +41,7 @@ class RadioLibWrapper : public mesh::Radio { virtual float getCurrentRSSI() =0; virtual uint8_t getSpreadingFactor() const { return LORA_SF; } static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; } + void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); } int getNoiseFloor() const override { return _noise_floor; } void triggerNoiseFloorCalibrate(int threshold) override; diff --git a/variants/ebyte_eora_s3/target.cpp b/variants/ebyte_eora_s3/target.cpp index 501f560be8..9e656cc2dc 100644 --- a/variants/ebyte_eora_s3/target.cpp +++ b/variants/ebyte_eora_s3/target.cpp @@ -73,6 +73,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/generic-e22/target.cpp b/variants/generic-e22/target.cpp index f76bb979ab..6ec1915f8b 100644 --- a/variants/generic-e22/target.cpp +++ b/variants/generic-e22/target.cpp @@ -36,6 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_ct62/target.cpp b/variants/heltec_ct62/target.cpp index 5cc621a13a..fad39a27f4 100644 --- a/variants/heltec_ct62/target.cpp +++ b/variants/heltec_ct62/target.cpp @@ -25,6 +25,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_e213/target.cpp b/variants/heltec_e213/target.cpp index c9233431cb..70b6ee35d7 100644 --- a/variants/heltec_e213/target.cpp +++ b/variants/heltec_e213/target.cpp @@ -42,6 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_e290/target.cpp b/variants/heltec_e290/target.cpp index b0c9630cf2..856ea0afe8 100644 --- a/variants/heltec_e290/target.cpp +++ b/variants/heltec_e290/target.cpp @@ -42,6 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index 9852b68f8d..776f581348 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -32,6 +32,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 6a30a4d18e..3658717795 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -52,6 +52,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_t190/target.cpp b/variants/heltec_t190/target.cpp index d22f8b8cfc..c39ca35c59 100644 --- a/variants/heltec_t190/target.cpp +++ b/variants/heltec_t190/target.cpp @@ -42,6 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index f801bacb94..95cd9c9b86 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -46,6 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_tracker_v2/target.cpp b/variants/heltec_tracker_v2/target.cpp index c2e26b20d7..11ae4a600d 100644 --- a/variants/heltec_tracker_v2/target.cpp +++ b/variants/heltec_tracker_v2/target.cpp @@ -48,6 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_v2/target.cpp b/variants/heltec_v2/target.cpp index c5a0475281..b29f04e4a5 100644 --- a/variants/heltec_v2/target.cpp +++ b/variants/heltec_v2/target.cpp @@ -41,6 +41,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index cdd2535e86..bedd737c65 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -48,6 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 54fc05e891..242ad60631 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -48,6 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/heltec_wireless_paper/target.cpp b/variants/heltec_wireless_paper/target.cpp index 06f548fc0e..4455435f5c 100644 --- a/variants/heltec_wireless_paper/target.cpp +++ b/variants/heltec_wireless_paper/target.cpp @@ -41,6 +41,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/ikoka_handheld_nrf/target.cpp b/variants/ikoka_handheld_nrf/target.cpp index 48244e1722..5d7e8b3fac 100644 --- a/variants/ikoka_handheld_nrf/target.cpp +++ b/variants/ikoka_handheld_nrf/target.cpp @@ -34,6 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/ikoka_nano_nrf/target.cpp b/variants/ikoka_nano_nrf/target.cpp index be20cfb436..ca128f7c44 100644 --- a/variants/ikoka_nano_nrf/target.cpp +++ b/variants/ikoka_nano_nrf/target.cpp @@ -32,6 +32,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/ikoka_stick_nrf/target.cpp b/variants/ikoka_stick_nrf/target.cpp index 4f6befc609..6b48be6791 100644 --- a/variants/ikoka_stick_nrf/target.cpp +++ b/variants/ikoka_stick_nrf/target.cpp @@ -32,6 +32,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp index e2e183a709..4cc164960c 100644 --- a/variants/keepteen_lt1/target.cpp +++ b/variants/keepteen_lt1/target.cpp @@ -38,6 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_t3s3/target.cpp b/variants/lilygo_t3s3/target.cpp index 284811881c..e3fb363ca0 100644 --- a/variants/lilygo_t3s3/target.cpp +++ b/variants/lilygo_t3s3/target.cpp @@ -36,6 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_t3s3_sx1276/target.cpp b/variants/lilygo_t3s3_sx1276/target.cpp index e7fe07a0c4..a62724fbdd 100644 --- a/variants/lilygo_t3s3_sx1276/target.cpp +++ b/variants/lilygo_t3s3_sx1276/target.cpp @@ -42,6 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp index 8cb6bdfa31..22f98cd9c5 100644 --- a/variants/lilygo_tbeam_1w/target.cpp +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -52,6 +52,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index f85049d7c9..11ceaf742b 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -43,6 +43,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tbeam_SX1276/target.cpp b/variants/lilygo_tbeam_SX1276/target.cpp index 5fe82e111d..fca380f1cc 100644 --- a/variants/lilygo_tbeam_SX1276/target.cpp +++ b/variants/lilygo_tbeam_SX1276/target.cpp @@ -48,6 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index 6fec6f5831..a47e23c47b 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -40,6 +40,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index 731ecfd86b..0e36206dee 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -43,6 +43,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_techo/target.cpp b/variants/lilygo_techo/target.cpp index 12d222ff78..7db5079dc6 100644 --- a/variants/lilygo_techo/target.cpp +++ b/variants/lilygo_techo/target.cpp @@ -40,6 +40,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp index 40a94526ed..b4240dd2a0 100644 --- a/variants/lilygo_techo_lite/target.cpp +++ b/variants/lilygo_techo_lite/target.cpp @@ -39,6 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tlora_c6/target.cpp b/variants/lilygo_tlora_c6/target.cpp index 3566fbe482..5a1a545d4f 100644 --- a/variants/lilygo_tlora_c6/target.cpp +++ b/variants/lilygo_tlora_c6/target.cpp @@ -36,6 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/lilygo_tlora_v2_1/target.cpp b/variants/lilygo_tlora_v2_1/target.cpp index ead62e7978..3a14abd02a 100644 --- a/variants/lilygo_tlora_v2_1/target.cpp +++ b/variants/lilygo_tlora_v2_1/target.cpp @@ -37,6 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/m5stack_unit_c6l/UnitC6LBoard.cpp b/variants/m5stack_unit_c6l/UnitC6LBoard.cpp index 6538ef4840..9a71d9a833 100644 --- a/variants/m5stack_unit_c6l/UnitC6LBoard.cpp +++ b/variants/m5stack_unit_c6l/UnitC6LBoard.cpp @@ -37,6 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(uint8_t dbm) { diff --git a/variants/mesh_pocket/target.cpp b/variants/mesh_pocket/target.cpp index 6fabb31742..ee01f668fc 100644 --- a/variants/mesh_pocket/target.cpp +++ b/variants/mesh_pocket/target.cpp @@ -32,6 +32,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index 0edd440309..ddcb29eb0a 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -39,6 +39,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/meshtiny/target.cpp b/variants/meshtiny/target.cpp index 9188db1741..ad57feb925 100644 --- a/variants/meshtiny/target.cpp +++ b/variants/meshtiny/target.cpp @@ -35,6 +35,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/minewsemi_me25ls01/target.cpp b/variants/minewsemi_me25ls01/target.cpp index fcec194190..c67663d64d 100644 --- a/variants/minewsemi_me25ls01/target.cpp +++ b/variants/minewsemi_me25ls01/target.cpp @@ -86,6 +86,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index aad10c5050..b7b77f19e1 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -34,6 +34,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/nibble_screen_connect/target.cpp b/variants/nibble_screen_connect/target.cpp index 6edaaad7ac..9cebae0de3 100644 --- a/variants/nibble_screen_connect/target.cpp +++ b/variants/nibble_screen_connect/target.cpp @@ -36,6 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index 61eab91c29..5db29f281d 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -38,6 +38,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rak11310/target.cpp b/variants/rak11310/target.cpp index 67432998a0..f0cb3e616b 100644 --- a/variants/rak11310/target.cpp +++ b/variants/rak11310/target.cpp @@ -27,6 +27,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rak3112/target.cpp b/variants/rak3112/target.cpp index 6cddfce519..61b2201eb0 100644 --- a/variants/rak3112/target.cpp +++ b/variants/rak3112/target.cpp @@ -48,6 +48,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index ec4fc28c00..e9ae88df3b 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -46,6 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rak3x72/target.cpp b/variants/rak3x72/target.cpp index 48e7f42284..161c0145e5 100644 --- a/variants/rak3x72/target.cpp +++ b/variants/rak3x72/target.cpp @@ -64,6 +64,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index ea6a2bd4a5..4d6ff30b52 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -46,6 +46,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rak_wismesh_tag/target.cpp b/variants/rak_wismesh_tag/target.cpp index 9646375e60..284aa40e5c 100644 --- a/variants/rak_wismesh_tag/target.cpp +++ b/variants/rak_wismesh_tag/target.cpp @@ -42,6 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/rpi_picow/target.cpp b/variants/rpi_picow/target.cpp index e3d4bf0900..8758355387 100644 --- a/variants/rpi_picow/target.cpp +++ b/variants/rpi_picow/target.cpp @@ -27,6 +27,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/sensecap_solar/target.cpp b/variants/sensecap_solar/target.cpp index 2c2ff0dc70..8a2bad06f7 100644 --- a/variants/sensecap_solar/target.cpp +++ b/variants/sensecap_solar/target.cpp @@ -27,6 +27,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 026b25de72..dd74ae4123 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -49,6 +49,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index da8fa48bb3..bbf453caf7 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -83,6 +83,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/tenstar_c3/target.cpp b/variants/tenstar_c3/target.cpp index d4f189b52f..2b7aa00c0d 100644 --- a/variants/tenstar_c3/target.cpp +++ b/variants/tenstar_c3/target.cpp @@ -36,6 +36,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index ec2438d404..83c9f7e396 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -33,6 +33,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/thinknode_m2/target.cpp b/variants/thinknode_m2/target.cpp index e7e36d05f1..a7b873a6e3 100644 --- a/variants/thinknode_m2/target.cpp +++ b/variants/thinknode_m2/target.cpp @@ -44,6 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index ca2b0aa067..547584ea13 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -87,6 +87,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index a7a049ef4f..f617e0e44f 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -51,6 +51,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp index 36ca861805..346e9c2d1b 100644 --- a/variants/thinknode_m6/target.cpp +++ b/variants/thinknode_m6/target.cpp @@ -37,6 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/tiny_relay/target.cpp b/variants/tiny_relay/target.cpp index 313dfaa941..c05f552efd 100644 --- a/variants/tiny_relay/target.cpp +++ b/variants/tiny_relay/target.cpp @@ -68,6 +68,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) diff --git a/variants/waveshare_rp2040_lora/target.cpp b/variants/waveshare_rp2040_lora/target.cpp index a9121b0c35..9773f9b5c4 100644 --- a/variants/waveshare_rp2040_lora/target.cpp +++ b/variants/waveshare_rp2040_lora/target.cpp @@ -37,6 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/wio-e5-dev/target.cpp b/variants/wio-e5-dev/target.cpp index 3e59b6cee7..8d8069ebf3 100644 --- a/variants/wio-e5-dev/target.cpp +++ b/variants/wio-e5-dev/target.cpp @@ -61,6 +61,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/wio-e5-mini/target.cpp b/variants/wio-e5-mini/target.cpp index 2e95ad6d1f..338f398dfe 100644 --- a/variants/wio-e5-mini/target.cpp +++ b/variants/wio-e5-mini/target.cpp @@ -59,6 +59,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 4575a76c85..40bf61ae1c 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -42,6 +42,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/wio_wm1110/target.cpp b/variants/wio_wm1110/target.cpp index 457d5bda2c..9be3061c3d 100644 --- a/variants/wio_wm1110/target.cpp +++ b/variants/wio_wm1110/target.cpp @@ -79,6 +79,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index f8ee3d92c9..ee0fc049fa 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -44,6 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/xiao_c6/XiaoC6Board.cpp b/variants/xiao_c6/XiaoC6Board.cpp index 5710c4ccb6..dc62c20ecc 100644 --- a/variants/xiao_c6/XiaoC6Board.cpp +++ b/variants/xiao_c6/XiaoC6Board.cpp @@ -37,6 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/xiao_nrf52/target.cpp b/variants/xiao_nrf52/target.cpp index a8f4162eab..1236b935d5 100644 --- a/variants/xiao_nrf52/target.cpp +++ b/variants/xiao_nrf52/target.cpp @@ -32,6 +32,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/xiao_rp2040/target.cpp b/variants/xiao_rp2040/target.cpp index 6c9a91434b..8a0f650801 100644 --- a/variants/xiao_rp2040/target.cpp +++ b/variants/xiao_rp2040/target.cpp @@ -37,6 +37,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { diff --git a/variants/xiao_s3_wio/target.cpp b/variants/xiao_s3_wio/target.cpp index 50981ab64a..82f870a9ac 100644 --- a/variants/xiao_s3_wio/target.cpp +++ b/variants/xiao_s3_wio/target.cpp @@ -44,6 +44,7 @@ void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { radio.setSpreadingFactor(sf); radio.setBandwidth(bw); radio.setCodingRate(cr); + radio_driver.updatePreamble(sf); } void radio_set_tx_power(int8_t dbm) { From 3fcf5b548b753d407cef1e364a306e440219231f Mon Sep 17 00:00:00 2001 From: Confi <9595028+Confituurke@users.noreply.github.com> Date: Wed, 25 Mar 2026 22:05:28 +0100 Subject: [PATCH 07/48] fix(sensecap_solar): set P_LORA_TX_LED to 12 after LED remap Align TX activity LED with LED_BLUE / variant.h after LED definition and power-button pin mapping changes. --- variants/sensecap_solar/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/sensecap_solar/platformio.ini b/variants/sensecap_solar/platformio.ini index aabbcf000b..effef38ccd 100644 --- a/variants/sensecap_solar/platformio.ini +++ b/variants/sensecap_solar/platformio.ini @@ -13,7 +13,7 @@ build_flags = ${nrf52_base.build_flags} -D NRF52_POWER_MANAGEMENT -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper - -D P_LORA_TX_LED=11 + -D P_LORA_TX_LED=12 -D P_LORA_DIO_1=1 -D P_LORA_RESET=2 -D P_LORA_BUSY=3 From ecd0cfc1c133aad93e65257f002151591f6bcfd9 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Fri, 24 Apr 2026 15:57:34 +1200 Subject: [PATCH 08/48] update discord links --- README.md | 2 +- docs/faq.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ebad1f6f59..f8b9e5e083 100644 --- a/README.md +++ b/README.md @@ -117,4 +117,4 @@ There are a number of fairly major features in the pipeline, with no particular - Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page. - Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz). -- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community. +- Join [MeshCore Discord](https://meshcore.gg) to chat with the developers and get help from the community. diff --git a/docs/faq.md b/docs/faq.md index 9fe1534a1a..3edc0a6953 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -194,7 +194,7 @@ Recently, as of October 2025, many regions have moved to the "narrow" setting, a After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions. -If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://discord.gg/cYtQNYCCRK) to let Liam Cottle know. +If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://meshcore.gg) to let Liam Cottle know. @@ -526,7 +526,7 @@ The third character is the capital letter 'O', not zero `0` - 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 . +**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://meshcore.gg). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at . Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase From 277331381e11ad70e0c62e58488252649e919c0c Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Fri, 24 Apr 2026 11:56:06 +0700 Subject: [PATCH 09/48] Added Xiao S3 variant. This is for Xiao S3 and a normal Wio SX1262 for Xiao shield. --- variants/xiao_s3/XiaoS3Board.h | 13 +++ variants/xiao_s3/platformio.ini | 154 ++++++++++++++++++++++++++++++++ variants/xiao_s3/target.cpp | 56 ++++++++++++ variants/xiao_s3/target.h | 30 +++++++ 4 files changed, 253 insertions(+) create mode 100644 variants/xiao_s3/XiaoS3Board.h create mode 100644 variants/xiao_s3/platformio.ini create mode 100644 variants/xiao_s3/target.cpp create mode 100644 variants/xiao_s3/target.h diff --git a/variants/xiao_s3/XiaoS3Board.h b/variants/xiao_s3/XiaoS3Board.h new file mode 100644 index 0000000000..288fcf6262 --- /dev/null +++ b/variants/xiao_s3/XiaoS3Board.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +class XiaoS3Board : public ESP32Board { +public: + XiaoS3Board() { } + + const char* getManufacturerName() const override { + return "Xiao S3"; + } +}; diff --git a/variants/xiao_s3/platformio.ini b/variants/xiao_s3/platformio.ini new file mode 100644 index 0000000000..bc00022048 --- /dev/null +++ b/variants/xiao_s3/platformio.ini @@ -0,0 +1,154 @@ +[Xiao_S3] +extends = esp32_base +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 + -UENV_INCLUDE_GPS + -D SEEED_XIAO_S3 + -D PIN_VBAT_READ=1 ; D0 + -D P_LORA_DIO_1=2 ; D1 + -D P_LORA_NSS=5 ; D4 + -D P_LORA_RESET=3 ; D2 + -D P_LORA_BUSY=4 ; D3 + -D P_LORA_SCLK=7 ; D8 + -D P_LORA_MISO=8 ; D0 + -D P_LORA_MOSI=9 ; D10 + -D PIN_USER_BTN=-1 ; NC + -D PIN_STATUS_LED=21 ; Orange user led, LOW=On + -D PIN_BOARD_SDA=D6 ; D6=43 + -D PIN_BOARD_SCL=D7 ; D7=44 + -D SX126X_RXEN=6 ; D5 + -D SX126X_TXEN=RADIOLIB_NC + -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/xiao_s3> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[env:Xiao_S3_repeater] +extends = Xiao_S3 +build_src_filter = ${Xiao_S3.build_src_filter} + +<../examples/simple_repeater/*.cpp> +build_flags = + ${Xiao_S3.build_flags} + -D ADVERT_NAME='"Xiao S3 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 = + ${Xiao_S3.lib_deps} + ${esp32_ota.lib_deps} + +[env:Xiao_S3_repeater_bridge_espnow] +extends = Xiao_S3 +build_src_filter = ${Xiao_S3.build_src_filter} + + + +<../examples/simple_repeater/*.cpp> +build_flags = + ${Xiao_S3.build_flags} + -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 +lib_deps = + ${Xiao_S3.lib_deps} + ${esp32_ota.lib_deps} + +[env:Xiao_S3_room_server] +extends = Xiao_S3 +build_src_filter = ${Xiao_S3.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${Xiao_S3.build_flags} + -D ADVERT_NAME='"Xiao S3 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_S3.lib_deps} + ${esp32_ota.lib_deps} + +[env:Xiao_S3_companion_radio_ble] +extends = Xiao_S3 +build_flags = + ${Xiao_S3.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D DISPLAY_CLASS=SSD1306Display + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Xiao_S3.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Xiao_S3.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Xiao_S3_companion_radio_usb] +extends = Xiao_S3 +build_flags = + ${Xiao_S3.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D OFFLINE_QUEUE_SIZE=256 +; -D BLE_DEBUG_LOGGING=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${Xiao_S3.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Xiao_S3.lib_deps} + densaugeo/base64 @ ~1.4.0 + adafruit/Adafruit SSD1306 @ ^2.5.13 + +[env:Xiao_S3_sensor] +extends = Xiao_S3 +build_src_filter = ${Xiao_S3.build_src_filter} + +<../examples/simple_sensor> +build_flags = + ${Xiao_S3.build_flags} + -D ADVERT_NAME='"Xiao S3 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.lib_deps} + ${esp32_ota.lib_deps} \ No newline at end of file diff --git a/variants/xiao_s3/target.cpp b/variants/xiao_s3/target.cpp new file mode 100644 index 0000000000..014b25529d --- /dev/null +++ b/variants/xiao_s3/target.cpp @@ -0,0 +1,56 @@ +#include +#include "target.h" + +XiaoS3Board 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); +EnvironmentSensorManager 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(int8_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/xiao_s3/target.h b/variants/xiao_s3/target.h new file mode 100644 index 0000000000..93b2786295 --- /dev/null +++ b/variants/xiao_s3/target.h @@ -0,0 +1,30 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif +#include "XiaoS3Board.h" + +extern XiaoS3Board 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(int8_t dbm); +mesh::LocalIdentity radio_new_identity(); From b705d5489f136477a93f13b9e407277903717787 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Fri, 24 Apr 2026 04:24:20 -0700 Subject: [PATCH 10/48] Update companion protocol documentation and enhance data type definitions for clarity --- docs/companion_protocol.md | 135 +++++++++++++++++++++++++++-------- src/helpers/TxtDataHelpers.h | 3 + 2 files changed, 109 insertions(+), 29 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index bbad1e40f0..fc2ad32da6 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -283,32 +283,101 @@ Bytes 7+: Message Text (UTF-8, variable length) ### 6. Send Channel Data Datagram -**Purpose**: Send binary datagram data to a channel. +**Purpose**: Send a binary datagram to a channel. Unlike channel text messages, datagrams carry no built-in sender identity and no timestamp — applications needing either must encode them inside the binary payload. **Command Format**: ``` -Byte 0: 0x3E -Bytes 1-2: Data Type (`data_type`, 16-bit little-endian) -Byte 3: Channel Index (0-7) -Bytes 4+: Binary payload bytes (variable length) +Byte 0: 0x3E +Byte 1: Channel Index (0-7) +Byte 2: Path Length (0xFF = flood, otherwise actual path length) +Bytes 3 .. 2+path_len: Path (omitted when path_len == 0xFF) +Next 2 bytes (little-endian): Data Type (`data_type`, uint16) +Remaining bytes: Binary payload (variable length) +``` + +**Example** (flood, `DATA_TYPE_DEV`, payload `A1 B2 C3`, channel 1): +``` +3E 01 FF FF FF A1 B2 C3 ``` **Data Type / Transport Mapping**: -- `0x0000` is invalid for this command. +- `0x0000` (`DATA_TYPE_RESERVED`) is invalid and rejected with `PACKET_ERROR`. - `0xFFFF` (`DATA_TYPE_DEV`) is the developer namespace for experimenting and developing apps. -- Other non-zero values can be used as assigned application/community namespaces. - -**Note**: Applications that need a timestamp should encode it inside the binary payload. +- Values `0x0001`–`0xFFFE` are available for registered application/community namespaces. See the [Registered data_type values](#registered-data_type-values) table below. **Limits**: -- Maximum payload length is `163` bytes. -- Larger payloads are rejected with `PACKET_ERROR`. +- Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163` bytes. +- Larger payloads are rejected with `PACKET_ERROR` (`ERR_CODE_ILLEGAL_ARG`). + +**Response**: `PACKET_OK` (0x00) on success, or `PACKET_ERROR` (0x01) with one of: +- `ERR_CODE_NOT_FOUND` (2) — unknown `channel_idx` +- `ERR_CODE_ILLEGAL_ARG` (6) — invalid `path_len`, reserved `data_type` (`0x0000`), or payload larger than `MAX_CHANNEL_DATA_LENGTH` +- `ERR_CODE_TABLE_FULL` (3) — outbound send queue is full; retry later + +**Inbound datagrams** are delivered to the host via `RESP_CODE_CHANNEL_DATA_RECV` (0x1B); see [Receive Channel Data Datagram](#receive-channel-data-datagram). + +#### Registered `data_type` values + +Declared in `src/helpers/TxtDataHelpers.h`. These values have agreed-upon payload schemas so different client apps on the same channel can interoperate. + +| Value | Constant | Purpose | Payload schema | +|--------|----------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------| +| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send | — | +| 0x0001 | `DATA_TYPE_SMAZ_TEXT`| Raw SMAZ-compressed UTF-8 text | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][smaz_bytes: remaining]` | +| 0x0002 | `DATA_TYPE_GIPHY_GIF`| Giphy GIF id (avoids base64 tax) | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][giphy_id: ASCII, remaining]` | +| 0x0003 | `DATA_TYPE_REACTION` | Emoji reaction targeting a prior message | `[target_hash: 2 bytes LE][emoji_index: u8][sender_name_len: u8][sender_name: UTF-8 × sender_name_len]` | +| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace | Application-defined | + +The firmware does not inspect the payload contents — `data_type` is transported opaquely, and the schemas above are a client-side contract between cooperating apps. + +To request a new registered value, submit a PR adding a `#define` to `TxtDataHelpers.h` and a row to this table. + +--- + +### Receive Channel Data Datagram + +Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarded to the host as `RESP_CODE_CHANNEL_DATA_RECV` notifications. + +**Frame Format** (`RESP_CODE_CHANNEL_DATA_RECV`, 0x1B): +``` +Byte 0: 0x1B (packet type) +Byte 1: SNR (signed int8, scaled ×4 — divide by 4.0 to recover dB) +Bytes 2-3: Reserved +Byte 4: Channel Index (0-7) +Byte 5: Path Length (actual path when flooded, otherwise 0xFF) +Bytes 6-7: Data Type (uint16 little-endian) +Byte 8: Data Length +Bytes 9 .. 8+data_len: Payload +``` -**Response**: `PACKET_OK` (0x00) on success +**Note**: The device may also emit `PACKET_MESSAGES_WAITING` (0x83) to notify the host that datagrams are queued; poll with `CMD_SYNC_NEXT_MESSAGE` (0x0A) to retrieve them. + +**Parsing Pseudocode**: +```python +def parse_channel_data_recv(data): + if len(data) < 9: + return None + snr_byte = data[1] + snr = (snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0 + channel_idx = data[4] + path_len = data[5] + data_type = int.from_bytes(data[6:8], 'little') + data_len = data[8] + if 9 + data_len > len(data): + return None + payload = data[9:9 + data_len] + return { + 'snr': snr, + 'channel_idx': channel_idx, + 'path_len': path_len, + 'data_type': data_type, + 'payload': bytes(payload), + } +``` --- -### 6. Get Message +### 7. Get Message **Purpose**: Request the next queued message from the device. @@ -325,13 +394,14 @@ Byte 0: 0x0A **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_CHANNEL_DATA_RECV` (0x1B) for channel data datagrams - `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 and Storage +### 8. Get Battery and Storage **Purpose**: Query device battery voltage and storage usage. @@ -527,6 +597,15 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)). ## Response Parsing +### Terminology + +This document uses a spec-level naming convention (`PACKET_*`) for bytes the firmware sends back to the host. In the firmware source these same values are split across two `#define` families by purpose: + +- `RESP_CODE_*` — direct replies to a command (e.g. `RESP_CODE_CHANNEL_DATA_RECV` = `PACKET_CHANNEL_DATA_RECV` = 0x1B). +- `PUSH_CODE_*` — asynchronous notifications not tied to a specific command (e.g. `PUSH_CODE_MSG_WAITING` = `PACKET_MESSAGES_WAITING` = 0x83). + +Byte values are authoritative; names are aliases. When reading firmware source, `RESP_CODE_X` / `PUSH_CODE_X` correspond to this doc's `PACKET_X` of the same numeric value. + ### Packet Types | Value | Name | Description | @@ -547,6 +626,7 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)). | 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 | +| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data datagram | | 0x80 | PACKET_ADVERTISEMENT | Advertisement packet | | 0x82 | PACKET_ACK | Acknowledgment | | 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification | @@ -718,22 +798,18 @@ Bytes 1-6: ACK Code (6 bytes, hex) ### Error Codes -**PACKET_ERROR** (0x01) may include an error code in byte 1: +`PACKET_ERROR` (0x01) carries a single-byte error code in byte 1. Values match the `ERR_CODE_*` constants defined in `examples/companion_radio/MyMesh.cpp`: -| 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 | +| Code | Constant (firmware) | Description | +|------|----------------------------|------------------------------------------------------------------------------| +| 1 | `ERR_CODE_UNSUPPORTED_CMD` | Unknown or unsupported command byte / sub-command | +| 2 | `ERR_CODE_NOT_FOUND` | Target not found (channel, contact, message, etc.) | +| 3 | `ERR_CODE_TABLE_FULL` | Internal queue or table is full — retry later | +| 4 | `ERR_CODE_BAD_STATE` | Operation not valid in current device state (e.g. iterator already running) | +| 5 | `ERR_CODE_FILE_IO_ERROR` | Filesystem or storage I/O failure | +| 6 | `ERR_CODE_ILLEGAL_ARG` | Invalid argument (bad length, out-of-range value, reserved field, etc.) | -**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response. +**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response, and treat unknown codes as generic errors. ### Frame Handling @@ -765,7 +841,8 @@ BLE implementations enqueue and deliver one protocol frame per BLE write/notific - `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_MESSAGE` → `PACKET_CHANNEL_MSG_RECV`, `PACKET_CONTACT_MSG_RECV`, `PACKET_CHANNEL_DATA_RECV`, or `PACKET_NO_MORE_MSGS` + - `SEND_CHANNEL_DATA` → `PACKET_OK` or `PACKET_ERROR` - `GET_BATTERY` → `PACKET_BATTERY` 4. **Timeout Handling**: diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index ece494f291..432c0f1233 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -7,6 +7,9 @@ #define TXT_TYPE_CLI_DATA 1 // a CLI command #define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender #define DATA_TYPE_RESERVED 0x0000 // reserved for future use +#define DATA_TYPE_SMAZ_TEXT 0x0001 // raw SMAZ-compressed UTF-8 text meshcore-open Flutter client. +#define DATA_TYPE_GIPHY_GIF 0x0002 // ASCII Giphy GIF id meshcore-open Flutter client. +#define DATA_TYPE_REACTION 0x0003 // 2-byte target message hash + 1-byte emoji index meshcore-open Flutter client. #define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams and building apps class StrHelper { From 7f38e3f145fd758f0bccc8befa7004fcda9bb376 Mon Sep 17 00:00:00 2001 From: zevaryx Date: Fri, 24 Apr 2026 20:24:50 -0600 Subject: [PATCH 11/48] feat: Enable GPS on RAK 1W kit --- variants/rak3401/platformio.ini | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 3d2d4a3ec5..b3693a58be 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -6,6 +6,7 @@ build_flags = ${nrf52_base.build_flags} ${sensor_base.build_flags} -I variants/rak3401 -D RAK_3401 + -D RAK_BOARD -D NRF52_POWER_MANAGEMENT -D RADIO_CLASS=CustomSX1262 -D WRAPPER_CLASS=CustomSX1262Wrapper @@ -82,6 +83,7 @@ board_upload.maximum_size = 712704 build_flags = ${rak3401.build_flags} -I examples/companion_radio/ui-new + -D PIN_GPS_EN=-1 -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 From 835b0e9114985ed800d67d4f4d52cd8eccee88c5 Mon Sep 17 00:00:00 2001 From: Confi <9595028+Confituurke@users.noreply.github.com> Date: Sat, 25 Apr 2026 09:47:44 +0200 Subject: [PATCH 12/48] fix(sensecap_solar): init LED_WHITE LOW to prevent always-on at boot --- variants/sensecap_solar/SenseCapSolarBoard.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/sensecap_solar/SenseCapSolarBoard.cpp b/variants/sensecap_solar/SenseCapSolarBoard.cpp index d713176d67..da7964c9e6 100644 --- a/variants/sensecap_solar/SenseCapSolarBoard.cpp +++ b/variants/sensecap_solar/SenseCapSolarBoard.cpp @@ -49,7 +49,7 @@ void SenseCapSolarBoard::begin() { #ifdef LED_WHITE pinMode(LED_WHITE, OUTPUT); - digitalWrite(LED_WHITE, HIGH); + digitalWrite(LED_WHITE, LOW); #endif #ifdef LED_BLUE pinMode(LED_BLUE, OUTPUT); From b91f127e4ebbdca0fa728e1feed9dc4fca4dc761 Mon Sep 17 00:00:00 2001 From: me Date: Sun, 26 Apr 2026 00:23:09 -0700 Subject: [PATCH 13/48] fix: correct device model name for Heltec Wireless Paper --- variants/heltec_wireless_paper/target.cpp | 2 +- variants/heltec_wireless_paper/target.h | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/variants/heltec_wireless_paper/target.cpp b/variants/heltec_wireless_paper/target.cpp index ffb042cfba..83e73332cd 100644 --- a/variants/heltec_wireless_paper/target.cpp +++ b/variants/heltec_wireless_paper/target.cpp @@ -1,7 +1,7 @@ #include "target.h" #include -HeltecV3Board board; +HeltecWirelessPaperBoard board; #if defined(P_LORA_SCLK) static SPIClass spi; diff --git a/variants/heltec_wireless_paper/target.h b/variants/heltec_wireless_paper/target.h index 661da5e4e4..479083149b 100644 --- a/variants/heltec_wireless_paper/target.h +++ b/variants/heltec_wireless_paper/target.h @@ -4,6 +4,12 @@ #include #include #include <../heltec_v3/HeltecV3Board.h> +class HeltecWirelessPaperBoard : public HeltecV3Board { +public: + const char* getManufacturerName() const override { + return "Heltec Wireless Paper"; + } +}; #include #include #include @@ -12,7 +18,7 @@ #include #endif -extern HeltecV3Board board; +extern HeltecWirelessPaperBoard board; extern WRAPPER_CLASS radio_driver; extern AutoDiscoverRTCClock rtc_clock; extern SensorManager sensors; From b6d0b7a5dd06d3e141e089697ddb44dd154dcc15 Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 27 Apr 2026 10:22:17 -0700 Subject: [PATCH 14/48] Refine data type definitions and update registration process in documentation --- docs/companion_protocol.md | 20 +++++++++----------- docs/number_allocations.md | 1 + src/helpers/TxtDataHelpers.h | 3 --- 3 files changed, 10 insertions(+), 14 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index fc2ad32da6..8129bc903e 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -318,19 +318,17 @@ Remaining bytes: Binary payload (variable length) #### Registered `data_type` values -Declared in `src/helpers/TxtDataHelpers.h`. These values have agreed-upon payload schemas so different client apps on the same channel can interoperate. +`data_type` is an **application identifier**, not a payload-format identifier. Each registered value identifies an application that owns its own internal payload schemas. The firmware does not inspect payload contents — `data_type` is transported opaquely. -| Value | Constant | Purpose | Payload schema | -|--------|----------------------|---------------------------------------------|--------------------------------------------------------------------------------------------------------| -| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send | — | -| 0x0001 | `DATA_TYPE_SMAZ_TEXT`| Raw SMAZ-compressed UTF-8 text | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][smaz_bytes: remaining]` | -| 0x0002 | `DATA_TYPE_GIPHY_GIF`| Giphy GIF id (avoids base64 tax) | `[sender_name_len: u8][sender_name: UTF-8 × sender_name_len][giphy_id: ASCII, remaining]` | -| 0x0003 | `DATA_TYPE_REACTION` | Emoji reaction targeting a prior message | `[target_hash: 2 bytes LE][emoji_index: u8][sender_name_len: u8][sender_name: UTF-8 × sender_name_len]` | -| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace | Application-defined | +| Value | Constant | Purpose | +|-----------------|----------------------|--------------------------------------------------------------------------| +| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send | +| 0x0001 – 0x00FF | — | Reserved for internal use | +| 0x0100 – 0xFEFF | — | Registered application namespaces (see [number_allocations.md](number_allocations.md)) | +| 0xFF00 – 0xFFFE | — | Testing/development; no registration required | +| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace | -The firmware does not inspect the payload contents — `data_type` is transported opaquely, and the schemas above are a client-side contract between cooperating apps. - -To request a new registered value, submit a PR adding a `#define` to `TxtDataHelpers.h` and a row to this table. +To register a new application, submit a PR adding a row to the table in [docs/number_allocations.md](number_allocations.md). Internal sub-formats within an allocated application ID are owned by that application and are not tracked in MeshCore firmware or this document. --- diff --git a/docs/number_allocations.md b/docs/number_allocations.md index 94ad1efda1..c4715c5eb6 100644 --- a/docs/number_allocations.md +++ b/docs/number_allocations.md @@ -15,6 +15,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist | Data-Type range | App name | Contact | |-----------------|-----------------------------|------------------------------------------------------| | 0000 - 00FF | -reserved for internal use- | | +| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open | | FF00 - FFFF | -reserved for testing/dev- | | (add rows, inside the range 0100 - FEFF for custom apps) diff --git a/src/helpers/TxtDataHelpers.h b/src/helpers/TxtDataHelpers.h index 432c0f1233..ece494f291 100644 --- a/src/helpers/TxtDataHelpers.h +++ b/src/helpers/TxtDataHelpers.h @@ -7,9 +7,6 @@ #define TXT_TYPE_CLI_DATA 1 // a CLI command #define TXT_TYPE_SIGNED_PLAIN 2 // plain text, signed by sender #define DATA_TYPE_RESERVED 0x0000 // reserved for future use -#define DATA_TYPE_SMAZ_TEXT 0x0001 // raw SMAZ-compressed UTF-8 text meshcore-open Flutter client. -#define DATA_TYPE_GIPHY_GIF 0x0002 // ASCII Giphy GIF id meshcore-open Flutter client. -#define DATA_TYPE_REACTION 0x0003 // 2-byte target message hash + 1-byte emoji index meshcore-open Flutter client. #define DATA_TYPE_DEV 0xFFFF // developer namespace for experimenting with group/channel datagrams and building apps class StrHelper { From 9c8eb301a484413c358742f69d37ee32070ec01d Mon Sep 17 00:00:00 2001 From: zjs81 Date: Mon, 27 Apr 2026 10:49:55 -0700 Subject: [PATCH 15/48] Clarify path length semantics and data handling in inbound group datagrams --- docs/companion_protocol.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/docs/companion_protocol.md b/docs/companion_protocol.md index 8129bc903e..9cc610ff39 100644 --- a/docs/companion_protocol.md +++ b/docs/companion_protocol.md @@ -340,14 +340,25 @@ Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarde ``` Byte 0: 0x1B (packet type) Byte 1: SNR (signed int8, scaled ×4 — divide by 4.0 to recover dB) -Bytes 2-3: Reserved +Bytes 2-3: Reserved (clients MUST ignore) Byte 4: Channel Index (0-7) -Byte 5: Path Length (actual path when flooded, otherwise 0xFF) +Byte 5: Path Length (actual path length when flooded, otherwise 0xFF for direct) Bytes 6-7: Data Type (uint16 little-endian) Byte 8: Data Length Bytes 9 .. 8+data_len: Payload ``` +**Path bytes are not forwarded**: Only `path_len` is reported in the receive frame — the path itself is not copied to the host. There are no path bytes between byte 5 and the data_type field at bytes 6–7, regardless of `path_len`. + +**Path Length semantics differ between send and receive**: + +| Direction | `path_len = 0xFF` | `path_len ≠ 0xFF` | +|-----------|---------------------------------|-------------------------------------------------------------------------------------| +| Send | Flood the network | Direct route; the encoded path follows (low 6 bits = hash count, top 2 bits + 1 = hash size; on-wire byte count = `hash_count × hash_size`) | +| Receive | Packet arrived via direct route | Packet was flooded; this is the encoded `pkt->path_len` field as observed (no path bytes follow) | + +In other words, the meaning of `0xFF` is inverted between the two directions, and on receive the field carries metadata only — never a routable path. `path_len` is an encoded byte (see `Packet::isValidPathLen` / `Packet::writePath` in `src/Packet.cpp`), not a raw byte count. + **Note**: The device may also emit `PACKET_MESSAGES_WAITING` (0x83) to notify the host that datagrams are queued; poll with `CMD_SYNC_NEXT_MESSAGE` (0x0A) to retrieve them. **Parsing Pseudocode**: From c8d81bc04ce826cd5a4227968aea50e0d8e450aa Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 28 Apr 2026 17:02:20 +1200 Subject: [PATCH 16/48] added website to repeater splash screen --- examples/simple_repeater/UITask.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index acb4632581..6a85143887 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -52,17 +52,25 @@ void UITask::renderCurrScreen() { int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + // meshcore website + const char* website = "https://meshcore.io"; + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + uint16_t websiteWidth = _display->getTextWidth(website); + _display->setCursor((_display->width() - websiteWidth) / 2, 22); + _display->print(website); + // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); uint16_t versionWidth = _display->getTextWidth(_version_info); - _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->setCursor((_display->width() - versionWidth) / 2, 35); _display->print(_version_info); // node type const char* node_type = "< Repeater >"; uint16_t typeWidth = _display->getTextWidth(node_type); - _display->setCursor((_display->width() - typeWidth) / 2, 35); + _display->setCursor((_display->width() - typeWidth) / 2, 48); _display->print(node_type); } else { // home screen // node name From 5f75b90ff9ca504f02f8953edfea29d35c135640 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 28 Apr 2026 20:37:17 +1200 Subject: [PATCH 17/48] added website to companion splash screen --- examples/companion_radio/ui-new/UITask.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 94a8ee3efa..dd93098c0f 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -57,13 +57,21 @@ class SplashScreen : public UIScreen { int logoWidth = 128; display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + // meshcore website + const char* website = "https://meshcore.io"; + display.setColor(DisplayDriver::LIGHT); + display.setTextSize(1); + uint16_t websiteWidth = display.getTextWidth(website); + display.setCursor((display.width() - websiteWidth) / 2, 22); + display.print(website); + // version info display.setColor(DisplayDriver::LIGHT); - display.setTextSize(2); - display.drawTextCentered(display.width()/2, 22, _version_info); + display.setTextSize(1); + display.drawTextCentered(display.width()/2, 35, _version_info); display.setTextSize(1); - display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE); + display.drawTextCentered(display.width()/2, 48, FIRMWARE_BUILD_DATE); return 1000; } From 3cd40902b3d686d43941d8e868b0379757e0aa92 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 28 Apr 2026 20:39:07 +1200 Subject: [PATCH 18/48] added website to room server splash screen --- examples/simple_room_server/UITask.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index 42bc14d4a5..640a1d2d10 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -52,17 +52,25 @@ void UITask::renderCurrScreen() { int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + // meshcore website + const char* website = "https://meshcore.io"; + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + uint16_t websiteWidth = _display->getTextWidth(website); + _display->setCursor((_display->width() - websiteWidth) / 2, 22); + _display->print(website); + // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); uint16_t versionWidth = _display->getTextWidth(_version_info); - _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->setCursor((_display->width() - versionWidth) / 2, 35); _display->print(_version_info); // node type const char* node_type = "< Room Server >"; uint16_t typeWidth = _display->getTextWidth(node_type); - _display->setCursor((_display->width() - typeWidth) / 2, 35); + _display->setCursor((_display->width() - typeWidth) / 2, 48); _display->print(node_type); } else { // home screen // node name From e111f710647727d23a30a14b393b454909927ce3 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 28 Apr 2026 20:40:13 +1200 Subject: [PATCH 19/48] added website to sensor splash screen --- examples/simple_sensor/UITask.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/examples/simple_sensor/UITask.cpp b/examples/simple_sensor/UITask.cpp index 0e78fee005..757ea1dc60 100644 --- a/examples/simple_sensor/UITask.cpp +++ b/examples/simple_sensor/UITask.cpp @@ -52,17 +52,25 @@ void UITask::renderCurrScreen() { int logoWidth = 128; _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + // meshcore website + const char* website = "https://meshcore.io"; + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + uint16_t websiteWidth = _display->getTextWidth(website); + _display->setCursor((_display->width() - websiteWidth) / 2, 22); + _display->print(website); + // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); uint16_t versionWidth = _display->getTextWidth(_version_info); - _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->setCursor((_display->width() - versionWidth) / 2, 35); _display->print(_version_info); // node type const char* node_type = "< Sensor >"; uint16_t typeWidth = _display->getTextWidth(node_type); - _display->setCursor((_display->width() - typeWidth) / 2, 35); + _display->setCursor((_display->width() - typeWidth) / 2, 48); _display->print(node_type); } else { // home screen // node name From 12d9cc37529fa949b7748a610a6e6cc76b59910c Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 28 Apr 2026 12:24:45 +0200 Subject: [PATCH 20/48] make g2 more in line with other variants --- variants/nano_g2_ultra/platformio.ini | 65 +++++++++++++++++---------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 116a1f2598..957c38025b 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -1,20 +1,11 @@ -[nrf52840_g2_ultra] +[Nano_G2_Ultra] extends = nrf52_base -platform_packages = framework-arduinoadafruitnrf52 +board = nano-g2-ultra +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 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - lewisxhe/PCF8563_Library@^1.0.1 - -[Nano_G2_Ultra] -extends = nrf52840_g2_ultra -board = nano-g2-ultra -board_build.ldscript = boards/nrf52840_s140_v6.ld -build_flags = ${nrf52840_g2_ultra.build_flags} -I variants/nano_g2_ultra -D NANO_G2_ULTRA -D RADIO_CLASS=CustomSX1262 @@ -23,11 +14,47 @@ build_flags = ${nrf52840_g2_ultra.build_flags} -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D PIN_USER_BTN=38 -build_src_filter = ${nrf52840_g2_ultra.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/nano_g2_ultra> debug_tool = jlink upload_protocol = nrfutil +lib_deps = ${nrf52_base.lib_deps} + adafruit/Adafruit SH110X @ ~2.1.13 + adafruit/Adafruit GFX Library @ ^1.12.1 + stevemarple/MicroNMEA @ ^2.0.6 + +[env:Nano_G2_Ultra_repeater] +extends = Nano_G2_Ultra +build_flags = + ${Nano_G2_Ultra.build_flags} + -D DISPLAY_CLASS=SH1106Display + -D ADVERT_NAME='"Nano G2 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 = ${Nano_G2_Ultra.build_src_filter} + + + +<../examples/simple_repeater> + +[env:Nano_G2_Ultra_room_server] +extends = Nano_G2_Ultra +build_flags = + ${Nano_G2_Ultra.build_flags} + -D DISPLAY_CLASS=SH1106Display + -D ADVERT_NAME='"Nano G2 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 = ${Nano_G2_Ultra.build_src_filter} + + + +<../examples/simple_room_server> [env:Nano_G2_Ultra_companion_radio_ble] extends = Nano_G2_Ultra @@ -54,12 +81,8 @@ build_src_filter = ${Nano_G2_Ultra.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${Nano_G2_Ultra.lib_deps} +lib_deps = ${Nano_G2_Ultra.lib_deps} densaugeo/base64 @ ~1.4.0 - adafruit/Adafruit SH110X @ ~2.1.13 - adafruit/Adafruit GFX Library @ ^1.12.1 - stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 [env:Nano_G2_Ultra_companion_radio_usb] @@ -84,10 +107,6 @@ build_src_filter = ${Nano_G2_Ultra.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> -lib_deps = - ${Nano_G2_Ultra.lib_deps} +lib_deps = ${Nano_G2_Ultra.lib_deps} densaugeo/base64 @ ~1.4.0 - adafruit/Adafruit SH110X @ ~2.1.13 - adafruit/Adafruit GFX Library @ ^1.12.1 - stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 From b9636818300831eb4300c1a708df24a33fdcdf63 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 28 Apr 2026 12:37:30 +0200 Subject: [PATCH 21/48] replace all intendation tabs to spaces in pio ini files --- platformio.ini | 4 ++-- variants/keepteen_lt1/platformio.ini | 2 +- variants/promicro/platformio.ini | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/platformio.ini b/platformio.ini index 864e5e1ffe..b10b317926 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,8 +10,8 @@ [platformio] extra_configs = - variants/*/platformio.ini - platformio.local.ini + variants/*/platformio.ini + platformio.local.ini [arduino_base] framework = arduino diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini index b82ceb7ea8..55ee21576e 100644 --- a/variants/keepteen_lt1/platformio.ini +++ b/variants/keepteen_lt1/platformio.ini @@ -17,7 +17,7 @@ build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/keepteen_lt1> lib_deps= ${nrf52_base.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.13 + adafruit/Adafruit SSD1306 @ ^2.5.13 stevemarple/MicroNMEA @ ^2.0.6 [env:KeepteenLT1_repeater] diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 317537a9e7..390b060530 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -27,7 +27,7 @@ build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/promicro> lib_deps= ${nrf52_base.lib_deps} - adafruit/Adafruit SSD1306 @ ^2.5.13 + 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 From 04b69e86bee15ba9db24d6e1ed009f809b022961 Mon Sep 17 00:00:00 2001 From: taco Date: Mon, 27 Apr 2026 12:01:24 +1000 Subject: [PATCH 22/48] revert bluefruit patch in preparation to switch to using our own fork of Adafruit NRF52 Arduino with the patch included --- arch/nrf52/extra_scripts/patch_bluefruit.py | 198 -------------------- platformio.ini | 4 +- 2 files changed, 1 insertion(+), 201 deletions(-) delete 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 deleted file mode 100644 index b43bffb5db..0000000000 --- a/arch/nrf52/extra_scripts/patch_bluefruit.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -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 b10b317926..cc1518a727 100644 --- a/platformio.ini +++ b/platformio.ini @@ -82,9 +82,7 @@ extends = arduino_base platform = nordicnrf52 platform_packages = framework-arduinoadafruitnrf52 @ 1.10700.0 -extra_scripts = - create-uf2.py - arch/nrf52/extra_scripts/patch_bluefruit.py +extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM -D LFS_NO_ASSERT=1 From f7d8fa342053b17aeff783a63b923641306f6728 Mon Sep 17 00:00:00 2001 From: taco Date: Tue, 28 Apr 2026 21:46:14 +1000 Subject: [PATCH 23/48] use internal fork of Adafruit nRF52 Arduino --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index cc1518a727..6a3c28c367 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,7 +81,7 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ extends = arduino_base platform = nordicnrf52 platform_packages = - framework-arduinoadafruitnrf52 @ 1.10700.0 + framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301 extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} -D NRF52_PLATFORM From 66009069cd14d28777781cb66923254b0a1999e5 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 28 Apr 2026 13:53:35 +0200 Subject: [PATCH 24/48] standardize the minewsemi variant --- variants/minewsemi_me25ls01/platformio.ini | 40 +++++----------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index dacd8d34e5..91e62243c1 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -1,23 +1,11 @@ -; ----------------- NRF52 me25ls01--------------------- -[nrf52840_me25ls01] +[me25ls01] extends = nrf52_base -platform_packages = framework-arduinoadafruitnrf52 +board = minewsemi_me25ls01 +board_build.ldscript = boards/nrf52840_s140_v7.ld build_flags = ${nrf52_base.build_flags} -I src/helpers/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 - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - -[me25ls01] -extends = nrf52840_me25ls01 -board = minewsemi_me25ls01 -board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_me25ls01.build_flags} -I variants/minewsemi_me25ls01 -D me25ls01 -D PIN_USER_BTN=27 @@ -31,13 +19,14 @@ build_flags = ${nrf52840_me25ls01.build_flags} -D ENV_INCLUDE_BME280=1 -D ENV_INCLUDE_INA3221=1 -D ENV_INCLUDE_INA219=1 -build_src_filter = ${nrf52840_me25ls01.build_src_filter} +build_src_filter = ${nrf52_base.build_src_filter} + +<../variants/minewsemi_me25ls01> + debug_tool = jlink upload_protocol = nrfutil -lib_deps = ${nrf52840_me25ls01.lib_deps} +lib_deps = ${nrf52_base.lib_deps} + adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 @@ -45,7 +34,7 @@ lib_deps = ${nrf52840_me25ls01.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 BME280 Library @ ^2.3.0 [env:Minewsemi_me25ls01_companion_radio_ble] extends = me25ls01 @@ -64,14 +53,11 @@ build_flags = ${me25ls01.build_flags} -D RF_SWITCH_TABLE -D DISPLAY_CLASS=NullDisplayDriver ;-D PIN_BUZZER=25 - ;-D PIN_BUZZER_EN=37 + ;-D PIN_BUZZER_EN=37 build_src_filter = ${me25ls01.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - [env:Minewsemi_me25ls01_repeater] extends = me25ls01 @@ -93,10 +79,6 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_repeater> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - - [env:Minewsemi_me25ls01_room_server] extends = me25ls01 @@ -119,8 +101,6 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_room_server> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 [env:Minewsemi_me25ls01_terminal_chat] extends = me25ls01 @@ -143,8 +123,6 @@ build_flags = ${me25ls01.build_flags} -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${me25ls01.build_src_filter} +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 [env:Minewsemi_me25ls01_companion_radio_usb] extends = me25ls01 @@ -166,6 +144,4 @@ build_src_filter = ${me25ls01.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 From 5beef490a0794f2969967b5a825bdf739240d886 Mon Sep 17 00:00:00 2001 From: Rastislav Vysoky Date: Tue, 28 Apr 2026 15:09:25 +0200 Subject: [PATCH 25/48] add kiss radio env to most of the variants --- examples/kiss_modem/main.cpp | 5 +- variants/ebyte_eora_s3/platformio.ini | 5 + variants/gat562_30s_mesh_kit/platformio.ini | 4 + variants/gat562_mesh_evb_pro/platformio.ini | 5 + .../gat562_mesh_tracker_pro/platformio.ini | 4 + variants/gat562_mesh_watch13/platformio.ini | 4 + variants/generic-e22/platformio.ini | 5 + variants/heltec_ct62/platformio.ini | 5 + variants/heltec_e213/platformio.ini | 5 + variants/heltec_e290/platformio.ini | 5 + variants/heltec_mesh_solar/platformio.ini | 7 +- variants/heltec_t096/platformio.ini | 9 +- variants/heltec_t114/platformio.ini | 12 +- variants/heltec_t190/platformio.ini | 5 + variants/heltec_tracker/platformio.ini | 5 + variants/heltec_tracker_v2/platformio.ini | 7 +- variants/heltec_v2/platformio.ini | 5 + variants/heltec_v3/platformio.ini | 4 - variants/heltec_v4/platformio.ini | 5 + variants/heltec_wireless_paper/platformio.ini | 5 + variants/ikoka_handheld_nrf/platformio.ini | 13 +- variants/ikoka_nano_nrf/platformio.ini | 15 + variants/ikoka_stick_nrf/platformio.ini | 15 + variants/keepteen_lt1/platformio.ini | 7 +- variants/lilygo_t3s3/platformio.ini | 5 + variants/lilygo_t3s3_sx1276/platformio.ini | 7 +- variants/lilygo_tbeam_1w/platformio.ini | 5 + variants/lilygo_tbeam_SX1262/platformio.ini | 5 + variants/lilygo_tbeam_SX1276/platformio.ini | 5 + .../platformio.ini | 5 + variants/lilygo_tdeck/platformio.ini | 10 +- variants/lilygo_techo/platformio.ini | 5 + variants/lilygo_techo_lite/platformio.ini | 5 + variants/lilygo_tlora_c6/platformio.ini | 5 + variants/lilygo_tlora_v2_1/platformio.ini | 5 + variants/m5stack_unit_c6l/platformio.ini | 5 + variants/mesh_pocket/platformio.ini | 7 +- variants/meshadventurer/platformio.ini | 38 +- variants/meshtiny/platformio.ini | 5 + variants/minewsemi_me25ls01/platformio.ini | 346 +++++++++--------- variants/muziworks_r1_neo/platformio.ini | 5 + variants/nano_g2_ultra/platformio.ini | 5 + variants/nibble_screen_connect/platformio.ini | 5 + variants/promicro/platformio.ini | 5 + variants/rak11310/platformio.ini | 5 + variants/rak3112/platformio.ini | 5 + variants/rak3401/platformio.ini | 5 + variants/rak3x72/platformio.ini | 5 + variants/rak_wismesh_tag/platformio.ini | 7 +- variants/rpi_picow/platformio.ini | 5 + variants/station_g2/platformio.ini | 5 + variants/t1000-e/platformio.ini | 7 + variants/tenstar_c3/platformio.ini | 20 + variants/thinknode_m1/platformio.ini | 7 +- variants/thinknode_m2/platformio.ini | 7 + variants/thinknode_m3/platformio.ini | 11 +- variants/thinknode_m5/platformio.ini | 5 + variants/thinknode_m6/platformio.ini | 7 +- variants/tiny_relay/platformio.ini | 5 + variants/waveshare_rp2040_lora/platformio.ini | 5 + variants/wio-e5-dev/platformio.ini | 7 + variants/wio-e5-mini/platformio.ini | 9 +- variants/wio-tracker-l1-eink/platformio.ini | 5 + variants/wio-tracker-l1/platformio.ini | 5 + variants/wio_wm1110/platformio.ini | 5 + variants/xiao_c3/platformio.ini | 5 + variants/xiao_c6/platformio.ini | 5 + variants/xiao_rp2040/platformio.ini | 5 + variants/xiao_s3_wio/platformio.ini | 5 + 69 files changed, 586 insertions(+), 220 deletions(-) diff --git a/examples/kiss_modem/main.cpp b/examples/kiss_modem/main.cpp index 3507959297..bc7508a871 100644 --- a/examples/kiss_modem/main.cpp +++ b/examples/kiss_modem/main.cpp @@ -10,7 +10,10 @@ #include #elif defined(ESP32) #include +#else + #include #endif + #if defined(KISS_UART_RX) && defined(KISS_UART_TX) #include #endif @@ -29,7 +32,7 @@ void halt() { } void loadOrCreateIdentity() { -#if defined(NRF52_PLATFORM) +#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) InternalFS.begin(); IdentityStore store(InternalFS, ""); #elif defined(ESP32) diff --git a/variants/ebyte_eora_s3/platformio.ini b/variants/ebyte_eora_s3/platformio.ini index d807b9782b..15fe761ba5 100644 --- a/variants/ebyte_eora_s3/platformio.ini +++ b/variants/ebyte_eora_s3/platformio.ini @@ -135,3 +135,8 @@ build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} lib_deps = ${Ebyte_EoRa-S3.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Ebyte_EoRa-S3_kiss_modem] +extends = Ebyte_EoRa-S3 +build_src_filter = ${Ebyte_EoRa-S3.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/gat562_30s_mesh_kit/platformio.ini b/variants/gat562_30s_mesh_kit/platformio.ini index 1467f0fa3d..2baac2561b 100644 --- a/variants/gat562_30s_mesh_kit/platformio.ini +++ b/variants/gat562_30s_mesh_kit/platformio.ini @@ -112,3 +112,7 @@ lib_deps = densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 +[env:GAT562_30S_Mesh_Kit_kiss_modem] +extends = GAT562_30S_Mesh_Kit +build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/gat562_mesh_evb_pro/platformio.ini b/variants/gat562_mesh_evb_pro/platformio.ini index cede9c97c0..b3e894174a 100644 --- a/variants/gat562_mesh_evb_pro/platformio.ini +++ b/variants/gat562_mesh_evb_pro/platformio.ini @@ -50,3 +50,8 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${GAT562_Mesh_EVB_Pro.build_src_filter} +<../examples/simple_room_server> + +[env:GAT562_Mesh_EVB_Pro_kiss_modem] +extends = GAT562_Mesh_EVB_Pro +build_src_filter = ${GAT562_Mesh_EVB_Pro.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/gat562_mesh_tracker_pro/platformio.ini b/variants/gat562_mesh_tracker_pro/platformio.ini index 8a947bce74..af153b8fc2 100644 --- a/variants/gat562_mesh_tracker_pro/platformio.ini +++ b/variants/gat562_mesh_tracker_pro/platformio.ini @@ -106,3 +106,7 @@ lib_deps = ${GAT562_Mesh_Tracker_Pro.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:GAT562_Mesh_Tracker_Pro_kiss_modem] +extends = GAT562_Mesh_Tracker_Pro +build_src_filter = ${GAT562_Mesh_Tracker_Pro.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/gat562_mesh_watch13/platformio.ini b/variants/gat562_mesh_watch13/platformio.ini index ef30829d5c..f3510b74aa 100644 --- a/variants/gat562_mesh_watch13/platformio.ini +++ b/variants/gat562_mesh_watch13/platformio.ini @@ -87,3 +87,7 @@ lib_deps = ${GAT562_Mesh_Watch13.lib_deps} densaugeo/base64 @ ~1.4.0 +[env:GAT562_Mesh_Watch13_kiss_modem] +extends = GAT562_Mesh_Watch13 +build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/generic-e22/platformio.ini b/variants/generic-e22/platformio.ini index 6b7bfd4eab..dfa2ff641f 100644 --- a/variants/generic-e22/platformio.ini +++ b/variants/generic-e22/platformio.ini @@ -95,6 +95,11 @@ lib_deps = ${Generic_E22.lib_deps} ${esp32_ota.lib_deps} +[env:Generic_E22_kiss_modem] +extends = Generic_E22 +build_src_filter = ${Generic_E22.build_src_filter} + +<../examples/kiss_modem/> + [env:Generic_E22_sx1268_repeater] extends = Generic_E22 build_src_filter = ${Generic_E22.build_src_filter} diff --git a/variants/heltec_ct62/platformio.ini b/variants/heltec_ct62/platformio.ini index 910385ecdf..0179d9658c 100644 --- a/variants/heltec_ct62/platformio.ini +++ b/variants/heltec_ct62/platformio.ini @@ -150,3 +150,8 @@ build_src_filter = ${Heltec_ct62.build_src_filter} lib_deps = ${Heltec_ct62.lib_deps} ${esp32_ota.lib_deps} + +[env:Heltec_ct62_kiss_modem] +extends = Heltec_ct62 +build_src_filter = ${Heltec_ct62.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_e213/platformio.ini b/variants/heltec_e213/platformio.ini index 395e22ea54..123edd91c4 100644 --- a/variants/heltec_e213/platformio.ini +++ b/variants/heltec_e213/platformio.ini @@ -166,3 +166,8 @@ lib_deps = ${Heltec_E213_base.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 + +[env:Heltec_E213_kiss_modem] +extends = Heltec_E213_base +build_src_filter = ${Heltec_E213_base.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_e290/platformio.ini b/variants/heltec_e290/platformio.ini index 475ae86805..6e0eaffc48 100644 --- a/variants/heltec_e290/platformio.ini +++ b/variants/heltec_e290/platformio.ini @@ -162,3 +162,8 @@ lib_deps = ${Heltec_E290_base.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 + +[env:Heltec_E290_kiss_modem] +extends = Heltec_E290_base +build_src_filter = ${Heltec_E290_base.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_mesh_solar/platformio.ini b/variants/heltec_mesh_solar/platformio.ini index 7bfbac85a7..92d641a357 100644 --- a/variants/heltec_mesh_solar/platformio.ini +++ b/variants/heltec_mesh_solar/platformio.ini @@ -92,4 +92,9 @@ build_src_filter = ${Heltec_mesh_solar.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${Heltec_mesh_solar.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_mesh_solar_kiss_modem] +extends = Heltec_mesh_solar +build_src_filter = ${Heltec_mesh_solar.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_t096/platformio.ini b/variants/heltec_t096/platformio.ini index 19b05f3ce4..14155f5ead 100644 --- a/variants/heltec_t096/platformio.ini +++ b/variants/heltec_t096/platformio.ini @@ -92,7 +92,7 @@ build_src_filter = ${Heltec_t096.build_src_filter} [env:Heltec_t096_room_server] extends = Heltec_t096 build_src_filter = ${Heltec_t096.build_src_filter} - +<../examples/simple_room_server> + +<../examples/simple_room_server> build_flags = ${Heltec_t096.build_flags} -D ADVERT_NAME='"Heltec_t096 Room"' @@ -145,4 +145,9 @@ build_src_filter = ${Heltec_t096.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t096.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_t096_kiss_modem] +extends = Heltec_t096 +build_src_filter = ${Heltec_t096.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_t114/platformio.ini b/variants/heltec_t114/platformio.ini index b985030f79..135babb1a2 100644 --- a/variants/heltec_t114/platformio.ini +++ b/variants/heltec_t114/platformio.ini @@ -50,7 +50,6 @@ upload_protocol = nrfutil extends = Heltec_t114 build_src_filter = ${Heltec_t114.build_src_filter} +<../examples/simple_repeater> - build_flags = ${Heltec_t114.build_flags} -D ADVERT_NAME='"Heltec_T114 Repeater"' @@ -127,10 +126,6 @@ build_flags = -D DISPLAY_CLASS=NullDisplayDriver -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -; -D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 -; -D MESH_PACKET_LOGGING=1 -; -D MESH_DEBUG=1 build_src_filter = ${Heltec_t114.build_src_filter} + +<../examples/companion_radio/*.cpp> @@ -251,4 +246,9 @@ build_src_filter = ${Heltec_t114_with_display.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Heltec_t114_with_display.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 + +[env:Heltec_t114_kiss_modem] +extends = Heltec_t114 +build_src_filter = ${Heltec_t114.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_t190/platformio.ini b/variants/heltec_t190/platformio.ini index 4b03403284..411fee8518 100644 --- a/variants/heltec_t190/platformio.ini +++ b/variants/heltec_t190/platformio.ini @@ -153,3 +153,8 @@ build_src_filter = ${Heltec_T190_base.build_src_filter} lib_deps = ${Heltec_T190_base.lib_deps} ${esp32_ota.lib_deps} + +[env:Heltec_T190_kiss_modem] +extends = Heltec_T190_base +build_src_filter = ${Heltec_T190_base.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index e0a8f5fab6..69293d7070 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -186,3 +186,8 @@ build_src_filter = ${Heltec_tracker_base.build_src_filter} lib_deps = ${Heltec_tracker_base.lib_deps} ${esp32_ota.lib_deps} + +[env:Heltec_Wireless_Tracker_kiss_modem] +extends = Heltec_tracker_base +build_src_filter = ${Heltec_tracker_base.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 956b1ec771..d57c2113f0 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -187,7 +187,7 @@ build_flags = -D WIFI_DEBUG_LOGGING=1 -D WIFI_SSID='"myssid"' -D WIFI_PWD='"mypwd"' - -D OFFLINE_QUEUE_SIZE=256 + -D OFFLINE_QUEUE_SIZE=256 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 build_src_filter = ${Heltec_tracker_v2.build_src_filter} @@ -217,3 +217,8 @@ build_src_filter = ${Heltec_tracker_v2.build_src_filter} lib_deps = ${Heltec_tracker_v2.lib_deps} ${esp32_ota.lib_deps} + +[env:heltec_tracker_v2_kiss_modem] +extends = Heltec_tracker_v2 +build_src_filter = ${Heltec_tracker_v2.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_v2/platformio.ini b/variants/heltec_v2/platformio.ini index 99f6f7e13c..c103dd8d53 100644 --- a/variants/heltec_v2/platformio.ini +++ b/variants/heltec_v2/platformio.ini @@ -195,3 +195,8 @@ build_src_filter = ${Heltec_lora32_v2.build_src_filter} lib_deps = ${Heltec_lora32_v2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Heltec_v2_kiss_modem] +extends = Heltec_lora32_v2 +build_src_filter = ${Heltec_lora32_v2.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_v3/platformio.ini b/variants/heltec_v3/platformio.ini index 803ee683e0..a70a93a508 100644 --- a/variants/heltec_v3/platformio.ini +++ b/variants/heltec_v3/platformio.ini @@ -371,9 +371,5 @@ 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/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 87632f32eb..fabf38272d 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -429,3 +429,8 @@ build_src_filter = ${heltec_v4_tft.build_src_filter} lib_deps = ${heltec_v4_tft.lib_deps} ${esp32_ota.lib_deps} + +[env:heltec_v4_kiss_modem] +extends = Heltec_lora32_v4 +build_src_filter = ${Heltec_lora32_v4.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_wireless_paper/platformio.ini b/variants/heltec_wireless_paper/platformio.ini index c6fe657d47..48723d169a 100644 --- a/variants/heltec_wireless_paper/platformio.ini +++ b/variants/heltec_wireless_paper/platformio.ini @@ -165,3 +165,8 @@ lib_deps = ${Heltec_Wireless_Paper_base.lib_deps} ${esp32_ota.lib_deps} bakercp/CRC32 @ ^2.0.0 + +[env:Heltec_Wireless_Paper_kiss_modem] +extends = Heltec_Wireless_Paper_base +build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/ikoka_handheld_nrf/platformio.ini b/variants/ikoka_handheld_nrf/platformio.ini index 9f48cd41d0..51b602e403 100644 --- a/variants/ikoka_handheld_nrf/platformio.ini +++ b/variants/ikoka_handheld_nrf/platformio.ini @@ -86,8 +86,7 @@ build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_repeater] extends = ikoka_handheld_nrf -build_flags = - ${ikoka_handheld_nrf.build_flags} +build_flags = ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Repeater"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -99,8 +98,7 @@ build_src_filter = ${ikoka_handheld_nrf.build_src_filter} [env:ikoka_handheld_nrf_e22_30dbm_room_server] extends = ikoka_handheld_nrf -build_flags = - ${ikoka_handheld_nrf.build_flags} +build_flags = ${ikoka_handheld_nrf.build_flags} -D ADVERT_NAME='"ikoka_handheld Room"' -D ADVERT_LAT=0.0 -D ADVERT_LON=0.0 @@ -108,3 +106,10 @@ build_flags = -D LORA_TX_POWER=20 build_src_filter = ${ikoka_handheld_nrf.build_src_filter} +<../examples/simple_room_server/*.cpp> + +[env:ikoka_handheld_nrf_kiss_modem] +extends = ikoka_handheld_nrf +build_flags = ${ikoka_handheld_nrf.build_flags} + -D LORA_TX_POWER=20 +build_src_filter = ${ikoka_handheld_nrf.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/ikoka_nano_nrf/platformio.ini b/variants/ikoka_nano_nrf/platformio.ini index d631174d90..e72f83ce0d 100644 --- a/variants/ikoka_nano_nrf/platformio.ini +++ b/variants/ikoka_nano_nrf/platformio.ini @@ -279,3 +279,18 @@ build_flags = build_src_filter = ${ikoka_nano_nrf_room_server.build_src_filter} ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_nano_nrf_22dbm_kiss_modem] +extends = ikoka_nano_nrf_e22_22dbm +build_src_filter = ${ikoka_nano_nrf_e22_22dbm.build_src_filter} + +<../examples/kiss_modem/> + +[env:ikoka_nano_nrf_30dbm_kiss_modem] +extends = ikoka_nano_nrf_e22_30dbm +build_src_filter = ${ikoka_nano_nrf_e22_30dbm.build_src_filter} + +<../examples/kiss_modem/> + +[env:ikoka_nano_nrf_33dbm_kiss_modem] +extends = ikoka_nano_nrf_e22_33dbm +build_src_filter = ${ikoka_nano_nrf_e22_33dbm.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/ikoka_stick_nrf/platformio.ini b/variants/ikoka_stick_nrf/platformio.ini index e95c637500..06e39e84c3 100644 --- a/variants/ikoka_stick_nrf/platformio.ini +++ b/variants/ikoka_stick_nrf/platformio.ini @@ -290,3 +290,18 @@ build_flags = build_src_filter = ${ikoka_stick_nrf_room_server.build_src_filter} ${ikoka_stick_nrf_e22_33dbm.build_src_filter} + +[env:ikoka_stick_nrf_22dbm_kiss_modem] +extends = ikoka_stick_nrf_e22_22dbm +build_src_filter = ${ikoka_stick_nrf_e22_22dbm.build_src_filter} + +<../examples/kiss_modem/> + +[env:ikoka_stick_nrf_30dbm_kiss_modem] +extends = ikoka_stick_nrf_e22_30dbm +build_src_filter = ${ikoka_stick_nrf_e22_30dbm.build_src_filter} + +<../examples/kiss_modem/> + +[env:ikoka_stick_nrf_33dbm_kiss_modem] +extends = ikoka_stick_nrf_e22_33dbm +build_src_filter = ${ikoka_stick_nrf_e22_33dbm.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/keepteen_lt1/platformio.ini b/variants/keepteen_lt1/platformio.ini index b82ceb7ea8..8aa407a206 100644 --- a/variants/keepteen_lt1/platformio.ini +++ b/variants/keepteen_lt1/platformio.ini @@ -99,4 +99,9 @@ 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 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 + +[env:KeepteenLT1_kiss_modem] +extends = KeepteenLT1 +build_src_filter = ${KeepteenLT1.build_src_filter} + +<../examples/kiss_modem/> \ No newline at end of file diff --git a/variants/lilygo_t3s3/platformio.ini b/variants/lilygo_t3s3/platformio.ini index 1fefceceb3..54990117cc 100644 --- a/variants/lilygo_t3s3/platformio.ini +++ b/variants/lilygo_t3s3/platformio.ini @@ -173,3 +173,8 @@ build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} lib_deps = ${LilyGo_T3S3_sx1262.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_T3S3_sx1262_kiss_modem] +extends = LilyGo_T3S3_sx1262 +build_src_filter = ${LilyGo_T3S3_sx1262.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_t3s3_sx1276/platformio.ini b/variants/lilygo_t3s3_sx1276/platformio.ini index 5a7ece2cbd..5df632020e 100644 --- a/variants/lilygo_t3s3_sx1276/platformio.ini +++ b/variants/lilygo_t3s3_sx1276/platformio.ini @@ -168,4 +168,9 @@ build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${LilyGo_T3S3_sx1276.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_T3S3_sx1276_kiss_modem] +extends = LilyGo_T3S3_sx1276 +build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tbeam_1w/platformio.ini b/variants/lilygo_tbeam_1w/platformio.ini index 7c8453077f..c7a595520e 100644 --- a/variants/lilygo_tbeam_1w/platformio.ini +++ b/variants/lilygo_tbeam_1w/platformio.ini @@ -192,3 +192,8 @@ build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} lib_deps = ${LilyGo_TBeam_1W.lib_deps} ${esp32_ota.lib_deps} + +[env:LilyGo_TBeam_1W_kiss_modem] +extends = LilyGo_TBeam_1W +build_src_filter = ${LilyGo_TBeam_1W.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tbeam_SX1262/platformio.ini b/variants/lilygo_tbeam_SX1262/platformio.ini index d3bc7c9978..77b39304ab 100644 --- a/variants/lilygo_tbeam_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_SX1262/platformio.ini @@ -133,3 +133,8 @@ build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} lib_deps = ${LilyGo_TBeam_SX1262.lib_deps} ${esp32_ota.lib_deps} + +[env:Tbeam_SX1262_kiss_modem] +extends = LilyGo_TBeam_SX1262 +build_src_filter = ${LilyGo_TBeam_SX1262.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tbeam_SX1276/platformio.ini b/variants/lilygo_tbeam_SX1276/platformio.ini index 3562c40e94..6853a232bd 100644 --- a/variants/lilygo_tbeam_SX1276/platformio.ini +++ b/variants/lilygo_tbeam_SX1276/platformio.ini @@ -131,3 +131,8 @@ build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} lib_deps = ${LilyGo_TBeam_SX1276.lib_deps} ${esp32_ota.lib_deps} + +[env:Tbeam_SX1276_kiss_modem] +extends = LilyGo_TBeam_SX1276 +build_src_filter = ${LilyGo_TBeam_SX1276.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini index ffee37a969..845dd41500 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/platformio.ini +++ b/variants/lilygo_tbeam_supreme_SX1262/platformio.ini @@ -158,3 +158,8 @@ 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_kiss_modem] +extends = T_Beam_S3_Supreme_SX1262 +build_src_filter = ${T_Beam_S3_Supreme_SX1262.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tdeck/platformio.ini b/variants/lilygo_tdeck/platformio.ini index a8408afa43..745d8ff53d 100644 --- a/variants/lilygo_tdeck/platformio.ini +++ b/variants/lilygo_tdeck/platformio.ini @@ -57,6 +57,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/lilygo_tdeck> + + + lib_deps = ${esp32_base.lib_deps} ${sensor_base.lib_deps} @@ -75,7 +76,6 @@ build_src_filter = ${LilyGo_TDeck.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> - + lib_deps = ${LilyGo_TDeck.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -94,7 +94,6 @@ build_src_filter = ${LilyGo_TDeck.build_src_filter} + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> - + lib_deps = ${LilyGo_TDeck.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -113,4 +112,9 @@ build_src_filter = ${LilyGo_TDeck.build_src_filter} + lib_deps = ${LilyGo_TDeck.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} + +[env:LilyGo_TDeck_kiss_modem] +extends = LilyGo_TDeck +build_src_filter = ${LilyGo_TDeck.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_techo/platformio.ini b/variants/lilygo_techo/platformio.ini index e2172b1dab..5df77f95cb 100644 --- a/variants/lilygo_techo/platformio.ini +++ b/variants/lilygo_techo/platformio.ini @@ -126,3 +126,8 @@ build_src_filter = ${LilyGo_T-Echo.build_src_filter} lib_deps = ${LilyGo_T-Echo.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_T-Echo_kiss_modem] +extends = LilyGo_T-Echo +build_src_filter = ${LilyGo_T-Echo.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 8861685824..d3408f95a3 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -140,3 +140,8 @@ build_src_filter = ${nrf52_base.build_src_filter} lib_deps = ${LilyGo_T-Echo-Lite.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_T-Echo-Lite_kiss_modem] +extends = LilyGo_T-Echo-Lite +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tlora_c6/platformio.ini b/variants/lilygo_tlora_c6/platformio.ini index b06a0b7cb9..3acfa7615d 100644 --- a/variants/lilygo_tlora_c6/platformio.ini +++ b/variants/lilygo_tlora_c6/platformio.ini @@ -84,3 +84,8 @@ build_src_filter = ${tlora_c6.build_src_filter} lib_deps = ${tlora_c6.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:LilyGo_Tlora_C6_kiss_modem] +extends = tlora_c6 +build_src_filter = ${tlora_c6.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/lilygo_tlora_v2_1/platformio.ini b/variants/lilygo_tlora_v2_1/platformio.ini index 3641f12705..f710d0700f 100644 --- a/variants/lilygo_tlora_v2_1/platformio.ini +++ b/variants/lilygo_tlora_v2_1/platformio.ini @@ -192,3 +192,8 @@ build_flags = lib_deps = ${LilyGo_TLora_V2_1_1_6.lib_deps} ${esp32_ota.lib_deps} + +[env:LilyGo_TLora_V2_1_1_6_kiss_modem] +extends = LilyGo_TLora_V2_1_1_6 +build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/m5stack_unit_c6l/platformio.ini b/variants/m5stack_unit_c6l/platformio.ini index 84c6562afa..94083eb486 100644 --- a/variants/m5stack_unit_c6l/platformio.ini +++ b/variants/m5stack_unit_c6l/platformio.ini @@ -105,3 +105,8 @@ lib_deps = ${M5Stack_Unit_C6L.lib_deps} densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:M5Stack_Unit_C6L_kiss_modem] +extends = M5Stack_Unit_C6L +build_src_filter = ${M5Stack_Unit_C6L.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/mesh_pocket/platformio.ini b/variants/mesh_pocket/platformio.ini index 015c2ca4be..52a0d83523 100644 --- a/variants/mesh_pocket/platformio.ini +++ b/variants/mesh_pocket/platformio.ini @@ -109,4 +109,9 @@ build_src_filter = ${Mesh_pocket.build_src_filter} +<../examples/companion_radio/ui-new/*.cpp> lib_deps = ${Mesh_pocket.lib_deps} - densaugeo/base64 @ ~1.4.0 \ No newline at end of file + densaugeo/base64 @ ~1.4.0 + +[env:Mesh_pocket_kiss_modem] +extends = Mesh_pocket +build_src_filter = ${Mesh_pocket.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index 18b64ac329..2006a1801f 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -29,6 +29,7 @@ build_flags = build_src_filter = ${esp32_base.build_src_filter} +<../variants/meshadventurer> + + + lib_deps = ${esp32_base.lib_deps} stevemarple/MicroNMEA @ ^2.0.6 @@ -38,7 +39,6 @@ lib_deps = extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} +<../examples/simple_repeater> - + build_flags = ${Meshadventurer.build_flags} -D RADIO_CLASS=CustomSX1262 @@ -60,7 +60,6 @@ lib_deps = ; build_src_filter = ${Meshadventurer.build_src_filter} ; + ; +<../examples/simple_repeater> -; + ; build_flags = ; ${Meshadventurer.build_flags} ; -D RADIO_CLASS=CustomSX1262 @@ -86,7 +85,6 @@ extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_repeater> - + build_flags = ${Meshadventurer.build_flags} -D RADIO_CLASS=CustomSX1262 @@ -109,7 +107,6 @@ lib_deps = extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} +<../examples/simple_repeater> - + build_flags = ${Meshadventurer.build_flags} -D RADIO_CLASS=CustomSX1268 @@ -157,7 +154,6 @@ extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/simple_repeater> - + build_flags = ${Meshadventurer.build_flags} -D RADIO_CLASS=CustomSX1268 @@ -179,8 +175,6 @@ lib_deps = [env:Meshadventurer_sx1262_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio/*.cpp> - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> build_flags = @@ -200,9 +194,7 @@ lib_deps = [env:Meshadventurer_sx1262_companion_radio_ble] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio/*.cpp> + - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> build_flags = @@ -235,7 +227,6 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Meshadventurer.build_src_filter} +<../examples/simple_secure_chat/main.cpp> - + lib_deps = ${Meshadventurer.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -256,7 +247,6 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Meshadventurer.build_src_filter} +<../examples/simple_room_server> - + lib_deps = ${Meshadventurer.lib_deps} ${esp32_ota.lib_deps} @@ -264,8 +254,6 @@ lib_deps = [env:Meshadventurer_sx1268_companion_radio_usb] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio/*.cpp> - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> build_flags = @@ -285,9 +273,7 @@ lib_deps = [env:Meshadventurer_sx1268_companion_radio_ble] extends = Meshadventurer build_src_filter = ${Meshadventurer.build_src_filter} - +<../examples/companion_radio/*.cpp> + - + +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-new/*.cpp> build_flags = @@ -320,7 +306,6 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Meshadventurer.build_src_filter} +<../examples/simple_secure_chat/main.cpp> - + lib_deps = ${Meshadventurer.lib_deps} densaugeo/base64 @ ~1.4.0 @@ -341,7 +326,24 @@ build_flags = ; -D MESH_DEBUG=1 build_src_filter = ${Meshadventurer.build_src_filter} +<../examples/simple_room_server> - + lib_deps = ${Meshadventurer.lib_deps} - ${esp32_ota.lib_deps} \ No newline at end of file + ${esp32_ota.lib_deps} + +[env:Meshadventurer_sx1262_kiss_modem] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/kiss_modem/> +build_flags = ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + +[env:Meshadventurer_sx1268_kiss_modem] +extends = Meshadventurer +build_src_filter = ${Meshadventurer.build_src_filter} + +<../examples/kiss_modem/> +build_flags = ${Meshadventurer.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 diff --git a/variants/meshtiny/platformio.ini b/variants/meshtiny/platformio.ini index 0d5de517d4..c5439c88f4 100644 --- a/variants/meshtiny/platformio.ini +++ b/variants/meshtiny/platformio.ini @@ -67,3 +67,8 @@ build_src_filter = ${Meshtiny.build_src_filter} lib_deps = ${Meshtiny.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Meshtiny_kiss_modem] +extends = Meshtiny +build_src_filter = ${Meshtiny.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/minewsemi_me25ls01/platformio.ini b/variants/minewsemi_me25ls01/platformio.ini index dacd8d34e5..ab60988233 100644 --- a/variants/minewsemi_me25ls01/platformio.ini +++ b/variants/minewsemi_me25ls01/platformio.ini @@ -1,171 +1,175 @@ -; ----------------- NRF52 me25ls01--------------------- -[nrf52840_me25ls01] -extends = nrf52_base -platform_packages = framework-arduinoadafruitnrf52 -build_flags = ${nrf52_base.build_flags} - -I src/helpers/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 - lib5b4 -lib_deps = - ${nrf52_base.lib_deps} - rweather/Crypto @ ^0.4.0 - -[me25ls01] -extends = nrf52840_me25ls01 -board = minewsemi_me25ls01 -board_build.ldscript = boards/nrf52840_s140_v7.ld -build_flags = ${nrf52840_me25ls01.build_flags} - -I variants/minewsemi_me25ls01 - -D me25ls01 - -D PIN_USER_BTN=27 - -D PIN_STATUS_LED=39 - -D P_LORA_TX_LED=22 - -D RADIO_CLASS=CustomLR1110 - -D WRAPPER_CLASS=CustomLR1110Wrapper - -D LORA_TX_POWER=22 - -D ENV_INCLUDE_GPS=0 - -D ENV_INCLUDE_AHTX0=1 - -D ENV_INCLUDE_BME280=1 - -D ENV_INCLUDE_INA3221=1 - -D ENV_INCLUDE_INA219=1 -build_src_filter = ${nrf52840_me25ls01.build_src_filter} - + - +<../variants/minewsemi_me25ls01> - + -debug_tool = jlink -upload_protocol = nrfutil -lib_deps = ${nrf52840_me25ls01.lib_deps} - densaugeo/base64 @ ~1.4.0 - stevemarple/MicroNMEA @ ^2.0.6 - end2endzone/NonBlockingRTTTL@^1.3.0 - 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 - -[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 - -D MAX_GROUP_CHANNELS=40 - -D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 - -D OFFLINE_QUEUE_SIZE=256 - -D RX_BOOSTED_GAIN=true - -D RF_SWITCH_TABLE - -D DISPLAY_CLASS=NullDisplayDriver - ;-D PIN_BUZZER=25 - ;-D PIN_BUZZER_EN=37 -build_src_filter = ${me25ls01.build_src_filter} - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-orig/*.cpp> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - - -[env:Minewsemi_me25ls01_repeater] -extends = me25ls01 -build_flags = ${me25ls01.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 - -D OFFLINE_QUEUE_SIZE=256 - -D RX_BOOSTED_GAIN=true - -D RF_SWITCH_TABLE - -D ADVERT_NAME='"ME25LS01 Repeater"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D MAX_NEIGHBOURS=50 - -D DISPLAY_CLASS=NullDisplayDriver -build_src_filter = ${me25ls01.build_src_filter} - +<../examples/simple_repeater> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - - - -[env:Minewsemi_me25ls01_room_server] -extends = me25ls01 -build_flags = ${me25ls01.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 - -D OFFLINE_QUEUE_SIZE=256 - -D RX_BOOSTED_GAIN=true - -D RF_SWITCH_TABLE - -D ADVERT_NAME='"ME25LS01 Room"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ROOM_PASSWORD='"hello"' - -D MAX_NEIGHBOURS=50 - -D DISPLAY_CLASS=NullDisplayDriver -build_src_filter = ${me25ls01.build_src_filter} - +<../examples/simple_room_server> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - -[env:Minewsemi_me25ls01_terminal_chat] -extends = me25ls01 -build_flags = ${me25ls01.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 - -D OFFLINE_QUEUE_SIZE=256 - -D RX_BOOSTED_GAIN=true - -D RF_SWITCH_TABLE - -D ADVERT_NAME='"ME25LS01 Chat"' - -D ADVERT_LAT=0.0 - -D ADVERT_LON=0.0 - -D ADMIN_PASSWORD='"password"' - -D ROOM_PASSWORD='"hello"' - -D MAX_NEIGHBOURS=50 - -D DISPLAY_CLASS=NullDisplayDriver -build_src_filter = ${me25ls01.build_src_filter} - +<../examples/simple_secure_chat/main.cpp> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - -[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 - -D MAX_GROUP_CHANNELS=40 - ;-D BLE_PIN_CODE=123456 -; -D BLE_DEBUG_LOGGING=1 - -D MESH_PACKET_LOGGING=1 - -D MESH_DEBUG=1 - -D OFFLINE_QUEUE_SIZE=256 - -D RX_BOOSTED_GAIN=true - -D RF_SWITCH_TABLE - -D DISPLAY_CLASS=NullDisplayDriver -build_src_filter = ${me25ls01.build_src_filter} - + - +<../examples/companion_radio/*.cpp> - +<../examples/companion_radio/ui-orig/*.cpp> -lib_deps = ${me25ls01.lib_deps} - adafruit/RTClib @ ^2.1.3 - +; ----------------- NRF52 me25ls01--------------------- +[nrf52840_me25ls01] +extends = nrf52_base +platform_packages = framework-arduinoadafruitnrf52 +build_flags = ${nrf52_base.build_flags} + -I src/helpers/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 + lib5b4 +lib_deps = + ${nrf52_base.lib_deps} + rweather/Crypto @ ^0.4.0 + +[me25ls01] +extends = nrf52840_me25ls01 +board = minewsemi_me25ls01 +board_build.ldscript = boards/nrf52840_s140_v7.ld +build_flags = ${nrf52840_me25ls01.build_flags} + -I variants/minewsemi_me25ls01 + -D me25ls01 + -D PIN_USER_BTN=27 + -D PIN_STATUS_LED=39 + -D P_LORA_TX_LED=22 + -D RADIO_CLASS=CustomLR1110 + -D WRAPPER_CLASS=CustomLR1110Wrapper + -D LORA_TX_POWER=22 + -D ENV_INCLUDE_GPS=0 + -D ENV_INCLUDE_AHTX0=1 + -D ENV_INCLUDE_BME280=1 + -D ENV_INCLUDE_INA3221=1 + -D ENV_INCLUDE_INA219=1 +build_src_filter = ${nrf52840_me25ls01.build_src_filter} + + + +<../variants/minewsemi_me25ls01> + + +debug_tool = jlink +upload_protocol = nrfutil +lib_deps = ${nrf52840_me25ls01.lib_deps} + densaugeo/base64 @ ~1.4.0 + stevemarple/MicroNMEA @ ^2.0.6 + end2endzone/NonBlockingRTTTL@^1.3.0 + 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 + +[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 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE + -D DISPLAY_CLASS=NullDisplayDriver + ;-D PIN_BUZZER=25 + ;-D PIN_BUZZER_EN=37 +build_src_filter = ${me25ls01.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${me25ls01.lib_deps} + adafruit/RTClib @ ^2.1.3 + + +[env:Minewsemi_me25ls01_repeater] +extends = me25ls01 +build_flags = ${me25ls01.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 + -D OFFLINE_QUEUE_SIZE=256 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE + -D ADVERT_NAME='"ME25LS01 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=NullDisplayDriver +build_src_filter = ${me25ls01.build_src_filter} + +<../examples/simple_repeater> +lib_deps = ${me25ls01.lib_deps} + adafruit/RTClib @ ^2.1.3 + + + +[env:Minewsemi_me25ls01_room_server] +extends = me25ls01 +build_flags = ${me25ls01.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 + -D OFFLINE_QUEUE_SIZE=256 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE + -D ADVERT_NAME='"ME25LS01 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=NullDisplayDriver +build_src_filter = ${me25ls01.build_src_filter} + +<../examples/simple_room_server> +lib_deps = ${me25ls01.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:Minewsemi_me25ls01_terminal_chat] +extends = me25ls01 +build_flags = ${me25ls01.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 + -D OFFLINE_QUEUE_SIZE=256 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE + -D ADVERT_NAME='"ME25LS01 Chat"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' + -D MAX_NEIGHBOURS=50 + -D DISPLAY_CLASS=NullDisplayDriver +build_src_filter = ${me25ls01.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = ${me25ls01.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[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 + -D MAX_GROUP_CHANNELS=40 + ;-D BLE_PIN_CODE=123456 +; -D BLE_DEBUG_LOGGING=1 + -D MESH_PACKET_LOGGING=1 + -D MESH_DEBUG=1 + -D OFFLINE_QUEUE_SIZE=256 + -D RX_BOOSTED_GAIN=true + -D RF_SWITCH_TABLE + -D DISPLAY_CLASS=NullDisplayDriver +build_src_filter = ${me25ls01.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-orig/*.cpp> +lib_deps = ${me25ls01.lib_deps} + adafruit/RTClib @ ^2.1.3 + +[env:Minewsemi_me25ls01_kiss_modem] +extends = me25ls01 +build_src_filter = ${me25ls01.build_src_filter} + +<../examples/kiss_modem/> \ No newline at end of file diff --git a/variants/muziworks_r1_neo/platformio.ini b/variants/muziworks_r1_neo/platformio.ini index 39ef8728fd..3dbecf1e84 100644 --- a/variants/muziworks_r1_neo/platformio.ini +++ b/variants/muziworks_r1_neo/platformio.ini @@ -130,3 +130,8 @@ build_flags = -D MESH_DEBUG=1 build_src_filter = ${R1Neo.build_src_filter} +<../examples/simple_sensor> + +[env:R1Neo_kiss_modem] +extends = R1Neo +build_src_filter = ${R1Neo.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/nano_g2_ultra/platformio.ini b/variants/nano_g2_ultra/platformio.ini index 116a1f2598..fa1b5ef1c7 100644 --- a/variants/nano_g2_ultra/platformio.ini +++ b/variants/nano_g2_ultra/platformio.ini @@ -91,3 +91,8 @@ lib_deps = adafruit/Adafruit GFX Library @ ^1.12.1 stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:Nano_G2_Ultra_kiss_modem] +extends = Nano_G2_Ultra +build_src_filter = ${Nano_G2_Ultra.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/nibble_screen_connect/platformio.ini b/variants/nibble_screen_connect/platformio.ini index 8a4e63cabc..a496a2fe68 100644 --- a/variants/nibble_screen_connect/platformio.ini +++ b/variants/nibble_screen_connect/platformio.ini @@ -159,3 +159,8 @@ lib_deps = ${nibble_screen_connect_base.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:nibble_screen_connect_kiss_modem] +extends = nibble_screen_connect_base +build_src_filter = ${nibble_screen_connect_base.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/promicro/platformio.ini b/variants/promicro/platformio.ini index 317537a9e7..a0477e1710 100644 --- a/variants/promicro/platformio.ini +++ b/variants/promicro/platformio.ini @@ -171,3 +171,8 @@ build_src_filter = ${Promicro.build_src_filter} +<../examples/simple_sensor> lib_deps = ${Promicro.lib_deps} + +[env:ProMicro_kiss_modem] +extends = Promicro +build_src_filter = ${Promicro.build_src_filter} + +<../examples/kiss_modem/> \ No newline at end of file diff --git a/variants/rak11310/platformio.ini b/variants/rak11310/platformio.ini index d55376270a..3019f9c55c 100644 --- a/variants/rak11310/platformio.ini +++ b/variants/rak11310/platformio.ini @@ -132,3 +132,8 @@ build_src_filter = ${rak11310.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${rak11310.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:RAK_11310_kiss_modem] +extends = rak11310 +build_src_filter = ${rak11310.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/rak3112/platformio.ini b/variants/rak3112/platformio.ini index b4165bb2b4..9cd32c4b4d 100644 --- a/variants/rak3112/platformio.ini +++ b/variants/rak3112/platformio.ini @@ -204,3 +204,8 @@ build_src_filter = ${rak3112.build_src_filter} lib_deps = ${rak3112.lib_deps} ${esp32_ota.lib_deps} + +[env:RAK_3112_kiss_modem] +extends = rak3112 +build_src_filter = ${rak3112.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 3d2d4a3ec5..b50140977b 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -126,3 +126,8 @@ build_flags = build_src_filter = ${rak3401.build_src_filter} + +<../examples/simple_sensor> + +[env:RAK_3401_kiss_modem] +extends = rak3401 +build_src_filter = ${rak3401.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/rak3x72/platformio.ini b/variants/rak3x72/platformio.ini index 12ea413ab9..f966786087 100644 --- a/variants/rak3x72/platformio.ini +++ b/variants/rak3x72/platformio.ini @@ -41,3 +41,8 @@ build_src_filter = ${rak3x72.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${rak3x72.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:RAK_3x72_kiss_modem] +extends = rak3x72 +build_src_filter = ${rak3x72.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/rak_wismesh_tag/platformio.ini b/variants/rak_wismesh_tag/platformio.ini index 081cb0d077..29124daf49 100644 --- a/variants/rak_wismesh_tag/platformio.ini +++ b/variants/rak_wismesh_tag/platformio.ini @@ -118,4 +118,9 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 -D MESH_DEBUG=1 build_src_filter = ${rak4631.build_src_filter} - +<../examples/simple_sensor> \ No newline at end of file + +<../examples/simple_sensor> + +[env:RAK_WisMesh_Tag_kiss_modem] +extends = rak_wismesh_tag +build_src_filter = ${rak_wismesh_tag.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/rpi_picow/platformio.ini b/variants/rpi_picow/platformio.ini index 2d261b25ae..9253d36f65 100644 --- a/variants/rpi_picow/platformio.ini +++ b/variants/rpi_picow/platformio.ini @@ -106,3 +106,8 @@ build_src_filter = ${rpi_picow.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${rpi_picow.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:PicoW_kiss_modem] +extends = rpi_picow +build_src_filter = ${rpi_picow.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/station_g2/platformio.ini b/variants/station_g2/platformio.ini index 87e77152b8..6432b52386 100644 --- a/variants/station_g2/platformio.ini +++ b/variants/station_g2/platformio.ini @@ -238,3 +238,8 @@ build_src_filter = ${Station_G2.build_src_filter} lib_deps = ${Station_G2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Station_G2_kiss_modem] +extends = Station_G2 +build_src_filter = ${Station_G2.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 429390c486..fb88e3347a 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -117,3 +117,10 @@ lib_deps = ${t1000-e.lib_deps} densaugeo/base64 @ ~1.4.0 stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:t1000e_kiss_modem] +extends = t1000-e +build_src_filter = ${t1000-e.build_src_filter} + +<../examples/kiss_modem/> +lib_deps = ${t1000-e.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/tenstar_c3/platformio.ini b/variants/tenstar_c3/platformio.ini index 183a56840b..e798882366 100644 --- a/variants/tenstar_c3/platformio.ini +++ b/variants/tenstar_c3/platformio.ini @@ -161,3 +161,23 @@ build_flags = lib_deps = ${Tenstar_esp32_C3.lib_deps} ${esp32_ota.lib_deps} + +[env:Tenstar_C3_sx1262_kiss_modem] +extends = Tenstar_esp32_C3 +build_src_filter = ${Tenstar_esp32_C3.build_src_filter} + +<../examples/kiss_modem/> +build_flags = + ${Tenstar_esp32_C3.build_flags} + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + +[env:Tenstar_C3_sx1268_kiss_modem] +extends = Tenstar_esp32_C3 +build_src_filter = ${Tenstar_esp32_C3.build_src_filter} + +<../examples/kiss_modem/> +build_flags = + ${Tenstar_esp32_C3.build_flags} + -D RADIO_CLASS=CustomSX1268 + -D WRAPPER_CLASS=CustomSX1268Wrapper + -D LORA_TX_POWER=22 diff --git a/variants/thinknode_m1/platformio.ini b/variants/thinknode_m1/platformio.ini index 397bf8e30f..356edfee69 100644 --- a/variants/thinknode_m1/platformio.ini +++ b/variants/thinknode_m1/platformio.ini @@ -127,4 +127,9 @@ lib_deps = densaugeo/base64 @ ~1.4.0 zinggjm/GxEPD2 @ 1.6.2 bakercp/CRC32 @ ^2.0.0 - end2endzone/NonBlockingRTTTL@^1.3.0 \ No newline at end of file + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M1_kiss_modem] +extends = ThinkNode_M1 +build_src_filter = ${ThinkNode_M1.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/thinknode_m2/platformio.ini b/variants/thinknode_m2/platformio.ini index a765d9c7e5..9901550178 100644 --- a/variants/thinknode_m2/platformio.ini +++ b/variants/thinknode_m2/platformio.ini @@ -209,3 +209,10 @@ build_src_filter = ${ThinkNode_M2.build_src_filter} lib_deps = ${ThinkNode_M2.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:ThinkNode_M2_kiss_modem] +extends = ThinkNode_M2 +build_flags = ${ThinkNode_M2.build_flags} + -UPIN_BUZZER +build_src_filter = ${ThinkNode_M2.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/thinknode_m3/platformio.ini b/variants/thinknode_m3/platformio.ini index 88fd487aa6..0a3d4eda92 100644 --- a/variants/thinknode_m3/platformio.ini +++ b/variants/thinknode_m3/platformio.ini @@ -17,11 +17,11 @@ build_flags = ${nrf52_base.build_flags} -D RF_SWITCH_TABLE -D RX_BOOSTED_GAIN=true -D P_LORA_BUSY=43 - -D P_LORA_SCLK=45 + -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_MOSI=46 -D P_LORA_RESET=42 -D P_LORA_TX_LED=PIN_LED_BLUE -D P_LORA_TX_LED_ON=LOW @@ -119,3 +119,10 @@ lib_deps = ${ThinkNode_M3.lib_deps} densaugeo/base64 @ ~1.4.0 stevemarple/MicroNMEA @ ^2.0.6 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M3_kiss_modem] +extends = ThinkNode_M3 +build_src_filter = ${ThinkNode_M3.build_src_filter} + +<../examples/kiss_modem/> +lib_deps = ${ThinkNode_M3.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 diff --git a/variants/thinknode_m5/platformio.ini b/variants/thinknode_m5/platformio.ini index 16df472a22..8572d1ebe0 100644 --- a/variants/thinknode_m5/platformio.ini +++ b/variants/thinknode_m5/platformio.ini @@ -227,3 +227,8 @@ lib_deps = ${ThinkNode_M5.lib_deps} densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M5_kiss_modem] +extends = ThinkNode_M5 +build_src_filter = ${ThinkNode_M5.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/thinknode_m6/platformio.ini b/variants/thinknode_m6/platformio.ini index 2bd34f3186..6fe9043668 100644 --- a/variants/thinknode_m6/platformio.ini +++ b/variants/thinknode_m6/platformio.ini @@ -116,4 +116,9 @@ build_src_filter = ${ThinkNode_M6.build_src_filter} lib_deps = ${ThinkNode_M6.lib_deps} densaugeo/base64 @ ~1.4.0 - end2endzone/NonBlockingRTTTL@^1.3.0 \ No newline at end of file + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:ThinkNode_M6_kiss_modem] +extends = ThinkNode_M6 +build_src_filter = ${ThinkNode_M6.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/tiny_relay/platformio.ini b/variants/tiny_relay/platformio.ini index ed17872759..82cb251fdb 100644 --- a/variants/tiny_relay/platformio.ini +++ b/variants/tiny_relay/platformio.ini @@ -48,3 +48,8 @@ build_src_filter = ${Tiny_Relay.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${Tiny_Relay.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Tiny_Relay_kiss_modem] +extends = Tiny_Relay +build_src_filter = ${Tiny_Relay.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/waveshare_rp2040_lora/platformio.ini b/variants/waveshare_rp2040_lora/platformio.ini index 880f238f2f..36658d824d 100644 --- a/variants/waveshare_rp2040_lora/platformio.ini +++ b/variants/waveshare_rp2040_lora/platformio.ini @@ -131,3 +131,8 @@ build_src_filter = ${waveshare_rp2040_lora.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${waveshare_rp2040_lora.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:waveshare_rp2040_lora_kiss_modem] +extends = waveshare_rp2040_lora +build_src_filter = ${waveshare_rp2040_lora.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/wio-e5-dev/platformio.ini b/variants/wio-e5-dev/platformio.ini index d7e63c83e3..22bdc3c837 100644 --- a/variants/wio-e5-dev/platformio.ini +++ b/variants/wio-e5-dev/platformio.ini @@ -5,6 +5,8 @@ board_upload.maximum_size = 229376 ; 32kb for FS build_flags = ${stm32_base.build_flags} -D RADIO_CLASS=CustomSTM32WLx -D WRAPPER_CLASS=CustomSTM32WLxWrapper + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 -D SPI_INTERFACES_COUNT=0 -D RX_BOOSTED_GAIN=true -D PIN_SERIAL_RX=PB7 @@ -48,3 +50,8 @@ build_src_filter = ${lora_e5.build_src_filter} +<../examples/companion_radio/*.cpp> lib_deps = ${lora_e5.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:wio-e5_kiss_modem] +extends = lora_e5 +build_src_filter = ${lora_e5.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/wio-e5-mini/platformio.ini b/variants/wio-e5-mini/platformio.ini index f589ea0323..82f01331bb 100644 --- a/variants/wio-e5-mini/platformio.ini +++ b/variants/wio-e5-mini/platformio.ini @@ -7,6 +7,8 @@ build_flags = ${stm32_base.build_flags} -D WRAPPER_CLASS=CustomSTM32WLxWrapper -D SPI_INTERFACES_COUNT=0 -D RX_BOOSTED_GAIN=true + -D LORA_TX_POWER=22 + -D SX126X_CURRENT_LIMIT=140 -D P_LORA_TX_LED=LED_RED -D PIN_USER_BTN=USER_BTN -I variants/wio-e5-mini @@ -40,10 +42,15 @@ build_flags = ${lora_e5_mini.build_flags} -I examples/companion_radio/ui-orig -D LORA_TX_POWER=22 -D MAX_CONTACTS=100 - -D MAX_GROUP_CHANNELS=8 + -D MAX_GROUP_CHANNELS=8 -D DISPLAY_CLASS=NullDisplayDriver build_src_filter = ${lora_e5_mini.build_src_filter} +<../examples/companion_radio/*.cpp> +<../examples/companion_radio/ui-orig/*.cpp> lib_deps = ${lora_e5_mini.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:wio-e5-mini_kiss_modem] +extends = lora_e5_mini +build_src_filter = ${lora_e5_mini.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/wio-tracker-l1-eink/platformio.ini b/variants/wio-tracker-l1-eink/platformio.ini index 42c83477b7..2c74baded3 100644 --- a/variants/wio-tracker-l1-eink/platformio.ini +++ b/variants/wio-tracker-l1-eink/platformio.ini @@ -68,3 +68,8 @@ lib_deps = ${WioTrackerL1Eink.lib_deps} densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 debug_tool=stlink + +[env:WioTrackerL1Eink_kiss_modem] +extends = WioTrackerL1Eink +build_src_filter = ${WioTrackerL1Eink.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/wio-tracker-l1/platformio.ini b/variants/wio-tracker-l1/platformio.ini index 6c1e8f634a..7bb175bb9a 100644 --- a/variants/wio-tracker-l1/platformio.ini +++ b/variants/wio-tracker-l1/platformio.ini @@ -111,3 +111,8 @@ lib_deps = ${WioTrackerL1.lib_deps} adafruit/RTClib @ ^2.1.3 densaugeo/base64 @ ~1.4.0 end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:WioTrackerL1_kiss_modem] +extends = WioTrackerL1 +build_src_filter = ${WioTrackerL1.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/wio_wm1110/platformio.ini b/variants/wio_wm1110/platformio.ini index ec65e706b3..a7eac916b0 100644 --- a/variants/wio_wm1110/platformio.ini +++ b/variants/wio_wm1110/platformio.ini @@ -84,3 +84,8 @@ build_src_filter = ${wio_wm1110.build_src_filter} lib_deps = ${wio_wm1110.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:wio_wm1110_kiss_modem] +extends = wio_wm1110 +build_src_filter = ${wio_wm1110.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/xiao_c3/platformio.ini b/variants/xiao_c3/platformio.ini index c5254b46c9..c0e8458d0e 100644 --- a/variants/xiao_c3/platformio.ini +++ b/variants/xiao_c3/platformio.ini @@ -128,3 +128,8 @@ lib_deps = ${Xiao_esp32_C3.lib_deps} ${esp32_ota.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Xiao_C3_kiss_modem] +extends = Xiao_esp32_C3 +build_src_filter = ${Xiao_esp32_C3.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/xiao_c6/platformio.ini b/variants/xiao_c6/platformio.ini index 717be7b9a0..9f504b8ed0 100644 --- a/variants/xiao_c6/platformio.ini +++ b/variants/xiao_c6/platformio.ini @@ -184,3 +184,8 @@ build_src_filter = ${WHY2025_badge.build_src_filter} lib_deps = ${WHY2025_badge.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Xiao_C6_kiss_modem] +extends = Xiao_C6 +build_src_filter = ${Xiao_C6.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/xiao_rp2040/platformio.ini b/variants/xiao_rp2040/platformio.ini index e5d652c327..9a0fdaf4a2 100644 --- a/variants/xiao_rp2040/platformio.ini +++ b/variants/xiao_rp2040/platformio.ini @@ -108,3 +108,8 @@ build_src_filter = ${Xiao_rp2040.build_src_filter} +<../examples/simple_secure_chat/main.cpp> lib_deps = ${Xiao_rp2040.lib_deps} densaugeo/base64 @ ~1.4.0 + +[env:Xiao_rp2040_kiss_modem] +extends = Xiao_rp2040 +build_src_filter = ${Xiao_rp2040.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/xiao_s3_wio/platformio.ini b/variants/xiao_s3_wio/platformio.ini index 13d406792b..db8c5a9486 100644 --- a/variants/xiao_s3_wio/platformio.ini +++ b/variants/xiao_s3_wio/platformio.ini @@ -234,3 +234,8 @@ build_flags = lib_deps = ${Xiao_S3_WIO.lib_deps} ${esp32_ota.lib_deps} + +[env:Xiao_S3_WIO_kiss_modem] +extends = Xiao_S3_WIO +build_src_filter = ${Xiao_S3_WIO.build_src_filter} + +<../examples/kiss_modem/> From 0265851621b3c0a821a99eaf4d5ab05e811bc623 Mon Sep 17 00:00:00 2001 From: chrisdavis2110 Date: Tue, 28 Apr 2026 21:26:56 -0700 Subject: [PATCH 26/48] added PIN_USER_BTN_ANA for rak3401 companion usb and companion ble --- examples/companion_radio/ui-new/UITask.cpp | 16 ++++++++-------- variants/rak3401/platformio.ini | 2 ++ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 94a8ee3efa..e06a6ecc3b 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -146,7 +146,7 @@ class HomeScreen : public UIScreen { bool sensors_scroll = false; int sensors_scroll_offset = 0; int next_sensors_refresh = 0; - + void refresh_sensors() { if (millis() > next_sensors_refresh) { sensors_lpp.reset(); @@ -170,7 +170,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), + : _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), _shutdown_init(false), sensors_lpp(200) { } void poll() override { @@ -213,7 +213,7 @@ class HomeScreen : public UIScreen { IPAddress ip = WiFi.localIP(); 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, tmp); + display.drawTextCentered(display.width() / 2, 54, tmp); #endif if (_task->hasConnection()) { display.setColor(DisplayDriver::GREEN); @@ -241,10 +241,10 @@ class HomeScreen : public UIScreen { } else { sprintf(tmp, "%dh", secs / (60*60)); } - + 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); @@ -310,7 +310,7 @@ class HomeScreen : public UIScreen { display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; display.drawTextLeftAlign(0, y, "pos"); - sprintf(buf, "%.4f %.4f", + sprintf(buf, "%.4f %.4f", nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); display.drawTextRightAlign(display.width()-1, y, buf); y = y + 12; @@ -741,7 +741,7 @@ void UITask::loop() { #endif #if defined(PIN_USER_BTN_ANA) if (abs(millis() - _analogue_pin_read_millis) > 10) { - ev = analog_btn.check(); + int ev = analog_btn.check(); if (ev == BUTTON_EVENT_CLICK) { c = checkDisplayOn(KEY_NEXT); } else if (ev == BUTTON_EVENT_LONG_PRESS) { @@ -878,7 +878,7 @@ bool UITask::getGPSState() { return !strcmp(_sensors->getSettingValue(i), "1"); } } - } + } return false; } diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index b3693a58be..1246b72588 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -64,6 +64,7 @@ board_upload.maximum_size = 712704 build_flags = ${rak3401.build_flags} -I examples/companion_radio/ui-new + -D PIN_USER_BTN_ANA=31 -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 @@ -83,6 +84,7 @@ board_upload.maximum_size = 712704 build_flags = ${rak3401.build_flags} -I examples/companion_radio/ui-new + -D PIN_USER_BTN_ANA=31 -D PIN_GPS_EN=-1 -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 From 5fbd473298f0e365e0add4df0c9ff8db5083138c Mon Sep 17 00:00:00 2001 From: chrisdavis2110 Date: Tue, 28 Apr 2026 21:59:10 -0700 Subject: [PATCH 27/48] added PIN_GPS_EN=-1 to rak3401 companion usb --- variants/rak3401/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/rak3401/platformio.ini b/variants/rak3401/platformio.ini index 1246b72588..d94db6a598 100644 --- a/variants/rak3401/platformio.ini +++ b/variants/rak3401/platformio.ini @@ -65,6 +65,7 @@ build_flags = ${rak3401.build_flags} -I examples/companion_radio/ui-new -D PIN_USER_BTN_ANA=31 + -D PIN_GPS_EN=-1 -D DISPLAY_CLASS=SSD1306Display -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 From 696aae6ed11d7e4d859a5f0b944ef8fdde43ef0a Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 29 Apr 2026 16:20:21 +0800 Subject: [PATCH 28/48] Heltec boards have LNA disabled by default. --- variants/heltec_t096/LoRaFEMControl.h | 2 +- variants/heltec_tracker_v2/LoRaFEMControl.h | 2 +- variants/heltec_v4/LoRaFEMControl.h | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/variants/heltec_t096/LoRaFEMControl.h b/variants/heltec_t096/LoRaFEMControl.h index fe61b7e441..2c50b74289 100644 --- a/variants/heltec_t096/LoRaFEMControl.h +++ b/variants/heltec_t096/LoRaFEMControl.h @@ -16,6 +16,6 @@ class LoRaFEMControl void setLnaCanControl(bool can_control) { lna_can_control = can_control; } private: - bool lna_enabled = true; + bool lna_enabled = false; bool lna_can_control = false; }; diff --git a/variants/heltec_tracker_v2/LoRaFEMControl.h b/variants/heltec_tracker_v2/LoRaFEMControl.h index fe61b7e441..2c50b74289 100644 --- a/variants/heltec_tracker_v2/LoRaFEMControl.h +++ b/variants/heltec_tracker_v2/LoRaFEMControl.h @@ -16,6 +16,6 @@ class LoRaFEMControl void setLnaCanControl(bool can_control) { lna_can_control = can_control; } private: - bool lna_enabled = true; + bool lna_enabled = false; bool lna_can_control = false; }; diff --git a/variants/heltec_v4/LoRaFEMControl.h b/variants/heltec_v4/LoRaFEMControl.h index 7545296503..13225bd56b 100644 --- a/variants/heltec_v4/LoRaFEMControl.h +++ b/variants/heltec_v4/LoRaFEMControl.h @@ -23,7 +23,7 @@ class LoRaFEMControl LoRaFEMType getFEMType(void) const { return fem_type; } private: LoRaFEMType fem_type=OTHER_FEM_TYPES; - bool lna_enabled=true; + bool lna_enabled=false; bool lna_can_control=false; }; From c5bf23f4b1dbf2376dd141b40f21f64b3c623efa Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 30 Apr 2026 11:12:31 +0200 Subject: [PATCH 29/48] bump simple_sensor version to v1.15.0 --- examples/simple_sensor/SensorMesh.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index ee5d5e025a..424b16c175 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -34,11 +34,11 @@ #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "20 Mar 2026" + #define FIRMWARE_BUILD_DATE "19 Apr 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.14.1" + #define FIRMWARE_VERSION "v1.15.0" #endif #define FIRMWARE_ROLE "sensor" From 2b0f74a7e9e8e72fa398dbc7945b596cb7de8bcc Mon Sep 17 00:00:00 2001 From: liamcottle Date: Thu, 30 Apr 2026 21:36:04 +1200 Subject: [PATCH 30/48] add comments --- platformio.ini | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platformio.ini b/platformio.ini index 6a3c28c367..ba27abb9c2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -81,6 +81,9 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/ extends = arduino_base platform = nordicnrf52 platform_packages = + ; use internal fork that includes patch to ble stack to prevent firmware lockup during rapid connect/disconnect + ; https://github.com/meshcore-dev/MeshCore/pull/1177 + ; https://github.com/meshcore-dev/MeshCore/pull/1295 framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301 extra_scripts = create-uf2.py build_flags = ${arduino_base.build_flags} From 0be082b5f9fef923132efd6a2aa726a56d9b2f07 Mon Sep 17 00:00:00 2001 From: Avi0n <14863961+Avi0n@users.noreply.github.com> Date: Mon, 9 Mar 2026 11:37:58 -0700 Subject: [PATCH 31/48] fix typos --- docs/cli_commands.md | 4 ++-- docs/faq.md | 34 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index fb698228ed..dd2626ccc1 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -456,7 +456,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 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. +**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propagation 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 supported. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes. --- @@ -927,7 +927,7 @@ region save --- -#### View or change thevalue of a sensor +#### View or change the value of a sensor **Usage:** - `sensor get ` - `sensor set ` diff --git a/docs/faq.md b/docs/faq.md index 3edc0a6953..c04eb6aa22 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -23,8 +23,8 @@ A list of frequently-asked questions and answers for MeshCore - [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) - [3.5. Q: Can I retrieve a repeater's private key or set a repeater's private key?](#35-q-can-i-retrieve-a-repeaters-private-key-or-set-a-repeaters-private-key) - - [3.6. Q: The first byte of my repeater's public key collides with an exisitng repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?](#36-q-the-first-byte-of-my-repeaters-public-key-collides-with-an-exisitng-repeater-on-the-mesh--how-do-i-get-a-new-private-key-with-a-matching-public-key-that-has-its-first-byte-of-my-choosing) - - [3.7. Q: My repeater maybe suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?](#37-q-my-repeater-maybe-suffering-from-deafness-due-to-high-power-interference-near-my-meshs-frequency-it-is-not-hearing-other-in-range-meshcore-radios--what-can-i-do) + - [3.6. Q: The first byte of my repeater's public key collides with an existing repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?](#36-q-the-first-byte-of-my-repeaters-public-key-collides-with-an-existing-repeater-on-the-mesh--how-do-i-get-a-new-private-key-with-a-matching-public-key-that-has-its-first-byte-of-my-choosing) + - [3.7. Q: My repeater may be suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?](#37-q-my-repeater-may-be-suffering-from-deafness-due-to-high-power-interference-near-my-meshs-frequency-it-is-not-hearing-other-in-range-meshcore-radios--what-can-i-do) - [3.8. Q: How do I make my repeater an observer on the mesh?](#38-q-how-do-i-make-my-repeater-an-observer-on-the-mesh) - [3.9. Q: What is multi-byte support? What do 1-byte, 2-byte, 3-byte adverts and messages mean?](#39-q-what-is-multi-byte-support--what-do-1-byte-2-byte-3-byte-adverts-and-messages-mean) - [3.9.1. Q: **What path hash sizes will my repeater forward?**](#391-q-what-path-hash-sizes-will-my-repeater-forward) @@ -39,7 +39,7 @@ A list of frequently-asked questions and answers for MeshCore - [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.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) @@ -52,9 +52,9 @@ A list of frequently-asked questions and answers for MeshCore - [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) - [5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?](#53-q-what-happens-when-a-node-learns-a-route-via-a-mobile-repeater-and-that-repeater-is-gone) - - [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?](#54-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) + - [5.4. Q: How does a node discover a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discover-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic) - [5.5. Q: Do public channels always flood? Do private channels always flood?](#55-q-do-public-channels-always-flood-do-private-channels-always-flood) - - [5.6. Q: what is the public key for the default public channel?](#56-q-what-is-the-public-key-for-the-default-public-channel) + - [5.6. Q: What is the public key for the default public channel?](#56-q-what-is-the-public-key-for-the-default-public-channel) - [5.7. Q: Is MeshCore open source?](#57-q-is-meshcore-open-source) - [5.8. Q: How can I support MeshCore?](#58-q-how-can-i-support-meshcore) - [5.9. Q: How do I build MeshCore firmware from source?](#59-q-how-do-i-build-meshcore-firmware-from-source) @@ -62,7 +62,7 @@ A list of frequently-asked questions and answers for MeshCore - [5.11. Q: Does MeshCore support ATAK](#511-q-does-meshcore-support-atak) - [5.12. Q: How do I add a node to the MeshCore Map](#512-q-how-do-i-add-a-node-to-the-meshcore-map) - [5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio?](#513-q-can-i-use-a-raspberry-pi-to-update-a-meshcore-radio) - - [5.14. Q: Are there are projects built around MeshCore?](#514-q-are-there-are-projects-built-around-meshcore) + - [5.14. Q: Are there projects built around MeshCore?](#514-q-are-there-projects-built-around-meshcore) - [5.15. Q: Are there client applications for Windows or Mac?](#515-q-are-there-client-applications-for-windows-or-mac) - [5.16. Q: Are there any resources that compare MeshCore to other LoRa systems?](#516-q-are-there-any-resources-that-compare-meshcore-to-other-lora-systems) - [6. Troubleshooting](#6-troubleshooting) @@ -188,7 +188,7 @@ The T-Deck firmware is free to download and most features are available without ### 2.3. Q: What frequencies are supported by MeshCore? **A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported. -Use the smartphone client or the repeater setup feature on there web flasher to set your radios' RF settings by choosing the preset for your regions. +Use the smartphone client or the repeater setup feature on the web flasher to set your radios' RF settings by choosing the preset for your regions. Recently, as of October 2025, many regions have moved to the "narrow" setting, aka using BW62.5 and a lower SF number (instead of the original SF11). For example, USA/Canada (Recommended) preset is 910.525MHz, SF7, BW62.5, CR5. @@ -272,7 +272,7 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo Reboot the repeater after `set prv.key ` command for the new private key to take effect. -### 3.6. Q: The first byte of my repeater's public key collides with an exisitng repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing? +### 3.6. Q: The first byte of my repeater's public key collides with an existing repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing? **A:** You can generate a new private key and specific the first byte of its public key here: https://gessaman.com/mc-keygen/ @@ -281,7 +281,7 @@ Having multiple repeaters with the same first byte ID does not negatively affect Best practice is when you set up a new repeater, choose a public key that is not in use. If it is not possible to find a unique first byte for your repeater's public key, choose one that is unique within about 10 miles (16 km) to minimize collision with nearby repeaters. -### 3.7. Q: My repeater maybe suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do? +### 3.7. Q: My repeater may be suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do? **A:** This may be due to the SX1262 radio's auto gain control feature. You can use this command to periodically reset its AGC. @@ -380,7 +380,7 @@ GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-De ### 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.6. 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==` @@ -499,7 +499,7 @@ In MeshCore, only repeaters and room server with `set repeat on` repeat. 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? +### 5.4. Q: How does a node discover a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic? Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing. When your destination node gets the message, it will send back a delivery report to the sender with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. When you send the next message, the path will get embedded into the packet and be evaluated by repeaters. If the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization. @@ -512,7 +512,7 @@ Routes are stored in sender's contact list. When you send a message the first t [Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350023009527664672) -### 5.6. Q: what is the public key for the default public channel? +### 5.6. Q: What is the public key for the default public channel? **A:** The smartphone app key is in hex: ` 8b3387e9c5cdea6ac9e5edbaa115cd72` @@ -658,11 +658,11 @@ From here, reference repeater and room server command line commands on MeshCore - https://github.com/meshcore-dev/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference -### 5.14. Q: Are there are projects built around MeshCore? +### 5.14. Q: Are there projects built around MeshCore? -**A:** Yes, there are many. MeshCore's protocol is open source using the MIT license. The MIT license and the open source protocol makes it very easy for the MeshCore community to build new firmware for radios, applications on mobile devices, map tools, and analysis tools, and integration with other projects like Home Asistant. +**A:** Yes, there are many. MeshCore's protocol is open source using the MIT license. The MIT license and the open source protocol makes it very easy for the MeshCore community to build new firmware for radios, applications on mobile devices, map tools, and analysis tools, and integration with other projects like Home Assistant. -As new MeshCore community projects become available on a weekly basis, we have stopped tracking them here in this FAQ. [samuk](https://github.com/samuk) maintains a very exhausive list of MeshCore community project at https://github.com/samuk/awesome-meshcore/blob/main/README.md. samuk accepts PRs and merges them regularly. +As new MeshCore community projects become available on a weekly basis, we have stopped tracking them here in this FAQ. [samuk](https://github.com/samuk) maintains a very exhaustive list of MeshCore community project at https://github.com/samuk/awesome-meshcore/blob/main/README.md. samuk accepts PRs and merges them regularly. ### 5.15. Q: Are there client applications for Windows or Mac? @@ -716,7 +716,7 @@ You can get the epoch time on and use it to se - For RAK, click the reset button **TWICE** - For T1000-e, quickly disconnect and reconnect the magnetic side of the cable from the device **TWICE** - For Heltec T114, click the reset button **TWICE** (the bottom button) - - For Xiao nRF52, click the reset button once. If that doesn't work, quickly double click the reset button twice. If that doesn't work, disconnection the board from your PC and reconnect again ([seeed studio wiki](https://wiki.seeedstudio.com/XIAO_BLE/#access-the-swd-pins-for-debugging-and-reflashing-bootloader)) + - For Xiao nRF52, click the reset button once. If that doesn't work, quickly double click the reset button twice. If that doesn't work, disconnect the board from your PC and reconnect again ([seeed studio wiki](https://wiki.seeedstudio.com/XIAO_BLE/#access-the-swd-pins-for-debugging-and-reflashing-bootloader)) 5. A new folder will appear on your computer's desktop 6. Download the `flash_erase*.uf2` file for your device on https://flasher.meshcore.io - RAK WisBlock and Heltec T114: `Flash_erase-nRF32_softdevice_v6.uf2` @@ -826,7 +826,7 @@ Edit WIFI_SSID and WIFI_PWD in `./variants/heltec_v3/platformio.ini` and then fl ### 7.7. Q: I have a Station G2, or a Heltec V4, or an Ikoka Stick, or a radio with a EByte E22-900M30S or a E22-900M33S module, what should their transmit power be set to? **A:** -For companion radios, you can set these radios' transmit power in the smartphone app. For repeater and room server radios, you can set their transmit power using the command line command `set tx`. You can get their current value using command line comand `get tx` +For companion radios, you can set these radios' transmit power in the smartphone app. For repeater and room server radios, you can set their transmit power using the command line command `set tx`. You can get their current value using command line command `get tx` ⚠️ **WARNING: Set these values at your own risk. Incorrect power settings can permanently damage your radio hardware.** From a5b8c6598d60a3ff53780e73b191eac1306a9ea3 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 24 Apr 2026 20:04:01 +0000 Subject: [PATCH 32/48] Add native advert data round-trip tests --- platformio.ini | 1 + test/mocks/Arduino.h | 6 ++++ test/test_utils/test_advert_data.cpp | 52 ++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+) create mode 100644 test/mocks/Arduino.h create mode 100644 test/test_utils/test_advert_data.cpp diff --git a/platformio.ini b/platformio.ini index 5c6072b405..7ff42af8d3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -164,5 +164,6 @@ test_build_src = yes build_src_filter = -<*> +<../src/Utils.cpp> + +<../src/helpers/AdvertDataHelpers.cpp> lib_deps = google/googletest @ 1.17.0 diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h new file mode 100644 index 0000000000..d265a16e6a --- /dev/null +++ b/test/mocks/Arduino.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include +#include +#include diff --git a/test/test_utils/test_advert_data.cpp b/test/test_utils/test_advert_data.cpp new file mode 100644 index 0000000000..ef71983601 --- /dev/null +++ b/test/test_utils/test_advert_data.cpp @@ -0,0 +1,52 @@ +#include + +#include "helpers/AdvertDataHelpers.h" + +namespace { + +TEST(AdvertData, RoundTripsNameOnly) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_CHAT, "alice"); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(ADV_TYPE_CHAT, parser.getType()); + EXPECT_TRUE(parser.hasName()); + EXPECT_STREQ("alice", parser.getName()); + EXPECT_FALSE(parser.hasLatLon()); +} + +TEST(AdvertData, RoundTripsNameAndCoordinates) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_REPEATER, "node", 37.7749, -122.4194); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(ADV_TYPE_REPEATER, parser.getType()); + EXPECT_TRUE(parser.hasName()); + EXPECT_STREQ("node", parser.getName()); + EXPECT_TRUE(parser.hasLatLon()); + EXPECT_EQ(37774900, parser.getIntLat()); + EXPECT_EQ(-122419400, parser.getIntLon()); + EXPECT_DOUBLE_EQ(37.7749, parser.getLat()); + EXPECT_DOUBLE_EQ(-122.4194, parser.getLon()); +} + +TEST(AdvertData, RoundTripsCoordinateExtremes) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_SENSOR, "edge", -90.0, 180.0); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + ASSERT_TRUE(parser.isValid()); + EXPECT_TRUE(parser.hasLatLon()); + EXPECT_EQ(-90000000, parser.getIntLat()); + EXPECT_EQ(180000000, parser.getIntLon()); +} + +} // namespace From 987f345ab63b4345443e42566c45c91a29771d51 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 24 Apr 2026 23:29:57 +0000 Subject: [PATCH 33/48] Add invalid GPS advert data tests --- test/test_utils/test_advert_data.cpp | 40 ++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/test_utils/test_advert_data.cpp b/test/test_utils/test_advert_data.cpp index ef71983601..9645a516eb 100644 --- a/test/test_utils/test_advert_data.cpp +++ b/test/test_utils/test_advert_data.cpp @@ -49,4 +49,44 @@ TEST(AdvertData, RoundTripsCoordinateExtremes) { EXPECT_EQ(180000000, parser.getIntLon()); } +TEST(AdvertData, RejectsLongitudeOutsideValidRange) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", 37.7749, 180.000001); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + EXPECT_FALSE(parser.isValid()); +} + +TEST(AdvertData, RejectsLongitudeBelowValidRange) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", 37.7749, -180.000001); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + EXPECT_FALSE(parser.isValid()); +} + +TEST(AdvertData, RejectsLatitudeOutsideValidRange) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", 90.000001, -122.4194); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + EXPECT_FALSE(parser.isValid()); +} + +TEST(AdvertData, RejectsLatitudeBelowValidRange) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", -90.000001, -122.4194); + + uint8_t len = builder.encodeTo(app_data); + AdvertDataParser parser(app_data, len); + + EXPECT_FALSE(parser.isValid()); +} + } // namespace From a3600d94a4e6c24d0f7ac11b23e0bf1273597928 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 24 Apr 2026 23:42:36 +0000 Subject: [PATCH 34/48] Rewrite advert data tests with raw payloads --- src/helpers/AdvertDataHelpers.cpp | 5 +- test/test_utils/test_advert_data.cpp | 124 ++++++++++++++++++++++----- 2 files changed, 106 insertions(+), 23 deletions(-) diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index 0e05620ec2..0802cac952 100644 --- a/src/helpers/AdvertDataHelpers.cpp +++ b/src/helpers/AdvertDataHelpers.cpp @@ -37,6 +37,9 @@ if (_flags & ADV_LATLON_MASK) { memcpy(&_lat, &app_data[i], 4); i += 4; memcpy(&_lon, &app_data[i], 4); i += 4; + if (_lat < -90000000 || _lat > 90000000 || _lon < -180000000 || _lon > 180000000) { + return; + } } if (_flags & ADV_FEAT1_MASK) { memcpy(&_extra1, &app_data[i], 2); i += 2; @@ -84,4 +87,4 @@ void AdvertTimeHelper::formatRelativeTimeDiff(char dest[], int32_t seconds_from_ } } } -} \ No newline at end of file +} diff --git a/test/test_utils/test_advert_data.cpp b/test/test_utils/test_advert_data.cpp index 9645a516eb..0de8e92aa4 100644 --- a/test/test_utils/test_advert_data.cpp +++ b/test/test_utils/test_advert_data.cpp @@ -1,15 +1,47 @@ +#include +#include + #include #include "helpers/AdvertDataHelpers.h" namespace { +void WriteU8(uint8_t* dest, size_t* offset, uint8_t value) { + dest[(*offset)++] = value; +} + +void WriteI32Le(uint8_t* dest, size_t* offset, int32_t value) { + const uint32_t raw = static_cast(value); + dest[(*offset)++] = static_cast(raw & 0xFF); + dest[(*offset)++] = static_cast((raw >> 8) & 0xFF); + dest[(*offset)++] = static_cast((raw >> 16) & 0xFF); + dest[(*offset)++] = static_cast((raw >> 24) & 0xFF); +} + +void WriteBytes(uint8_t* dest, size_t* offset, const uint8_t* bytes, size_t length) { + for (size_t i = 0; i < length; ++i) { + dest[*offset + i] = bytes[i]; + } + *offset += length; +} + +template +void WriteStringLiteral(uint8_t* dest, size_t* offset, const char (&value)[N]) { + static_assert(N > 0, "string literal must include a null terminator"); + WriteBytes(dest, offset, reinterpret_cast(value), N - 1); +} + TEST(AdvertData, RoundTripsNameOnly) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_CHAT, "alice"); + size_t offset = 0; + + // flags/type byte: chat advert with a trailing name field. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); + // name field: raw bytes for "alice", consuming the rest of app_data. + WriteStringLiteral(app_data, &offset, "alice"); - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + AdvertDataParser parser(app_data, offset); ASSERT_TRUE(parser.isValid()); EXPECT_EQ(ADV_TYPE_CHAT, parser.getType()); @@ -20,10 +52,18 @@ TEST(AdvertData, RoundTripsNameOnly) { TEST(AdvertData, RoundTripsNameAndCoordinates) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_REPEATER, "node", 37.7749, -122.4194); + size_t offset = 0; - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + // flags/type byte: repeater advert with lat/lon followed by a name. + WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: signed little-endian microdegrees for 37.7749. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: signed little-endian microdegrees for -122.4194. + WriteI32Le(app_data, &offset, -122419400); + // name field: raw bytes for "node" after the coordinate fields. + WriteStringLiteral(app_data, &offset, "node"); + + AdvertDataParser parser(app_data, offset); ASSERT_TRUE(parser.isValid()); EXPECT_EQ(ADV_TYPE_REPEATER, parser.getType()); @@ -38,10 +78,18 @@ TEST(AdvertData, RoundTripsNameAndCoordinates) { TEST(AdvertData, RoundTripsCoordinateExtremes) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_SENSOR, "edge", -90.0, 180.0); + size_t offset = 0; + + // flags/type byte: sensor advert with both location fields and a name. + WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: minimum supported latitude, -90.000000 degrees. + WriteI32Le(app_data, &offset, -90000000); + // longitude field: maximum supported longitude, 180.000000 degrees. + WriteI32Le(app_data, &offset, 180000000); + // name field: raw bytes for "edge". + WriteStringLiteral(app_data, &offset, "edge"); - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + AdvertDataParser parser(app_data, offset); ASSERT_TRUE(parser.isValid()); EXPECT_TRUE(parser.hasLatLon()); @@ -51,40 +99,72 @@ TEST(AdvertData, RoundTripsCoordinateExtremes) { TEST(AdvertData, RejectsLongitudeOutsideValidRange) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", 37.7749, 180.000001); + size_t offset = 0; - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: valid latitude so the failure comes from longitude. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: one microdegree above +180.0, which is invalid. + WriteI32Le(app_data, &offset, 180000001); + // name field: parser should reject before the trailing name matters. + WriteStringLiteral(app_data, &offset, "node"); + + AdvertDataParser parser(app_data, offset); EXPECT_FALSE(parser.isValid()); } TEST(AdvertData, RejectsLongitudeBelowValidRange) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", 37.7749, -180.000001); + size_t offset = 0; + + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: valid latitude so the failure comes from longitude. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: one microdegree below -180.0, which is invalid. + WriteI32Le(app_data, &offset, -180000001); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "node"); - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + AdvertDataParser parser(app_data, offset); EXPECT_FALSE(parser.isValid()); } TEST(AdvertData, RejectsLatitudeOutsideValidRange) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", 90.000001, -122.4194); + size_t offset = 0; - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: one microdegree above +90.0, which is invalid. + WriteI32Le(app_data, &offset, 90000001); + // longitude field: valid longitude so the failure comes from latitude. + WriteI32Le(app_data, &offset, -122419400); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "node"); + + AdvertDataParser parser(app_data, offset); EXPECT_FALSE(parser.isValid()); } TEST(AdvertData, RejectsLatitudeBelowValidRange) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - AdvertDataBuilder builder(ADV_TYPE_CHAT, "node", -90.000001, -122.4194); - - uint8_t len = builder.encodeTo(app_data); - AdvertDataParser parser(app_data, len); + size_t offset = 0; + + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: one microdegree below -90.0, which is invalid. + WriteI32Le(app_data, &offset, -90000001); + // longitude field: valid longitude so the failure comes from latitude. + WriteI32Le(app_data, &offset, -122419400); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "node"); + + AdvertDataParser parser(app_data, offset); EXPECT_FALSE(parser.isValid()); } From c736435d7081bc307e240a98574d1f24d267e860 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:04:29 +0000 Subject: [PATCH 35/48] Test advert parsing via mesh receive path --- platformio.ini | 6 + test/mocks/Arduino.h | 10 + test/mocks/Ed25519.h | 18 + test/mocks/SHA256.h | 40 +- test/mocks/Stream.h | 11 +- test/test_utils/test_advert_data.cpp | 551 +++++++++++++++++++++------ 6 files changed, 502 insertions(+), 134 deletions(-) create mode 100644 test/mocks/Ed25519.h diff --git a/platformio.ini b/platformio.ini index 7ff42af8d3..8e30c804ac 100644 --- a/platformio.ini +++ b/platformio.ini @@ -163,7 +163,13 @@ build_flags = -std=c++17 test_build_src = yes build_src_filter = -<*> + +<../src/Dispatcher.cpp> + +<../src/Identity.cpp> + +<../src/Mesh.cpp> + +<../src/Packet.cpp> +<../src/Utils.cpp> +<../src/helpers/AdvertDataHelpers.cpp> + +<../src/helpers/BaseChatMesh.cpp> + +<../src/helpers/TxtDataHelpers.cpp> lib_deps = google/googletest @ 1.17.0 diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h index d265a16e6a..d35b9fb9ef 100644 --- a/test/mocks/Arduino.h +++ b/test/mocks/Arduino.h @@ -1,6 +1,16 @@ #pragma once +#include #include #include #include #include + +inline char* ltoa(long value, char* buffer, int base) { + if (base == 10) { + snprintf(buffer, 32, "%ld", value); + } else { + buffer[0] = 0; + } + return buffer; +} diff --git a/test/mocks/Ed25519.h b/test/mocks/Ed25519.h new file mode 100644 index 0000000000..1a57f5d1ea --- /dev/null +++ b/test/mocks/Ed25519.h @@ -0,0 +1,18 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif +#include +#ifdef __cplusplus +} +#endif + +#include + +class Ed25519 { +public: + static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) { + return ed25519_verify(sig, message, msg_len, pub_key) != 0; + } +}; diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index b6e551a077..0c783dc02a 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -2,13 +2,41 @@ #include #include +#include -// Mock SHA256 class for testing -// Provides minimal interface to allow Utils.cpp to compile class SHA256 { + uint32_t state_ = 2166136261u; + public: - void update(const uint8_t* data, size_t len) {} - void finalize(uint8_t* hash, size_t hashLen) {} - void resetHMAC(const uint8_t* key, size_t keyLen) {} - void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {} + void update(const void* data, size_t len) { + update(static_cast(data), len); + } + + void update(const uint8_t* data, size_t len) { + for (size_t i = 0; i < len; ++i) { + state_ ^= data[i]; + state_ *= 16777619u; + state_ += 0x9E3779B9u; + } + } + + void finalize(uint8_t* hash, size_t hashLen) { + uint32_t value = state_; + for (size_t i = 0; i < hashLen; ++i) { + value ^= value >> 13; + value *= 1274126177u; + hash[i] = static_cast((value >> ((i & 3) * 8)) & 0xFF); + } + } + + void resetHMAC(const uint8_t* key, size_t keyLen) { + state_ = 2166136261u; + update(key, keyLen); + state_ ^= 0x36363636u; + } + + void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) { + update(key, keyLen); + finalize(hash, hashLen); + } }; diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index 195a302973..5cc399eb3e 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -1,10 +1,13 @@ #pragma once -// Mock Stream class for native testing -// Provides minimal interface needed by Utils.h +#include +#include class Stream { public: - virtual void print(char c) {} - virtual void print(const char* str) {} + virtual size_t readBytes(uint8_t*, size_t) { return 0; } + virtual size_t write(const uint8_t*, size_t len) { return len; } + virtual void print(char) {} + virtual void print(const char*) {} + virtual void println() {} }; diff --git a/test/test_utils/test_advert_data.cpp b/test/test_utils/test_advert_data.cpp index 0de8e92aa4..5870fce015 100644 --- a/test/test_utils/test_advert_data.cpp +++ b/test/test_utils/test_advert_data.cpp @@ -1,172 +1,475 @@ #include #include +#include +#include #include -#include "helpers/AdvertDataHelpers.h" +#include "helpers/BaseChatMesh.h" +#include "helpers/SimpleMeshTables.h" namespace { +constexpr char kSenderPrivateKeyHex[] = + "70" + "65e18fd9fabb70c1ed90dca19907de698c88b709ea146eafd93d9b830c7b60" + "c4681193c79bbc39945ba8064104bb618f8fd7a84a0af6f57033d6e8ddcd6471"; +constexpr char kSenderPublicKeyHex[] = + "1ec77175b0918ed206f9ae04ec136d6d5d4315bb26305427f645b492e9350c10"; + void WriteU8(uint8_t* dest, size_t* offset, uint8_t value) { - dest[(*offset)++] = value; + dest[(*offset)++] = value; } void WriteI32Le(uint8_t* dest, size_t* offset, int32_t value) { - const uint32_t raw = static_cast(value); - dest[(*offset)++] = static_cast(raw & 0xFF); - dest[(*offset)++] = static_cast((raw >> 8) & 0xFF); - dest[(*offset)++] = static_cast((raw >> 16) & 0xFF); - dest[(*offset)++] = static_cast((raw >> 24) & 0xFF); + const uint32_t raw = static_cast(value); + dest[(*offset)++] = static_cast(raw & 0xFF); + dest[(*offset)++] = static_cast((raw >> 8) & 0xFF); + dest[(*offset)++] = static_cast((raw >> 16) & 0xFF); + dest[(*offset)++] = static_cast((raw >> 24) & 0xFF); } void WriteBytes(uint8_t* dest, size_t* offset, const uint8_t* bytes, size_t length) { - for (size_t i = 0; i < length; ++i) { - dest[*offset + i] = bytes[i]; - } - *offset += length; + memcpy(dest + *offset, bytes, length); + *offset += length; } template void WriteStringLiteral(uint8_t* dest, size_t* offset, const char (&value)[N]) { - static_assert(N > 0, "string literal must include a null terminator"); - WriteBytes(dest, offset, reinterpret_cast(value), N - 1); + static_assert(N > 0, "string literal must include a null terminator"); + WriteBytes(dest, offset, reinterpret_cast(value), N - 1); } -TEST(AdvertData, RoundTripsNameOnly) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; +class FakeMillis final : public mesh::MillisecondClock { +public: + unsigned long getMillis() override { + return 0; + } +}; + +class FakeRtc final : public mesh::RTCClock { +public: + explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {} + + uint32_t getCurrentTime() override { + return _time; + } + + void setCurrentTime(uint32_t time) override { + _time = time; + } + +private: + uint32_t _time; +}; - // flags/type byte: chat advert with a trailing name field. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); - // name field: raw bytes for "alice", consuming the rest of app_data. - WriteStringLiteral(app_data, &offset, "alice"); +class FakeRng final : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + memset(dest, 0x5A, sz); + } +}; - AdvertDataParser parser(app_data, offset); +class FakeRadio final : public mesh::Radio { +public: + int recvRaw(uint8_t*, int) override { + return 0; + } - ASSERT_TRUE(parser.isValid()); - EXPECT_EQ(ADV_TYPE_CHAT, parser.getType()); - EXPECT_TRUE(parser.hasName()); - EXPECT_STREQ("alice", parser.getName()); - EXPECT_FALSE(parser.hasLatLon()); -} + uint32_t getEstAirtimeFor(int) override { + return 1; + } -TEST(AdvertData, RoundTripsNameAndCoordinates) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; - - // flags/type byte: repeater advert with lat/lon followed by a name. - WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: signed little-endian microdegrees for 37.7749. - WriteI32Le(app_data, &offset, 37774900); - // longitude field: signed little-endian microdegrees for -122.4194. - WriteI32Le(app_data, &offset, -122419400); - // name field: raw bytes for "node" after the coordinate fields. - WriteStringLiteral(app_data, &offset, "node"); - - AdvertDataParser parser(app_data, offset); - - ASSERT_TRUE(parser.isValid()); - EXPECT_EQ(ADV_TYPE_REPEATER, parser.getType()); - EXPECT_TRUE(parser.hasName()); - EXPECT_STREQ("node", parser.getName()); - EXPECT_TRUE(parser.hasLatLon()); - EXPECT_EQ(37774900, parser.getIntLat()); - EXPECT_EQ(-122419400, parser.getIntLon()); - EXPECT_DOUBLE_EQ(37.7749, parser.getLat()); - EXPECT_DOUBLE_EQ(-122.4194, parser.getLon()); -} + float packetScore(float, int) override { + return 1.0f; + } -TEST(AdvertData, RoundTripsCoordinateExtremes) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; - - // flags/type byte: sensor advert with both location fields and a name. - WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: minimum supported latitude, -90.000000 degrees. - WriteI32Le(app_data, &offset, -90000000); - // longitude field: maximum supported longitude, 180.000000 degrees. - WriteI32Le(app_data, &offset, 180000000); - // name field: raw bytes for "edge". - WriteStringLiteral(app_data, &offset, "edge"); - - AdvertDataParser parser(app_data, offset); - - ASSERT_TRUE(parser.isValid()); - EXPECT_TRUE(parser.hasLatLon()); - EXPECT_EQ(-90000000, parser.getIntLat()); - EXPECT_EQ(180000000, parser.getIntLon()); -} + bool startSendRaw(const uint8_t*, int) override { + return true; + } + + bool isSendComplete() override { + return true; + } + + void onSendFinished() override {} + + bool isInRecvMode() const override { + return true; + } +}; + +class NoopPacketManager final : public mesh::PacketManager { +public: + mesh::Packet* allocNew() override { + return nullptr; + } + + void free(mesh::Packet*) override {} + + void queueOutbound(mesh::Packet*, uint8_t, uint32_t) override {} + + mesh::Packet* getNextOutbound(uint32_t) override { + return nullptr; + } + + int getOutboundCount(uint32_t) const override { + return 0; + } + + int getOutboundTotal() const override { + return 0; + } + + int getFreeCount() const override { + return 0; + } + + mesh::Packet* getOutboundByIdx(int) override { + return nullptr; + } + + mesh::Packet* removeOutboundByIdx(int) override { + return nullptr; + } + + void queueInbound(mesh::Packet*, uint32_t) override {} + + mesh::Packet* getNextInbound(uint32_t) override { + return nullptr; + } +}; + +class TestChatMesh final : public BaseChatMesh { +public: + TestChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, + mesh::PacketManager& mgr, mesh::MeshTables& tables) + : BaseChatMesh(radio, ms, rng, rtc, mgr, tables) {} + + mesh::DispatcherAction recv(mesh::Packet* pkt) { + return onRecvPacket(pkt); + } -TEST(AdvertData, RejectsLongitudeOutsideValidRange) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; + std::optional discovered_contact; - // flags/type byte: chat advert with location and name fields present. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: valid latitude so the failure comes from longitude. - WriteI32Le(app_data, &offset, 37774900); - // longitude field: one microdegree above +180.0, which is invalid. - WriteI32Le(app_data, &offset, 180000001); - // name field: parser should reject before the trailing name matters. - WriteStringLiteral(app_data, &offset, "node"); +protected: + void onDiscoveredContact(ContactInfo& contact, bool, uint8_t, const uint8_t*) override { + discovered_contact = contact; + } - AdvertDataParser parser(app_data, offset); + ContactInfo* processAck(const uint8_t*) override { + return nullptr; + } - EXPECT_FALSE(parser.isValid()); + void onContactPathUpdated(const ContactInfo&) override {} + + void onMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} + + void onCommandDataRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} + + void onSignedMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const uint8_t*, const char*) override {} + + uint32_t calcFloodTimeoutMillisFor(uint32_t) const override { + return 0; + } + + uint32_t calcDirectTimeoutMillisFor(uint32_t, uint8_t) const override { + return 0; + } + + void onSendTimeout() override {} + + void onChannelMessageRecv(const mesh::GroupChannel&, mesh::Packet*, uint32_t, const char*) override {} + + uint8_t onContactRequest(const ContactInfo&, uint32_t, const uint8_t*, uint8_t, uint8_t*) override { + return 0; + } + + void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} +}; + +mesh::LocalIdentity MakeSenderIdentity() { + return mesh::LocalIdentity(kSenderPrivateKeyHex, kSenderPublicKeyHex); } -TEST(AdvertData, RejectsLongitudeBelowValidRange) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; +mesh::Packet BuildSignedAdvertPacket(const mesh::LocalIdentity& sender, uint32_t timestamp, + const uint8_t* app_data, uint8_t app_data_len) { + mesh::Packet packet; + + // Wire header: flood-routed advert packet with no path hashes yet. + packet.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT); + packet.path_len = 0; + + int offset = 0; + // Sender public key: used by the receiver to identify who signed the advert. + memcpy(&packet.payload[offset], sender.pub_key, PUB_KEY_SIZE); + offset += PUB_KEY_SIZE; + + // Advert timestamp: the sender's monotonic advert time used for replay checks. + memcpy(&packet.payload[offset], ×tamp, sizeof(timestamp)); + offset += sizeof(timestamp); + + // Signature field: filled after the signed message bytes are assembled below. + uint8_t* signature = &packet.payload[offset]; + offset += SIGNATURE_SIZE; + + // Raw advert app_data: arbitrary bytes authored by the test, not by createAdvert(). + memcpy(&packet.payload[offset], app_data, app_data_len); + offset += app_data_len; + packet.payload_len = offset; + + uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE]; + int message_len = 0; + memcpy(&message[message_len], sender.pub_key, PUB_KEY_SIZE); + message_len += PUB_KEY_SIZE; + memcpy(&message[message_len], ×tamp, sizeof(timestamp)); + message_len += sizeof(timestamp); + memcpy(&message[message_len], app_data, app_data_len); + message_len += app_data_len; + + sender.sign(signature, message, message_len); + return packet; +} - // flags/type byte: chat advert with location and name fields present. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: valid latitude so the failure comes from longitude. - WriteI32Le(app_data, &offset, 37774900); - // longitude field: one microdegree below -180.0, which is invalid. - WriteI32Le(app_data, &offset, -180000001); - // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "node"); +TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with a trailing name field. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); + // name field: raw bytes for "alice", consuming the rest of app_data. + WriteStringLiteral(app_data, &offset, "alice"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + ASSERT_TRUE(mesh.discovered_contact.has_value()); + EXPECT_EQ(ADV_TYPE_CHAT, mesh.discovered_contact->type); + EXPECT_STREQ("alice", mesh.discovered_contact->name); + EXPECT_EQ(1704067201U, mesh.discovered_contact->last_advert_timestamp); + EXPECT_EQ(1704067200U, mesh.discovered_contact->lastmod); + EXPECT_EQ(0, mesh.discovered_contact->gps_lat); + EXPECT_EQ(0, mesh.discovered_contact->gps_lon); +} - AdvertDataParser parser(app_data, offset); +TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: repeater advert with lat/lon followed by a name. + WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: signed little-endian microdegrees for 37.7749. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: signed little-endian microdegrees for -122.4194. + WriteI32Le(app_data, &offset, -122419400); + // name field: raw bytes for "node" after the coordinate fields. + WriteStringLiteral(app_data, &offset, "node"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + ASSERT_TRUE(mesh.discovered_contact.has_value()); + EXPECT_EQ(ADV_TYPE_REPEATER, mesh.discovered_contact->type); + EXPECT_STREQ("node", mesh.discovered_contact->name); + EXPECT_EQ(37774900, mesh.discovered_contact->gps_lat); + EXPECT_EQ(-122419400, mesh.discovered_contact->gps_lon); +} - EXPECT_FALSE(parser.isValid()); +TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: sensor advert with both location fields and a name. + WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: minimum supported latitude, -90.000000 degrees. + WriteI32Le(app_data, &offset, -90000000); + // longitude field: maximum supported longitude, 180.000000 degrees. + WriteI32Le(app_data, &offset, 180000000); + // name field: raw bytes for "edge". + WriteStringLiteral(app_data, &offset, "edge"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + ASSERT_TRUE(mesh.discovered_contact.has_value()); + EXPECT_EQ(ADV_TYPE_SENSOR, mesh.discovered_contact->type); + EXPECT_STREQ("edge", mesh.discovered_contact->name); + EXPECT_EQ(-90000000, mesh.discovered_contact->gps_lat); + EXPECT_EQ(180000000, mesh.discovered_contact->gps_lon); } -TEST(AdvertData, RejectsLatitudeOutsideValidRange) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; +TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: valid latitude so the failure comes from longitude. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: one microdegree above +180.0, which is invalid. + WriteI32Le(app_data, &offset, 180000001); + // name field: parser should reject before the trailing name matters. + WriteStringLiteral(app_data, &offset, "node"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + EXPECT_FALSE(mesh.discovered_contact.has_value()); +} - // flags/type byte: chat advert with location and name fields present. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: one microdegree above +90.0, which is invalid. - WriteI32Le(app_data, &offset, 90000001); - // longitude field: valid longitude so the failure comes from latitude. - WriteI32Le(app_data, &offset, -122419400); - // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "node"); +TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: valid latitude so the failure comes from longitude. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: one microdegree below -180.0, which is invalid. + WriteI32Le(app_data, &offset, -180000001); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "node"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + EXPECT_FALSE(mesh.discovered_contact.has_value()); +} - AdvertDataParser parser(app_data, offset); +TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: one microdegree above +90.0, which is invalid. + WriteI32Le(app_data, &offset, 90000001); + // longitude field: valid longitude so the failure comes from latitude. + WriteI32Le(app_data, &offset, -122419400); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "node"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + EXPECT_FALSE(mesh.discovered_contact.has_value()); +} - EXPECT_FALSE(parser.isValid()); +TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: one microdegree below -90.0, which is invalid. + WriteI32Le(app_data, &offset, -90000001); + // longitude field: valid longitude so the failure comes from latitude. + WriteI32Le(app_data, &offset, -122419400); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "node"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + + mesh.recv(&packet); + + EXPECT_FALSE(mesh.discovered_contact.has_value()); } -TEST(AdvertData, RejectsLatitudeBelowValidRange) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; +TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc(1704067200U); + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); + + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with a trailing name field. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); + // name field: raw bytes for "mallory". + WriteStringLiteral(app_data, &offset, "mallory"); + + mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); - // flags/type byte: chat advert with location and name fields present. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: one microdegree below -90.0, which is invalid. - WriteI32Le(app_data, &offset, -90000001); - // longitude field: valid longitude so the failure comes from latitude. - WriteI32Le(app_data, &offset, -122419400); - // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "node"); + // Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket(). + packet.payload[PUB_KEY_SIZE + 4] ^= 0xFF; - AdvertDataParser parser(app_data, offset); + mesh.recv(&packet); - EXPECT_FALSE(parser.isValid()); + EXPECT_FALSE(mesh.discovered_contact.has_value()); } } // namespace From 44bcaf77715cb36bed4d207480e74e77cb32b4c1 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:10:52 +0000 Subject: [PATCH 36/48] Move advert data test to helpers --- test/{test_utils => helpers}/test_advert_data.cpp | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename test/{test_utils => helpers}/test_advert_data.cpp (100%) diff --git a/test/test_utils/test_advert_data.cpp b/test/helpers/test_advert_data.cpp similarity index 100% rename from test/test_utils/test_advert_data.cpp rename to test/helpers/test_advert_data.cpp From 84983ed0b88353dd20c0f1dfecdcbc786a6d5a97 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:30:12 +0000 Subject: [PATCH 37/48] Fix native helper test discovery --- test/{helpers => test_helpers}/test_advert_data.cpp | 0 test/test_helpers/test_main.cpp | 6 ++++++ 2 files changed, 6 insertions(+) rename test/{helpers => test_helpers}/test_advert_data.cpp (100%) create mode 100644 test/test_helpers/test_main.cpp diff --git a/test/helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp similarity index 100% rename from test/helpers/test_advert_data.cpp rename to test/test_helpers/test_advert_data.cpp diff --git a/test/test_helpers/test_main.cpp b/test/test_helpers/test_main.cpp new file mode 100644 index 0000000000..697a9d70c0 --- /dev/null +++ b/test/test_helpers/test_main.cpp @@ -0,0 +1,6 @@ +#include + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 2dac5740968ab7f255d27f8b9a35a058c322da16 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:35:18 +0000 Subject: [PATCH 38/48] Inline advert test sender identity --- test/test_helpers/test_advert_data.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index 5870fce015..bc287a4359 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -188,10 +188,6 @@ class TestChatMesh final : public BaseChatMesh { void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} }; -mesh::LocalIdentity MakeSenderIdentity() { - return mesh::LocalIdentity(kSenderPrivateKeyHex, kSenderPublicKeyHex); -} - mesh::Packet BuildSignedAdvertPacket(const mesh::LocalIdentity& sender, uint32_t timestamp, const uint8_t* app_data, uint8_t app_data_len) { mesh::Packet packet; @@ -248,7 +244,7 @@ TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { // name field: raw bytes for "alice", consuming the rest of app_data. WriteStringLiteral(app_data, &offset, "alice"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -283,7 +279,7 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { // name field: raw bytes for "node" after the coordinate fields. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -316,7 +312,7 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { // name field: raw bytes for "edge". WriteStringLiteral(app_data, &offset, "edge"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -349,7 +345,7 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { // name field: parser should reject before the trailing name matters. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -378,7 +374,7 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -407,7 +403,7 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -436,7 +432,7 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); mesh.recv(&packet); @@ -461,7 +457,7 @@ TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { // name field: raw bytes for "mallory". WriteStringLiteral(app_data, &offset, "mallory"); - mesh::LocalIdentity sender = MakeSenderIdentity(); + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); // Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket(). From 390a74d6610dbd79cf6dc1f79f2415b8105c8acb Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:39:54 +0000 Subject: [PATCH 39/48] Simplify advert test packet helper --- test/test_helpers/test_advert_data.cpp | 38 +++++++++++++------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index bc287a4359..510b1c4975 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -188,8 +188,8 @@ class TestChatMesh final : public BaseChatMesh { void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} }; -mesh::Packet BuildSignedAdvertPacket(const mesh::LocalIdentity& sender, uint32_t timestamp, - const uint8_t* app_data, uint8_t app_data_len) { +mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data, uint8_t app_data_len) { + mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet; // Wire header: flood-routed advert packet with no path hashes yet. @@ -238,21 +238,21 @@ TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with a trailing name field. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); // name field: raw bytes for "alice", consuming the rest of app_data. WriteStringLiteral(app_data, &offset, "alice"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); ASSERT_TRUE(mesh.discovered_contact.has_value()); EXPECT_EQ(ADV_TYPE_CHAT, mesh.discovered_contact->type); EXPECT_STREQ("alice", mesh.discovered_contact->name); - EXPECT_EQ(1704067201U, mesh.discovered_contact->last_advert_timestamp); + EXPECT_EQ(advert_timestamp, mesh.discovered_contact->last_advert_timestamp); EXPECT_EQ(1704067200U, mesh.discovered_contact->lastmod); EXPECT_EQ(0, mesh.discovered_contact->gps_lat); EXPECT_EQ(0, mesh.discovered_contact->gps_lon); @@ -269,6 +269,7 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: repeater advert with lat/lon followed by a name. WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -279,8 +280,7 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { // name field: raw bytes for "node" after the coordinate fields. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -302,6 +302,7 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: sensor advert with both location fields and a name. WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -312,8 +313,7 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { // name field: raw bytes for "edge". WriteStringLiteral(app_data, &offset, "edge"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -335,6 +335,7 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -345,8 +346,7 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { // name field: parser should reject before the trailing name matters. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -364,6 +364,7 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -374,8 +375,7 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -393,6 +393,7 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -403,8 +404,7 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -422,6 +422,7 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -432,8 +433,7 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -451,14 +451,14 @@ TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; + constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with a trailing name field. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); // name field: raw bytes for "mallory". WriteStringLiteral(app_data, &offset, "mallory"); - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet = BuildSignedAdvertPacket(sender, 1704067201U, app_data, offset); + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); // Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket(). packet.payload[PUB_KEY_SIZE + 4] ^= 0xFF; From 1ac857af78ea1ff0a2965ea5bac2fad4544dcfe8 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:42:34 +0000 Subject: [PATCH 40/48] Move advert test timestamp declarations --- test/test_helpers/test_advert_data.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index 510b1c4975..396770d007 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -238,13 +238,13 @@ TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with a trailing name field. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); // name field: raw bytes for "alice", consuming the rest of app_data. WriteStringLiteral(app_data, &offset, "alice"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -269,7 +269,6 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: repeater advert with lat/lon followed by a name. WriteU8(app_data, &offset, ADV_TYPE_REPEATER | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -280,6 +279,7 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { // name field: raw bytes for "node" after the coordinate fields. WriteStringLiteral(app_data, &offset, "node"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -302,7 +302,6 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: sensor advert with both location fields and a name. WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -313,6 +312,7 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { // name field: raw bytes for "edge". WriteStringLiteral(app_data, &offset, "edge"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -335,7 +335,6 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -346,6 +345,7 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { // name field: parser should reject before the trailing name matters. WriteStringLiteral(app_data, &offset, "node"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -364,7 +364,6 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -375,6 +374,7 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -393,7 +393,6 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -404,6 +403,7 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -422,7 +422,6 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with location and name fields present. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); @@ -433,6 +432,7 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); mesh.recv(&packet); @@ -451,13 +451,13 @@ TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t advert_timestamp = 1704067201U; // flags/type byte: chat advert with a trailing name field. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); // name field: raw bytes for "mallory". WriteStringLiteral(app_data, &offset, "mallory"); + constexpr uint32_t advert_timestamp = 1704067201U; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); // Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket(). From ae0312e233e0fb640d5ab3490988fcdd36332e1e Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:54:39 +0000 Subject: [PATCH 41/48] Refactor advert data test mesh setup --- test/test_helpers/test_advert_data.cpp | 181 +++++++++++-------------- 1 file changed, 79 insertions(+), 102 deletions(-) diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index 396770d007..b821bed42b 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -188,6 +188,31 @@ class TestChatMesh final : public BaseChatMesh { void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} }; +struct TestMeshContext { + explicit TestMeshContext(uint32_t current_timestamp) + : rtc(current_timestamp), mesh(radio, ms, rng, rtc, packet_manager, tables) {} + + TestChatMesh* operator->() { + return &mesh; + } + + const TestChatMesh* operator->() const { + return &mesh; + } + + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc; + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh; +}; + +TestMeshContext MakeTestMesh(uint32_t current_timestamp) { + return TestMeshContext(current_timestamp); +} + mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data, uint8_t app_data_len) { mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet; @@ -228,14 +253,6 @@ mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data } TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -244,29 +261,23 @@ TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { // name field: raw bytes for "alice", consuming the rest of app_data. WriteStringLiteral(app_data, &offset, "alice"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - ASSERT_TRUE(mesh.discovered_contact.has_value()); - EXPECT_EQ(ADV_TYPE_CHAT, mesh.discovered_contact->type); - EXPECT_STREQ("alice", mesh.discovered_contact->name); - EXPECT_EQ(advert_timestamp, mesh.discovered_contact->last_advert_timestamp); - EXPECT_EQ(1704067200U, mesh.discovered_contact->lastmod); - EXPECT_EQ(0, mesh.discovered_contact->gps_lat); - EXPECT_EQ(0, mesh.discovered_contact->gps_lon); + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(ADV_TYPE_CHAT, test_mesh->discovered_contact->type); + EXPECT_STREQ("alice", test_mesh->discovered_contact->name); + EXPECT_EQ(advert_timestamp, test_mesh->discovered_contact->last_advert_timestamp); + EXPECT_EQ(current_timestamp, test_mesh->discovered_contact->lastmod); + EXPECT_EQ(0, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(0, test_mesh->discovered_contact->gps_lon); } TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -279,27 +290,21 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { // name field: raw bytes for "node" after the coordinate fields. WriteStringLiteral(app_data, &offset, "node"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - ASSERT_TRUE(mesh.discovered_contact.has_value()); - EXPECT_EQ(ADV_TYPE_REPEATER, mesh.discovered_contact->type); - EXPECT_STREQ("node", mesh.discovered_contact->name); - EXPECT_EQ(37774900, mesh.discovered_contact->gps_lat); - EXPECT_EQ(-122419400, mesh.discovered_contact->gps_lon); + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(ADV_TYPE_REPEATER, test_mesh->discovered_contact->type); + EXPECT_STREQ("node", test_mesh->discovered_contact->name); + EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); } TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -312,27 +317,21 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { // name field: raw bytes for "edge". WriteStringLiteral(app_data, &offset, "edge"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - ASSERT_TRUE(mesh.discovered_contact.has_value()); - EXPECT_EQ(ADV_TYPE_SENSOR, mesh.discovered_contact->type); - EXPECT_STREQ("edge", mesh.discovered_contact->name); - EXPECT_EQ(-90000000, mesh.discovered_contact->gps_lat); - EXPECT_EQ(180000000, mesh.discovered_contact->gps_lon); + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(ADV_TYPE_SENSOR, test_mesh->discovered_contact->type); + EXPECT_STREQ("edge", test_mesh->discovered_contact->name); + EXPECT_EQ(-90000000, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(180000000, test_mesh->discovered_contact->gps_lon); } TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -345,23 +344,17 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { // name field: parser should reject before the trailing name matters. WriteStringLiteral(app_data, &offset, "node"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - EXPECT_FALSE(mesh.discovered_contact.has_value()); + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -374,23 +367,17 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - EXPECT_FALSE(mesh.discovered_contact.has_value()); + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -403,23 +390,17 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - EXPECT_FALSE(mesh.discovered_contact.has_value()); + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -432,23 +413,17 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "node"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - EXPECT_FALSE(mesh.discovered_contact.has_value()); + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc(1704067200U); - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh(radio, ms, rng, rtc, packet_manager, tables); - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -457,15 +432,17 @@ TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { // name field: raw bytes for "mallory". WriteStringLiteral(app_data, &offset, "mallory"); - constexpr uint32_t advert_timestamp = 1704067201U; + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); // Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket(). packet.payload[PUB_KEY_SIZE + 4] ^= 0xFF; - mesh.recv(&packet); + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); - EXPECT_FALSE(mesh.discovered_contact.has_value()); + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } } // namespace From 339754c644fa6677e82066e7cfb10b512578b77c Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 00:57:29 +0000 Subject: [PATCH 42/48] Clean up advert data test names --- test/test_helpers/test_advert_data.cpp | 48 +++++++------------------- 1 file changed, 13 insertions(+), 35 deletions(-) diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index b821bed42b..a40ba573e9 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -258,8 +258,8 @@ TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { // flags/type byte: chat advert with a trailing name field. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); - // name field: raw bytes for "alice", consuming the rest of app_data. - WriteStringLiteral(app_data, &offset, "alice"); + // name field: raw bytes for "dummy-node-name", consuming the rest of app_data. + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -270,7 +270,7 @@ TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { ASSERT_TRUE(test_mesh->discovered_contact.has_value()); EXPECT_EQ(ADV_TYPE_CHAT, test_mesh->discovered_contact->type); - EXPECT_STREQ("alice", test_mesh->discovered_contact->name); + EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); EXPECT_EQ(advert_timestamp, test_mesh->discovered_contact->last_advert_timestamp); EXPECT_EQ(current_timestamp, test_mesh->discovered_contact->lastmod); EXPECT_EQ(0, test_mesh->discovered_contact->gps_lat); @@ -287,8 +287,8 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { WriteI32Le(app_data, &offset, 37774900); // longitude field: signed little-endian microdegrees for -122.4194. WriteI32Le(app_data, &offset, -122419400); - // name field: raw bytes for "node" after the coordinate fields. - WriteStringLiteral(app_data, &offset, "node"); + // name field: trailing contact name bytes after the coordinate fields. + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -299,7 +299,7 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { ASSERT_TRUE(test_mesh->discovered_contact.has_value()); EXPECT_EQ(ADV_TYPE_REPEATER, test_mesh->discovered_contact->type); - EXPECT_STREQ("node", test_mesh->discovered_contact->name); + EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); } @@ -314,8 +314,8 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { WriteI32Le(app_data, &offset, -90000000); // longitude field: maximum supported longitude, 180.000000 degrees. WriteI32Le(app_data, &offset, 180000000); - // name field: raw bytes for "edge". - WriteStringLiteral(app_data, &offset, "edge"); + // name field: raw bytes for "dummy-node-name". + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -326,7 +326,7 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { ASSERT_TRUE(test_mesh->discovered_contact.has_value()); EXPECT_EQ(ADV_TYPE_SENSOR, test_mesh->discovered_contact->type); - EXPECT_STREQ("edge", test_mesh->discovered_contact->name); + EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); EXPECT_EQ(-90000000, test_mesh->discovered_contact->gps_lat); EXPECT_EQ(180000000, test_mesh->discovered_contact->gps_lon); } @@ -342,7 +342,7 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { // longitude field: one microdegree above +180.0, which is invalid. WriteI32Le(app_data, &offset, 180000001); // name field: parser should reject before the trailing name matters. - WriteStringLiteral(app_data, &offset, "node"); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -365,7 +365,7 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { // longitude field: one microdegree below -180.0, which is invalid. WriteI32Le(app_data, &offset, -180000001); // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "node"); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -388,7 +388,7 @@ TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { // longitude field: valid longitude so the failure comes from latitude. WriteI32Le(app_data, &offset, -122419400); // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "node"); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -411,7 +411,7 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { // longitude field: valid longitude so the failure comes from latitude. WriteI32Le(app_data, &offset, -122419400); // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "node"); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; @@ -423,26 +423,4 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } -TEST(AdvertData, RejectsForgedSignatureFromNetworkPacket) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; - - // flags/type byte: chat advert with a trailing name field. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); - // name field: raw bytes for "mallory". - WriteStringLiteral(app_data, &offset, "mallory"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - // Corrupt the signature bytes after signing so verification must fail in Mesh::onRecvPacket(). - packet.payload[PUB_KEY_SIZE + 4] ^= 0xFF; - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); -} - } // namespace From ac10c13337842e72c08da84c7d34ea10fe1aa30d Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 01:18:30 +0000 Subject: [PATCH 43/48] Add sanitizer coverage for advert GPS parsing --- .github/workflows/run-unit-tests.yml | 5 + lib/ed25519/library.json | 7 + platformio.ini | 6 + src/helpers/AdvertDataHelpers.cpp | 18 ++- test/test_helpers/test_advert_data.cpp | 207 ++++++++++++++++++++++--- 5 files changed, 216 insertions(+), 27 deletions(-) create mode 100644 lib/ed25519/library.json diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index e3af3aafbf..dd03e8f954 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -21,6 +21,11 @@ jobs: - name: Run Unit Tests run: pio test -e native -vv + env: + # Fail CI on leaks or memory errors reported by ASAN. + ASAN_OPTIONS: detect_leaks=1:halt_on_error=1 + # Stop immediately and print a stack trace for undefined behavior. + UBSAN_OPTIONS: halt_on_error=1:print_stacktrace=1 - name: Upload Test Results # Upload test results even if the test step failed. diff --git a/lib/ed25519/library.json b/lib/ed25519/library.json new file mode 100644 index 0000000000..4cafd5e89e --- /dev/null +++ b/lib/ed25519/library.json @@ -0,0 +1,7 @@ +{ + "build": { + "flags": [ + "-fno-sanitize=undefined" + ] + } +} diff --git a/platformio.ini b/platformio.ini index 8e30c804ac..9696fc3f83 100644 --- a/platformio.ini +++ b/platformio.ini @@ -158,6 +158,12 @@ lib_deps = [env:native] platform = native build_flags = -std=c++17 + ; Preserve stack frames and debug info so ASAN/UBSAN reports are actionable. + -g + -fno-omit-frame-pointer + ; Run native unit tests with AddressSanitizer and UndefinedBehaviorSanitizer. + -fsanitize=address + -fsanitize=undefined -I src -I test/mocks test_build_src = yes diff --git a/src/helpers/AdvertDataHelpers.cpp b/src/helpers/AdvertDataHelpers.cpp index 0802cac952..9d5e344ff0 100644 --- a/src/helpers/AdvertDataHelpers.cpp +++ b/src/helpers/AdvertDataHelpers.cpp @@ -29,12 +29,20 @@ AdvertDataParser::AdvertDataParser(const uint8_t app_data[], uint8_t app_data_len) { _name[0] = 0; _lat = _lon = 0; - _flags = app_data[0]; + _flags = 0; _valid = false; _extra1 = _extra2 = 0; - + + if (app_data_len < 1) { + return; + } + + _flags = app_data[0]; int i = 1; if (_flags & ADV_LATLON_MASK) { + if (app_data_len < i + 8) { + return; + } memcpy(&_lat, &app_data[i], 4); i += 4; memcpy(&_lon, &app_data[i], 4); i += 4; if (_lat < -90000000 || _lat > 90000000 || _lon < -180000000 || _lon > 180000000) { @@ -42,9 +50,15 @@ } } if (_flags & ADV_FEAT1_MASK) { + if (app_data_len < i + 2) { + return; + } memcpy(&_extra1, &app_data[i], 2); i += 2; } if (_flags & ADV_FEAT2_MASK) { + if (app_data_len < i + 2) { + return; + } memcpy(&_extra2, &app_data[i], 2); i += 2; } diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index a40ba573e9..af0cb9d515 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -213,6 +213,19 @@ TestMeshContext MakeTestMesh(uint32_t current_timestamp) { return TestMeshContext(current_timestamp); } +ContactInfo MakeSenderContact(uint32_t advert_timestamp, int32_t gps_lat, int32_t gps_lon) { + ContactInfo contact = {}; + contact.id = mesh::Identity(kSenderPublicKeyHex); + strcpy(contact.name, "existing-contact"); + contact.type = ADV_TYPE_CHAT; + contact.out_path_len = OUT_PATH_UNKNOWN; + contact.last_advert_timestamp = advert_timestamp; + contact.lastmod = advert_timestamp; + contact.gps_lat = gps_lat; + contact.gps_lon = gps_lon; + return contact; +} + mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data, uint8_t app_data_len) { mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); mesh::Packet packet; @@ -252,31 +265,6 @@ mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data return packet; } -TEST(AdvertData, ParsesNameOnlyFromNetworkPacket) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; - - // flags/type byte: chat advert with a trailing name field. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); - // name field: raw bytes for "dummy-node-name", consuming the rest of app_data. - WriteStringLiteral(app_data, &offset, "dummy-node-name"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(ADV_TYPE_CHAT, test_mesh->discovered_contact->type); - EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); - EXPECT_EQ(advert_timestamp, test_mesh->discovered_contact->last_advert_timestamp); - EXPECT_EQ(current_timestamp, test_mesh->discovered_contact->lastmod); - EXPECT_EQ(0, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(0, test_mesh->discovered_contact->gps_lon); -} - TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -331,6 +319,52 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { EXPECT_EQ(180000000, test_mesh->discovered_contact->gps_lon); } +TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetworkPacket) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: sensor advert with both location fields and a name. + WriteU8(app_data, &offset, ADV_TYPE_SENSOR | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: maximum supported latitude, +90.000000 degrees. + WriteI32Le(app_data, &offset, 90000000); + // longitude field: minimum supported longitude, -180.000000 degrees. + WriteI32Le(app_data, &offset, -180000000); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); + + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(90000000, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(-180000000, test_mesh->discovered_contact->gps_lon); +} + +TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with zero-valued coordinates and a name. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + WriteI32Le(app_data, &offset, 0); + WriteI32Le(app_data, &offset, 0); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); + + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(0, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(0, test_mesh->discovered_contact->gps_lon); +} + TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -377,6 +411,42 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } +void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // Advert claims to carry GPS coordinates and a name, but the payload is truncated. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + WriteI32Le(app_data, &offset, 37774900); + WriteI32Le(app_data, &offset, -122419400); + WriteStringLiteral(app_data, &offset, "dummy-node-name"); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, app_data_len); + + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); + + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); +} + +TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { + ExpectTruncatedGpsPayloadIsRejected(0); +} + +TEST(AdvertData, RejectsGpsPayloadWithOnlyFlagsByte) { + ExpectTruncatedGpsPayloadIsRejected(1); +} + +TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { + ExpectTruncatedGpsPayloadIsRejected(5); +} + +TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { + ExpectTruncatedGpsPayloadIsRejected(8); +} + TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -423,4 +493,91 @@ TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } +TEST(AdvertData, KeepsExistingGpsWhenUpdatedAdvertOmitsCoordinates) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with a new name but no GPS fields. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); + WriteStringLiteral(app_data, &offset, "updated-name"); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; + constexpr uint32_t new_advert_timestamp = current_timestamp + 1; + mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); + + test_mesh->recv(&packet); + + ContactInfo* updated = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); + ASSERT_NE(nullptr, updated); + EXPECT_STREQ("updated-name", updated->name); + EXPECT_EQ(37774900, updated->gps_lat); + EXPECT_EQ(-122419400, updated->gps_lon); + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); +} + +TEST(AdvertData, OverwritesExistingGpsWhenUpdatedAdvertIncludesCoordinates) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with replacement GPS coordinates and a new name. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + WriteI32Le(app_data, &offset, 40712800); + WriteI32Le(app_data, &offset, -74006000); + WriteStringLiteral(app_data, &offset, "updated-name"); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; + constexpr uint32_t new_advert_timestamp = current_timestamp + 1; + mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); + + test_mesh->recv(&packet); + + ContactInfo* updated = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); + ASSERT_NE(nullptr, updated); + EXPECT_STREQ("updated-name", updated->name); + EXPECT_EQ(40712800, updated->gps_lat); + EXPECT_EQ(-74006000, updated->gps_lon); + ASSERT_TRUE(test_mesh->discovered_contact.has_value()); + EXPECT_EQ(40712800, test_mesh->discovered_contact->gps_lat); + EXPECT_EQ(-74006000, test_mesh->discovered_contact->gps_lon); +} + +TEST(AdvertData, LeavesExistingGpsUntouchedWhenUpdatedAdvertHasInvalidCoordinates) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with invalid longitude and a new name. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + WriteI32Le(app_data, &offset, 37774900); + WriteI32Le(app_data, &offset, 180000001); + WriteStringLiteral(app_data, &offset, "updated-name"); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; + constexpr uint32_t new_advert_timestamp = current_timestamp + 1; + mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); + + test_mesh->recv(&packet); + + ContactInfo* existing = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); + ASSERT_NE(nullptr, existing); + EXPECT_STREQ("existing-contact", existing->name); + EXPECT_EQ(37774900, existing->gps_lat); + EXPECT_EQ(-122419400, existing->gps_lon); + EXPECT_EQ(existing_advert_timestamp, existing->last_advert_timestamp); + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); +} + } // namespace From 5b2b577c6a539d292bb8a17db63c8e1f63345195 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Sat, 25 Apr 2026 01:29:20 +0000 Subject: [PATCH 44/48] Clarify native mocks and advert truncation tests --- test/mocks/Arduino.h | 2 + test/mocks/Ed25519.h | 2 + test/mocks/SHA256.h | 2 + test/mocks/Stream.h | 2 + test/test_helpers/test_advert_data.cpp | 86 +++++++++++++++++++++----- 5 files changed, 78 insertions(+), 16 deletions(-) diff --git a/test/mocks/Arduino.h b/test/mocks/Arduino.h index d35b9fb9ef..b7acefbc2a 100644 --- a/test/mocks/Arduino.h +++ b/test/mocks/Arduino.h @@ -6,6 +6,8 @@ #include #include +// Mock Arduino compatibility header for native testing. +// Provides the small libc-backed helpers needed by Arduino-oriented code. inline char* ltoa(long value, char* buffer, int base) { if (base == 10) { snprintf(buffer, 32, "%ld", value); diff --git a/test/mocks/Ed25519.h b/test/mocks/Ed25519.h index 1a57f5d1ea..2c649505c3 100644 --- a/test/mocks/Ed25519.h +++ b/test/mocks/Ed25519.h @@ -10,6 +10,8 @@ extern "C" { #include +// Mock Ed25519 wrapper for native testing. +// Adapts the test-only C ed25519 implementation to the Arduino-style class API. class Ed25519 { public: static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) { diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index 0c783dc02a..38e779b7d1 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -4,6 +4,8 @@ #include #include +// Mock SHA256 class for native testing. +// Provides a deterministic stand-in so code can hash data without Arduino crypto deps. class SHA256 { uint32_t state_ = 2166136261u; diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index 5cc399eb3e..e83b04294e 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -3,6 +3,8 @@ #include #include +// Mock Stream class for native testing. +// Provides the minimal interface needed by code that depends on Arduino Stream. class Stream { public: virtual size_t readBytes(uint8_t*, size_t) { return 0; } diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index af0cb9d515..83daa4288b 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -329,6 +329,7 @@ TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetwork WriteI32Le(app_data, &offset, 90000000); // longitude field: minimum supported longitude, -180.000000 degrees. WriteI32Le(app_data, &offset, -180000000); + // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; @@ -349,8 +350,11 @@ TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) { // flags/type byte: chat advert with zero-valued coordinates and a name. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: Null Island latitude at exactly 0.000000 degrees. WriteI32Le(app_data, &offset, 0); + // longitude field: Null Island longitude at exactly 0.000000 degrees. WriteI32Le(app_data, &offset, 0); + // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); constexpr uint32_t current_timestamp = 1704067200U; @@ -411,19 +415,15 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } -void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; +TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { + // Backing storage is unused because the advertised app_data length is zero. + uint8_t app_data[1] = {}; size_t offset = 0; - // Advert claims to carry GPS coordinates and a name, but the payload is truncated. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - WriteI32Le(app_data, &offset, 37774900); - WriteI32Le(app_data, &offset, -122419400); - WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, app_data_len); + // Leave the app_data length at zero so the parser never sees the flags byte. + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); auto test_mesh = MakeTestMesh(current_timestamp); test_mesh->recv(&packet); @@ -431,20 +431,67 @@ void ExpectTruncatedGpsPayloadIsRejected(uint8_t app_data_len) { EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } -TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { - ExpectTruncatedGpsPayloadIsRejected(0); -} - TEST(AdvertData, RejectsGpsPayloadWithOnlyFlagsByte) { - ExpectTruncatedGpsPayloadIsRejected(1); + uint8_t app_data[1] = {}; + size_t offset = 0; + + // flags/type byte: chat advert that claims to carry coordinates and a name. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; + // Pass only the flags byte so no latitude bytes remain. + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); + + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { - ExpectTruncatedGpsPayloadIsRejected(5); + uint8_t app_data[5] = {}; + size_t offset = 0; + + // flags/type byte: chat advert that claims to carry coordinates and a name. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: complete latitude bytes are present before truncation. + WriteI32Le(app_data, &offset, 37774900); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; + // Pass only the flags byte and latitude field so longitude is missing. + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); + + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { - ExpectTruncatedGpsPayloadIsRejected(8); + uint8_t app_data[8] = {}; + size_t offset = 0; + uint8_t lon_bytes[sizeof(int32_t)] = {}; + size_t lon_offset = 0; + + // flags/type byte: chat advert that claims to carry coordinates and a name. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: complete latitude bytes are present before truncation. + WriteI32Le(app_data, &offset, 37774900); + // longitude field: only the first three longitude bytes are present before truncation. + WriteI32Le(lon_bytes, &lon_offset, -122419400); + WriteBytes(app_data, &offset, lon_bytes, 3); + + constexpr uint32_t current_timestamp = 1704067200U; + constexpr uint32_t advert_timestamp = current_timestamp + 1; + // Pass only the flags byte, latitude field, and three longitude bytes. + mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + + auto test_mesh = MakeTestMesh(current_timestamp); + test_mesh->recv(&packet); + + EXPECT_FALSE(test_mesh->discovered_contact.has_value()); } TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { @@ -499,6 +546,7 @@ TEST(AdvertData, KeepsExistingGpsWhenUpdatedAdvertOmitsCoordinates) { // flags/type byte: chat advert with a new name but no GPS fields. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); + // name field: replacement contact name with no coordinate payload following it. WriteStringLiteral(app_data, &offset, "updated-name"); constexpr uint32_t current_timestamp = 1704067200U; @@ -527,8 +575,11 @@ TEST(AdvertData, OverwritesExistingGpsWhenUpdatedAdvertIncludesCoordinates) { // flags/type byte: chat advert with replacement GPS coordinates and a new name. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: replacement latitude for 40.712800 degrees. WriteI32Le(app_data, &offset, 40712800); + // longitude field: replacement longitude for -74.006000 degrees. WriteI32Le(app_data, &offset, -74006000); + // name field: replacement contact name applied with the new coordinates. WriteStringLiteral(app_data, &offset, "updated-name"); constexpr uint32_t current_timestamp = 1704067200U; @@ -557,8 +608,11 @@ TEST(AdvertData, LeavesExistingGpsUntouchedWhenUpdatedAdvertHasInvalidCoordinate // flags/type byte: chat advert with invalid longitude and a new name. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: valid latitude so the update failure comes from longitude. WriteI32Le(app_data, &offset, 37774900); + // longitude field: one microdegree above +180.0, which should reject the update. WriteI32Le(app_data, &offset, 180000001); + // name field: replacement name that should not be applied when parsing fails. WriteStringLiteral(app_data, &offset, "updated-name"); constexpr uint32_t current_timestamp = 1704067200U; From 5a9c0a2a21bfb819875d1bbffad66a89aa71d14a Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 1 May 2026 07:56:28 -0400 Subject: [PATCH 45/48] Split test helpers to mocks.h --- test/test_helpers/mocks.h | 178 +++++++++++++++++++++++++ test/test_helpers/test_advert_data.cpp | 173 +----------------------- 2 files changed, 179 insertions(+), 172 deletions(-) create mode 100644 test/test_helpers/mocks.h diff --git a/test/test_helpers/mocks.h b/test/test_helpers/mocks.h new file mode 100644 index 0000000000..655a44cea5 --- /dev/null +++ b/test/test_helpers/mocks.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include +#include + +#include "helpers/BaseChatMesh.h" +#include "helpers/SimpleMeshTables.h" + +class FakeMillis final : public mesh::MillisecondClock { +public: + unsigned long getMillis() override { + return 0; + } +}; + +class FakeRtc final : public mesh::RTCClock { +public: + explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {} + + uint32_t getCurrentTime() override { + return _time; + } + + void setCurrentTime(uint32_t time) override { + _time = time; + } + +private: + uint32_t _time; +}; + +class FakeRng final : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + memset(dest, 0x5A, sz); + } +}; + +class FakeRadio final : public mesh::Radio { +public: + int recvRaw(uint8_t*, int) override { + return 0; + } + + uint32_t getEstAirtimeFor(int) override { + return 1; + } + + float packetScore(float, int) override { + return 1.0f; + } + + bool startSendRaw(const uint8_t*, int) override { + return true; + } + + bool isSendComplete() override { + return true; + } + + void onSendFinished() override {} + + bool isInRecvMode() const override { + return true; + } +}; + +class NoopPacketManager final : public mesh::PacketManager { +public: + mesh::Packet* allocNew() override { + return nullptr; + } + + void free(mesh::Packet*) override {} + + void queueOutbound(mesh::Packet*, uint8_t, uint32_t) override {} + + mesh::Packet* getNextOutbound(uint32_t) override { + return nullptr; + } + + int getOutboundCount(uint32_t) const override { + return 0; + } + + int getOutboundTotal() const override { + return 0; + } + + int getFreeCount() const override { + return 0; + } + + mesh::Packet* getOutboundByIdx(int) override { + return nullptr; + } + + mesh::Packet* removeOutboundByIdx(int) override { + return nullptr; + } + + void queueInbound(mesh::Packet*, uint32_t) override {} + + mesh::Packet* getNextInbound(uint32_t) override { + return nullptr; + } +}; + +class TestChatMesh final : public BaseChatMesh { +public: + TestChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, + mesh::PacketManager& mgr, mesh::MeshTables& tables) + : BaseChatMesh(radio, ms, rng, rtc, mgr, tables) {} + + mesh::DispatcherAction recv(mesh::Packet* pkt) { + return onRecvPacket(pkt); + } + + std::optional discovered_contact; + +protected: + void onDiscoveredContact(ContactInfo& contact, bool, uint8_t, const uint8_t*) override { + discovered_contact = contact; + } + + ContactInfo* processAck(const uint8_t*) override { + return nullptr; + } + + void onContactPathUpdated(const ContactInfo&) override {} + + void onMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} + + void onCommandDataRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} + + void onSignedMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const uint8_t*, const char*) override {} + + uint32_t calcFloodTimeoutMillisFor(uint32_t) const override { + return 0; + } + + uint32_t calcDirectTimeoutMillisFor(uint32_t, uint8_t) const override { + return 0; + } + + void onSendTimeout() override {} + + void onChannelMessageRecv(const mesh::GroupChannel&, mesh::Packet*, uint32_t, const char*) override {} + + uint8_t onContactRequest(const ContactInfo&, uint32_t, const uint8_t*, uint8_t, uint8_t*) override { + return 0; + } + + void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} +}; + +struct TestMeshContext { + explicit TestMeshContext(uint32_t current_timestamp) + : rtc(current_timestamp), mesh(radio, ms, rng, rtc, packet_manager, tables) {} + + TestChatMesh* operator->() { + return &mesh; + } + + const TestChatMesh* operator->() const { + return &mesh; + } + + FakeRadio radio; + FakeMillis ms; + FakeRng rng; + FakeRtc rtc; + NoopPacketManager packet_manager; + SimpleMeshTables tables; + TestChatMesh mesh; +}; diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index 83daa4288b..9d9f77095b 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -1,12 +1,10 @@ #include #include #include -#include #include -#include "helpers/BaseChatMesh.h" -#include "helpers/SimpleMeshTables.h" +#include "mocks.h" namespace { @@ -40,175 +38,6 @@ void WriteStringLiteral(uint8_t* dest, size_t* offset, const char (&value)[N]) { WriteBytes(dest, offset, reinterpret_cast(value), N - 1); } -class FakeMillis final : public mesh::MillisecondClock { -public: - unsigned long getMillis() override { - return 0; - } -}; - -class FakeRtc final : public mesh::RTCClock { -public: - explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {} - - uint32_t getCurrentTime() override { - return _time; - } - - void setCurrentTime(uint32_t time) override { - _time = time; - } - -private: - uint32_t _time; -}; - -class FakeRng final : public mesh::RNG { -public: - void random(uint8_t* dest, size_t sz) override { - memset(dest, 0x5A, sz); - } -}; - -class FakeRadio final : public mesh::Radio { -public: - int recvRaw(uint8_t*, int) override { - return 0; - } - - uint32_t getEstAirtimeFor(int) override { - return 1; - } - - float packetScore(float, int) override { - return 1.0f; - } - - bool startSendRaw(const uint8_t*, int) override { - return true; - } - - bool isSendComplete() override { - return true; - } - - void onSendFinished() override {} - - bool isInRecvMode() const override { - return true; - } -}; - -class NoopPacketManager final : public mesh::PacketManager { -public: - mesh::Packet* allocNew() override { - return nullptr; - } - - void free(mesh::Packet*) override {} - - void queueOutbound(mesh::Packet*, uint8_t, uint32_t) override {} - - mesh::Packet* getNextOutbound(uint32_t) override { - return nullptr; - } - - int getOutboundCount(uint32_t) const override { - return 0; - } - - int getOutboundTotal() const override { - return 0; - } - - int getFreeCount() const override { - return 0; - } - - mesh::Packet* getOutboundByIdx(int) override { - return nullptr; - } - - mesh::Packet* removeOutboundByIdx(int) override { - return nullptr; - } - - void queueInbound(mesh::Packet*, uint32_t) override {} - - mesh::Packet* getNextInbound(uint32_t) override { - return nullptr; - } -}; - -class TestChatMesh final : public BaseChatMesh { -public: - TestChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, - mesh::PacketManager& mgr, mesh::MeshTables& tables) - : BaseChatMesh(radio, ms, rng, rtc, mgr, tables) {} - - mesh::DispatcherAction recv(mesh::Packet* pkt) { - return onRecvPacket(pkt); - } - - std::optional discovered_contact; - -protected: - void onDiscoveredContact(ContactInfo& contact, bool, uint8_t, const uint8_t*) override { - discovered_contact = contact; - } - - ContactInfo* processAck(const uint8_t*) override { - return nullptr; - } - - void onContactPathUpdated(const ContactInfo&) override {} - - void onMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} - - void onCommandDataRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} - - void onSignedMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const uint8_t*, const char*) override {} - - uint32_t calcFloodTimeoutMillisFor(uint32_t) const override { - return 0; - } - - uint32_t calcDirectTimeoutMillisFor(uint32_t, uint8_t) const override { - return 0; - } - - void onSendTimeout() override {} - - void onChannelMessageRecv(const mesh::GroupChannel&, mesh::Packet*, uint32_t, const char*) override {} - - uint8_t onContactRequest(const ContactInfo&, uint32_t, const uint8_t*, uint8_t, uint8_t*) override { - return 0; - } - - void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} -}; - -struct TestMeshContext { - explicit TestMeshContext(uint32_t current_timestamp) - : rtc(current_timestamp), mesh(radio, ms, rng, rtc, packet_manager, tables) {} - - TestChatMesh* operator->() { - return &mesh; - } - - const TestChatMesh* operator->() const { - return &mesh; - } - - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc; - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh; -}; - TestMeshContext MakeTestMesh(uint32_t current_timestamp) { return TestMeshContext(current_timestamp); } From 9bb9d31f432dc7859989c2011011ca9f68ecc93d Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 1 May 2026 08:12:12 -0400 Subject: [PATCH 46/48] Split up mocks.h --- test/{test_helpers/mocks.h => mocks/mesh.h} | 70 +++------------------ test/mocks/radio.h | 36 +++++++++++ test/mocks/random.h | 25 ++++++++ test/mocks/time.h | 42 +++++++++++++ test/test_helpers/test_advert_data.cpp | 2 +- 5 files changed, 113 insertions(+), 62 deletions(-) rename test/{test_helpers/mocks.h => mocks/mesh.h} (73%) create mode 100644 test/mocks/radio.h create mode 100644 test/mocks/random.h create mode 100644 test/mocks/time.h diff --git a/test/test_helpers/mocks.h b/test/mocks/mesh.h similarity index 73% rename from test/test_helpers/mocks.h rename to test/mocks/mesh.h index 655a44cea5..9b2598fd99 100644 --- a/test/test_helpers/mocks.h +++ b/test/mocks/mesh.h @@ -1,72 +1,16 @@ #pragma once -#include #include -#include #include #include "helpers/BaseChatMesh.h" #include "helpers/SimpleMeshTables.h" +#include "radio.h" +#include "random.h" +#include "time.h" -class FakeMillis final : public mesh::MillisecondClock { -public: - unsigned long getMillis() override { - return 0; - } -}; - -class FakeRtc final : public mesh::RTCClock { -public: - explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {} - - uint32_t getCurrentTime() override { - return _time; - } - - void setCurrentTime(uint32_t time) override { - _time = time; - } - -private: - uint32_t _time; -}; - -class FakeRng final : public mesh::RNG { -public: - void random(uint8_t* dest, size_t sz) override { - memset(dest, 0x5A, sz); - } -}; - -class FakeRadio final : public mesh::Radio { -public: - int recvRaw(uint8_t*, int) override { - return 0; - } - - uint32_t getEstAirtimeFor(int) override { - return 1; - } - - float packetScore(float, int) override { - return 1.0f; - } - - bool startSendRaw(const uint8_t*, int) override { - return true; - } - - bool isSendComplete() override { - return true; - } - - void onSendFinished() override {} - - bool isInRecvMode() const override { - return true; - } -}; - +// No-op packet manager for native tests. +// Satisfies Mesh dependencies while preventing packet allocation or queuing. class NoopPacketManager final : public mesh::PacketManager { public: mesh::Packet* allocNew() override { @@ -108,6 +52,8 @@ class NoopPacketManager final : public mesh::PacketManager { } }; +// Test chat mesh for native tests. +// Exposes packet receive handling and captures discovered contacts for assertions. class TestChatMesh final : public BaseChatMesh { public: TestChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, @@ -156,6 +102,8 @@ class TestChatMesh final : public BaseChatMesh { void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} }; +// Test mesh context for native tests. +// Owns mock dependencies in construction order and exposes the mesh via operator->. struct TestMeshContext { explicit TestMeshContext(uint32_t current_timestamp) : rtc(current_timestamp), mesh(radio, ms, rng, rtc, packet_manager, tables) {} diff --git a/test/mocks/radio.h b/test/mocks/radio.h new file mode 100644 index 0000000000..59a6939547 --- /dev/null +++ b/test/mocks/radio.h @@ -0,0 +1,36 @@ +#pragma once + +#include + +#include "Dispatcher.h" + +// Fake radio for native tests. +// Provides successful no-op send/receive behavior without hardware access. +class FakeRadio final : public mesh::Radio { +public: + int recvRaw(uint8_t*, int) override { + return 0; + } + + uint32_t getEstAirtimeFor(int) override { + return 1; + } + + float packetScore(float, int) override { + return 1.0f; + } + + bool startSendRaw(const uint8_t*, int) override { + return true; + } + + bool isSendComplete() override { + return true; + } + + void onSendFinished() override {} + + bool isInRecvMode() const override { + return true; + } +}; diff --git a/test/mocks/random.h b/test/mocks/random.h new file mode 100644 index 0000000000..4b6897360b --- /dev/null +++ b/test/mocks/random.h @@ -0,0 +1,25 @@ +#pragma once + +#if defined(__has_include_next) + #if __has_include_next() + #include_next + #endif +#endif + +#ifdef __cplusplus + +#include +#include + +#include "Utils.h" + +// Fake random generator for native tests. +// Fills buffers with deterministic bytes so generated packets are repeatable. +class FakeRng final : public mesh::RNG { +public: + void random(uint8_t* dest, size_t sz) override { + memset(dest, 0x5A, sz); + } +}; + +#endif diff --git a/test/mocks/time.h b/test/mocks/time.h new file mode 100644 index 0000000000..b16f5d309b --- /dev/null +++ b/test/mocks/time.h @@ -0,0 +1,42 @@ +#pragma once + +#if defined(__has_include_next) + #if __has_include_next() + #include_next + #endif +#endif + +#ifdef __cplusplus + +#include + +#include "Dispatcher.h" + +// Fake millisecond clock for native tests. +// Returns a stable timestamp so timer-dependent code stays deterministic. +class FakeMillis final : public mesh::MillisecondClock { +public: + unsigned long getMillis() override { + return 0; + } +}; + +// Fake RTC clock for native tests. +// Stores caller-controlled Unix time without depending on hardware RTC APIs. +class FakeRtc final : public mesh::RTCClock { +public: + explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {} + + uint32_t getCurrentTime() override { + return _time; + } + + void setCurrentTime(uint32_t time) override { + _time = time; + } + +private: + uint32_t _time; +}; + +#endif diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index 9d9f77095b..a6ad03a9c6 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -4,7 +4,7 @@ #include -#include "mocks.h" +#include "../mocks/mesh.h" namespace { From 837fcf96237944178c4ded37357b8bc03d46e029 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 1 May 2026 12:12:49 -0400 Subject: [PATCH 47/48] Simplify to a more obviously fake SHA256 algorithm --- test/mocks/SHA256.h | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index 38e779b7d1..d81c1add70 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -2,43 +2,26 @@ #include #include -#include // Mock SHA256 class for native testing. -// Provides a deterministic stand-in so code can hash data without Arduino crypto deps. +// Provides a fixed stand-in so code can compile without Arduino crypto deps. +// update() and resetHMAC() ignore all input; finalize() and finalizeHMAC() +// fill the requested output buffer with 0x11 bytes. class SHA256 { - uint32_t state_ = 2166136261u; - public: - void update(const void* data, size_t len) { - update(static_cast(data), len); - } + void update(const void*, size_t) {} - void update(const uint8_t* data, size_t len) { - for (size_t i = 0; i < len; ++i) { - state_ ^= data[i]; - state_ *= 16777619u; - state_ += 0x9E3779B9u; - } - } + void update(const uint8_t*, size_t) {} void finalize(uint8_t* hash, size_t hashLen) { - uint32_t value = state_; for (size_t i = 0; i < hashLen; ++i) { - value ^= value >> 13; - value *= 1274126177u; - hash[i] = static_cast((value >> ((i & 3) * 8)) & 0xFF); + hash[i] = 0x11; } } - void resetHMAC(const uint8_t* key, size_t keyLen) { - state_ = 2166136261u; - update(key, keyLen); - state_ ^= 0x36363636u; - } + void resetHMAC(const uint8_t*, size_t) {} - void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) { - update(key, keyLen); + void finalizeHMAC(const uint8_t*, size_t, uint8_t* hash, size_t hashLen) { finalize(hash, hashLen); } }; From 5375246409c7a753a614f76aa2c3f1ba2b936107 Mon Sep 17 00:00:00 2001 From: Michael Lynch Date: Fri, 1 May 2026 16:38:23 -0400 Subject: [PATCH 48/48] Simplify to just test AdvertDataParser --- lib/ed25519/library.json | 7 - platformio.ini | 6 - test/mocks/Ed25519.h | 20 -- test/mocks/SHA256.h | 25 +- test/mocks/Stream.h | 13 +- test/mocks/mesh.h | 126 -------- test/mocks/radio.h | 36 --- test/mocks/random.h | 25 -- test/mocks/time.h | 42 --- test/test_helpers/test_advert_data.cpp | 400 ++++++++----------------- 10 files changed, 128 insertions(+), 572 deletions(-) delete mode 100644 lib/ed25519/library.json delete mode 100644 test/mocks/Ed25519.h delete mode 100644 test/mocks/mesh.h delete mode 100644 test/mocks/radio.h delete mode 100644 test/mocks/random.h delete mode 100644 test/mocks/time.h diff --git a/lib/ed25519/library.json b/lib/ed25519/library.json deleted file mode 100644 index 4cafd5e89e..0000000000 --- a/lib/ed25519/library.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "build": { - "flags": [ - "-fno-sanitize=undefined" - ] - } -} diff --git a/platformio.ini b/platformio.ini index 9696fc3f83..e70f21c584 100644 --- a/platformio.ini +++ b/platformio.ini @@ -169,13 +169,7 @@ build_flags = -std=c++17 test_build_src = yes build_src_filter = -<*> - +<../src/Dispatcher.cpp> - +<../src/Identity.cpp> - +<../src/Mesh.cpp> - +<../src/Packet.cpp> +<../src/Utils.cpp> +<../src/helpers/AdvertDataHelpers.cpp> - +<../src/helpers/BaseChatMesh.cpp> - +<../src/helpers/TxtDataHelpers.cpp> lib_deps = google/googletest @ 1.17.0 diff --git a/test/mocks/Ed25519.h b/test/mocks/Ed25519.h deleted file mode 100644 index 2c649505c3..0000000000 --- a/test/mocks/Ed25519.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif -#include -#ifdef __cplusplus -} -#endif - -#include - -// Mock Ed25519 wrapper for native testing. -// Adapts the test-only C ed25519 implementation to the Arduino-style class API. -class Ed25519 { -public: - static bool verify(const uint8_t* sig, const uint8_t* pub_key, const uint8_t* message, int msg_len) { - return ed25519_verify(sig, message, msg_len, pub_key) != 0; - } -}; diff --git a/test/mocks/SHA256.h b/test/mocks/SHA256.h index d81c1add70..b6e551a077 100644 --- a/test/mocks/SHA256.h +++ b/test/mocks/SHA256.h @@ -3,25 +3,12 @@ #include #include -// Mock SHA256 class for native testing. -// Provides a fixed stand-in so code can compile without Arduino crypto deps. -// update() and resetHMAC() ignore all input; finalize() and finalizeHMAC() -// fill the requested output buffer with 0x11 bytes. +// Mock SHA256 class for testing +// Provides minimal interface to allow Utils.cpp to compile class SHA256 { public: - void update(const void*, size_t) {} - - void update(const uint8_t*, size_t) {} - - void finalize(uint8_t* hash, size_t hashLen) { - for (size_t i = 0; i < hashLen; ++i) { - hash[i] = 0x11; - } - } - - void resetHMAC(const uint8_t*, size_t) {} - - void finalizeHMAC(const uint8_t*, size_t, uint8_t* hash, size_t hashLen) { - finalize(hash, hashLen); - } + void update(const uint8_t* data, size_t len) {} + void finalize(uint8_t* hash, size_t hashLen) {} + void resetHMAC(const uint8_t* key, size_t keyLen) {} + void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {} }; diff --git a/test/mocks/Stream.h b/test/mocks/Stream.h index e83b04294e..195a302973 100644 --- a/test/mocks/Stream.h +++ b/test/mocks/Stream.h @@ -1,15 +1,10 @@ #pragma once -#include -#include +// Mock Stream class for native testing +// Provides minimal interface needed by Utils.h -// Mock Stream class for native testing. -// Provides the minimal interface needed by code that depends on Arduino Stream. class Stream { public: - virtual size_t readBytes(uint8_t*, size_t) { return 0; } - virtual size_t write(const uint8_t*, size_t len) { return len; } - virtual void print(char) {} - virtual void print(const char*) {} - virtual void println() {} + virtual void print(char c) {} + virtual void print(const char* str) {} }; diff --git a/test/mocks/mesh.h b/test/mocks/mesh.h deleted file mode 100644 index 9b2598fd99..0000000000 --- a/test/mocks/mesh.h +++ /dev/null @@ -1,126 +0,0 @@ -#pragma once - -#include -#include - -#include "helpers/BaseChatMesh.h" -#include "helpers/SimpleMeshTables.h" -#include "radio.h" -#include "random.h" -#include "time.h" - -// No-op packet manager for native tests. -// Satisfies Mesh dependencies while preventing packet allocation or queuing. -class NoopPacketManager final : public mesh::PacketManager { -public: - mesh::Packet* allocNew() override { - return nullptr; - } - - void free(mesh::Packet*) override {} - - void queueOutbound(mesh::Packet*, uint8_t, uint32_t) override {} - - mesh::Packet* getNextOutbound(uint32_t) override { - return nullptr; - } - - int getOutboundCount(uint32_t) const override { - return 0; - } - - int getOutboundTotal() const override { - return 0; - } - - int getFreeCount() const override { - return 0; - } - - mesh::Packet* getOutboundByIdx(int) override { - return nullptr; - } - - mesh::Packet* removeOutboundByIdx(int) override { - return nullptr; - } - - void queueInbound(mesh::Packet*, uint32_t) override {} - - mesh::Packet* getNextInbound(uint32_t) override { - return nullptr; - } -}; - -// Test chat mesh for native tests. -// Exposes packet receive handling and captures discovered contacts for assertions. -class TestChatMesh final : public BaseChatMesh { -public: - TestChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, - mesh::PacketManager& mgr, mesh::MeshTables& tables) - : BaseChatMesh(radio, ms, rng, rtc, mgr, tables) {} - - mesh::DispatcherAction recv(mesh::Packet* pkt) { - return onRecvPacket(pkt); - } - - std::optional discovered_contact; - -protected: - void onDiscoveredContact(ContactInfo& contact, bool, uint8_t, const uint8_t*) override { - discovered_contact = contact; - } - - ContactInfo* processAck(const uint8_t*) override { - return nullptr; - } - - void onContactPathUpdated(const ContactInfo&) override {} - - void onMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} - - void onCommandDataRecv(const ContactInfo&, mesh::Packet*, uint32_t, const char*) override {} - - void onSignedMessageRecv(const ContactInfo&, mesh::Packet*, uint32_t, const uint8_t*, const char*) override {} - - uint32_t calcFloodTimeoutMillisFor(uint32_t) const override { - return 0; - } - - uint32_t calcDirectTimeoutMillisFor(uint32_t, uint8_t) const override { - return 0; - } - - void onSendTimeout() override {} - - void onChannelMessageRecv(const mesh::GroupChannel&, mesh::Packet*, uint32_t, const char*) override {} - - uint8_t onContactRequest(const ContactInfo&, uint32_t, const uint8_t*, uint8_t, uint8_t*) override { - return 0; - } - - void onContactResponse(const ContactInfo&, const uint8_t*, uint8_t) override {} -}; - -// Test mesh context for native tests. -// Owns mock dependencies in construction order and exposes the mesh via operator->. -struct TestMeshContext { - explicit TestMeshContext(uint32_t current_timestamp) - : rtc(current_timestamp), mesh(radio, ms, rng, rtc, packet_manager, tables) {} - - TestChatMesh* operator->() { - return &mesh; - } - - const TestChatMesh* operator->() const { - return &mesh; - } - - FakeRadio radio; - FakeMillis ms; - FakeRng rng; - FakeRtc rtc; - NoopPacketManager packet_manager; - SimpleMeshTables tables; - TestChatMesh mesh; -}; diff --git a/test/mocks/radio.h b/test/mocks/radio.h deleted file mode 100644 index 59a6939547..0000000000 --- a/test/mocks/radio.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once - -#include - -#include "Dispatcher.h" - -// Fake radio for native tests. -// Provides successful no-op send/receive behavior without hardware access. -class FakeRadio final : public mesh::Radio { -public: - int recvRaw(uint8_t*, int) override { - return 0; - } - - uint32_t getEstAirtimeFor(int) override { - return 1; - } - - float packetScore(float, int) override { - return 1.0f; - } - - bool startSendRaw(const uint8_t*, int) override { - return true; - } - - bool isSendComplete() override { - return true; - } - - void onSendFinished() override {} - - bool isInRecvMode() const override { - return true; - } -}; diff --git a/test/mocks/random.h b/test/mocks/random.h deleted file mode 100644 index 4b6897360b..0000000000 --- a/test/mocks/random.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -#if defined(__has_include_next) - #if __has_include_next() - #include_next - #endif -#endif - -#ifdef __cplusplus - -#include -#include - -#include "Utils.h" - -// Fake random generator for native tests. -// Fills buffers with deterministic bytes so generated packets are repeatable. -class FakeRng final : public mesh::RNG { -public: - void random(uint8_t* dest, size_t sz) override { - memset(dest, 0x5A, sz); - } -}; - -#endif diff --git a/test/mocks/time.h b/test/mocks/time.h deleted file mode 100644 index b16f5d309b..0000000000 --- a/test/mocks/time.h +++ /dev/null @@ -1,42 +0,0 @@ -#pragma once - -#if defined(__has_include_next) - #if __has_include_next() - #include_next - #endif -#endif - -#ifdef __cplusplus - -#include - -#include "Dispatcher.h" - -// Fake millisecond clock for native tests. -// Returns a stable timestamp so timer-dependent code stays deterministic. -class FakeMillis final : public mesh::MillisecondClock { -public: - unsigned long getMillis() override { - return 0; - } -}; - -// Fake RTC clock for native tests. -// Stores caller-controlled Unix time without depending on hardware RTC APIs. -class FakeRtc final : public mesh::RTCClock { -public: - explicit FakeRtc(uint32_t initial_time) : _time(initial_time) {} - - uint32_t getCurrentTime() override { - return _time; - } - - void setCurrentTime(uint32_t time) override { - _time = time; - } - -private: - uint32_t _time; -}; - -#endif diff --git a/test/test_helpers/test_advert_data.cpp b/test/test_helpers/test_advert_data.cpp index a6ad03a9c6..cc1c5a2db5 100644 --- a/test/test_helpers/test_advert_data.cpp +++ b/test/test_helpers/test_advert_data.cpp @@ -4,17 +4,10 @@ #include -#include "../mocks/mesh.h" +#include "helpers/AdvertDataHelpers.h" namespace { -constexpr char kSenderPrivateKeyHex[] = - "70" - "65e18fd9fabb70c1ed90dca19907de698c88b709ea146eafd93d9b830c7b60" - "c4681193c79bbc39945ba8064104bb618f8fd7a84a0af6f57033d6e8ddcd6471"; -constexpr char kSenderPublicKeyHex[] = - "1ec77175b0918ed206f9ae04ec136d6d5d4315bb26305427f645b492e9350c10"; - void WriteU8(uint8_t* dest, size_t* offset, uint8_t value) { dest[(*offset)++] = value; } @@ -38,63 +31,11 @@ void WriteStringLiteral(uint8_t* dest, size_t* offset, const char (&value)[N]) { WriteBytes(dest, offset, reinterpret_cast(value), N - 1); } -TestMeshContext MakeTestMesh(uint32_t current_timestamp) { - return TestMeshContext(current_timestamp); -} - -ContactInfo MakeSenderContact(uint32_t advert_timestamp, int32_t gps_lat, int32_t gps_lon) { - ContactInfo contact = {}; - contact.id = mesh::Identity(kSenderPublicKeyHex); - strcpy(contact.name, "existing-contact"); - contact.type = ADV_TYPE_CHAT; - contact.out_path_len = OUT_PATH_UNKNOWN; - contact.last_advert_timestamp = advert_timestamp; - contact.lastmod = advert_timestamp; - contact.gps_lat = gps_lat; - contact.gps_lon = gps_lon; - return contact; +AdvertDataParser Parse(const uint8_t* app_data, size_t app_data_len) { + return AdvertDataParser(app_data, static_cast(app_data_len)); } -mesh::Packet BuildSignedAdvertPacket(uint32_t timestamp, const uint8_t* app_data, uint8_t app_data_len) { - mesh::LocalIdentity sender(kSenderPrivateKeyHex, kSenderPublicKeyHex); - mesh::Packet packet; - - // Wire header: flood-routed advert packet with no path hashes yet. - packet.header = ROUTE_TYPE_FLOOD | (PAYLOAD_TYPE_ADVERT << PH_TYPE_SHIFT); - packet.path_len = 0; - - int offset = 0; - // Sender public key: used by the receiver to identify who signed the advert. - memcpy(&packet.payload[offset], sender.pub_key, PUB_KEY_SIZE); - offset += PUB_KEY_SIZE; - - // Advert timestamp: the sender's monotonic advert time used for replay checks. - memcpy(&packet.payload[offset], ×tamp, sizeof(timestamp)); - offset += sizeof(timestamp); - - // Signature field: filled after the signed message bytes are assembled below. - uint8_t* signature = &packet.payload[offset]; - offset += SIGNATURE_SIZE; - - // Raw advert app_data: arbitrary bytes authored by the test, not by createAdvert(). - memcpy(&packet.payload[offset], app_data, app_data_len); - offset += app_data_len; - packet.payload_len = offset; - - uint8_t message[PUB_KEY_SIZE + 4 + MAX_ADVERT_DATA_SIZE]; - int message_len = 0; - memcpy(&message[message_len], sender.pub_key, PUB_KEY_SIZE); - message_len += PUB_KEY_SIZE; - memcpy(&message[message_len], ×tamp, sizeof(timestamp)); - message_len += sizeof(timestamp); - memcpy(&message[message_len], app_data, app_data_len); - message_len += app_data_len; - - sender.sign(signature, message, message_len); - return packet; -} - -TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { +TEST(AdvertDataParser, ParsesNameAndCoordinates) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -107,21 +48,18 @@ TEST(AdvertData, ParsesNameAndCoordinatesFromNetworkPacket) { // name field: trailing contact name bytes after the coordinate fields. WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + const AdvertDataParser parser = Parse(app_data, offset); - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(ADV_TYPE_REPEATER, test_mesh->discovered_contact->type); - EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); - EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(ADV_TYPE_REPEATER, parser.getType()); + ASSERT_TRUE(parser.hasLatLon()); + EXPECT_EQ(37774900, parser.getIntLat()); + EXPECT_EQ(-122419400, parser.getIntLon()); + ASSERT_TRUE(parser.hasName()); + EXPECT_STREQ("dummy-node-name", parser.getName()); } -TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { +TEST(AdvertDataParser, ParsesCoordinateExtremes) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -134,21 +72,16 @@ TEST(AdvertData, ParsesCoordinateExtremesFromNetworkPacket) { // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + const AdvertDataParser parser = Parse(app_data, offset); - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(ADV_TYPE_SENSOR, test_mesh->discovered_contact->type); - EXPECT_STREQ("dummy-node-name", test_mesh->discovered_contact->name); - EXPECT_EQ(-90000000, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(180000000, test_mesh->discovered_contact->gps_lon); + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(ADV_TYPE_SENSOR, parser.getType()); + EXPECT_EQ(-90000000, parser.getIntLat()); + EXPECT_EQ(180000000, parser.getIntLon()); + EXPECT_STREQ("dummy-node-name", parser.getName()); } -TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetworkPacket) { +TEST(AdvertDataParser, ParsesPositiveLatitudeAndNegativeLongitudeBoundaries) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -161,19 +94,14 @@ TEST(AdvertData, ParsesPositiveLatitudeAndNegativeLongitudeBoundariesFromNetwork // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + const AdvertDataParser parser = Parse(app_data, offset); - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(90000000, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(-180000000, test_mesh->discovered_contact->gps_lon); + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(90000000, parser.getIntLat()); + EXPECT_EQ(-180000000, parser.getIntLon()); } -TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) { +TEST(AdvertDataParser, ParsesNullIslandCoordinates) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -186,19 +114,32 @@ TEST(AdvertData, ParsesNullIslandCoordinatesFromNetworkPacket) { // name field: raw bytes for "dummy-node-name". WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + const AdvertDataParser parser = Parse(app_data, offset); - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(0, parser.getIntLat()); + EXPECT_EQ(0, parser.getIntLon()); +} - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(0, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(0, test_mesh->discovered_contact->gps_lon); +TEST(AdvertDataParser, ParsesNameWithoutCoordinates) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; + + // flags/type byte: chat advert with a name but no GPS fields. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); + // name field: contact name with no coordinate payload before it. + WriteStringLiteral(app_data, &offset, "updated-name"); + + const AdvertDataParser parser = Parse(app_data, offset); + + ASSERT_TRUE(parser.isValid()); + EXPECT_EQ(ADV_TYPE_CHAT, parser.getType()); + EXPECT_FALSE(parser.hasLatLon()); + ASSERT_TRUE(parser.hasName()); + EXPECT_STREQ("updated-name", parser.getName()); } -TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { +TEST(AdvertDataParser, RejectsLongitudeOutsideValidRange) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -211,17 +152,12 @@ TEST(AdvertData, RejectsLongitudeOutsideValidRangeFromNetworkPacket) { // name field: parser should reject before the trailing name matters. WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + const AdvertDataParser parser = Parse(app_data, offset); - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { +TEST(AdvertDataParser, RejectsLongitudeBelowValidRange) { uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; @@ -234,51 +170,70 @@ TEST(AdvertData, RejectsLongitudeBelowValidRangeFromNetworkPacket) { // name field: included to keep the payload shape consistent. WriteStringLiteral(app_data, &offset, "dummy-node-name"); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + const AdvertDataParser parser = Parse(app_data, offset); - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + EXPECT_FALSE(parser.isValid()); +} + +TEST(AdvertDataParser, RejectsLatitudeOutsideValidRange) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; + size_t offset = 0; - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: one microdegree above +90.0, which is invalid. + WriteI32Le(app_data, &offset, 90000001); + // longitude field: valid longitude so the failure comes from latitude. + WriteI32Le(app_data, &offset, -122419400); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "dummy-node-name"); + + const AdvertDataParser parser = Parse(app_data, offset); + + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, RejectsGpsPayloadWithMissingFlagsByte) { - // Backing storage is unused because the advertised app_data length is zero. - uint8_t app_data[1] = {}; +TEST(AdvertDataParser, RejectsLatitudeBelowValidRange) { + uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; size_t offset = 0; - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - // Leave the app_data length at zero so the parser never sees the flags byte. - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + // flags/type byte: chat advert with location and name fields present. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); + // latitude field: one microdegree below -90.0, which is invalid. + WriteI32Le(app_data, &offset, -90000001); + // longitude field: valid longitude so the failure comes from latitude. + WriteI32Le(app_data, &offset, -122419400); + // name field: included to keep the payload shape consistent. + WriteStringLiteral(app_data, &offset, "dummy-node-name"); + + const AdvertDataParser parser = Parse(app_data, offset); + + EXPECT_FALSE(parser.isValid()); +} + +TEST(AdvertDataParser, RejectsPayloadWithMissingFlagsByte) { + // Backing storage is unused because the advertised app_data length is zero. + const uint8_t app_data[1] = {}; - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + const AdvertDataParser parser = Parse(app_data, 0); - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, RejectsGpsPayloadWithOnlyFlagsByte) { +TEST(AdvertDataParser, RejectsPayloadWithOnlyFlagsByte) { uint8_t app_data[1] = {}; size_t offset = 0; // flags/type byte: chat advert that claims to carry coordinates and a name. WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; // Pass only the flags byte so no latitude bytes remain. - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); + const AdvertDataParser parser = Parse(app_data, offset); - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { +TEST(AdvertDataParser, RejectsPayloadWithLatitudeButMissingLongitude) { uint8_t app_data[5] = {}; size_t offset = 0; @@ -287,18 +242,13 @@ TEST(AdvertData, RejectsGpsPayloadWithLatitudeButMissingLongitude) { // latitude field: complete latitude bytes are present before truncation. WriteI32Le(app_data, &offset, 37774900); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; // Pass only the flags byte and latitude field so longitude is missing. - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + const AdvertDataParser parser = Parse(app_data, offset); - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { +TEST(AdvertDataParser, RejectsPayloadOneByteShortOfFullCoordinates) { uint8_t app_data[8] = {}; size_t offset = 0; uint8_t lon_bytes[sizeof(int32_t)] = {}; @@ -312,155 +262,41 @@ TEST(AdvertData, RejectsGpsPayloadOneByteShortOfFullCoordinates) { WriteI32Le(lon_bytes, &lon_offset, -122419400); WriteBytes(app_data, &offset, lon_bytes, 3); - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; // Pass only the flags byte, latitude field, and three longitude bytes. - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); + const AdvertDataParser parser = Parse(app_data, offset); - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, RejectsLatitudeOutsideValidRangeFromNetworkPacket) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; +TEST(AdvertDataParser, RejectsPayloadWithIncompleteFeat1) { + uint8_t app_data[2] = {}; size_t offset = 0; - // flags/type byte: chat advert with location and name fields present. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: one microdegree above +90.0, which is invalid. - WriteI32Le(app_data, &offset, 90000001); - // longitude field: valid longitude so the failure comes from latitude. - WriteI32Le(app_data, &offset, -122419400); - // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "dummy-node-name"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); -} - -TEST(AdvertData, RejectsLatitudeBelowValidRangeFromNetworkPacket) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; - - // flags/type byte: chat advert with location and name fields present. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: one microdegree below -90.0, which is invalid. - WriteI32Le(app_data, &offset, -90000001); - // longitude field: valid longitude so the failure comes from latitude. - WriteI32Le(app_data, &offset, -122419400); - // name field: included to keep the payload shape consistent. - WriteStringLiteral(app_data, &offset, "dummy-node-name"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - test_mesh->recv(&packet); - - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); -} + // flags/type byte: chat advert that claims to carry a two-byte feature field. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_FEAT1_MASK); + // feature field: only the first byte is present before truncation. + WriteU8(app_data, &offset, 0x34); -TEST(AdvertData, KeepsExistingGpsWhenUpdatedAdvertOmitsCoordinates) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; - - // flags/type byte: chat advert with a new name but no GPS fields. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_NAME_MASK); - // name field: replacement contact name with no coordinate payload following it. - WriteStringLiteral(app_data, &offset, "updated-name"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; - constexpr uint32_t new_advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); - - test_mesh->recv(&packet); - - ContactInfo* updated = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); - ASSERT_NE(nullptr, updated); - EXPECT_STREQ("updated-name", updated->name); - EXPECT_EQ(37774900, updated->gps_lat); - EXPECT_EQ(-122419400, updated->gps_lon); - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(37774900, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(-122419400, test_mesh->discovered_contact->gps_lon); -} - -TEST(AdvertData, OverwritesExistingGpsWhenUpdatedAdvertIncludesCoordinates) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; - size_t offset = 0; + const AdvertDataParser parser = Parse(app_data, offset); - // flags/type byte: chat advert with replacement GPS coordinates and a new name. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: replacement latitude for 40.712800 degrees. - WriteI32Le(app_data, &offset, 40712800); - // longitude field: replacement longitude for -74.006000 degrees. - WriteI32Le(app_data, &offset, -74006000); - // name field: replacement contact name applied with the new coordinates. - WriteStringLiteral(app_data, &offset, "updated-name"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; - constexpr uint32_t new_advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); - - test_mesh->recv(&packet); - - ContactInfo* updated = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); - ASSERT_NE(nullptr, updated); - EXPECT_STREQ("updated-name", updated->name); - EXPECT_EQ(40712800, updated->gps_lat); - EXPECT_EQ(-74006000, updated->gps_lon); - ASSERT_TRUE(test_mesh->discovered_contact.has_value()); - EXPECT_EQ(40712800, test_mesh->discovered_contact->gps_lat); - EXPECT_EQ(-74006000, test_mesh->discovered_contact->gps_lon); + EXPECT_FALSE(parser.isValid()); } -TEST(AdvertData, LeavesExistingGpsUntouchedWhenUpdatedAdvertHasInvalidCoordinates) { - uint8_t app_data[MAX_ADVERT_DATA_SIZE] = {}; +TEST(AdvertDataParser, RejectsPayloadWithIncompleteFeat2) { + uint8_t app_data[4] = {}; size_t offset = 0; - // flags/type byte: chat advert with invalid longitude and a new name. - WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_LATLON_MASK | ADV_NAME_MASK); - // latitude field: valid latitude so the update failure comes from longitude. - WriteI32Le(app_data, &offset, 37774900); - // longitude field: one microdegree above +180.0, which should reject the update. - WriteI32Le(app_data, &offset, 180000001); - // name field: replacement name that should not be applied when parsing fails. - WriteStringLiteral(app_data, &offset, "updated-name"); - - constexpr uint32_t current_timestamp = 1704067200U; - constexpr uint32_t existing_advert_timestamp = current_timestamp - 10; - constexpr uint32_t new_advert_timestamp = current_timestamp + 1; - mesh::Packet packet = BuildSignedAdvertPacket(new_advert_timestamp, app_data, offset); - - auto test_mesh = MakeTestMesh(current_timestamp); - ASSERT_TRUE(test_mesh->addContact(MakeSenderContact(existing_advert_timestamp, 37774900, -122419400))); + // flags/type byte: chat advert that claims to carry both two-byte feature fields. + WriteU8(app_data, &offset, ADV_TYPE_CHAT | ADV_FEAT1_MASK | ADV_FEAT2_MASK); + // feature 1 field: complete two-byte value before the truncated feature 2 field. + WriteU8(app_data, &offset, 0x34); + WriteU8(app_data, &offset, 0x12); + // feature 2 field: only the first byte is present before truncation. + WriteU8(app_data, &offset, 0x78); - test_mesh->recv(&packet); + const AdvertDataParser parser = Parse(app_data, offset); - ContactInfo* existing = test_mesh->lookupContactByPubKey(mesh::Identity(kSenderPublicKeyHex).pub_key, PUB_KEY_SIZE); - ASSERT_NE(nullptr, existing); - EXPECT_STREQ("existing-contact", existing->name); - EXPECT_EQ(37774900, existing->gps_lat); - EXPECT_EQ(-122419400, existing->gps_lon); - EXPECT_EQ(existing_advert_timestamp, existing->last_advert_timestamp); - EXPECT_FALSE(test_mesh->discovered_contact.has_value()); + EXPECT_FALSE(parser.isValid()); } } // namespace