diff --git a/.github/workflows/build_firmware.yml b/.github/workflows/build_firmware.yml
index cee38fdaa..d384540a4 100644
--- a/.github/workflows/build_firmware.yml
+++ b/.github/workflows/build_firmware.yml
@@ -56,16 +56,18 @@ jobs:
ota_firmware_source: ${{ steps.ota_dir.outputs.src || '' }}
ota_firmware_target: ${{ steps.ota_dir.outputs.tgt || '' }}
- - name: Echo manifest from release/firmware-*.mt.json to job summary
- if: ${{ always() }}
+ - name: Job summary
env:
PIO_ENV: ${{ inputs.pio_env }}
run: |
- echo "## Manifest: \`$PIO_ENV\`" >> $GITHUB_STEP_SUMMARY
+ echo "## $PIO_ENV" >> $GITHUB_STEP_SUMMARY
+ echo "Manifest
" >> $GITHUB_STEP_SUMMARY
+ echo '' >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat release/firmware-*.mt.json >> $GITHUB_STEP_SUMMARY
echo '' >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
+ echo " " >> $GITHUB_STEP_SUMMARY
- name: Store binaries as an artifact
uses: actions/upload-artifact@v6
diff --git a/.github/workflows/test_native.yml b/.github/workflows/test_native.yml
index cabe0dd97..b527c2fd9 100644
--- a/.github/workflows/test_native.yml
+++ b/.github/workflows/test_native.yml
@@ -143,7 +143,7 @@ jobs:
merge-multiple: true
- name: Test Report
- uses: dorny/test-reporter@v2.3.0
+ uses: dorny/test-reporter@v2.5.0
with:
name: PlatformIO Tests
path: testreport.xml
diff --git a/.trunk/trunk.yaml b/.trunk/trunk.yaml
index c74db9374..b3d6d98e2 100644
--- a/.trunk/trunk.yaml
+++ b/.trunk/trunk.yaml
@@ -8,20 +8,20 @@ plugins:
uri: https://github.com/trunk-io/plugins
lint:
enabled:
- - checkov@3.2.495
- - renovate@42.58.4
+ - checkov@3.2.497
+ - renovate@42.71.2
- prettier@3.7.4
- - trufflehog@3.92.3
+ - trufflehog@3.92.4
- yamllint@1.37.1
- bandit@1.9.2
- trivy@0.68.2
- taplo@0.10.0
- - ruff@0.14.9
+ - ruff@0.14.10
- isort@7.0.0
- markdownlint@0.47.0
- oxipng@10.0.0
- svgo@4.0.0
- - actionlint@1.7.9
+ - actionlint@1.7.10
- flake8@7.3.0
- hadolint@2.14.0
- shfmt@3.6.0
diff --git a/bin/build-nrf52.sh b/bin/build-nrf52.sh
index e3a421865..edcc2add2 100755
--- a/bin/build-nrf52.sh
+++ b/bin/build-nrf52.sh
@@ -21,13 +21,14 @@ rm -f $BUILDDIR/firmware*
export APP_VERSION=$VERSION
basename=firmware-$1-$VERSION
+ota_basename=${basename}-ota
pio run --environment $1 -t mtjson # -v
cp $BUILDDIR/$basename.elf $OUTDIR/$basename.elf
echo "Copying NRF52 dfu (OTA) file"
-cp $BUILDDIR/$basename.zip $OUTDIR/$basename.zip
+cp $BUILDDIR/$basename.zip $OUTDIR/$ota_basename.zip
echo "Copying NRF52 UF2 file"
cp $BUILDDIR/$basename.uf2 $OUTDIR/$basename.uf2
diff --git a/bin/platformio-custom.py b/bin/platformio-custom.py
index 3fdbffb70..b6560f35b 100644
--- a/bin/platformio-custom.py
+++ b/bin/platformio-custom.py
@@ -17,6 +17,8 @@ lfsbin = f"{progname.replace('firmware-', 'littlefs-')}.bin"
def manifest_gather(source, target, env):
out = []
+ board_platform = env.BoardConfig().get("platform")
+ needs_ota_suffix = board_platform == "nordicnrf52"
check_paths = [
progname,
f"{progname}.elf",
@@ -32,8 +34,11 @@ def manifest_gather(source, target, env):
for p in check_paths:
f = env.File(env.subst(f"$BUILD_DIR/{p}"))
if f.exists():
+ manifest_name = p
+ if needs_ota_suffix and p == f"{progname}.zip":
+ manifest_name = f"{progname}-ota.zip"
d = {
- "name": p,
+ "name": manifest_name,
"md5": f.get_content_hash(), # Returns MD5 hash
"bytes": f.get_size() # Returns file size in bytes
}
diff --git a/platformio.ini b/platformio.ini
index 9cef4f375..dd8111346 100644
--- a/platformio.ini
+++ b/platformio.ini
@@ -64,7 +64,7 @@ monitor_speed = 115200
monitor_filters = direct
lib_deps =
# renovate: datasource=git-refs depName=meshtastic-esp8266-oled-ssd1306 packageName=https://github.com/meshtastic/esp8266-oled-ssd1306 gitBranch=master
- https://github.com/meshtastic/esp8266-oled-ssd1306/archive/2887bf4a19f64d92c984dcc8fd5ca7429e425e4a.zip
+ https://github.com/meshtastic/esp8266-oled-ssd1306/archive/b34c6817c25d6faabb3a8a162b5d14fb75395433.zip
# renovate: datasource=git-refs depName=meshtastic-OneButton packageName=https://github.com/meshtastic/OneButton gitBranch=master
https://github.com/meshtastic/OneButton/archive/fa352d668c53f290cfa480a5f79ad422cd828c70.zip
# renovate: datasource=git-refs depName=meshtastic-arduino-fsm packageName=https://github.com/meshtastic/arduino-fsm gitBranch=master
@@ -123,7 +123,7 @@ lib_deps =
[device-ui_base]
lib_deps =
# renovate: datasource=git-refs depName=meshtastic/device-ui packageName=https://github.com/meshtastic/device-ui gitBranch=master
- https://github.com/meshtastic/device-ui/archive/862ed040c4ab44f0dfbbe492691f144886102588.zip
+ https://github.com/meshtastic/device-ui/archive/272defcb35651461830ebfd1b39c9167c8f49317.zip
; Common libs for environmental measurements in telemetry module
[environmental_base]
diff --git a/protobufs b/protobufs
index 9beb80f1d..c2e45a3fc 160000
--- a/protobufs
+++ b/protobufs
@@ -1 +1 @@
-Subproject commit 9beb80f1d302f70d05f9c4bc9dd543b8f7bc8796
+Subproject commit c2e45a3fc9cda6aedb72ad3b5b88fcccfa78073e
diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp
index db269ac64..45e7fda2d 100644
--- a/src/detect/ScanI2CTwoWire.cpp
+++ b/src/detect/ScanI2CTwoWire.cpp
@@ -68,7 +68,7 @@ ScanI2C::DeviceType ScanI2CTwoWire::probeOLED(ScanI2C::DeviceAddress addr) const
if (r == 0x08 || r == 0x00) {
logFoundDevice("SH1106", (uint8_t)addr.address);
o_probe = SCREEN_SH1106; // SH1106
- } else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07) {
+ } else if (r == 0x03 || r == 0x04 || r == 0x06 || r == 0x07 || r == 0x05) {
logFoundDevice("SSD1306", (uint8_t)addr.address);
o_probe = SCREEN_SSD1306; // SSD1306
}
diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp
index 4209baf5d..1678da793 100644
--- a/src/graphics/EInkDisplay2.cpp
+++ b/src/graphics/EInkDisplay2.cpp
@@ -148,7 +148,7 @@ bool EInkDisplay::connect()
#endif
#endif
-#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE)
+#if defined(TTGO_T_ECHO) || defined(ELECROW_ThinkNode_M1) || defined(T_ECHO_LITE) || defined(TTGO_T_ECHO_PLUS)
{
auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, SPI1);
diff --git a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
index 88bed998d..ad0f9fc47 100644
--- a/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
+++ b/src/graphics/niche/InkHUD/Applets/User/Positions/PositionsApplet.cpp
@@ -1,6 +1,7 @@
#ifdef MESHTASTIC_INCLUDE_INKHUD
#include "./PositionsApplet.h"
+#include "NodeDB.h"
using namespace NicheGraphics;
@@ -49,8 +50,8 @@ ProcessMessage InkHUD::PositionsApplet::handleReceived(const meshtastic_MeshPack
if (!hasPosition)
return ProcessMessage::CONTINUE;
- bool hasHopsAway = (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start); // From NodeDB::updateFrom
- uint8_t hopsAway = mp.hop_start - mp.hop_limit;
+ const int8_t hopsAway = getHopsAway(mp);
+ const bool hasHopsAway = hopsAway >= 0;
// Determine if the position packet would change anything on-screen
// -----------------------------------------------------------------
diff --git a/src/input/ButtonThread.cpp b/src/input/ButtonThread.cpp
index 9f53b06f4..63fd8b0d7 100644
--- a/src/input/ButtonThread.cpp
+++ b/src/input/ButtonThread.cpp
@@ -37,6 +37,9 @@ bool ButtonThread::initButton(const ButtonConfig &config)
_activeLow = config.activeLow;
_touchQuirk = config.touchQuirk;
_intRoutine = config.intRoutine;
+ _pressHandler = config.onPress;
+ _releaseHandler = config.onRelease;
+ _suppressLeadUp = config.suppressLeadUpSound;
_longLongPress = config.longLongPress;
userButton = OneButton(config.pinNumber, config.activeLow, config.activePullup);
@@ -133,6 +136,8 @@ int32_t ButtonThread::runOnce()
// Detect start of button press
if (buttonCurrentlyPressed && !buttonWasPressed) {
+ if (_pressHandler)
+ _pressHandler();
buttonPressStartTime = millis();
leadUpPlayed = false;
leadUpSequenceActive = false;
@@ -140,7 +145,7 @@ int32_t ButtonThread::runOnce()
}
// Progressive lead-up sound system
- if (buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
+ if (!_suppressLeadUp && buttonCurrentlyPressed && (millis() - buttonPressStartTime) >= BUTTON_LEADUP_MS) {
// Start the progressive sequence if not already active
if (!leadUpSequenceActive) {
@@ -160,6 +165,8 @@ int32_t ButtonThread::runOnce()
// Reset when button is released
if (!buttonCurrentlyPressed && buttonWasPressed) {
+ if (_releaseHandler)
+ _releaseHandler();
leadUpSequenceActive = false;
resetLeadUpSequence();
}
diff --git a/src/input/ButtonThread.h b/src/input/ButtonThread.h
index 7de38341c..e724c3596 100644
--- a/src/input/ButtonThread.h
+++ b/src/input/ButtonThread.h
@@ -13,6 +13,9 @@ struct ButtonConfig {
bool activePullup = true;
uint32_t pullupSense = 0;
voidFuncPtr intRoutine = nullptr;
+ voidFuncPtr onPress = nullptr; // Optional edge callbacks
+ voidFuncPtr onRelease = nullptr; // Optional edge callbacks
+ bool suppressLeadUpSound = false;
input_broker_event singlePress = INPUT_BROKER_NONE;
input_broker_event longPress = INPUT_BROKER_NONE;
uint16_t longPressTime = 500;
@@ -94,6 +97,9 @@ class ButtonThread : public Observable, public concurrency::
input_broker_event _shortLong = INPUT_BROKER_NONE;
voidFuncPtr _intRoutine = nullptr;
+ voidFuncPtr _pressHandler = nullptr;
+ voidFuncPtr _releaseHandler = nullptr;
+ bool _suppressLeadUp = false;
uint16_t _longPressTime = 500;
uint16_t _longLongPressTime = 3900;
int _pinNum = 0;
diff --git a/src/input/RotaryEncoderInterruptImpl1.cpp b/src/input/RotaryEncoderInterruptImpl1.cpp
index 12cbc36fb..1da2ea008 100644
--- a/src/input/RotaryEncoderInterruptImpl1.cpp
+++ b/src/input/RotaryEncoderInterruptImpl1.cpp
@@ -27,7 +27,9 @@ bool RotaryEncoderInterruptImpl1::init()
RotaryEncoderInterruptImpl1::handleIntA, RotaryEncoderInterruptImpl1::handleIntB,
RotaryEncoderInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
+#endif
return true;
}
diff --git a/src/input/TrackballInterruptBase.cpp b/src/input/TrackballInterruptBase.cpp
index d2025c192..bbd07e199 100644
--- a/src/input/TrackballInterruptBase.cpp
+++ b/src/input/TrackballInterruptBase.cpp
@@ -45,7 +45,9 @@ void TrackballInterruptBase::init(uint8_t pinDown, uint8_t pinUp, uint8_t pinLef
LOG_DEBUG("Trackball GPIO initialized - UP:%d DOWN:%d LEFT:%d RIGHT:%d PRESS:%d", this->_pinUp, this->_pinDown,
this->_pinLeft, this->_pinRight, pinPress);
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
+#endif
this->setInterval(100);
}
diff --git a/src/input/UpDownInterruptImpl1.cpp b/src/input/UpDownInterruptImpl1.cpp
index 9b0b1f39e..906dcd2a8 100644
--- a/src/input/UpDownInterruptImpl1.cpp
+++ b/src/input/UpDownInterruptImpl1.cpp
@@ -29,7 +29,9 @@ bool UpDownInterruptImpl1::init()
eventDownLong, UpDownInterruptImpl1::handleIntDown, UpDownInterruptImpl1::handleIntUp,
UpDownInterruptImpl1::handleIntPressed);
inputBroker->registerSource(this);
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
+#endif
return true;
}
diff --git a/src/main.cpp b/src/main.cpp
index eb6dea327..97bb21475 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -107,6 +107,10 @@ NRF52Bluetooth *nrf52Bluetooth = nullptr;
#if defined(BUTTON_PIN_TOUCH)
ButtonThread *TouchButtonThread = nullptr;
+#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
+static bool touchBacklightWasOn = false;
+static bool touchBacklightActive = false;
+#endif
#endif
#if defined(BUTTON_PIN) || defined(ARCH_PORTDUINO)
@@ -205,7 +209,7 @@ ScanI2C::FoundDevice rgb_found = ScanI2C::FoundDevice(ScanI2C::DeviceType::NONE,
/// The I2C address of our Air Quality Indicator (if found)
ScanI2C::DeviceAddress aqi_found = ScanI2C::ADDRESS_NONE;
-#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
+#ifdef HAS_DRV2605
Adafruit_DRV2605 drv;
#endif
@@ -440,9 +444,11 @@ void setup()
LOG_INFO("\n\n//\\ E S H T /\\ S T / C\n");
#if defined(ARCH_ESP32) && defined(BOARD_HAS_PSRAM)
+#ifndef SENSECAP_INDICATOR
// use PSRAM for malloc calls > 256 bytes
heap_caps_malloc_extmem_enable(256);
#endif
+#endif
#if defined(DEBUG_MUTE) && defined(DEBUG_PORT)
DEBUG_PORT.printf("\r\n\r\n//\\ E S H T /\\ S T / C\r\n");
@@ -786,7 +792,6 @@ void setup()
// We do this as early as possible because this loads preferences from flash
// but we need to do this after main cpu init (esp32setup), because we need the random seed set
nodeDB = new NodeDB;
-
#if HAS_TFT
if (config.display.displaymode == meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
tftSetup();
@@ -832,7 +837,12 @@ void setup()
#endif
#endif
-#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
+#ifdef HAS_DRV2605
+#if defined(PIN_DRV_EN)
+ pinMode(PIN_DRV_EN, OUTPUT);
+ digitalWrite(PIN_DRV_EN, HIGH);
+ delay(10);
+#endif
drv.begin();
drv.selectLibrary(1);
// I2C trigger by sending 'go' command
@@ -868,7 +878,7 @@ void setup()
SPI.begin();
#endif
#else
- // ESP32
+// ESP32
#if defined(HW_SPI1_DEVICE)
SPI1.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
LOG_DEBUG("SPI1.begin(SCK=%d, MISO=%d, MOSI=%d, NSS=%d)", LORA_SCK, LORA_MISO, LORA_MOSI, LORA_CS);
@@ -1037,6 +1047,24 @@ void setup()
};
touchConfig.singlePress = INPUT_BROKER_NONE;
touchConfig.longPress = INPUT_BROKER_BACK;
+#if defined(TTGO_T_ECHO_PLUS) && defined(PIN_EINK_EN)
+ // On T-Echo Plus the touch pad should only drive the backlight, not UI navigation/sounds
+ touchConfig.longPress = INPUT_BROKER_NONE;
+ touchConfig.suppressLeadUpSound = true;
+ touchConfig.onPress = []() {
+ touchBacklightWasOn = uiconfig.screen_brightness == 1;
+ if (!touchBacklightWasOn) {
+ digitalWrite(PIN_EINK_EN, HIGH);
+ }
+ touchBacklightActive = true;
+ };
+ touchConfig.onRelease = []() {
+ if (touchBacklightActive && !touchBacklightWasOn) {
+ digitalWrite(PIN_EINK_EN, LOW);
+ }
+ touchBacklightActive = false;
+ };
+#endif
TouchButtonThread->initButton(touchConfig);
#endif
@@ -1456,8 +1484,10 @@ void setup()
#endif
#if defined(HAS_TRACKBALL) || (defined(INPUTDRIVER_ENCODER_TYPE) && INPUTDRIVER_ENCODER_TYPE == 2)
+#ifndef HAS_PHYSICAL_KEYBOARD
osk_found = true;
#endif
+#endif
#if defined(ARCH_ESP32) && !MESHTASTIC_EXCLUDE_WEBSERVER
// Start web server thread.
diff --git a/src/main.h b/src/main.h
index 414752b5c..7ca14d825 100644
--- a/src/main.h
+++ b/src/main.h
@@ -42,7 +42,7 @@ extern bool eink_found;
extern bool pmu_found;
extern bool isUSBPowered;
-#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
+#ifdef HAS_DRV2605
#include
extern Adafruit_DRV2605 drv;
#endif
diff --git a/src/mesh/FloodingRouter.cpp b/src/mesh/FloodingRouter.cpp
index 032be241b..b7459abe0 100644
--- a/src/mesh/FloodingRouter.cpp
+++ b/src/mesh/FloodingRouter.cpp
@@ -124,6 +124,10 @@ void FloodingRouter::perhapsCancelDupe(const meshtastic_MeshPacket *p)
if (config.device.role == meshtastic_Config_DeviceConfig_Role_ROUTER_LATE && iface) {
iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
}
+ if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE && iface && nodeDB &&
+ nodeDB->isFromOrToFavoritedNode(*p)) {
+ iface->clampToLateRebroadcastWindow(getFrom(p), p->id);
+ }
}
bool FloodingRouter::isRebroadcaster()
diff --git a/src/mesh/MeshModule.cpp b/src/mesh/MeshModule.cpp
index c5748a560..83b64a873 100644
--- a/src/mesh/MeshModule.cpp
+++ b/src/mesh/MeshModule.cpp
@@ -195,7 +195,7 @@ void MeshModule::callModules(meshtastic_MeshPacket &mp, RxSource src)
// but opted NOT TO. Because it is not a good idea to let remote nodes 'probe' to find out which PSKs were "good" vs
// bad.
routingModule->sendAckNak(meshtastic_Routing_Error_NO_RESPONSE, getFrom(&mp), mp.id, mp.channel,
- routingModule->getHopLimitForResponse(mp.hop_start, mp.hop_limit));
+ routingModule->getHopLimitForResponse(mp));
}
}
@@ -235,7 +235,7 @@ void setReplyTo(meshtastic_MeshPacket *p, const meshtastic_MeshPacket &to)
assert(p->which_payload_variant == meshtastic_MeshPacket_decoded_tag); // Should already be set by now
p->to = getFrom(&to); // Make sure that if we are sending to the local node, we use our local node addr, not 0
p->channel = to.channel; // Use the same channel that the request came in on
- p->hop_limit = routingModule->getHopLimitForResponse(to.hop_start, to.hop_limit);
+ p->hop_limit = routingModule->getHopLimitForResponse(to);
// No need for an ack if we are just delivering locally (it just generates an ignored ack)
p->want_ack = (to.from != 0) ? to.want_ack : false;
diff --git a/src/mesh/MeshService.cpp b/src/mesh/MeshService.cpp
index 1b2af082d..368642bf8 100644
--- a/src/mesh/MeshService.cpp
+++ b/src/mesh/MeshService.cpp
@@ -93,11 +93,8 @@ int MeshService::handleFromRadio(const meshtastic_MeshPacket *mp)
} else if (mp->which_payload_variant == meshtastic_MeshPacket_decoded_tag && !nodeDB->getMeshNode(mp->from)->has_user &&
nodeInfoModule && !isPreferredRebroadcaster && !nodeDB->isFull()) {
if (airTime->isTxAllowedChannelUtil(true)) {
- // Hops used by the request. If somebody in between running modified firmware modified it, ignore it
- auto hopStart = mp->hop_start;
- auto hopLimit = mp->hop_limit;
- uint8_t hopsUsed = hopStart < hopLimit ? config.lora.hop_limit : hopStart - hopLimit;
- if (hopsUsed > config.lora.hop_limit + 2) {
+ const int8_t hopsUsed = getHopsAway(*mp, config.lora.hop_limit);
+ if (hopsUsed > (int32_t)(config.lora.hop_limit + 2)) {
LOG_DEBUG("Skip send NodeInfo: %d hops away is too far away", hopsUsed);
} else {
LOG_INFO("Heard new node on ch. %d, send NodeInfo and ask for response", mp->channel);
@@ -276,6 +273,10 @@ bool MeshService::trySendPosition(NodeNum dest, bool wantReplies)
if (nodeDB->hasValidPosition(node)) {
#if HAS_GPS && !MESHTASTIC_EXCLUDE_GPS
if (positionModule) {
+ if (!config.position.fixed_position && !nodeDB->hasLocalPositionSinceBoot()) {
+ LOG_DEBUG("Skip position ping; no fresh position since boot");
+ return false;
+ }
LOG_INFO("Send position ping to 0x%x, wantReplies=%d, channel=%d", dest, wantReplies, node->channel);
positionModule->sendOurPosition(dest, wantReplies, node->channel);
return true;
diff --git a/src/mesh/NextHopRouter.cpp b/src/mesh/NextHopRouter.cpp
index afdb4d096..5230e5b85 100644
--- a/src/mesh/NextHopRouter.cpp
+++ b/src/mesh/NextHopRouter.cpp
@@ -64,7 +64,7 @@ bool NextHopRouter::shouldFilterReceived(const meshtastic_MeshPacket *p)
perhapsRebroadcast(p);
}
} else {
- bool isRepeated = p->hop_start > 0 && p->hop_start == p->hop_limit;
+ bool isRepeated = getHopsAway(*p) == 0;
// If repeated and not in Tx queue anymore, try relaying again, or if we are the destination, send the ACK again
if (isRepeated) {
if (!findInTxQueue(p->from, p->id)) {
@@ -101,8 +101,7 @@ void NextHopRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtast
bool wasAlreadyRelayer = wasRelayer(p->relay_node, p->decoded.request_id, p->to);
bool weWereSoleRelayer = false;
bool weWereRelayer = wasRelayer(ourRelayID, p->decoded.request_id, p->to, &weWereSoleRelayer);
- if ((weWereRelayer && wasAlreadyRelayer) ||
- (p->hop_start != 0 && p->hop_start == p->hop_limit && weWereSoleRelayer)) {
+ if ((weWereRelayer && wasAlreadyRelayer) || (getHopsAway(*p) == 0 && weWereSoleRelayer)) {
if (origTx->next_hop != p->relay_node) { // Not already set
LOG_INFO("Update next hop of 0x%x to 0x%x based on ACK/reply (was relayer %d we were sole %d)", p->from,
p->relay_node, wasAlreadyRelayer, weWereSoleRelayer);
diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp
index 10303437d..3c408f01f 100644
--- a/src/mesh/NodeDB.cpp
+++ b/src/mesh/NodeDB.cpp
@@ -805,7 +805,8 @@ void NodeDB::installDefaultModuleConfig()
moduleConfig.external_notification.output_ms = 500;
moduleConfig.external_notification.nag_timeout = 2;
#endif
-#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3)
+#if defined(RAK4630) || defined(RAK11310) || defined(RAK3312) || defined(MUZI_BASE) || defined(ELECROW_ThinkNode_M3) || \
+ defined(ELECROW_ThinkNode_M6)
// Default to PIN_LED2 for external notification output (LED color depends on device variant)
moduleConfig.external_notification.enabled = true;
moduleConfig.external_notification.output = PIN_LED2;
@@ -1043,6 +1044,7 @@ void NodeDB::clearLocalPosition()
node->position.altitude = 0;
node->position.time = 0;
setLocalPosition(meshtastic_Position_init_default);
+ localPositionUpdatedSinceBoot = false;
}
void NodeDB::cleanupMeshDB()
@@ -1547,6 +1549,23 @@ uint32_t sinceReceived(const meshtastic_MeshPacket *p)
return delta;
}
+int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown)
+{
+ // Firmware prior to 2.3.0 (585805c) lacked a hop_start field. Firmware version 2.5.0 (bf34329) introduced a
+ // bitfield that is always present. Use the presence of the bitfield to determine if the origin's firmware
+ // version is guaranteed to have hop_start populated. Note that this can only be done for decoded packets as
+ // the bitfield is encrypted under the channel encryption key. For encrypted packets, this returns
+ // defaultIfUnknown when hop_start is 0.
+ if (p.hop_start == 0 && !(p.which_payload_variant == meshtastic_MeshPacket_decoded_tag && p.decoded.has_bitfield))
+ return defaultIfUnknown; // Cannot reliably determine the number of hops.
+
+ // Guard against invalid values.
+ if (p.hop_start < p.hop_limit)
+ return defaultIfUnknown;
+
+ return p.hop_start - p.hop_limit;
+}
+
#define NUM_ONLINE_SECS (60 * 60 * 2) // 2 hrs to consider someone offline
size_t NodeDB::getNumOnlineMeshNodes(bool localOnly)
@@ -1799,9 +1818,10 @@ void NodeDB::updateFrom(const meshtastic_MeshPacket &mp)
info->via_mqtt = mp.via_mqtt; // Store if we received this packet via MQTT
// If hopStart was set and there wasn't someone messing with the limit in the middle, add hopsAway
- if (mp.hop_start != 0 && mp.hop_limit <= mp.hop_start) {
+ const int8_t hopsAway = getHopsAway(mp);
+ if (hopsAway >= 0) {
info->has_hops_away = true;
- info->hops_away = mp.hop_start - mp.hop_limit;
+ info->hops_away = hopsAway;
}
sortMeshDB();
}
diff --git a/src/mesh/NodeDB.h b/src/mesh/NodeDB.h
index 306acc0a5..817e31617 100644
--- a/src/mesh/NodeDB.h
+++ b/src/mesh/NodeDB.h
@@ -110,6 +110,10 @@ uint32_t sinceLastSeen(const meshtastic_NodeInfoLite *n);
/// Given a packet, return how many seconds in the past (vs now) it was received
uint32_t sinceReceived(const meshtastic_MeshPacket *p);
+/// Given a packet, return the number of hops used to reach this node.
+/// Returns defaultIfUnknown if the number of hops couldn't be determined.
+int8_t getHopsAway(const meshtastic_MeshPacket &p, int8_t defaultIfUnknown = -1);
+
enum LoadFileResult {
// Successfully opened the file
LOAD_SUCCESS = 1,
@@ -279,9 +283,13 @@ class NodeDB
LOG_DEBUG("Set local position: lat=%i lon=%i time=%u timestamp=%u", position.latitude_i, position.longitude_i,
position.time, position.timestamp);
localPosition = position;
+ if (position.latitude_i != 0 || position.longitude_i != 0) {
+ localPositionUpdatedSinceBoot = true;
+ }
}
bool hasValidPosition(const meshtastic_NodeInfoLite *n);
+ bool hasLocalPositionSinceBoot() const { return localPositionUpdatedSinceBoot; }
#if !defined(MESHTASTIC_EXCLUDE_PKI)
bool checkLowEntropyPublicKey(const meshtastic_Config_SecurityConfig_public_key_t &keyToTest);
@@ -301,6 +309,7 @@ class NodeDB
private:
bool duplicateWarned = false;
+ bool localPositionUpdatedSinceBoot = false;
uint32_t lastNodeDbSave = 0; // when we last saved our db to flash
uint32_t lastBackupAttempt = 0; // when we last tried a backup automatically or manually
uint32_t lastSort = 0; // When last sorted the nodeDB
diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp
index db8677821..f7daf1122 100644
--- a/src/mesh/RadioInterface.cpp
+++ b/src/mesh/RadioInterface.cpp
@@ -296,11 +296,6 @@ bool RadioInterface::shouldRebroadcastEarlyLikeRouter(meshtastic_MeshPacket *p)
return true;
}
- // If we are a CLIENT_BASE and the packet is from or to a favorited node, we should rebroadcast early
- if (config.device.role == meshtastic_Config_DeviceConfig_Role_CLIENT_BASE) {
- return nodeDB->isFromOrToFavoritedNode(*p);
- }
-
return false;
}
diff --git a/src/mesh/ReliableRouter.cpp b/src/mesh/ReliableRouter.cpp
index 7619fc106..2b9b17183 100644
--- a/src/mesh/ReliableRouter.cpp
+++ b/src/mesh/ReliableRouter.cpp
@@ -1,6 +1,7 @@
#include "ReliableRouter.h"
#include "Default.h"
#include "MeshTypes.h"
+#include "NodeDB.h"
#include "configuration.h"
#include "memGet.h"
#include "mesh-pb-constants.h"
@@ -108,12 +109,12 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
// If this packet should always be ACKed reliably with want_ack back to the original sender, make sure we
// do that unconditionally.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
- routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit), true);
+ routingModule->getHopLimitForResponse(*p), true);
} else if (!p->decoded.request_id && !p->decoded.reply_id) {
// If it's not an ACK or a reply, send an ACK.
sendAckNak(meshtastic_Routing_Error_NONE, getFrom(p), p->id, p->channel,
- routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
- } else if ((p->hop_start > 0 && p->hop_start == p->hop_limit) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
+ routingModule->getHopLimitForResponse(*p));
+ } else if ((getHopsAway(*p) == 0) || p->next_hop != NO_NEXT_HOP_PREFERENCE) {
// If we received the packet directly from the original sender, send a 0-hop ACK since the original sender
// won't overhear any implicit ACKs. If we received the packet via NextHopRouter, also send a 0-hop ACK to
// stop the immediate relayer's retransmissions.
@@ -123,11 +124,11 @@ void ReliableRouter::sniffReceived(const meshtastic_MeshPacket *p, const meshtas
(nodeDB->getMeshNode(p->from) == nullptr || nodeDB->getMeshNode(p->from)->user.public_key.size == 0)) {
LOG_INFO("PKI packet from unknown node, send PKI_UNKNOWN_PUBKEY");
sendAckNak(meshtastic_Routing_Error_PKI_UNKNOWN_PUBKEY, getFrom(p), p->id, channels.getPrimaryIndex(),
- routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+ routingModule->getHopLimitForResponse(*p));
} else {
// Send a 'NO_CHANNEL' error on the primary channel if want_ack packet destined for us cannot be decoded
sendAckNak(meshtastic_Routing_Error_NO_CHANNEL, getFrom(p), p->id, channels.getPrimaryIndex(),
- routingModule->getHopLimitForResponse(p->hop_start, p->hop_limit));
+ routingModule->getHopLimitForResponse(*p));
}
} else if (p->next_hop == nodeDB->getLastByteOfNodeNum(getNodeNum()) && p->hop_limit > 0) {
// No wantAck, but we need to ACK with hop limit of 0 if we were the next hop to stop their retransmissions
diff --git a/src/mesh/Router.cpp b/src/mesh/Router.cpp
index ad0c0be6f..3ed4a898a 100644
--- a/src/mesh/Router.cpp
+++ b/src/mesh/Router.cpp
@@ -81,8 +81,7 @@ Router::Router() : concurrency::OSThread("Router"), fromRadioQueue(MAX_RX_FROMRA
bool Router::shouldDecrementHopLimit(const meshtastic_MeshPacket *p)
{
// First hop MUST always decrement to prevent retry issues
- bool isFirstHop = (p->hop_start != 0 && p->hop_start == p->hop_limit);
- if (isFirstHop) {
+ if (getHopsAway(*p) == 0) {
return true; // Always decrement on first hop
}
@@ -745,15 +744,19 @@ void Router::handleReceived(meshtastic_MeshPacket *p, RxSource src)
MeshModule::callModules(*p, src);
#if !MESHTASTIC_EXCLUDE_MQTT
- // Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not to
- // us (because we would be able to decrypt it)
- if (decodedState == DecodeState::DECODE_FAILURE && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 &&
- !isBroadcast(p->to) && !isToUs(p))
- p_encrypted->pki_encrypted = true;
- // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
- if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
- !isFromUs(p) && mqtt)
- mqtt->onSend(*p_encrypted, *p, p->channel);
+ if (p_encrypted == nullptr) {
+ LOG_WARN("p_encrypted is null, skipping MQTT publish");
+ } else {
+ // Mark as pki_encrypted if it is not yet decoded and MQTT encryption is also enabled, hash matches and it's a DM not
+ // to us (because we would be able to decrypt it)
+ if (decodedState == DecodeState::DECODE_FAILURE && moduleConfig.mqtt.encryption_enabled && p->channel == 0x00 &&
+ !isBroadcast(p->to) && !isToUs(p))
+ p_encrypted->pki_encrypted = true;
+ // After potentially altering it, publish received message to MQTT if we're not the original transmitter of the packet
+ if ((decodedState == DecodeState::DECODE_SUCCESS || p_encrypted->pki_encrypted) && moduleConfig.mqtt.enabled &&
+ !isFromUs(p) && mqtt)
+ mqtt->onSend(*p_encrypted, *p, p->channel);
+ }
#endif
}
diff --git a/src/mesh/generated/meshtastic/admin.pb.cpp b/src/mesh/generated/meshtastic/admin.pb.cpp
index 4c4d0e3d1..e358bc96d 100644
--- a/src/mesh/generated/meshtastic/admin.pb.cpp
+++ b/src/mesh/generated/meshtastic/admin.pb.cpp
@@ -12,6 +12,9 @@ PB_BIND(meshtastic_AdminMessage, meshtastic_AdminMessage, 2)
PB_BIND(meshtastic_AdminMessage_InputEvent, meshtastic_AdminMessage_InputEvent, AUTO)
+PB_BIND(meshtastic_AdminMessage_OTAEvent, meshtastic_AdminMessage_OTAEvent, AUTO)
+
+
PB_BIND(meshtastic_HamParameters, meshtastic_HamParameters, AUTO)
@@ -33,3 +36,5 @@ PB_BIND(meshtastic_KeyVerificationAdmin, meshtastic_KeyVerificationAdmin, AUTO)
+
+
diff --git a/src/mesh/generated/meshtastic/admin.pb.h b/src/mesh/generated/meshtastic/admin.pb.h
index a542cf29c..ec6e13e9e 100644
--- a/src/mesh/generated/meshtastic/admin.pb.h
+++ b/src/mesh/generated/meshtastic/admin.pb.h
@@ -16,6 +16,16 @@
#endif
/* Enum definitions */
+/* Firmware update mode for OTA updates */
+typedef enum _meshtastic_OTAMode {
+ /* Do not reboot into OTA mode */
+ meshtastic_OTAMode_NO_REBOOT_OTA = 0,
+ /* Reboot into OTA mode for BLE firmware update */
+ meshtastic_OTAMode_OTA_BLE = 1,
+ /* Reboot into OTA mode for WiFi firmware update */
+ meshtastic_OTAMode_OTA_WIFI = 2
+} meshtastic_OTAMode;
+
/* TODO: REPLACE */
typedef enum _meshtastic_AdminMessage_ConfigType {
/* TODO: REPLACE */
@@ -103,6 +113,17 @@ typedef struct _meshtastic_AdminMessage_InputEvent {
uint16_t touch_y;
} meshtastic_AdminMessage_InputEvent;
+typedef PB_BYTES_ARRAY_T(32) meshtastic_AdminMessage_OTAEvent_ota_hash_t;
+/* User is requesting an over the air update.
+ Node will reboot into the OTA loader */
+typedef struct _meshtastic_AdminMessage_OTAEvent {
+ /* Tell the node to reboot into OTA mode for firmware update via BLE or WiFi (ESP32 only for now) */
+ meshtastic_OTAMode reboot_ota_mode;
+ /* A 32 byte hash of the OTA firmware.
+ Used to verify the integrity of the firmware before applying an update. */
+ meshtastic_AdminMessage_OTAEvent_ota_hash_t ota_hash;
+} meshtastic_AdminMessage_OTAEvent;
+
/* Parameters for setting up Meshtastic for ameteur radio usage */
typedef struct _meshtastic_HamParameters {
/* Amateur radio call sign, eg. KD2ABC */
@@ -261,7 +282,8 @@ typedef struct _meshtastic_AdminMessage {
/* Tell the node to factory reset config everything; all device state and configuration will be returned to factory defaults and BLE bonds will be cleared. */
int32_t factory_reset_device;
/* Tell the node to reboot into the OTA Firmware in this many seconds (or <0 to cancel reboot)
- Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth. */
+ Only Implemented for ESP32 Devices. This needs to be issued to send a new main firmware via bluetooth.
+ Deprecated in favor of reboot_ota_mode in 2.7.17 */
int32_t reboot_ota_seconds;
/* This message is only supported for the simulator Portduino build.
If received the simulator will exit successfully. */
@@ -275,6 +297,8 @@ typedef struct _meshtastic_AdminMessage {
/* Tell the node to reset the nodedb.
When true, favorites are preserved through reset. */
bool nodedb_reset;
+ /* Tell the node to reset into the OTA Loader */
+ meshtastic_AdminMessage_OTAEvent ota_request;
};
/* The node generates this key and sends it with any get_x_response packets.
The client MUST include the same key with any set_x commands. Key expires after 300 seconds.
@@ -288,6 +312,10 @@ extern "C" {
#endif
/* Helper constants for enums */
+#define _meshtastic_OTAMode_MIN meshtastic_OTAMode_NO_REBOOT_OTA
+#define _meshtastic_OTAMode_MAX meshtastic_OTAMode_OTA_WIFI
+#define _meshtastic_OTAMode_ARRAYSIZE ((meshtastic_OTAMode)(meshtastic_OTAMode_OTA_WIFI+1))
+
#define _meshtastic_AdminMessage_ConfigType_MIN meshtastic_AdminMessage_ConfigType_DEVICE_CONFIG
#define _meshtastic_AdminMessage_ConfigType_MAX meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG
#define _meshtastic_AdminMessage_ConfigType_ARRAYSIZE ((meshtastic_AdminMessage_ConfigType)(meshtastic_AdminMessage_ConfigType_DEVICEUI_CONFIG+1))
@@ -311,6 +339,8 @@ extern "C" {
#define meshtastic_AdminMessage_payload_variant_remove_backup_preferences_ENUMTYPE meshtastic_AdminMessage_BackupLocation
+#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_ENUMTYPE meshtastic_OTAMode
+
@@ -320,12 +350,14 @@ extern "C" {
/* Initializer values for message structs */
#define meshtastic_AdminMessage_init_default {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_default {0, 0, 0, 0}
+#define meshtastic_AdminMessage_OTAEvent_init_default {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_HamParameters_init_default {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_default {0, {meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default, meshtastic_NodeRemoteHardwarePin_init_default}}
#define meshtastic_SharedContact_init_default {0, false, meshtastic_User_init_default, 0, 0}
#define meshtastic_KeyVerificationAdmin_init_default {_meshtastic_KeyVerificationAdmin_MessageType_MIN, 0, 0, false, 0}
#define meshtastic_AdminMessage_init_zero {0, {0}, {0, {0}}}
#define meshtastic_AdminMessage_InputEvent_init_zero {0, 0, 0, 0}
+#define meshtastic_AdminMessage_OTAEvent_init_zero {_meshtastic_OTAMode_MIN, {0, {0}}}
#define meshtastic_HamParameters_init_zero {"", 0, 0, ""}
#define meshtastic_NodeRemoteHardwarePinsResponse_init_zero {0, {meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero, meshtastic_NodeRemoteHardwarePin_init_zero}}
#define meshtastic_SharedContact_init_zero {0, false, meshtastic_User_init_zero, 0, 0}
@@ -336,6 +368,8 @@ extern "C" {
#define meshtastic_AdminMessage_InputEvent_kb_char_tag 2
#define meshtastic_AdminMessage_InputEvent_touch_x_tag 3
#define meshtastic_AdminMessage_InputEvent_touch_y_tag 4
+#define meshtastic_AdminMessage_OTAEvent_reboot_ota_mode_tag 1
+#define meshtastic_AdminMessage_OTAEvent_ota_hash_tag 2
#define meshtastic_HamParameters_call_sign_tag 1
#define meshtastic_HamParameters_tx_power_tag 2
#define meshtastic_HamParameters_frequency_tag 3
@@ -403,6 +437,7 @@ extern "C" {
#define meshtastic_AdminMessage_shutdown_seconds_tag 98
#define meshtastic_AdminMessage_factory_reset_config_tag 99
#define meshtastic_AdminMessage_nodedb_reset_tag 100
+#define meshtastic_AdminMessage_ota_request_tag 102
#define meshtastic_AdminMessage_session_passkey_tag 101
/* Struct field encoding specification for nanopb */
@@ -461,7 +496,8 @@ X(a, STATIC, ONEOF, INT32, (payload_variant,reboot_seconds,reboot_second
X(a, STATIC, ONEOF, INT32, (payload_variant,shutdown_seconds,shutdown_seconds), 98) \
X(a, STATIC, ONEOF, INT32, (payload_variant,factory_reset_config,factory_reset_config), 99) \
X(a, STATIC, ONEOF, BOOL, (payload_variant,nodedb_reset,nodedb_reset), 100) \
-X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
+X(a, STATIC, SINGULAR, BYTES, session_passkey, 101) \
+X(a, STATIC, ONEOF, MESSAGE, (payload_variant,ota_request,ota_request), 102)
#define meshtastic_AdminMessage_CALLBACK NULL
#define meshtastic_AdminMessage_DEFAULT NULL
#define meshtastic_AdminMessage_payload_variant_get_channel_response_MSGTYPE meshtastic_Channel
@@ -482,6 +518,7 @@ X(a, STATIC, SINGULAR, BYTES, session_passkey, 101)
#define meshtastic_AdminMessage_payload_variant_store_ui_config_MSGTYPE meshtastic_DeviceUIConfig
#define meshtastic_AdminMessage_payload_variant_add_contact_MSGTYPE meshtastic_SharedContact
#define meshtastic_AdminMessage_payload_variant_key_verification_MSGTYPE meshtastic_KeyVerificationAdmin
+#define meshtastic_AdminMessage_payload_variant_ota_request_MSGTYPE meshtastic_AdminMessage_OTAEvent
#define meshtastic_AdminMessage_InputEvent_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, event_code, 1) \
@@ -491,6 +528,12 @@ X(a, STATIC, SINGULAR, UINT32, touch_y, 4)
#define meshtastic_AdminMessage_InputEvent_CALLBACK NULL
#define meshtastic_AdminMessage_InputEvent_DEFAULT NULL
+#define meshtastic_AdminMessage_OTAEvent_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UENUM, reboot_ota_mode, 1) \
+X(a, STATIC, SINGULAR, BYTES, ota_hash, 2)
+#define meshtastic_AdminMessage_OTAEvent_CALLBACK NULL
+#define meshtastic_AdminMessage_OTAEvent_DEFAULT NULL
+
#define meshtastic_HamParameters_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, STRING, call_sign, 1) \
X(a, STATIC, SINGULAR, INT32, tx_power, 2) \
@@ -524,6 +567,7 @@ X(a, STATIC, OPTIONAL, UINT32, security_number, 4)
extern const pb_msgdesc_t meshtastic_AdminMessage_msg;
extern const pb_msgdesc_t meshtastic_AdminMessage_InputEvent_msg;
+extern const pb_msgdesc_t meshtastic_AdminMessage_OTAEvent_msg;
extern const pb_msgdesc_t meshtastic_HamParameters_msg;
extern const pb_msgdesc_t meshtastic_NodeRemoteHardwarePinsResponse_msg;
extern const pb_msgdesc_t meshtastic_SharedContact_msg;
@@ -532,6 +576,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define meshtastic_AdminMessage_fields &meshtastic_AdminMessage_msg
#define meshtastic_AdminMessage_InputEvent_fields &meshtastic_AdminMessage_InputEvent_msg
+#define meshtastic_AdminMessage_OTAEvent_fields &meshtastic_AdminMessage_OTAEvent_msg
#define meshtastic_HamParameters_fields &meshtastic_HamParameters_msg
#define meshtastic_NodeRemoteHardwarePinsResponse_fields &meshtastic_NodeRemoteHardwarePinsResponse_msg
#define meshtastic_SharedContact_fields &meshtastic_SharedContact_msg
@@ -540,6 +585,7 @@ extern const pb_msgdesc_t meshtastic_KeyVerificationAdmin_msg;
/* Maximum encoded size of messages (where known) */
#define MESHTASTIC_MESHTASTIC_ADMIN_PB_H_MAX_SIZE meshtastic_AdminMessage_size
#define meshtastic_AdminMessage_InputEvent_size 14
+#define meshtastic_AdminMessage_OTAEvent_size 36
#define meshtastic_AdminMessage_size 511
#define meshtastic_HamParameters_size 31
#define meshtastic_KeyVerificationAdmin_size 25
diff --git a/src/mesh/generated/meshtastic/mesh.pb.cpp b/src/mesh/generated/meshtastic/mesh.pb.cpp
index 9966e52f8..d8eee1203 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.cpp
+++ b/src/mesh/generated/meshtastic/mesh.pb.cpp
@@ -24,6 +24,9 @@ PB_BIND(meshtastic_Data, meshtastic_Data, 2)
PB_BIND(meshtastic_KeyVerification, meshtastic_KeyVerification, AUTO)
+PB_BIND(meshtastic_StoreForwardPlusPlus, meshtastic_StoreForwardPlusPlus, 2)
+
+
PB_BIND(meshtastic_Waypoint, meshtastic_Waypoint, AUTO)
@@ -121,6 +124,8 @@ PB_BIND(meshtastic_ChunkedPayloadResponse, meshtastic_ChunkedPayloadResponse, AU
+
+
diff --git a/src/mesh/generated/meshtastic/mesh.pb.h b/src/mesh/generated/meshtastic/mesh.pb.h
index 0c48a7891..344c0e68a 100644
--- a/src/mesh/generated/meshtastic/mesh.pb.h
+++ b/src/mesh/generated/meshtastic/mesh.pb.h
@@ -92,8 +92,8 @@ typedef enum _meshtastic_HardwareModel {
Less common/prototype boards listed here (needs one more byte over the air)
--------------------------------------------------------------------------- */
meshtastic_HardwareModel_LORA_RELAY_V1 = 32,
- /* TODO: REPLACE */
- meshtastic_HardwareModel_NRF52840DK = 33,
+ /* T-Echo Plus device from LilyGo */
+ meshtastic_HardwareModel_T_ECHO_PLUS = 33,
/* TODO: REPLACE */
meshtastic_HardwareModel_PPR = 34,
/* TODO: REPLACE */
@@ -475,9 +475,28 @@ typedef enum _meshtastic_Routing_Error {
meshtastic_Routing_Error_ADMIN_PUBLIC_KEY_UNAUTHORIZED = 37,
/* Airtime fairness rate limit exceeded for a packet
This typically enforced per portnum and is used to prevent a single node from monopolizing airtime */
- meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38
+ meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED = 38,
+ /* PKI encryption failed, due to no public key for the remote node
+ This is different from PKI_UNKNOWN_PUBKEY which indicates a failure upon receiving a packet */
+ meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY = 39
} meshtastic_Routing_Error;
+/* Enum of message types */
+typedef enum _meshtastic_StoreForwardPlusPlus_SFPP_message_type {
+ /* Send an announcement of the canonical tip of a chain */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE = 0,
+ /* Query whether a specific link is on the chain */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type_CHAIN_QUERY = 1,
+ /* Request the next link in the chain */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_REQUEST = 3,
+ /* Provide a link to add to the chain */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE = 4,
+ /* If we must fragment, send the first half */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_FIRSTHALF = 5,
+ /* If we must fragment, send the second half */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF = 6
+} meshtastic_StoreForwardPlusPlus_SFPP_message_type;
+
/* The priority of this message for sending.
Higher priorities are sent first (when managing the transmit queue).
This field is never sent over the air, it is only used internally inside of a local device node.
@@ -782,6 +801,34 @@ typedef struct _meshtastic_KeyVerification {
meshtastic_KeyVerification_hash2_t hash2;
} meshtastic_KeyVerification;
+typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_message_hash_t;
+typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_commit_hash_t;
+typedef PB_BYTES_ARRAY_T(32) meshtastic_StoreForwardPlusPlus_root_hash_t;
+typedef PB_BYTES_ARRAY_T(240) meshtastic_StoreForwardPlusPlus_message_t;
+/* The actual over-the-mesh message doing store and forward++ */
+typedef struct _meshtastic_StoreForwardPlusPlus {
+ /* Which message type is this */
+ meshtastic_StoreForwardPlusPlus_SFPP_message_type sfpp_message_type;
+ /* The hash of the specific message */
+ meshtastic_StoreForwardPlusPlus_message_hash_t message_hash;
+ /* The hash of a link on a chain */
+ meshtastic_StoreForwardPlusPlus_commit_hash_t commit_hash;
+ /* the root hash of a chain */
+ meshtastic_StoreForwardPlusPlus_root_hash_t root_hash;
+ /* The encrypted bytes from a message */
+ meshtastic_StoreForwardPlusPlus_message_t message;
+ /* Message ID of the contained message */
+ uint32_t encapsulated_id;
+ /* Destination of the contained message */
+ uint32_t encapsulated_to;
+ /* Sender of the contained message */
+ uint32_t encapsulated_from;
+ /* The receive time of the message in question */
+ uint32_t encapsulated_rxtime;
+ /* Used in a LINK_REQUEST to specify the message X spots back from head */
+ uint32_t chain_count;
+} meshtastic_StoreForwardPlusPlus;
+
/* Waypoint message, used to share arbitrary locations across the mesh */
typedef struct _meshtastic_Waypoint {
/* Id of the waypoint */
@@ -1307,8 +1354,12 @@ extern "C" {
#define _meshtastic_Position_AltSource_ARRAYSIZE ((meshtastic_Position_AltSource)(meshtastic_Position_AltSource_ALT_BAROMETRIC+1))
#define _meshtastic_Routing_Error_MIN meshtastic_Routing_Error_NONE
-#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED
-#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_RATE_LIMIT_EXCEEDED+1))
+#define _meshtastic_Routing_Error_MAX meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY
+#define _meshtastic_Routing_Error_ARRAYSIZE ((meshtastic_Routing_Error)(meshtastic_Routing_Error_PKI_SEND_FAIL_PUBLIC_KEY+1))
+
+#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN meshtastic_StoreForwardPlusPlus_SFPP_message_type_CANON_ANNOUNCE
+#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_MAX meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF
+#define _meshtastic_StoreForwardPlusPlus_SFPP_message_type_ARRAYSIZE ((meshtastic_StoreForwardPlusPlus_SFPP_message_type)(meshtastic_StoreForwardPlusPlus_SFPP_message_type_LINK_PROVIDE_SECONDHALF+1))
#define _meshtastic_MeshPacket_Priority_MIN meshtastic_MeshPacket_Priority_UNSET
#define _meshtastic_MeshPacket_Priority_MAX meshtastic_MeshPacket_Priority_MAX
@@ -1338,6 +1389,8 @@ extern "C" {
#define meshtastic_Data_portnum_ENUMTYPE meshtastic_PortNum
+#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_ENUMTYPE meshtastic_StoreForwardPlusPlus_SFPP_message_type
+
#define meshtastic_MeshPacket_priority_ENUMTYPE meshtastic_MeshPacket_Priority
@@ -1380,6 +1433,7 @@ extern "C" {
#define meshtastic_Routing_init_default {0, {meshtastic_RouteDiscovery_init_default}}
#define meshtastic_Data_init_default {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#define meshtastic_KeyVerification_init_default {0, {0, {0}}, {0, {0}}}
+#define meshtastic_StoreForwardPlusPlus_init_default {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_default {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_MqttClientProxyMessage_init_default {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_default {0, 0, 0, 0, {meshtastic_Data_init_default}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
@@ -1411,6 +1465,7 @@ extern "C" {
#define meshtastic_Routing_init_zero {0, {meshtastic_RouteDiscovery_init_zero}}
#define meshtastic_Data_init_zero {_meshtastic_PortNum_MIN, {0, {0}}, 0, 0, 0, 0, 0, 0, false, 0}
#define meshtastic_KeyVerification_init_zero {0, {0, {0}}, {0, {0}}}
+#define meshtastic_StoreForwardPlusPlus_init_zero {_meshtastic_StoreForwardPlusPlus_SFPP_message_type_MIN, {0, {0}}, {0, {0}}, {0, {0}}, {0, {0}}, 0, 0, 0, 0, 0}
#define meshtastic_Waypoint_init_zero {0, false, 0, false, 0, 0, 0, "", "", 0}
#define meshtastic_MqttClientProxyMessage_init_zero {"", 0, {{0, {0}}}, 0}
#define meshtastic_MeshPacket_init_zero {0, 0, 0, 0, {meshtastic_Data_init_zero}, 0, 0, 0, 0, 0, _meshtastic_MeshPacket_Priority_MIN, 0, _meshtastic_MeshPacket_Delayed_MIN, 0, 0, {0, {0}}, 0, 0, 0, 0, _meshtastic_MeshPacket_TransportMechanism_MIN}
@@ -1489,6 +1544,16 @@ extern "C" {
#define meshtastic_KeyVerification_nonce_tag 1
#define meshtastic_KeyVerification_hash1_tag 2
#define meshtastic_KeyVerification_hash2_tag 3
+#define meshtastic_StoreForwardPlusPlus_sfpp_message_type_tag 1
+#define meshtastic_StoreForwardPlusPlus_message_hash_tag 2
+#define meshtastic_StoreForwardPlusPlus_commit_hash_tag 3
+#define meshtastic_StoreForwardPlusPlus_root_hash_tag 4
+#define meshtastic_StoreForwardPlusPlus_message_tag 5
+#define meshtastic_StoreForwardPlusPlus_encapsulated_id_tag 6
+#define meshtastic_StoreForwardPlusPlus_encapsulated_to_tag 7
+#define meshtastic_StoreForwardPlusPlus_encapsulated_from_tag 8
+#define meshtastic_StoreForwardPlusPlus_encapsulated_rxtime_tag 9
+#define meshtastic_StoreForwardPlusPlus_chain_count_tag 10
#define meshtastic_Waypoint_id_tag 1
#define meshtastic_Waypoint_latitude_i_tag 2
#define meshtastic_Waypoint_longitude_i_tag 3
@@ -1705,6 +1770,20 @@ X(a, STATIC, SINGULAR, BYTES, hash2, 3)
#define meshtastic_KeyVerification_CALLBACK NULL
#define meshtastic_KeyVerification_DEFAULT NULL
+#define meshtastic_StoreForwardPlusPlus_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UENUM, sfpp_message_type, 1) \
+X(a, STATIC, SINGULAR, BYTES, message_hash, 2) \
+X(a, STATIC, SINGULAR, BYTES, commit_hash, 3) \
+X(a, STATIC, SINGULAR, BYTES, root_hash, 4) \
+X(a, STATIC, SINGULAR, BYTES, message, 5) \
+X(a, STATIC, SINGULAR, UINT32, encapsulated_id, 6) \
+X(a, STATIC, SINGULAR, UINT32, encapsulated_to, 7) \
+X(a, STATIC, SINGULAR, UINT32, encapsulated_from, 8) \
+X(a, STATIC, SINGULAR, UINT32, encapsulated_rxtime, 9) \
+X(a, STATIC, SINGULAR, UINT32, chain_count, 10)
+#define meshtastic_StoreForwardPlusPlus_CALLBACK NULL
+#define meshtastic_StoreForwardPlusPlus_DEFAULT NULL
+
#define meshtastic_Waypoint_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, id, 1) \
X(a, STATIC, OPTIONAL, SFIXED32, latitude_i, 2) \
@@ -1980,6 +2059,7 @@ extern const pb_msgdesc_t meshtastic_RouteDiscovery_msg;
extern const pb_msgdesc_t meshtastic_Routing_msg;
extern const pb_msgdesc_t meshtastic_Data_msg;
extern const pb_msgdesc_t meshtastic_KeyVerification_msg;
+extern const pb_msgdesc_t meshtastic_StoreForwardPlusPlus_msg;
extern const pb_msgdesc_t meshtastic_Waypoint_msg;
extern const pb_msgdesc_t meshtastic_MqttClientProxyMessage_msg;
extern const pb_msgdesc_t meshtastic_MeshPacket_msg;
@@ -2013,6 +2093,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_Routing_fields &meshtastic_Routing_msg
#define meshtastic_Data_fields &meshtastic_Data_msg
#define meshtastic_KeyVerification_fields &meshtastic_KeyVerification_msg
+#define meshtastic_StoreForwardPlusPlus_fields &meshtastic_StoreForwardPlusPlus_msg
#define meshtastic_Waypoint_fields &meshtastic_Waypoint_msg
#define meshtastic_MqttClientProxyMessage_fields &meshtastic_MqttClientProxyMessage_msg
#define meshtastic_MeshPacket_fields &meshtastic_MeshPacket_msg
@@ -2069,6 +2150,7 @@ extern const pb_msgdesc_t meshtastic_ChunkedPayloadResponse_msg;
#define meshtastic_QueueStatus_size 23
#define meshtastic_RouteDiscovery_size 256
#define meshtastic_Routing_size 259
+#define meshtastic_StoreForwardPlusPlus_size 377
#define meshtastic_ToRadio_size 504
#define meshtastic_User_size 115
#define meshtastic_Waypoint_size 165
diff --git a/src/mesh/generated/meshtastic/portnums.pb.h b/src/mesh/generated/meshtastic/portnums.pb.h
index 67adc60cc..6b89c6a37 100644
--- a/src/mesh/generated/meshtastic/portnums.pb.h
+++ b/src/mesh/generated/meshtastic/portnums.pb.h
@@ -86,6 +86,11 @@ typedef enum _meshtastic_PortNum {
/* Paxcounter lib included in the firmware
ENCODING: protobuf */
meshtastic_PortNum_PAXCOUNTER_APP = 34,
+ /* Store and Forward++ module included in the firmware
+ ENCODING: protobuf
+ This module is specifically for Native Linux nodes, and provides a Git-style
+ chain of messages. */
+ meshtastic_PortNum_STORE_FORWARD_PLUSPLUS_APP = 35,
/* Provides a hardware serial interface to send and receive from the Meshtastic network.
Connect to the RX/TX pins of a device with 38400 8N1. Packets received from the Meshtastic
network is forwarded to the RX pin while sending a packet to TX will go out to the Mesh network.
diff --git a/src/modules/AdminModule.cpp b/src/modules/AdminModule.cpp
index aa510a86d..5f0c27fff 100644
--- a/src/modules/AdminModule.cpp
+++ b/src/modules/AdminModule.cpp
@@ -417,6 +417,9 @@ bool AdminModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshta
}
case meshtastic_AdminMessage_enter_dfu_mode_request_tag: {
LOG_INFO("Client requesting to enter DFU mode");
+#if HAS_SCREEN
+ IF_SCREEN(screen->showSimpleBanner("Device is rebooting\ninto DFU mode.", 0));
+#endif
#if defined(ARCH_NRF52) || defined(ARCH_RP2040)
enterDfuMode();
#endif
diff --git a/src/modules/ExternalNotificationModule.cpp b/src/modules/ExternalNotificationModule.cpp
index 6d52a3e46..3f6375a65 100644
--- a/src/modules/ExternalNotificationModule.cpp
+++ b/src/modules/ExternalNotificationModule.cpp
@@ -168,7 +168,7 @@ int32_t ExternalNotificationModule::runOnce()
delay = EXT_NOTIFICATION_FAST_THREAD_MS;
#endif
-#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
+#ifdef HAS_DRV2605
drv.go();
#endif
}
@@ -283,7 +283,7 @@ void ExternalNotificationModule::setExternalState(uint8_t index, bool on)
#ifdef UNPHONE
unphone.rgb(red, green, blue);
#endif
-#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
+#ifdef HAS_DRV2605
if (on) {
drv.go();
} else {
@@ -319,7 +319,7 @@ void ExternalNotificationModule::stopNow()
externalTurnedOn[i] = 0;
}
setIntervalFromNow(0);
-#if defined(T_WATCH_S3) || defined(T_LORA_PAGER)
+#ifdef HAS_DRV2605
drv.stop();
#endif
diff --git a/src/modules/NeighborInfoModule.cpp b/src/modules/NeighborInfoModule.cpp
index 936a7b44a..2cd8ec5ed 100644
--- a/src/modules/NeighborInfoModule.cpp
+++ b/src/modules/NeighborInfoModule.cpp
@@ -170,7 +170,7 @@ bool NeighborInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp,
} else {
LOG_DEBUG(" Ignoring dummy neighbor info packet (single neighbor with nodeId 0, snr 0)");
}
- } else if (mp.hop_start != 0 && mp.hop_start == mp.hop_limit) {
+ } else if (getHopsAway(mp) == 0) {
LOG_DEBUG("Get or create neighbor: %u with snr %f", mp.from, mp.rx_snr);
// If the hopLimit is the same as hopStart, then it is a neighbor
getOrCreateNeighbor(mp.from, mp.from, 0,
diff --git a/src/modules/NodeInfoModule.cpp b/src/modules/NodeInfoModule.cpp
index aaab019d6..7db8b66cc 100644
--- a/src/modules/NodeInfoModule.cpp
+++ b/src/modules/NodeInfoModule.cpp
@@ -7,17 +7,41 @@
#include "configuration.h"
#include "main.h"
#include
+#include
+
+#ifndef USERPREFS_NODEINFO_REPLY_SUPPRESS_SECS
+#define USERPREFS_NODEINFO_REPLY_SUPPRESS_SECS (12 * 60 * 60)
+#endif
NodeInfoModule *nodeInfoModule;
+static constexpr uint32_t NodeInfoReplySuppressSeconds = USERPREFS_NODEINFO_REPLY_SUPPRESS_SECS;
+
bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_User *pptr)
{
+ suppressReplyForCurrentRequest = false;
+
if (mp.from == nodeDB->getNodeNum()) {
LOG_WARN("Ignoring packet supposed to be from our own node: %08x", mp.from);
return false;
}
auto p = *pptr;
+
+ if (mp.decoded.want_response) {
+ const NodeNum sender = getFrom(&mp);
+ const uint32_t now = mp.rx_time ? mp.rx_time : getTime();
+ auto it = lastNodeInfoSeen.find(sender);
+ if (it != lastNodeInfoSeen.end()) {
+ uint32_t sinceLast = now >= it->second ? now - it->second : 0;
+ if (sinceLast < NodeInfoReplySuppressSeconds) {
+ suppressReplyForCurrentRequest = true;
+ }
+ }
+ lastNodeInfoSeen[sender] = now;
+ pruneLastNodeInfoCache();
+ }
+
if (p.is_licensed != owner.is_licensed) {
LOG_WARN("Invalid nodeInfo detected, is_licensed mismatch!");
return true;
@@ -42,6 +66,8 @@ bool NodeInfoModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, mes
service->sendToPhone(packetCopy);
}
+ pruneLastNodeInfoCache();
+
// LOG_DEBUG("did handleReceived");
return false; // Let others look at this message also if they want
}
@@ -68,9 +94,11 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
if (p) { // Check whether we didn't ignore it
p->to = dest;
- p->decoded.want_response = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
+ bool requestWantResponse = (config.device.role != meshtastic_Config_DeviceConfig_Role_TRACKER &&
config.device.role != meshtastic_Config_DeviceConfig_Role_SENSOR) &&
wantReplies;
+
+ p->decoded.want_response = requestWantResponse;
if (_shorterTimeout)
p->priority = meshtastic_MeshPacket_Priority_DEFAULT;
else
@@ -89,6 +117,13 @@ void NodeInfoModule::sendOurNodeInfo(NodeNum dest, bool wantReplies, uint8_t cha
meshtastic_MeshPacket *NodeInfoModule::allocReply()
{
+ if (suppressReplyForCurrentRequest) {
+ LOG_DEBUG("Skip send NodeInfo since we heard the requester <12h ago");
+ ignoreRequest = true;
+ suppressReplyForCurrentRequest = false;
+ return NULL;
+ }
+
if (!airTime->isTxAllowedChannelUtil(false)) {
ignoreRequest = true; // Mark it as ignored for MeshModule
LOG_DEBUG("Skip send NodeInfo > 40%% ch. util");
@@ -125,6 +160,29 @@ meshtastic_MeshPacket *NodeInfoModule::allocReply()
}
}
+void NodeInfoModule::pruneLastNodeInfoCache()
+{
+ if (!nodeDB || !nodeDB->meshNodes)
+ return;
+
+ const size_t maxEntries = nodeDB->meshNodes->size();
+
+ for (auto it = lastNodeInfoSeen.begin(); it != lastNodeInfoSeen.end();) {
+ if (!nodeDB->getMeshNode(it->first)) {
+ it = lastNodeInfoSeen.erase(it);
+ } else {
+ ++it;
+ }
+ }
+
+ while (!lastNodeInfoSeen.empty() && lastNodeInfoSeen.size() > maxEntries) {
+ auto oldestIt = std::min_element(lastNodeInfoSeen.begin(), lastNodeInfoSeen.end(),
+ [](const std::pair &lhs,
+ const std::pair &rhs) { return lhs.second < rhs.second; });
+ lastNodeInfoSeen.erase(oldestIt);
+ }
+}
+
NodeInfoModule::NodeInfoModule()
: ProtobufModule("nodeinfo", meshtastic_PortNum_NODEINFO_APP, &meshtastic_User_msg), concurrency::OSThread("NodeInfo")
{
diff --git a/src/modules/NodeInfoModule.h b/src/modules/NodeInfoModule.h
index 572b81700..d16fbeac2 100644
--- a/src/modules/NodeInfoModule.h
+++ b/src/modules/NodeInfoModule.h
@@ -1,5 +1,6 @@
#pragma once
#include "ProtobufModule.h"
+#include