From fad11c90f338883f2cd7b728f3e1db61cdbd815e Mon Sep 17 00:00:00 2001 From: ViezeVingertjes Date: Mon, 3 Aug 2026 22:55:13 +0200 Subject: [PATCH 1/8] Fix replies dropped when flood.max.unscoped is low --- examples/simple_repeater/MyMesh.cpp | 57 ++++++--- examples/simple_repeater/MyMesh.h | 1 + examples/simple_room_server/MyMesh.cpp | 31 +++-- examples/simple_room_server/MyMesh.h | 1 + src/helpers/RoutingPolicy.h | 68 +++++++++++ .../test_routing_policy.cpp | 110 ++++++++++++++++++ 6 files changed, 237 insertions(+), 31 deletions(-) create mode 100644 src/helpers/RoutingPolicy.h create mode 100644 test/test_routing_policy/test_routing_policy.cpp diff --git a/examples/simple_repeater/MyMesh.cpp b/examples/simple_repeater/MyMesh.cpp index 09f74cbea..0d2f5c863 100644 --- a/examples/simple_repeater/MyMesh.cpp +++ b/examples/simple_repeater/MyMesh.cpp @@ -414,24 +414,31 @@ bool MyMesh::isLooped(const mesh::Packet* packet, const uint8_t max_counters[]) } void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) { - if (recv_pkt_region && !recv_pkt_region->isWildcard()) { // if _request_ packet scope is known, send reply with same scope - TransportKey scope; - if (region_map.getTransportKeysFor(*recv_pkt_region, &scope, 1) > 0) { - sendFloodScoped(scope, packet, delay_millis, path_hash_size); - } else { + TransportKey req_scope; + bool is_wildcard = recv_pkt_region != NULL && recv_pkt_region->isWildcard(); + bool req_scope_known = recv_pkt_region != NULL && !is_wildcard + && region_map.getTransportKeysFor(*recv_pkt_region, &req_scope, 1) > 0; + + switch (mesh::chooseReplyScope(req_scope_known, is_wildcard, !default_scope.isNull())) { + case mesh::REPLY_SCOPE_REQUEST: + sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request + break; + case mesh::REPLY_SCOPE_DEFAULT: + // requester's scope is unknown: DIRECT request (no transport codes), or code matched no Region. + // un-scoped would be dropped at hop 0 by repeaters running flood.max.unscoped=0 + sendFloodScoped(default_scope, packet, delay_millis, path_hash_size); + break; + case mesh::REPLY_SCOPE_NONE: sendFlood(packet, delay_millis, path_hash_size); // send un-scoped - } - } else { - sendFlood(packet, delay_millis, path_hash_size); // send un-scoped + break; } } bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood()) { - if (packet->getPathHashCount() >= _prefs.flood_max) return false; - if (packet->getRouteType() == ROUTE_TYPE_FLOOD && packet->getPathHashCount() >= _prefs.flood_max_unscoped) return false; - if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->getPathHashCount() >= _prefs.flood_max_advert) return false; + if (packet->isRouteFlood() + && mesh::isFloodHopLimitExceeded(packet, _prefs.flood_max, _prefs.flood_max_unscoped, _prefs.flood_max_advert)) { + return false; } if (packet->isRouteFlood() && recv_pkt_region == NULL) { MESH_DEBUG_PRINTLN("allowPacketForward: unknown transport code, or wildcard not allowed for FLOOD packet"); @@ -589,18 +596,30 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m if (reply_len == 0) return; // invalid request - if (packet->isRouteFlood()) { + // a DIRECT login can reply via the stored out_path, as onPeerDataRecv() does for REQ + ClientInfo* client = acl.getClient(sender.pub_key, PUB_KEY_SIZE); + bool have_out_path = client != NULL && client->out_path_len != OUT_PATH_UNKNOWN; + + auto route = mesh::chooseReplyRoute(packet->isRouteFlood(), reply_path_len >= 0, have_out_path); + + if (route == mesh::REPLY_ROUTE_PATH_RETURN) { // let this sender know path TO here, so they can use sendDirect(), and ALSO encode the response mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len, PAYLOAD_TYPE_RESPONSE, reply_data, reply_len); if (path) sendFloodReply(path, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); - } else if (reply_path_len < 0) { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); - if (reply) sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); - } else { - mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + return; + } + + mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len); + if (reply == NULL) return; + + if (route == mesh::REPLY_ROUTE_DIRECT_SUPPLIED) { uint8_t path_len = ((reply_path_hash_size - 1) << 6) | (reply_path_len & 63); - if (reply) sendDirect(reply, reply_path, path_len, SERVER_RESPONSE_DELAY); + sendDirect(reply, reply_path, path_len, SERVER_RESPONSE_DELAY); + } else if (route == mesh::REPLY_ROUTE_DIRECT_OUT_PATH) { + sendDirect(reply, client->out_path, client->out_path_len, SERVER_RESPONSE_DELAY); + } else { + sendFloodReply(reply, SERVER_RESPONSE_DELAY, packet->getPathHashSize()); } } } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index aa7d30b06..f1a1bcff2 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -34,6 +34,7 @@ #include #include #include +#include #include "RateLimiter.h" #ifdef WITH_BRIDGE diff --git a/examples/simple_room_server/MyMesh.cpp b/examples/simple_room_server/MyMesh.cpp index 0aff39cc1..b951f4275 100644 --- a/examples/simple_room_server/MyMesh.cpp +++ b/examples/simple_room_server/MyMesh.cpp @@ -299,10 +299,9 @@ uint32_t MyMesh::getDirectRetransmitDelay(const mesh::Packet *packet) { bool MyMesh::allowPacketForward(const mesh::Packet *packet) { if (_prefs.disable_fwd) return false; - if (packet->isRouteFlood()) { - if (packet->getPathHashCount() >= _prefs.flood_max) return false; - if (packet->getRouteType() == ROUTE_TYPE_FLOOD && packet->getPathHashCount() >= _prefs.flood_max_unscoped) return false; - if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->getPathHashCount() >= _prefs.flood_max_advert) return false; + if (packet->isRouteFlood() + && mesh::isFloodHopLimitExceeded(packet, _prefs.flood_max, _prefs.flood_max_unscoped, _prefs.flood_max_advert)) { + return false; } return true; } @@ -749,15 +748,23 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3 } void MyMesh::sendFloodReply(mesh::Packet* packet, unsigned long delay_millis, uint8_t path_hash_size) { - if (recv_pkt_region && !recv_pkt_region->isWildcard()) { // if _request_ packet scope is known, send reply with same scope - TransportKey scope; - if (region_map.getTransportKeysFor(*recv_pkt_region, &scope, 1) > 0) { - sendFloodScoped(scope, packet, delay_millis, path_hash_size); - } else { + TransportKey req_scope; + bool is_wildcard = recv_pkt_region != NULL && recv_pkt_region->isWildcard(); + bool req_scope_known = recv_pkt_region != NULL && !is_wildcard + && region_map.getTransportKeysFor(*recv_pkt_region, &req_scope, 1) > 0; + + switch (mesh::chooseReplyScope(req_scope_known, is_wildcard, !default_scope.isNull())) { + case mesh::REPLY_SCOPE_REQUEST: + sendFloodScoped(req_scope, packet, delay_millis, path_hash_size); // reply with same scope as request + break; + case mesh::REPLY_SCOPE_DEFAULT: + // requester's scope is unknown: DIRECT request (no transport codes), or code matched no Region. + // un-scoped would be dropped at hop 0 by repeaters running flood.max.unscoped=0 + sendFloodScoped(default_scope, packet, delay_millis, path_hash_size); + break; + case mesh::REPLY_SCOPE_NONE: sendFlood(packet, delay_millis, path_hash_size); // send un-scoped - } - } else { - sendFlood(packet, delay_millis, path_hash_size); // send un-scoped + break; } } diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 5f78bee81..a45ed9cd4 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -21,6 +21,7 @@ #include #include #include +#include #include #include diff --git a/src/helpers/RoutingPolicy.h b/src/helpers/RoutingPolicy.h new file mode 100644 index 000000000..5251a590a --- /dev/null +++ b/src/helpers/RoutingPolicy.h @@ -0,0 +1,68 @@ +#pragma once + +#include + +namespace mesh { + +/** + * \brief Test a flood packet against the configured hop limits. + * \param packet inbound flood packet (caller has already checked isRouteFlood()) + * \param flood_max max hops for any flood packet + * \param flood_max_unscoped max hops for ROUTE_TYPE_FLOOD (ie. un-scoped) packets + * \param flood_max_advert max hops for ADVERT packets + * \returns true if the packet has exceeded a limit, and must not be forwarded + */ +inline bool isFloodHopLimitExceeded(const Packet* packet, uint8_t flood_max, + uint8_t flood_max_unscoped, uint8_t flood_max_advert) { + uint8_t hops = packet->getPathHashCount(); + if (hops >= flood_max) return true; + if (packet->getRouteType() == ROUTE_TYPE_FLOOD && hops >= flood_max_unscoped) return true; + if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && hops >= flood_max_advert) return true; + return false; +} + +/** + * \brief How a server routes a reply back to the requesting client. + */ +enum ReplyRoute : uint8_t { + REPLY_ROUTE_PATH_RETURN, // request arrived by flood: reply with a PATH return, flooded back + REPLY_ROUTE_DIRECT_SUPPLIED, // reply DIRECT, along the return path supplied in the request + REPLY_ROUTE_DIRECT_OUT_PATH, // reply DIRECT, along the out_path already stored for this client + REPLY_ROUTE_FLOOD, // no return path known: flood the reply +}; + +/** + * \param inbound_is_flood the request arrived as a flood packet + * \param have_supplied_path the request payload carried an explicit reply path + * \param have_out_path this server already has a stored out_path for the client + */ +inline ReplyRoute chooseReplyRoute(bool inbound_is_flood, bool have_supplied_path, bool have_out_path) { + if (inbound_is_flood) return REPLY_ROUTE_PATH_RETURN; + if (have_supplied_path) return REPLY_ROUTE_DIRECT_SUPPLIED; + if (have_out_path) return REPLY_ROUTE_DIRECT_OUT_PATH; + return REPLY_ROUTE_FLOOD; +} + +/** + * \brief Which transport scope a flooded reply should be sent with. + */ +enum ReplyScope : uint8_t { + REPLY_SCOPE_REQUEST, // re-use the scope the request arrived on + REPLY_SCOPE_DEFAULT, // fall back to this node's default region scope + REPLY_SCOPE_NONE, // send un-scoped (ROUTE_TYPE_FLOOD) +}; + +/** + * \param request_scope_known request arrived scoped, and we resolved its Region's key + * \param request_was_unscoped_flood request arrived as an un-scoped flood + * \param default_scope_known this node has a default Region with a usable transport key + */ +inline ReplyScope chooseReplyScope(bool request_scope_known, bool request_was_unscoped_flood, + bool default_scope_known) { + if (request_scope_known) return REPLY_SCOPE_REQUEST; + if (request_was_unscoped_flood) return REPLY_SCOPE_NONE; // requester chose un-scoped, so mirror it + if (default_scope_known) return REPLY_SCOPE_DEFAULT; // scope unknowable: DIRECT, or unresolved Region + return REPLY_SCOPE_NONE; +} + +} diff --git a/test/test_routing_policy/test_routing_policy.cpp b/test/test_routing_policy/test_routing_policy.cpp new file mode 100644 index 000000000..bba9ec790 --- /dev/null +++ b/test/test_routing_policy/test_routing_policy.cpp @@ -0,0 +1,110 @@ +#include +#include "helpers/RoutingPolicy.h" + +using namespace mesh; + +static Packet makeFlood(uint8_t route_type, uint8_t payload_type, uint8_t hops) { + Packet p; + p.header = route_type | (payload_type << PH_TYPE_SHIFT); + p.setPathHashSizeAndCount(1, hops); + p.payload_len = 1; + return p; +} + +TEST(FloodHopLimit, UnscopedFloodIsDroppedAtFirstHopWhenMaxUnscopedIsZero) { + auto pkt = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_RESPONSE, 0); + EXPECT_TRUE(isFloodHopLimitExceeded(&pkt, 64, 0, 8)); +} + +TEST(FloodHopLimit, ScopedFloodIsForwardedWhenMaxUnscopedIsZero) { + for (uint8_t hops = 0; hops < 4; hops++) { + auto pkt = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_RESPONSE, hops); + EXPECT_FALSE(isFloodHopLimitExceeded(&pkt, 64, 0, 8)) << "hops=" << (int)hops; + } +} + +TEST(FloodHopLimit, UnscopedFloodSurvivesUpToMaxUnscopedHops) { + // matches the reported workaround: raising flood.max.unscoped to the expected hop count + auto ok = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_RESPONSE, 2); + EXPECT_FALSE(isFloodHopLimitExceeded(&ok, 64, 3, 8)); + + auto too_far = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_RESPONSE, 3); + EXPECT_TRUE(isFloodHopLimitExceeded(&too_far, 64, 3, 8)); +} + +TEST(FloodHopLimit, ScopedFloodStillHonoursFloodMaxAndAdvertMax) { + auto beyond_max = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_RESPONSE, 5); + EXPECT_TRUE(isFloodHopLimitExceeded(&beyond_max, 5, 64, 8)); + + auto advert = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_ADVERT, 8); + EXPECT_TRUE(isFloodHopLimitExceeded(&advert, 64, 64, 8)); +} + +// flood.max.unscoped=0 hits adverts too, well before flood_max_advert applies: a node +// still advertising un-scoped is invisible past its immediate neighbours +TEST(FloodHopLimit, UnscopedAdvertIsAlsoDroppedAtHopZero) { + auto advert = makeFlood(ROUTE_TYPE_FLOOD, PAYLOAD_TYPE_ADVERT, 0); + EXPECT_TRUE(isFloodHopLimitExceeded(&advert, 64, 0, 8)); + + auto scoped = makeFlood(ROUTE_TYPE_TRANSPORT_FLOOD, PAYLOAD_TYPE_ADVERT, 0); + EXPECT_FALSE(isFloodHopLimitExceeded(&scoped, 64, 0, 8)); +} + +TEST(ReplyRoute, FloodRequestGetsAPathReturn) { + EXPECT_EQ(REPLY_ROUTE_PATH_RETURN, + chooseReplyRoute(true, false, false)); + EXPECT_EQ(REPLY_ROUTE_PATH_RETURN, + chooseReplyRoute(true, false, true)); +} + +TEST(ReplyRoute, DirectRequestWithSuppliedPathRepliesDirect) { + EXPECT_EQ(REPLY_ROUTE_DIRECT_SUPPLIED, chooseReplyRoute(false, true, false)); +} + +// the reported bug: a DIRECT login (app already has a path) was answered by flooding, even +// with an out_path stored. Under flood.max.unscoped=0 that reply never arrives. +TEST(ReplyRoute, DirectRequestWithKnownOutPathRepliesDirect) { + EXPECT_EQ(REPLY_ROUTE_DIRECT_OUT_PATH, + chooseReplyRoute(false, false, true)); +} + +TEST(ReplyRoute, SuppliedPathWinsOverStoredOutPath) { + EXPECT_EQ(REPLY_ROUTE_DIRECT_SUPPLIED, chooseReplyRoute(false, true, true)); +} + +TEST(ReplyRoute, DirectRequestWithNoReturnPathFallsBackToFlood) { + EXPECT_EQ(REPLY_ROUTE_FLOOD, chooseReplyRoute(false, false, false)); +} + +TEST(ReplyScope, MirrorsTheRequestScopeWhenKnown) { + EXPECT_EQ(REPLY_SCOPE_REQUEST, chooseReplyScope(true, + false, + false)); + EXPECT_EQ(REPLY_SCOPE_REQUEST, chooseReplyScope(true, false, true)); +} + +// un-scoped is itself a known scope, so mirror it. Replying scoped would change a path that +// works today, and repeaters not holding our default Region would drop it anyway. +TEST(ReplyScope, RepliesUnscopedToAnUnscopedFloodEvenWhenADefaultScopeExists) { + EXPECT_EQ(REPLY_SCOPE_NONE, chooseReplyScope(false, + true, + true)); +} + +// second half of the bug: a DIRECT request carries no transport codes, so recv_pkt_region is +// always NULL. Un-scoped is dropped under flood.max.unscoped=0, and floods the mesh otherwise. +TEST(ReplyScope, FallsBackToDefaultScopeWhenRequestScopeUnknown) { + EXPECT_EQ(REPLY_SCOPE_DEFAULT, chooseReplyScope(false, + false, + true)); +} + +TEST(ReplyScope, SendsUnscopedOnlyWhenNoScopeIsAvailableAtAll) { + EXPECT_EQ(REPLY_SCOPE_NONE, chooseReplyScope(false, false, false)); + EXPECT_EQ(REPLY_SCOPE_NONE, chooseReplyScope(false, true, false)); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} From 3ea541f1f232c493039c15eb995f17321251605c Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 13 Aug 2026 00:12:37 +1000 Subject: [PATCH 2/8] Fix Heltec T1 pin definitions SPI pins that are set to -1 cause OOB reads on NRF52. The unused Serial2 pin definitions were removed to avoid potential issues with the Uart framework. --- variants/heltec_t1/variant.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/variants/heltec_t1/variant.h b/variants/heltec_t1/variant.h index 7ee218161..b52aeba0a 100644 --- a/variants/heltec_t1/variant.h +++ b/variants/heltec_t1/variant.h @@ -25,8 +25,8 @@ #define ST7735_SDA (0 + 24) #define ST7735_SCK (32 + 0) #define ST7735_RESET (0 + 20) -#define ST7735_MISO (-1) -#define ST7735_BUSY (-1) +#define ST7735_MISO (0) // 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED +#define ST7735_BUSY (0) // 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define ST7735_BL (0 + 15) #define VTFT_CTRL (0 + 13) @@ -66,8 +66,7 @@ // UART // No longer populated on PCB. -#define PIN_SERIAL2_RX (-1) -#define PIN_SERIAL2_TX (-1) +// Removed the pin definitions to avoid potential issues with Uart. //////////////////////////////////////////////////////////////////////////////// // I2C From c2a4ef082f1013eb527efeccc699f196f51f504e Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 13 Aug 2026 00:15:34 +1000 Subject: [PATCH 3/8] Fix Heltec MeshPocket SPI pins --- variants/mesh_pocket/variant.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/mesh_pocket/variant.h b/variants/mesh_pocket/variant.h index 870d062a7..9774276cf 100644 --- a/variants/mesh_pocket/variant.h +++ b/variants/mesh_pocket/variant.h @@ -108,7 +108,7 @@ #define PIN_DISPLAY_DC (31) #define PIN_DISPLAY_RST (32 + 4) -#define PIN_SPI1_MISO (-1) +#define PIN_SPI1_MISO (0) // 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define PIN_SPI1_MOSI (20) #define PIN_SPI1_SCK (22) From 7cd1d207af4a645246dc0c0390e54b5e7c335744 Mon Sep 17 00:00:00 2001 From: taco Date: Thu, 13 Aug 2026 00:53:58 +1000 Subject: [PATCH 4/8] Fix LilyGo T-Echo Lite SPI pins --- variants/lilygo_techo_lite/variant.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 38b79c66d..83bb6f07c 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -58,7 +58,7 @@ #define PIN_SPI_MISO _PINNUM(0, 17) // (MISO) #define PIN_SPI_MOSI _PINNUM(0, 15) // (MOSI) #define PIN_SPI_SCK _PINNUM(0, 13) // (SCK) -#define PIN_SPI_NSS (-1) +#define PIN_SPI_NSS (0) //////////////////////////////////////////////////////////////////////////////// // QSPI FLASH @@ -123,7 +123,7 @@ //////////////////////////////////////////////////////////////////////////////// // SPI1 -#define PIN_SPI1_MISO (-1) // Not used for Display +#define PIN_SPI1_MISO (0) // Not used for Display, 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define PIN_SPI1_MOSI _PINNUM(0, 20) #define PIN_SPI1_SCK _PINNUM(0, 19) @@ -135,7 +135,7 @@ extern const int SCK; //////////////////////////////////////////////////////////////////////////////// // Display -// #define DISP_MISO (-1) // Not used for Display +// #define DISP_MISO (0) // Not used for Display, 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define DISP_MOSI _PINNUM(0, 20) #define DISP_SCLK _PINNUM(0, 19) #define DISP_CS _PINNUM(0, 22) @@ -143,7 +143,7 @@ extern const int SCK; #define DISP_RST _PINNUM(0, 28) #define DISP_BUSY _PINNUM(0, 3) #define DISP_POWER _PINNUM(1, 12) -// #define DISP_BACKLIGHT (-1) // Display has no backlight +// #define DISP_BACKLIGHT (0) // Display has no backlight, 0 maps to 0xff on this device which is NRFX_SPIM_PIN_NOT_USED #define PIN_DISPLAY_CS DISP_CS #define PIN_DISPLAY_DC DISP_DC From 890a2e2cffa98dea3ca88db3c3aba72ef23ff3f7 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 14 Aug 2026 16:30:12 +1000 Subject: [PATCH 5/8] * commenting out the load/save of the fem_ properties, until they can be set --- examples/companion_radio/NodePrefs.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 453f28d5d..21766de82 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -53,8 +53,12 @@ private: //def("cad", _parent->cad_enabled); //def("int_thr", _parent->interference_threshold); def("rxgain", _parent->rx_boosted_gain); + #if 0 + // NOTE: these cannot be set (yet) so don't load/save until we can. + // also, fem_rxgain WAS mapped to wrong JSON property previously def("fem_rxgain", _parent->radio_fem_rxgain); def("fem_txgain", _parent->radio_fem_txgain); + #endif def("tx", _parent->tx_power_dbm); def("af", _parent->airtime_factor); def("rxdelay", _parent->rx_delay_base); From e78bff0041393e508f37a4fa45ffd46f5bcb4f90 Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 14 Aug 2026 16:42:54 +1000 Subject: [PATCH 6/8] * unit test no longer valid --- test/test_companion_node_prefs/test_companion_node_prefs.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/test_companion_node_prefs/test_companion_node_prefs.cpp b/test/test_companion_node_prefs/test_companion_node_prefs.cpp index 6d3cebdfe..433c97106 100644 --- a/test/test_companion_node_prefs/test_companion_node_prefs.cpp +++ b/test/test_companion_node_prefs/test_companion_node_prefs.cpp @@ -50,6 +50,8 @@ public: const std::string& text() const { return _text; } }; +#if 0 +// Re-enable test once we can SET fem_ values in companion TEST(CompanionNodePrefs, RxGainSettingsRoundTripIndependently) { NodePrefs saved; saved.rx_boosted_gain = 0; @@ -73,6 +75,7 @@ TEST(CompanionNodePrefs, RxGainSettingsRoundTripIndependently) { EXPECT_EQ(0, loaded.radio_fem_rxgain); EXPECT_EQ(1, loaded.radio_fem_txgain); } +#endif int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); From fcb9bdf2095f4f39d8ed433e1fb8df4278a4880f Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 14 Aug 2026 17:46:43 +1000 Subject: [PATCH 7/8] * combine radio's entropy with CC310 RNG --- src/helpers/radiolib/RadioLibWrappers.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/helpers/radiolib/RadioLibWrappers.h b/src/helpers/radiolib/RadioLibWrappers.h index 5db1e41f0..77dd93116 100644 --- a/src/helpers/radiolib/RadioLibWrappers.h +++ b/src/helpers/radiolib/RadioLibWrappers.h @@ -92,8 +92,10 @@ public: void random(uint8_t* dest, size_t sz) override { #ifdef USE_CC310_HW_CRYPTO - // CC310 TRNG is higher quality and environment-independent vs radio RSSI noise. nRFCrypto.Random.generate(dest, (uint16_t)sz); + for (int i = 0; i < sz; i++) { + dest[i] ^= _radio->randomByte() ^ (::random(0, 256) & 0xFF); // combine with Radio's entropy + } #else for (int i = 0; i < sz; i++) { dest[i] = _radio->randomByte() ^ (::random(0, 256) & 0xFF); From d92964352441e53b93e8667b802e04f6e072b39e Mon Sep 17 00:00:00 2001 From: Scott Powell Date: Fri, 14 Aug 2026 22:19:19 +1000 Subject: [PATCH 8/8] * version 1.17.1 --- examples/companion_radio/MyMesh.h | 4 ++-- examples/simple_repeater/MyMesh.h | 4 ++-- examples/simple_room_server/MyMesh.h | 4 ++-- examples/simple_sensor/SensorMesh.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index f73fe0e06..238adada9 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 13 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "9 Aug 2026" +#define FIRMWARE_BUILD_DATE "14 Aug 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "v1.17.0" +#define FIRMWARE_VERSION "v1.17.1" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 6d9cf459c..04bd4fb92 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -71,11 +71,11 @@ struct NeighbourInfo { }; #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "9 Aug 2026" + #define FIRMWARE_BUILD_DATE "14 Aug 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.17.0" + #define FIRMWARE_VERSION "v1.17.1" #endif #define FIRMWARE_ROLE "repeater" diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index ba9f7eeca..5cf949c6b 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -28,11 +28,11 @@ /* ------------------------------ Config -------------------------------- */ #ifndef FIRMWARE_BUILD_DATE - #define FIRMWARE_BUILD_DATE "9 Aug 2026" + #define FIRMWARE_BUILD_DATE "14 Aug 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.17.0" + #define FIRMWARE_VERSION "v1.17.1" #endif #ifndef LORA_FREQ diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 3a33b638c..b5e96d5cc 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 "9 Aug 2026" + #define FIRMWARE_BUILD_DATE "14 Aug 2026" #endif #ifndef FIRMWARE_VERSION - #define FIRMWARE_VERSION "v1.17.0" + #define FIRMWARE_VERSION "v1.17.1" #endif #define FIRMWARE_ROLE "sensor"