Merge remote-tracking branch 'upstream/dev' into keymindCascade

# Conflicts:
#	examples/companion_radio/MyMesh.cpp
#	examples/companion_radio/NodePrefs.h
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_room_server/MyMesh.cpp
#	examples/simple_room_server/MyMesh.h
#	examples/simple_sensor/SensorMesh.cpp
#	src/Identity.cpp
#	src/MeshCore.h
#	src/Utils.cpp
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	src/helpers/radiolib/CustomLR2021Wrapper.h
#	src/helpers/radiolib/CustomSX1262Wrapper.h
#	src/helpers/radiolib/CustomSX1268Wrapper.h
#	src/helpers/radiolib/RadioLibWrappers.cpp
#	src/helpers/radiolib/RadioLibWrappers.h
#	src/helpers/ui/SH1106Display.cpp
#	variants/minewsemi_me25ls01/target.cpp
#	variants/minewsemi_me25ls01/target.h
#	variants/muziworks_r1_neo/target.cpp
This commit is contained in:
mikecarper committed 2026-08-14 08:14:12 -07:00
commit c2e64da695
28 files changed
+704 -286

No files matched your search

+13
View File
@@ -15,6 +15,10 @@
#include <Adafruit_TinyUSB.h>
#endif
#ifdef USE_CC310_HW_CRYPTO
#include <Adafruit_nRFCrypto.h>
#endif
static BLEDfu bledfu;
static uint16_t ota_conn_handle = BLE_CONN_HANDLE_INVALID;
static bool ota_active = false;
@@ -108,6 +112,11 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
void NRF52Board::begin() {
startup_reason = BD_STARTUP_NORMAL;
#ifdef USE_CC310_HW_CRYPTO
// CC310 TRNG is higher quality and environment-independent vs radio RSSI noise.
nRFCrypto.begin();
#endif
}
#if NRF52_WATCHDOG_TIMEOUT_SECONDS > 0
@@ -541,6 +550,10 @@ void NRF52Board::shutdownPeripherals() {
sensors.getLocationProvider()->stop();
}
#ifdef USE_CC310_HW_CRYPTO
nRFCrypto.end();
#endif
// Flush serial buffers
Serial.flush();
delay(100);
+68
View File
@@ -0,0 +1,68 @@
#pragma once
#include <Packet.h>
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;
}
}
+57 -2
View File
@@ -4,6 +4,10 @@
#include "MeshCore.h"
class CustomLR2021 : public LR2021 {
uint32_t _preambleMillis = 66;
uint32_t _maxPayloadMillis = 3934;
uint32_t _activityAt = 0;
bool _headerSeen = false;
bool _rx_boosted = false;
uint32_t _rf_switch_pins[Module::RFSWITCH_MAX_PINS] = {};
const Module::RfSwitchMode_t* _rf_switch_table = nullptr;
@@ -109,12 +113,63 @@ class CustomLR2021 : public LR2021 {
return busy != RADIOLIB_NC && this->mod->hal->digitalRead(busy);
}
int16_t startReceive() override {
// include the PREAMBLE_DETECTED irq bit in reported flags
return LR2021::startReceive(RADIOLIB_LR2021_RX_TIMEOUT_INF, RADIOLIB_IRQ_RX_DEFAULT_FLAGS | (1UL << RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED), RADIOLIB_IRQ_RX_DEFAULT_MASK, 0);
}
bool isReceiving() {
if (isChipBusy()) return false;
uint32_t irq = getIrqStatus();
bool detected = ((irq & RADIOLIB_LR2021_IRQ_SYNCWORD_VALID) || (irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED));
return detected;
bool preamble = irq & RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED; // bit 5
bool header = irq & RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID; // bit 6
bool hdrErr = irq & RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR; // bit 9
uint32_t now = millis();
if (hdrErr) {
clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID | RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR);
_activityAt = 0;
_headerSeen = false;
return false;
}
if (!header && _headerSeen) {
// something cleared the header flag, reset our state.
_activityAt = 0; _headerSeen = false;
return false;
}
if (header) {
if (!_headerSeen) { _headerSeen = true; _activityAt = now; };
if (now - _activityAt > _maxPayloadMillis) {
MESH_DEBUG_PRINTLN("Clearing header IRQ after %ums", _maxPayloadMillis);
clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED | RADIOLIB_LR2021_IRQ_LORA_HEADER_VALID | RADIOLIB_LR2021_IRQ_LORA_HDR_CRC_ERROR);
_activityAt = 0; _headerSeen = false;
return false;
}
return true;
}
if (preamble) {
if (_activityAt == 0) _activityAt = now;
if (now - _activityAt > _preambleMillis) {
clearIrqFlags(RADIOLIB_LR2021_IRQ_PREAMBLE_DETECTED);
_activityAt = 0;
MESH_DEBUG_PRINTLN("Clearing preamble IRQ after %ums", _preambleMillis);
return false;
}
return true;
}
_activityAt = 0; _headerSeen = false;
return false;
}
void setPreambleMillis(uint32_t preambleMillis) {
_preambleMillis = preambleMillis;
MESH_DEBUG_PRINTLN("Set _preambleMillis=%u", _preambleMillis);
}
void setMaxPayloadMillis(uint32_t payloadMillis) {
_maxPayloadMillis = payloadMillis;
MESH_DEBUG_PRINTLN("Set _maxPayloadMillis=%u", _maxPayloadMillis);
}
uint8_t getSpreadingFactor() const { return spreadingFactor; }
};
+7 -1
View File
@@ -20,12 +20,18 @@ public:
protected:
bool applyParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
return ((CustomLR2021 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
bool success = ((CustomLR2021 *)_radio)->setFrequency(freq) == RADIOLIB_ERR_NONE
&& ((CustomLR2021 *)_radio)->setSpreadingFactor(sf) == RADIOLIB_ERR_NONE
&& ((CustomLR2021 *)_radio)->setBandwidth(bw) == RADIOLIB_ERR_NONE
&& ((CustomLR2021 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE
&& updatePreamble(sf)
&& applySideDetectorConfig(sf, bw);
if (!success) return false;
PacketMillis pm = calcMaxPacketMillis(sf, bw, cr, preambleLengthForSF(sf));
((CustomLR2021 *)_radio)->setPreambleMillis(pm.preambleMillis);
((CustomLR2021 *)_radio)->setMaxPayloadMillis(pm.payloadMillis);
return true;
}
public:
+1 -1
View File
@@ -22,7 +22,7 @@ static volatile uint8_t state = STATE_IDLE;
// this function is called when a complete packet
// is transmitted by the module
static
static
#if defined(ESP8266) || defined(ESP32)
ICACHE_RAM_ATTR
#endif
+16 -10
View File
@@ -24,20 +24,26 @@ bool SH1106Display::begin()
{
// Wire must already be initialised by board.begin() before this is called.
// Boards with non-standard SH1106 addresses should define DISPLAY_ADDRESS
// in their variant/platformio configuration. Some board revisions may have
// different solder-bridge address configurations, so variants can also
// provide DISPLAY_ADDRESS_ALT as a fallback.
if (i2c_probe(Wire, DISPLAY_ADDRESS) && display.begin(DISPLAY_ADDRESS, true)) {
return true;
// in their variant/platformio configuration. The SA0 strap selects 0x3C or
// 0x3D and differs between revisions of the same board. Variants may name a
// preferred alternate explicitly; otherwise try the other address in the
// pair. Always run the Adafruit init so its buffer and device objects exist,
// even when no panel answers the probe.
uint8_t addr = 0;
if (i2c_probe(Wire, DISPLAY_ADDRESS)) {
addr = DISPLAY_ADDRESS;
}
#ifdef DISPLAY_ADDRESS_ALT
if (DISPLAY_ADDRESS_ALT != DISPLAY_ADDRESS &&
i2c_probe(Wire, DISPLAY_ADDRESS_ALT) &&
display.begin(DISPLAY_ADDRESS_ALT, true)) {
return true;
if (addr == 0 && DISPLAY_ADDRESS_ALT != DISPLAY_ADDRESS &&
i2c_probe(Wire, DISPLAY_ADDRESS_ALT)) {
addr = DISPLAY_ADDRESS_ALT;
}
#endif
return false;
if (addr == 0 && i2c_probe(Wire, DISPLAY_ADDRESS ^ 1)) {
addr = DISPLAY_ADDRESS ^ 1;
}
bool ok = display.begin(addr ? addr : DISPLAY_ADDRESS, true);
return addr != 0 && ok;
}
void SH1106Display::turnOn()