Compare commits

...
Author SHA1 Message Date
mikecarper 28ee5d2c4b Add resume support for logging matrix builds 2026-06-29 23:09:39 -07:00
mikecarper 37892bb905 Update build all target handling 2026-06-29 23:05:32 -07:00
mikecarper d686c25108 Use logging infix for build artifacts 2026-06-29 13:10:54 -07:00
mikecarper 88fec6e512 Use keymind retry timing for direct paths 2026-06-29 12:55:58 -07:00
mikecarper cccdeff1b9 Fix ThinkNode M5 serial companion pins 2026-06-28 15:47:43 -07:00
mikecarper a92dd908d8 Fix Generic E22 KISS modem build 2026-06-28 02:12:27 -07:00
mikecarper dd31505339 Remove local ESP32 framework symlink overrides 2026-06-27 09:51:03 -07:00
mikecarper 4d2f304f63 Tune mesh retry behavior and builds 2026-06-27 01:51:23 -07:00
mikecarper 609e393b46 Fix repeater flood text and recent paging 2026-06-26 15:06:02 -07:00
mikecarper 98f8b11c57 Fix ESP32 repeater build targets 2026-06-25 14:02:01 -07:00
mikecarper 4e63941c92 Fix repeater build compile errors 2026-06-25 01:07:32 -07:00
mikecarper f606ae9aeb Fix repeater build artifacts and recent paging 2026-06-24 17:28:19 -07:00
mikecarper af131517a7 Fix nRF52 USB data detection build 2026-06-24 17:16:42 -07:00
mikecarper 48ac725d76 Tune keymind repeater behavior 2026-06-24 17:06:49 -07:00
mikecarper 7473719b92 build.sh: print build flags before build 2026-06-24 13:46:16 -07:00
mikecarper 4b6810d380 Restore uf2reset CLI after PR merges 2026-06-22 18:18:14 -07:00
mikecarper 915353c5d2 Merge remote-tracking branch 'refs/remotes/upstream/pr-2325' into keymindCascade 2026-06-22 18:14:17 -07:00
mikecarper 4813b9eea4 Merge remote-tracking branch 'upstream/pr-2367' into keymindCascade
# Conflicts:
#	build-iotthinks.sh
#	build.sh
#	docs/cli_commands.md
#	examples/companion_radio/MyMesh.cpp
#	examples/companion_radio/main.cpp
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_repeater/MyMesh.h
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	src/helpers/ESP32Board.h
#	src/helpers/SimpleMeshTables.h
#	src/helpers/sensors/EnvironmentSensorManager.cpp
#	variants/heltec_ct62/platformio.ini
#	variants/heltec_t096/T096Board.cpp
#	variants/heltec_t096/platformio.ini
#	variants/heltec_tracker/platformio.ini
#	variants/heltec_tracker_v2/platformio.ini
#	variants/heltec_v2/platformio.ini
#	variants/heltec_v3/platformio.ini
#	variants/heltec_v4/HeltecV4Board.cpp
#	variants/heltec_v4/platformio.ini
#	variants/heltec_wireless_paper/platformio.ini
#	variants/lilygo_tbeam_1w/platformio.ini
#	variants/lilygo_tbeam_SX1262/platformio.ini
#	variants/lilygo_tbeam_SX1276/platformio.ini
#	variants/lilygo_tbeam_supreme_SX1262/platformio.ini
#	variants/lilygo_tlora_v2_1/platformio.ini
#	variants/xiao_c3/platformio.ini
#	variants/xiao_s3/platformio.ini
#	variants/xiao_s3_wio/platformio.ini
2026-06-22 18:13:18 -07:00
mikecarper 90a3bd6b94 Merge remote-tracking branch 'upstream/pr-2489' into keymindCascade
# Conflicts:
#	build.sh
2026-06-22 17:51:09 -07:00
mikecarper d9cefed1b1 Merge remote-tracking branch 'IoTThinks/PowerSaving-v16' into keymindCascade
# Conflicts:
#	build-iotthinks.sh
#	build.sh
#	examples/simple_repeater/MyMesh.h
#	src/helpers/CommonCLI.cpp
#	variants/heltec_ct62/platformio.ini
#	variants/heltec_t096/T096Board.cpp
#	variants/heltec_t096/platformio.ini
#	variants/heltec_tracker/platformio.ini
#	variants/heltec_tracker_v2/platformio.ini
#	variants/heltec_v2/platformio.ini
#	variants/heltec_v3/HeltecV3Board.h
#	variants/heltec_v3/platformio.ini
#	variants/heltec_v4/HeltecV4Board.cpp
#	variants/heltec_v4/platformio.ini
#	variants/heltec_wireless_paper/platformio.ini
#	variants/lilygo_tbeam_1w/platformio.ini
#	variants/lilygo_tbeam_SX1262/platformio.ini
#	variants/lilygo_tbeam_SX1276/platformio.ini
#	variants/lilygo_tbeam_supreme_SX1262/platformio.ini
#	variants/lilygo_tlora_v2_1/platformio.ini
#	variants/xiao_c3/platformio.ini
#	variants/xiao_s3_wio/platformio.ini
2026-06-22 17:50:12 -07:00
mikecarper dfbb59fdda Merge remote-tracking branch 'upstream/dev' into keymindCascade 2026-06-22 17:45:22 -07:00
mikecarper b38e6e04d4 build.sh: refactor menu flow 2026-06-22 17:35:37 -07:00
Kevin Le f5d9e18517 Fixed poweroff for NRF52 boards 2026-06-21 23:14:03 +07:00
Kevin Le ddf8387703 Fixed missing rtc_clock for Heltec T114's GPS 2026-06-21 21:13:25 +07:00
Kevin Le 22e45d559d Power off board including radio, display, GPS and components 2026-06-20 12:17:40 +07:00
mikecarper 6b8135aa36 Fix direct retry review nits 2026-06-17 16:44:58 -07:00
mikecarper 19cfc43269 Trim recent repeater API and document retry defaults 2026-06-17 16:32:04 -07:00
Kevin Le 61e0e28e9a Added ThinkNode_M5_companion_radio_ble_ps 2026-06-17 16:58:37 +07:00
mikecarper 8d46ed4f21 Enable retry CR radio overrides 2026-06-16 16:25:04 -07:00
mikecarper 9c510ab7a6 Add Packet EOF newline 2026-06-16 13:30:49 -07:00
mikecarper 1f14f744af Clean Mesh header comments 2026-06-16 13:28:46 -07:00
mikecarper 4a9d6bae52 Remove duplicate Mesh comments 2026-06-16 13:25:04 -07:00
mikecarper f58cdef8a8 Revert Mesh comment noise 2026-06-16 13:21:51 -07:00
mikecarper 7d2ff38120 Trim direct retry cleanup 2026-06-16 13:15:52 -07:00
mikecarper 4ed9415866 Clean up Mesh comments 2026-06-15 18:15:00 -07:00
mikecarper e35886780c Simplify recent repeater paging 2026-06-15 18:10:40 -07:00
mikecarper 21c5e2701b Tune unknown retry SNR 2026-06-15 18:06:58 -07:00
mikecarper cb5e2e7c3d Document direct retry CLI 2026-06-15 17:42:53 -07:00
mikecarper 765ec1447b Add direct retry controls 2026-06-15 17:24:22 -07:00
mikecarper 7d8bb1d319 Keep Halo branch focused on direct retry 2026-06-15 16:05:23 -07:00
mikecarper 2acd61ec84 Remove PowerSaving changes from Halo retry branch 2026-06-15 16:02:22 -07:00
mikecarper 17dc51dbce Remove build and variant changes from Halo retry branch 2026-06-15 15:58:37 -07:00
mikecarper 29dc93b184 Document Halo direct retry settings only 2026-06-15 15:56:13 -07:00
mikecarper ccf36f5d94 Merge remote-tracking branch 'upstream/dev' into HEAD
# Conflicts:
#	src/helpers/CommonCLI.h
#	variants/heltec_t096/T096Board.cpp
#	variants/heltec_v4/HeltecV4Board.cpp
2026-06-15 15:52:14 -07:00
mikecarper 166261c73e Merge remote-tracking branch 'upstream/dev' into HEAD
# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
#	src/helpers/BaseChatMesh.cpp
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	variants/heltec_t096/T096Board.cpp
#	variants/heltec_v4/HeltecV4Board.cpp
2026-06-15 15:52:11 -07:00
mikecarper ae10246afe Merge remote-tracking branch 'upstream/dev' into HEAD 2026-06-15 15:52:07 -07:00
mikecarper fae4380649 Merge remote-tracking branch 'origin/add-uf2reset-cli' into add-uf2reset-cli 2026-06-15 15:47:34 -07:00
mikecarper eb104e4100 Merge remote-tracking branch 'upstream/main' into add-uf2reset-cli 2026-06-15 15:47:24 -07:00
mikecarper d7fb1ecbfe Merge branch 'meshcore-dev:main' into add-uf2reset-cli 2026-06-15 15:45:35 -07:00
mikecarper 40d1e29ad9 Merge remote-tracking branch 'upstream/main' into keymindCascade
# Conflicts:
#	docs/cli_commands.md
#	examples/companion_radio/MyMesh.cpp
#	examples/companion_radio/main.cpp
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_room_server/MyMesh.cpp
#	examples/simple_sensor/SensorMesh.cpp
#	platformio.ini
#	src/MeshCore.h
#	src/helpers/CommonCLI.cpp
#	src/helpers/ESP32Board.h
#	src/helpers/SimpleMeshTables.h
#	src/helpers/radiolib/CustomLLCC68Wrapper.h
#	src/helpers/radiolib/CustomLR1110Wrapper.h
#	src/helpers/radiolib/CustomSTM32WLxWrapper.h
#	src/helpers/radiolib/CustomSX1262Wrapper.h
#	src/helpers/radiolib/CustomSX1268Wrapper.h
#	src/helpers/radiolib/CustomSX1276Wrapper.h
#	src/helpers/sensors/EnvironmentSensorManager.cpp
#	variants/xiao_s3/platformio.ini
#	variants/xiao_s3/target.cpp
#	variants/xiao_s3/target.h
2026-06-15 15:45:05 -07:00
mikecarper d249c36915 Merge remote-tracking branch 'upstream/main' into halo-direct-path-retries
# Conflicts:
#	docs/cli_commands.md
2026-06-15 15:42:48 -07:00
mikecarper 4fc5476fa6 Merge branch 'halo-direct-path-retries' of github.com:mikecarper/MeshCore into halo-direct-path-retries
# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
#	src/helpers/CommonCLI.cpp
2026-06-15 15:40:50 -07:00
mikecarper 7b61a3665e Merge branch 'mikecarper-keymindCascade' into halo-direct-path-retries
# Conflicts:
#	build-iotthinks.sh
#	build.sh
#	docs/cli_commands.md
#	examples/companion_radio/MyMesh.cpp
#	examples/companion_radio/main.cpp
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_repeater/main.cpp
#	examples/simple_room_server/MyMesh.cpp
#	examples/simple_sensor/SensorMesh.cpp
#	platformio.ini
#	src/helpers/CommonCLI.cpp
#	src/helpers/ESP32Board.h
#	src/helpers/radiolib/CustomLLCC68Wrapper.h
#	src/helpers/radiolib/CustomLR1110Wrapper.h
#	src/helpers/radiolib/CustomSTM32WLxWrapper.h
#	src/helpers/radiolib/CustomSX1262Wrapper.h
#	src/helpers/radiolib/CustomSX1268Wrapper.h
#	src/helpers/radiolib/CustomSX1276Wrapper.h
#	src/helpers/sensors/EnvironmentSensorManager.cpp
#	variants/heltec_ct62/platformio.ini
#	variants/heltec_t096/platformio.ini
#	variants/heltec_tracker/platformio.ini
#	variants/heltec_tracker_v2/platformio.ini
#	variants/heltec_v2/HeltecV2Board.h
#	variants/heltec_v2/platformio.ini
#	variants/heltec_v3/HeltecV3Board.h
#	variants/heltec_v3/platformio.ini
#	variants/heltec_v4/HeltecV4Board.cpp
#	variants/heltec_v4/platformio.ini
#	variants/heltec_wireless_paper/platformio.ini
#	variants/lilygo_tbeam_1w/platformio.ini
#	variants/lilygo_tbeam_SX1262/platformio.ini
#	variants/lilygo_tbeam_SX1276/platformio.ini
#	variants/lilygo_tbeam_supreme_SX1262/platformio.ini
#	variants/lilygo_tlora_v2_1/platformio.ini
#	variants/xiao_c3/platformio.ini
#	variants/xiao_s3/platformio.ini
#	variants/xiao_s3/target.cpp
#	variants/xiao_s3/target.h
#	variants/xiao_s3_wio/platformio.ini
2026-06-15 15:39:08 -07:00
mikecarper 942bc6d361 Merge branch 'PowerSaving-v16-iotthinks' into halo-direct-path-retries
# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
#	examples/simple_repeater/MyMesh.h
#	src/Mesh.cpp
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	src/helpers/ESP32Board.h
2026-06-15 15:36:50 -07:00
mikecarper 60879c9973 Merge remote-tracking branch 'upstream/dev' into halo-direct-path-retries
# Conflicts:
#	examples/simple_repeater/MyMesh.cpp
#	src/Dispatcher.cpp
#	src/Dispatcher.h
#	src/Mesh.cpp
#	src/helpers/CommonCLI.cpp
#	src/helpers/CommonCLI.h
#	src/helpers/SimpleMeshTables.h
2026-06-15 15:32:58 -07:00
Kevin Le 19efda9f2e Reverted code change to GPS 2026-06-12 21:19:05 +07:00
Liam Cottle e8d3c53ba1 Merge pull request #2753 from formtapez/cli-docs
Added some missing CLI commands
2026-06-13 00:17:36 +12:00
formtapez d3444e6b0b fix formatting 2026-06-12 12:14:46 +02:00
formtapez 06130dce29 added some missing CLI commands 2026-06-12 12:11:12 +02:00
Liam Cottle 55ad7689d5 Merge pull request #2748 from liamcottle/github/stale-bot
Configure GitHub Stale Bot
2026-06-12 03:24:26 +12:00
liamcottle 3b39925399 use single quotes for repo name 2026-06-12 03:20:44 +12:00
liamcottle c94ed29ca3 add github workflow to close stale issues 2026-06-12 03:18:10 +12:00
Liam Cottle 5a342c7a20 Merge pull request #2691 from NoodlesNZ/security-policy
Add Github Security policy
2026-06-11 19:25:53 +12:00
Kevin Le 7c1b1c6e5e Hotfix: CMD_SEND_RAW_PACKET not freeing packet on parse failure.
https://github.com/meshcore-dev/MeshCore/pull/2722
2026-06-09 11:59:14 +07:00
Kevin Le e0733f8346 Added 0x77 for ENV_INCLUDE_BME680_BSEC 2026-06-09 11:34:36 +07:00
Kevin Le 4a733c61ce Renamed to add "femon" env to Heltec v4 and T096 2026-06-09 10:02:03 +07:00
Kevin Le e67933ca2a PowerSaving 16 2026-06-08 21:11:49 +07:00
Nick Le Mouton dea5ed790f Add SECURITY.md 2026-06-05 21:25:25 +12:00
mikecarper c20e57385a Fix direct retry diagnostics 2026-06-04 15:15:30 -07:00
mikecarper 399939f41b Refine direct retry SNR handling and recent repeater controls 2026-06-04 15:12:44 -07:00
mikecarper d1d50967cb Round up the SNR vs replacement to get a weighted average. 2026-06-04 15:11:14 -07:00
mikecarper d028c69d46 Fix issue with packet prefixes getting added to the table. 2026-06-04 15:11:14 -07:00
mikecarper b125d0a0d1 Max retries is now a var that can be set between 1 to 15 2026-06-04 15:11:14 -07:00
mikecarper dbc4dda0ca Retry 3 times with a 200ms,300ms,400ms backoff. 2026-06-04 15:09:37 -07:00
mikecarper 2af7b75d70 For packets with a path set; auto try again if no echo was heard 2026-06-04 15:07:11 -07:00
mikecarper 3d502b8044 Merge branch 'meshcore-dev:main' into halo-direct-path-retries 2026-06-04 15:02:44 -07:00
mikecarper fc8f212680 Merge branch 'meshcore-dev:main' into keymindCascade 2026-06-02 16:23:48 -07:00
mikecarper 618c2b6773 Support direct outpath override 2026-06-02 16:22:12 -07:00
mikecarper 4f9b8bf1ef Handle direct route path consumption 2026-05-28 17:33:33 -07:00
mikecarper fec6ddbfa5 Remove Cascadia power saving default 2026-05-28 17:28:38 -07:00
mikecarper 01f43b12c9 Merge branch 'PowerSaving-v15' of https://github.com/IoTThinks/MeshCore into keymindCascade
# Conflicts:
#	build-iotthinks.sh
2026-05-27 19:05:59 -07:00
Kevin Le 0f0f91c9ff Fixed directive and added isReadBusy for WiFi companion. 2026-05-27 09:41:29 +07:00
mikecarper bf389b34a1 Document Keymind branch settings 2026-05-22 15:15:36 -07:00
mikecarper 0b245d51bf Update Halo Keymind settings docs 2026-05-22 15:12:41 -07:00
mikecarper 1ded5b34e6 build.sh: show on-off prompts as on(1)/off(0) 2026-05-22 14:59:47 -07:00
mikecarper 815bd2c1c3 build.sh: accept 1/0 for on/off prompts 2026-05-22 14:59:47 -07:00
mikecarper 6d7dfa6814 build.sh: add interactive Wi-Fi build settings overrides 2026-05-22 14:59:47 -07:00
mikecarper f47b0872ac build.sh: fix radio override flags for PlatformIO ordering 2026-05-22 14:59:11 -07:00
mikecarper e1ba591a31 build.sh: retry radio preset API fetch with browser headers 2026-05-22 14:59:11 -07:00
mikecarper c3712737e9 build.sh: add optional Cascadia profile overrides 2026-05-22 14:41:37 -07:00
mikecarper c93d907e85 Make battery alert thresholds configurable 2026-05-22 14:34:19 -07:00
mikecarper f693bdb212 Add opt-in battery alert flood text 2026-05-22 14:28:03 -07:00
mikecarper c0e3860c3c Add repeater flood text command 2026-05-22 14:21:10 -07:00
mikecarper b59966b6fa Add direct path override support 2026-05-22 14:16:14 -07:00
Kevin Le 475b3ac20b Supported PowerSaving for 3 Tbeam boards 2026-05-22 23:12:22 +07:00
Kevin Le 49b6b632e8 Supported PowerSaving for USB companion 2026-05-22 23:11:05 +07:00
Kevin Le c15fdfeaf9 Fixed hasPendingWork for BLE companions. 2026-05-21 18:54:56 +07:00
Kevin Le e59a3f5c21 Minor sleep settings for room servers 2026-05-21 10:37:09 +07:00
Kevin Le 19703ddbda Added minor changes from PR1687 2026-05-21 10:24:00 +07:00
mikecarper f32f12ccde Merge upstream main into keymind 2026-05-18 11:43:13 -07:00
mikecarper 3e2db75706 Merge branch 'meshcore-dev:main' into add-uf2reset-cli 2026-05-18 11:41:04 -07:00
mikecarper b2d8b8a602 Merge remote-tracking branch 'origin/halo-direct-path-retries' into halo-direct-path-retries 2026-05-18 11:39:13 -07:00
mikecarper 76e11455bc Merge upstream main into halo-direct-path-retries 2026-05-18 11:38:57 -07:00
Kevin Le 69a76b0cda Fixed boot loop due to flash mode for Xiao C6 2026-05-15 21:07:10 +07:00
mikecarper 271e6f64fc Merge branch 'meshcore-dev:main' into halo-direct-path-retries 2026-05-12 23:54:36 -07:00
mikecarper 9ef5310651 Merge branch 'meshcore-dev:main' into keymind 2026-05-12 23:54:30 -07:00
Kevin Le 8abf4a92ff Added RADIO_FEM_RXGAIN in build option for T096 2026-05-10 22:09:56 +07:00
mikecarper 9a86090978 Update direct retry CR controls 2026-05-08 17:04:47 -07:00
mikecarper 7d237f8730 Update keymind retry controls 2026-05-08 16:57:03 -07:00
mikecarper ca0679e999 Merge adaptive CR direct retries into keymind 2026-05-08 12:43:24 -07:00
mikecarper bb8e7140a9 Add adaptive CR for direct retries 2026-05-08 12:37:08 -07:00
mikecarper c244effb52 Revise Halo and Keymind settings documentation
Updated settings and examples for Halo and Keymind configuration, including changes to retry settings and prefix handling.
2026-05-08 11:06:24 -07:00
mikecarper b475bde730 Unify retry preset settings 2026-05-08 10:53:21 -07:00
mikecarper ae67293bcc Honor flood retry ignore for echoes 2026-05-08 01:23:03 -07:00
mikecarper 3a6766b0fd Improve flood retry bridge buckets 2026-05-08 00:50:17 -07:00
mikecarper c3f78c1c94 Merge branch 'meshcore-dev:main' into add-uf2reset-cli 2026-05-07 23:50:38 -07:00
mikecarper 9f81ffc692 Merge branch 'meshcore-dev:main' into halo-direct-path-retries 2026-05-07 23:50:11 -07:00
mikecarper 4e1002029c Merge branch 'meshcore-dev:main' into keymind 2026-05-07 23:49:57 -07:00
mikecarper 8dcd4975d5 Delay direct retry final failure 2026-05-07 16:55:37 -07:00
mikecarper e0b19a1f60 Fix direct retry diagnostics 2026-05-07 16:51:49 -07:00
mikecarper 93bd0f0869 Fix flood retry diagnostics 2026-05-07 16:40:31 -07:00
mikecarper d4febe7867 Add flood retry controls 2026-05-07 11:48:29 -07:00
Kevin Le 1610c0a19b Added more boards to PowerSaving 2026-05-07 21:13:50 +07:00
Kevin Le cc1759aa42 Added PS BLE 2026-05-07 21:11:56 +07:00
Kevin Le 041f103b01 Reduced MAX_CONTACTS to 100 to compile and add PS BLE 2026-05-07 21:09:38 +07:00
Kevin Le 4b49449ff6 fix CustomLFS version pinning 2026-05-07 20:43:36 +07:00
mikecarper 674beb0e47 Improve repeater direct retry handling 2026-05-07 01:01:52 -07:00
mikecarper ea3f7acb4d Merge remote-tracking branch 'iotthinks/PowerSaving-v15' into halo-direct-path-retries
# Conflicts:
#	build.sh
#	src/helpers/CommonCLI.cpp
2026-05-06 15:37:48 -07:00
Kevin Le 3281ea2924 To drift forward less than 7s/day (3.5 minutes/month) for ESP32-based repeaters with Power Saving. 2026-05-06 15:07:59 +07:00
Kevin Le 517d3ed643 Added isReadBusy for NRF52 BLE 2026-05-06 12:40:52 +07:00
Kevin Le eac4f7201e Commented NRF52_POWER_MANAGEMENT and added PowerSaving BLE companions 2026-05-06 12:38:22 +07:00
Kevin Le 7f02cbe80b Delayed sleep for BLE companions if BLE read is busy. Reduced sleep period from 50 sticks to 10 sticks 2026-05-04 22:04:11 +07:00
mikecarper 6c20a1062f Refine direct retry SNR handling and recent repeater controls 2026-04-27 16:33:39 -07:00
Kevin Le e608503aa0 Merged latest changes from "Add CLI control to LoRa's fem LNA" https://github.com/meshcore-dev/MeshCore/pull/2140 2026-04-27 12:15:46 +07:00
Kevin Le fa9f3bd935 Added Heltec_Wireless_Tracker_companion_radio_ble_ps 2026-04-27 11:07:36 +07:00
Kevin Le 181ddb2268 Added Heltec_WSL3_companion_radio_ble_ps 2026-04-27 10:18:55 +07:00
mikecarper 8fadd31159 Merge branch 'main' of https://github.com/IoTThinks/MeshCore into halo-direct-path-retries 2026-04-26 19:28:16 -07:00
Kevin Le 5a45d54afe Added LilyGo_TBeam_1W_companion_radio_ble_ps 2026-04-26 16:16:43 +07:00
Kevin Le aa400da945 Clear stale wakeup sources to avoid ghost wakeup 2026-04-25 23:38:38 +07:00
Kevin Le a44137662e Added TimeTrim and Kept RTC 8M during sleep 2026-04-25 23:37:57 +07:00
mikecarper 0243ec41e8 Round up the SNR vs replacement to get a weighted average. 2026-04-24 15:19:48 -07:00
mikecarper 756268e2ee Fix issue with packet prefixes getting added to the table. 2026-04-24 14:41:07 -07:00
Kevin Le 0702065dd2 Delay sleep when BLE is active writing. 2026-04-24 23:05:31 +07:00
Kevin Le c5d8ada27d Added features from PowerSaving 14.1.1 2026-04-24 23:02:11 +07:00
mikecarper 9c2ac5aa1c Max retries is now a var that can be set between 1 to 15 2026-04-24 00:08:10 -07:00
mikecarper 577433ce47 Retry 3 times with a 200ms,300ms,400ms backoff. 2026-04-23 16:25:51 -07:00
mikecarper 6a53f6de27 Merge branch 'pr/buildsh-refactor-menu' into halo-direct-path-retries 2026-04-21 16:55:30 -07:00
mikecarper fae6dd83fd Merge branch 'main' into halo-direct-path-retries 2026-04-21 16:38:11 -07:00
mikecarper 8525b4e980 For packets with a path set; auto try again if no echo was heard 2026-04-21 16:24:00 -07:00
mikecarper d8552e3344 Better json arg passing in build.sh 2026-04-20 14:06:39 -07:00
mikecarper 4c393348df Add new line at the end of the file. 2026-04-20 13:53:38 -07:00
mikecarper 166f804da1 Remove unrelated CLI changes from PR branch 2026-04-20 13:47:01 -07:00
mikecarper 0ce1a55017 Refactor build.sh menu and build flow 2026-04-20 13:40:07 -07:00
mikecarper 00c14cd88c Add local-only uf2reset CLI command 2026-04-17 16:40:00 -07:00
154 changed files with 8329 additions and 850 deletions

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
name: 'Run Stale Bot'
on:
schedule:
- cron: '30 1 * * *' # daily at 1:30am
workflow_dispatch: {}
permissions:
actions: write
issues: write
pull-requests: write
jobs:
close-issues:
# only run on main repo, not forks
if: github.repository == 'meshcore-dev/MeshCore'
runs-on: ubuntu-latest
steps:
- name: Close Stale Issues
uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
# auto close issues
days-before-issue-stale: 60
days-before-issue-close: 7
exempt-issue-labels: "keep-open"
stale-issue-label: "stale"
stale-issue-message: "This issue is stale because it has been open for 60 days with no activity. Remove the stale label or add a comment if this issue is still relevant, otherwise this issue will automatically close in 7 days."
close-issue-message: "This issue was closed because it has been inactive for 7 days since being marked as stale."
# don't auto close prs
days-before-pr-stale: -1
days-before-pr-close: -1
+57
View File
@@ -0,0 +1,57 @@
# Security Policy
## Supported Versions
Security fixes are applied to the latest release only. We do not backport
fixes to older versions.
| Version | Supported |
|---------|-----------|
| 1.15+ | ✅ |
| <1.15 | ❌ |
## Reporting a Vulnerability
**Please do not report security vulnerabilities through public GitHub issues.**
Use GitHub's private vulnerability reporting instead:
1. Go to the **Security** tab of this repository
2. Click **Report a vulnerability**
3. Fill in the details and submit
### What to include
A useful report tells us:
- Which component or file is affected
- What an attacker can do (impact) and under what conditions
- A minimal reproduction case or proof-of-concept if you have one
- Whether you believe it is remotely exploitable
You do not need a working exploit to report. An incomplete report is better
than no report.
## What to expect
This is a volunteer-maintained open-source project. We will do our best to
respond in a reasonable timeframe, but cannot commit to specific deadlines.
We ask that you give us a fair opportunity to investigate and address the
issue before any public disclosure. If you have not heard back after
**90 days**, feel free to follow up or proceed with disclosure at your
discretion.
## Scope
In scope:
- Remote code execution, memory corruption, or denial-of-service via crafted
radio packets
- Authentication or encryption bypasses
- Vulnerabilities in the packet routing or path handling logic
Out of scope:
- Physical access attacks (e.g., JTAG, UART extraction of keys)
- Regulatory compliance (duty cycle, frequency restrictions)
- Jamming or other physical-layer radio interference
- Issues in third-party libraries (RadioLib, Crypto, etc.) — report those
upstream
- "Best practice" suggestions without a demonstrated attack path
+13 -2
View File
@@ -7,6 +7,9 @@ MEMORY
{
FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0xED000 - 0x26000
/* To keep data in RAM across resets */
PERSISTENT_RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 8
/* SRAM required by Softdevice depend on
* - Attribute Table Size (Number of Services and Characteristics)
* - Vendor UUID count
@@ -14,11 +17,19 @@ MEMORY
* - Concurrent connection peripheral + central + secure links
* - Event Len, HVN queue, Write CMD queue
*/
RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000
RAM (rwx) : ORIGIN = 0x20006000 + 8, LENGTH = 0x20040000 - 0x20006000 - 8
}
SECTIONS
{
. = ALIGN(4);
.persistent (NOLOAD) :
{
KEEP(*(.persistent_magic))
KEEP(*(.persistent_data))
. = ALIGN(4);
} > PERSISTENT_RAM
. = ALIGN(4);
.svc_data :
{
@@ -33,6 +44,6 @@ SECTIONS
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
} INSERT AFTER .data;
}
INCLUDE "nrf52_common.ld"
+13 -2
View File
@@ -7,6 +7,9 @@ MEMORY
{
FLASH (rx) : ORIGIN = 0x26000, LENGTH = 0xD4000 - 0x26000
/* To keep data in RAM across resets */
PERSISTENT_RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 8
/* SRAM required by Softdevice depend on
* - Attribute Table Size (Number of Services and Characteristics)
* - Vendor UUID count
@@ -14,11 +17,19 @@ MEMORY
* - Concurrent connection peripheral + central + secure links
* - Event Len, HVN queue, Write CMD queue
*/
RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000
RAM (rwx) : ORIGIN = 0x20006000 + 8, LENGTH = 0x20040000 - 0x20006000 - 8
}
SECTIONS
{
. = ALIGN(4);
.persistent (NOLOAD) :
{
KEEP(*(.persistent_magic))
KEEP(*(.persistent_data))
. = ALIGN(4);
} > PERSISTENT_RAM
. = ALIGN(4);
.svc_data :
{
@@ -33,6 +44,6 @@ SECTIONS
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
} INSERT AFTER .data;
}
INCLUDE "nrf52_common.ld"
+13 -2
View File
@@ -7,6 +7,9 @@ MEMORY
{
FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xED000 - 0x27000
/* To keep data in RAM across resets */
PERSISTENT_RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 8
/* SRAM required by Softdevice depend on
* - Attribute Table Size (Number of Services and Characteristics)
* - Vendor UUID count
@@ -14,11 +17,19 @@ MEMORY
* - Concurrent connection peripheral + central + secure links
* - Event Len, HVN queue, Write CMD queue
*/
RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000
RAM (rwx) : ORIGIN = 0x20006000 + 8, LENGTH = 0x20040000 - 0x20006000 - 8
}
SECTIONS
{
. = ALIGN(4);
.persistent (NOLOAD) :
{
KEEP(*(.persistent_magic))
KEEP(*(.persistent_data))
. = ALIGN(4);
} > PERSISTENT_RAM
. = ALIGN(4);
.svc_data :
{
@@ -33,6 +44,6 @@ SECTIONS
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
} INSERT AFTER .data;
}
INCLUDE "nrf52_common.ld"
+13 -2
View File
@@ -7,6 +7,9 @@ MEMORY
{
FLASH (rx) : ORIGIN = 0x27000, LENGTH = 0xD4000 - 0x27000
/* To keep data in RAM across resets */
PERSISTENT_RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 8
/* SRAM required by Softdevice depend on
* - Attribute Table Size (Number of Services and Characteristics)
* - Vendor UUID count
@@ -14,11 +17,19 @@ MEMORY
* - Concurrent connection peripheral + central + secure links
* - Event Len, HVN queue, Write CMD queue
*/
RAM (rwx) : ORIGIN = 0x20006000, LENGTH = 0x20040000 - 0x20006000
RAM (rwx) : ORIGIN = 0x20006000 + 8, LENGTH = 0x20040000 - 0x20006000 - 8
}
SECTIONS
{
. = ALIGN(4);
.persistent (NOLOAD) :
{
KEEP(*(.persistent_magic))
KEEP(*(.persistent_data))
. = ALIGN(4);
} > PERSISTENT_RAM
. = ALIGN(4);
.svc_data :
{
@@ -33,6 +44,6 @@ SECTIONS
KEEP(*(.fs_data))
PROVIDE(__stop_fs_data = .);
} > RAM
} INSERT AFTER .data;
}
INCLUDE "nrf52_common.ld"
+136
View File
@@ -0,0 +1,136 @@
#!/usr/bin/env bash
# ./build-iotthinks.sh
export FIRMWARE_VERSION="PowerSaving16"
############# Repeaters #############
# Commonly-used boards
## ESP32 - 17 boards
./build.sh build-firmware \
Heltec_v3_repeater \
Heltec_WSL3_repeater \
heltec_v4_repeater \
Station_G2_repeater \
T_Beam_S3_Supreme_SX1262_repeater \
Tbeam_SX1262_repeater \
LilyGo_T3S3_sx1262_repeater \
Xiao_S3_WIO_repeater \
Xiao_C3_repeater \
Xiao_C6_repeater_ \
Heltec_E290_repeater \
Heltec_Wireless_Tracker_repeater \
LilyGo_TBeam_1W_repeater \
Xiao_S3_repeater \
heltec_tracker_v2_repeater \
Heltec_Wireless_Paper_repeater \
Heltec_ct62_repeater
## NRF52 - 17 boards
./build.sh build-firmware \
RAK_4631_repeater \
Heltec_t114_repeater \
Xiao_nrf52_repeater \
Heltec_mesh_solar_repeater \
ProMicro_repeater \
SenseCap_Solar_repeater \
t1000e_repeater \
LilyGo_T-Echo_repeater \
WioTrackerL1_repeater \
RAK_3401_repeater \
RAK_WisMesh_Tag_repeater \
GAT562_30S_Mesh_Kit_repeater \
GAT562_Mesh_Tracker_Pro_repeater \
ikoka_nano_nrf_22dbm_repeater \
ikoka_nano_nrf_30dbm_repeater \
ikoka_nano_nrf_33dbm_repeater \
ThinkNode_M1_repeater \
Heltec_t096_repeater
## ESP32, SX1276 - 3 boards
./build.sh build-firmware \
Heltec_v2_repeater \
LilyGo_TLora_V2_1_1_6_repeater \
Tbeam_SX1276_repeater
############# Room Server #############
# ESP32 - 7 boards
./build.sh build-firmware \
Heltec_v3_room_server \
heltec_v4_room_server \
LilyGo_TBeam_1W_room_server \
Heltec_WSL3_room_server \
Xiao_S3_room_server \
heltec_tracker_v2_room_server \
Heltec_Wireless_Paper_room_server
# NRF52 - 6 boards
./build.sh build-firmware \
RAK_4631_room_server \
Heltec_t114_room_server \
Xiao_nrf52_room_server \
t1000e_room_server \
WioTrackerL1_room_server \
RAK_3401_room_server \
Heltec_t096_room_server
############# Companions BLE #############
# NRF52 - 12 boards
./build.sh build-firmware \
RAK_4631_companion_radio_ble \
Heltec_t114_companion_radio_ble \
Xiao_nrf52_companion_radio_ble \
t1000e_companion_radio_ble \
LilyGo_T-Echo_companion_radio_ble \
WioTrackerL1_companion_radio_ble \
RAK_3401_companion_radio_ble \
RAK_WisMesh_Tag_companion_radio_ble \
SenseCap_Solar_companion_radio_ble \
ThinkNode_M1_companion_radio_ble \
Heltec_t096_companion_radio_ble \
Heltec_t096_companion_radio_ble_femoff
############# Companions BLE PS #############
# ESP32 - 18 boards
./build.sh build-firmware \
Heltec_v3_companion_radio_ble_ps \
heltec_v4_companion_radio_ble_ps \
heltec_v4_3_companion_radio_ble_ps_femoff \
Xiao_C3_companion_radio_ble_ps \
Xiao_S3_companion_radio_ble_ps \
Xiao_S3_WIO_companion_radio_ble_ps \
Heltec_v2_companion_radio_ble_ps \
LilyGo_TBeam_1W_companion_radio_ble_ps \
Heltec_WSL3_companion_radio_ble_ps \
Heltec_Wireless_Tracker_companion_radio_ble_ps \
heltec_tracker_v2_companion_radio_ble_ps \
Heltec_Wireless_Paper_companion_radio_ble_ps \
LilyGo_TLora_V2_1_1_6_companion_radio_ble_ps \
Heltec_ct62_companion_radio_ble_ps \
T_Beam_S3_Supreme_SX1262_companion_radio_ble_ps \
Tbeam_SX1262_companion_radio_ble_ps \
heltec_v4_expansionkit_tft_companion_radio_ble_ps \
LilyGo_T3S3_sx1262_companion_radio_ble_ps
# Not working
# Tbeam_SX1276_companion_radio_ble_ps
############# Companions USB #############
./build.sh build-firmware \
Heltec_t096_companion_radio_usb
############# Sample builds #############
# 14 boards
./build.sh build-firmware \
Heltec_v3_repeater \
heltec_v4_repeater \
Xiao_C3_repeater \
Xiao_C6_repeater_ \
RAK_4631_repeater \
Heltec_t096_repeater \
Heltec_v3_companion_radio_ble_ps \
heltec_v4_companion_radio_ble_ps \
heltec_v4_3_companion_radio_ble_ps_femoff \
Xiao_C3_companion_radio_ble_ps \
Xiao_C6_companion_radio_ble_ \
RAK_4631_companion_radio_ble \
Heltec_t096_companion_radio_ble \
Heltec_t096_companion_radio_ble_femoff
+1638 -184
View File
File diff suppressed because it is too large. Load diff
+529 -5
View File
@@ -28,12 +28,35 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Usage:**
- `reboot`
**Note:** No reply is sent.
---
### Power-off the node
**Usage:**
- `poweroff`, or
- `shutdown`
**Note:** No reply is sent.
---
### Enter the UF2 bootloader (nRF52 only)
**Usage:**
- `uf2reset`
**Serial Only:** Yes
**Note:** Reboots directly into the UF2 bootloader on supported nRF52 boards.
---
### Reset the clock and reboot
**Usage:**
- `clkreboot`
**Note:** No reply is sent.
---
### Sync the clock with the remote device
@@ -69,9 +92,10 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
### Start an Over-The-Air (OTA) firmware update
### Start or stop an Over-The-Air (OTA) firmware update
**Usage:**
- `start ota`
- `stop ota`
---
@@ -191,7 +215,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:**
- `freq`: Frequency in MHz
- `bw`: Bandwidth in kHz
- `bw`: Bandwidth in kHz. Most targets allow `7.8`, `10.4`, `15.6`, `20.8`, `31.25`, `41.7`, `62.5`, `125`, `250`, `500`. LR1110 targets allow `62.5`, `125`, `250`, `500`.
- `sf`: Spreading factor (5-12)
- `cr`: Coding rate (5-8)
@@ -224,8 +248,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- `tempradio <freq>,<bw>,<sf>,<cr>,<timeout_mins>`
**Parameters:**
- `freq`: Frequency in MHz (300-2500)
- `bw`: Bandwidth in kHz (7.8-500)
- `freq`: Frequency in MHz (150-2500)
- `bw`: Bandwidth in kHz (same allowed values as `set radio`)
- `sf`: Spreading factor (5-12)
- `cr`: Coding rate (5-8)
- `timeout_mins`: Duration in minutes (must be > 0)
@@ -234,6 +258,32 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### Schedule radio parameter changes
**Usage:**
- `set radioat <freq>,<bw>,<sf>,<cr>,<start_time>`
- `get radioat [n|all]`
- `del radioat [n|all]`
- `set tempradioat <freq>,<bw>,<sf>,<cr>,<start_time>,<end_time>`
- `get tempradioat [n|all]`
- `del tempradioat [n|all]`
**Parameters:**
- `freq`: Frequency in MHz (150-2500)
- `bw`: Bandwidth in kHz (same allowed values as `set radio`)
- `sf`: Spreading factor (5-12)
- `cr`: Coding rate (5-8)
- `start_time`: Unix epoch time when the setting starts
- `end_time`: Unix epoch time when a temporary setting reverts
- `n`: Scheduled entry number from `get radioat` or `get tempradioat`
**Notes:**
- `get radioat` and `get tempradioat` list all entries when `n` is omitted.
- `del radioat` and `del tempradioat` delete all entries when `n` is omitted.
- Each queue supports 3 entries. Scheduled entries are not saved across reboot.
- `radioat` saves the new radio preferences when it fires. `tempradioat` applies temporarily, then reverts to the saved radio preferences.
---
#### View or change this node's frequency
**Usage:**
- `get freq`
@@ -400,6 +450,46 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### Send a repeater flood text
**Usage:**
- `send text.flood <message>`
**Parameters:**
- `message`: Text to send to the shared `#repeaters` flood channel, prefixed with this node's name. Any `:` in the node name is sent as `;` so the prefix delimiter stays unambiguous.
**Example:**
```
send text.flood checking ridge link
```
---
#### View or change battery alert state
**Usage:**
- `get battery.alert`
- `set battery.alert <on|off>`
**Default:** `off`
**Note:** When enabled, the repeater checks battery level once per minute and sends low-battery warnings to the `#repeaters` flood channel.
---
#### View or change battery alert thresholds
**Usage:**
- `get battery.alert.low`
- `set battery.alert.low <1-100>`
- `get battery.alert.critical`
- `set battery.alert.critical <0-99>`
**Defaults:**
- `battery.alert.low`: `20`
- `battery.alert.critical`: `10`
**Note:** The low threshold must be greater than the critical threshold.
---
#### View this node's public key
**Usage:** `get public.key`
@@ -427,7 +517,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Default:** `off`
**Note:** When enabled, device enters sleep mode between radio transmissions
**Note:** When enabled, device enters sleep mode between radio transmissions. Enabling is refused from the local serial console or while an active USB serial data connection is detected; USB power alone does not block power saving.
---
@@ -664,6 +754,17 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Note:** An alternative to `region denyf *`, setting `flood.max.unscoped` to a lower value such as `3` would allow for local unscoped messages to propagate, while preventing noisy neighbors from flooding a local region.
---
#### Limit the number of hops for an advert flood message
**Usage:**
- `get flood.max.advert`
- `set flood.max.advert <value>`
**Parameters:**
- `value`: Maximum flood hop count (0-64) for an advert packet
**Default:** `8`
---
@@ -950,6 +1051,429 @@ region save
- Enables flooding for all child regions automatically
- Useful for global networks with specific regional rules
---
### Direct Retry
Direct retry resends direct-routed packets when the downstream echo is not heard. It applies to direct messages, ACK packets, multipart packets carrying ACK payloads, and TRACE packets.
#### View or change direct retry state
**Usage:**
- `get direct.retry`
- `set direct.retry <state>`
**Parameters:**
- `state`: `on`|`off`
**Default:** `on`
**Notes:**
- New installs and older preference files without direct retry settings default to `on` with the `rooftop` preset.
**Examples:**
```
get direct.retry
set direct.retry on
set direct.retry off
```
---
#### View or change direct retry heard-table gate
**Usage:**
- `get direct.retry.heard`
- `set direct.retry.heard <state>`
**Parameters:**
- `state`: `on`|`off`
**Default:** `on`
**Note:** When enabled, the recent repeater table is the direct retry eligibility
gate. Prefixes missing from the table are assumed reachable; prefixes in the
table below the active SNR gate are blocked.
**Examples:**
```
get direct.retry.heard
set direct.retry.heard on
set direct.retry.heard off
```
---
#### View or apply a retry preset
**Usage:**
- `get retry.preset`
- `set retry.preset <preset>`
**Parameters:**
- `preset`: `infra`|`rooftop`|`mobile`
**Notes:**
- Applies shared direct retry and flood retry defaults.
- `infra`: fewer, slower retries for stable fixed infrastructure.
- `rooftop`: default long retry window for weak rooftop links.
- `mobile`: long retry count with shorter spacing for moving or changing links; flood retry count is `15`.
- Changing `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, `direct.retry.margin`, `flood.retry.count`, or `flood.retry.path` makes the preset report as `custom`.
**Examples:**
```
get retry.preset
set retry.preset infra
set retry.preset rooftop
set retry.preset mobile
```
---
### Flood Retry
Flood retry resends flood-routed packets when the same packet is not heard from
another qualifying repeater.
#### View or change flood retry count
**Usage:**
- `get flood.retry.count`
- `set flood.retry.count <count>`
**Parameters:**
- `count`: Base retry attempts after the original send, from `0` to `15`. `0` disables flood retry.
**Note:** Actual attempts are capped at `15`. Hop 1 flood retries use `count * 2`; hop 2 flood retries use `count * 1.5`, rounded up.
**Defaults:**
- `infra`: `1`
- `rooftop`: `3`
- `mobile`: `15`
**Examples:**
```
get flood.retry.count
set flood.retry.count 0
set flood.retry.count 15
```
---
#### View or change flood retry path gate
**Usage:**
- `get flood.retry.path`
- `set flood.retry.path <count|off>`
**Parameters:**
- `count`: Maximum flood path hash count eligible for retry, from `0` to `63`.
- `off`: Disable the path-length gate.
**Defaults:**
- `infra`: `1`
- `rooftop`: `2`
- `mobile`: `1`
**Examples:**
```
get flood.retry.path
set flood.retry.path 1
set flood.retry.path off
```
---
#### View or change flood retry advert handling
**Usage:**
- `get flood.retry.advert`
- `set flood.retry.advert <on|off>`
**Parameters:**
- `on`: Retry node advert floods.
- `off`: Do not retry node advert floods.
**Default:** `off`
**Examples:**
```
get flood.retry.advert
set flood.retry.advert off
```
---
#### View or change flood retry target prefixes
**Usage:**
- `get flood.retry.prefixes`
- `set flood.retry.prefixes <prefixes|none|off>`
**Parameters:**
- `prefixes`: Comma-separated 3-byte path hash prefixes, up to 8 entries.
- `none` or `off`: Clear the list.
**Note:** When set, non-bridge flood retry only accepts same-packet echoes whose
last hop matches one of these prefixes. When unset, any non-ignored last hop can
cancel the retry.
**Examples:**
```
get flood.retry.prefixes
set flood.retry.prefixes A58296,860CCA,425E5C
set flood.retry.prefixes none
```
---
#### View or change flood retry ignored prefixes
**Usage:**
- `get flood.retry.ignore`
- `set flood.retry.ignore <prefixes|none|off>`
**Parameters:**
- `prefixes`: Comma-separated 3-byte path hash prefixes, up to 8 entries.
- `none` or `off`: Clear the list.
**Note:** Non-bridge flood retry does not cancel on same-packet echoes whose
last hop matches this list. Bridge mode also excludes these prefixes from bucket
and `other` hits.
**Examples:**
```
get flood.retry.ignore
set flood.retry.ignore 71CE82,C7618C
set flood.retry.ignore none
```
---
#### View or change flood retry bridge mode
**Usage:**
- `get flood.retry.bridge`
- `set flood.retry.bridge <on|off>`
**Note:** Bridge mode retries until each configured fresh bucket, plus the non-source `other` bucket, has been heard or the retry count is exhausted.
**Examples:**
```
get flood.retry.bridge
set flood.retry.bridge on
```
---
#### View or change flood retry bridge buckets
**Usage:**
- `get flood.retry.bucket.<n>`
- `set flood.retry.bucket <n> <prefixes|none|off>`
**Parameters:**
- `n`: Bucket number from `1` to `6`.
- `prefixes`: Comma-separated 3-byte path hash prefixes, up to 17 entries per bucket.
- `none` or `off`: Clear the bucket.
**Examples:**
```
get flood.retry.bucket.1
set flood.retry.bucket 1 71CE82,C7618C
set flood.retry.bucket 2 none
```
---
#### View or change direct retry count
**Usage:**
- `get direct.retry.count`
- `set direct.retry.count <count>`
**Parameters:**
- `count`: Maximum retry attempts after the original send, from `1` to `15`.
**Default:** `15` with the `rooftop` preset
**Examples:**
```
get direct.retry.count
set direct.retry.count 1
set direct.retry.count 4
set direct.retry.count 15
```
---
#### View or change direct retry base delay
**Usage:**
- `get direct.retry.base`
- `set direct.retry.base <ms>`
**Parameters:**
- `ms`: First retry wait in milliseconds, from `10` to `5000`.
**Default:** `175` with the `rooftop` preset
**Explanation:**
- The first retry waits `base` milliseconds after the failed echo window.
- For non-TRACE direct paths shorter than 6 remaining hops, the effective wait is scaled by `hops / 6`.
- Non-TRACE direct paths with 6 or more remaining hops use the configured value unchanged.
- TRACE retries shorter than 16 remaining hops use `hops / 16`; 16 or more remaining hops use the configured value unchanged.
- Larger values reduce channel pressure and give slow repeaters more time.
- Smaller values recover faster but create tighter retry bursts.
**Examples:**
```
get direct.retry.base
set direct.retry.base 175
set direct.retry.base 275
set direct.retry.base 500
```
---
#### View or change direct retry step delay
**Usage:**
- `get direct.retry.step`
- `set direct.retry.step <ms>`
**Parameters:**
- `ms`: Extra milliseconds added for each subsequent retry, from `0` to `5000`.
**Default:** `100` with the `rooftop` preset
**Explanation:**
- Retry delay is `base + attempt_index * step`.
- For non-TRACE direct paths shorter than 6 remaining hops, that computed delay is scaled by `hops / 6`.
- Non-TRACE direct paths with 6 or more remaining hops use the computed delay unchanged.
- TRACE retries shorter than 16 remaining hops use `hops / 16`; 16 or more remaining hops use the computed delay unchanged.
- With `base=175` and `step=100`, non-TRACE paths with 6 or more remaining hops wait about `175`, `275`, `375`, `475` ms, and so on.
- `step=0` keeps every retry at the same delay.
- Larger steps spread retries over time and are safer on busy channels.
**Examples:**
```
get direct.retry.step
set direct.retry.step 0
set direct.retry.step 50
set direct.retry.step 100
set direct.retry.step 250
```
---
#### View or change direct retry SNR margin
**Usage:**
- `get direct.retry.margin`
- `set direct.retry.margin <snr_db>`
**Parameters:**
- `snr_db`: Extra SNR margin above the SF receive floor, from `0` to `40`.
**Default:** `5.00` with the `rooftop` preset
**Notes:**
- Unknown repeaters are still retried.
- Known repeaters below the receive floor plus this margin are skipped.
- Failed attempts lower the recent repeater SNR estimate by `0.25 dB`.
**Examples:**
```
get direct.retry.margin
set direct.retry.margin 0
set direct.retry.margin 2.5
set direct.retry.margin 5
set direct.retry.margin 10
```
---
#### View or change adaptive direct retry coding rate
**Usage:**
- `get direct.retry.cr`
- `set direct.retry.cr off`
- `set direct.retry.cr <cr4_min>,<cr5_min>,<cr7_min>,<cr8_max>`
**Parameters:**
- `cr4_min`: Minimum SNR in dB to retry at CR4.
- `cr5_min`: Minimum SNR in dB to retry at CR5.
- `cr7_min`: Minimum SNR in dB to retry at CR7.
- `cr8_max`: Maximum SNR in dB that forces CR8.
**Default:** `10.00,7.50,2.50,2.50`
**Explanation:**
- Higher SNR uses faster coding rates.
- Lower SNR uses more robust coding rates.
- Repeater retry attempts escalate from the adaptive starting CR. CR4 starts as CR4, CR5, CR7, CR7, then CR8. CR5 starts as CR5, CR7, CR7, then CR8. CR7 gets two attempts, then CR8.
- Repeater adaptive CR selection intentionally skips CR6.
- Non-repeater retry packets start at the current radio CR and follow the same escalation pattern, clamped at CR8. With the normal CR5 radio setting this is CR5, CR7, CR7, then CR8.
- `off` disables per-packet retry CR overrides and uses the current radio CR.
- Direct path retry packets sent at CR4 or CR5 temporarily use a shorter 16-symbol preamble, then restore the radio's default preamble.
- Unknown repeaters start at `+3.00 dB` for adaptive CR selection.
- A failed unknown repeater is seeded at `+2.75 dB`.
- Each later failure lowers the SNR estimate by `0.25 dB`.
**Examples:**
```
get direct.retry.cr
set direct.retry.cr off
set direct.retry.cr 10.0,7.5,2.5,2.5
set direct.retry.cr 12.0,8.0,4.0,1.0
set direct.retry.cr 8.0,5.0,1.5,0
set direct.retry.cr 6.0,3.0,0,-2.0
set direct.retry.cr 20.0,12.0,6.0,2.0
set direct.retry.cr 4.0,2.0,0,-4.0
```
**Example profiles:**
- Conservative weak-link profile:
```
set direct.retry.cr 12.0,8.0,4.0,1.0
```
- Balanced rooftop profile:
```
set direct.retry.cr 10.0,7.5,2.5,2.5
```
- Faster strong-link profile:
```
set direct.retry.cr 6.0,3.0,0,-2.0
```
- Very cautious noisy-link profile:
```
set direct.retry.cr 20.0,12.0,6.0,2.0
```
---
#### View, seed, or clear the recent repeater table
**Usage:**
- `get recent.repeater`
- `get recent.repeater <page>`
- `get recent.repeaters <page>`
- `set recent.repeater <prefix> [snr_db]`
- `clear recent.repeater`
**Parameters:**
- `prefix`: Repeater path-hash prefix as hex.
- `snr_db`: Optional SNR in dB. If omitted or invalid, defaults to `3.0`.
- `page`: 1-based result page.
**Output order:**
- `get recent.repeater` lists 3-byte prefixes first, then 2-byte prefixes, then 1-byte prefixes.
- Within each prefix length, entries are sorted from highest SNR to lowest SNR.
**SNR details:**
- Recent repeater SNR is stored internally in quarter-dB units.
- Heard repeater samples update an existing table entry with a weighted blend: `75%` existing SNR and `25%` new heard SNR, rounded up.
- Direct retry success also feeds the heard echo SNR back into the same weighted table.
- Direct retry failure is not weighted: each final echo-timeout failure lowers that repeater's SNR by `0.25 dB`.
- Unknown repeaters start at `+3.00 dB` for adaptive CR selection.
- If an unknown repeater fails, it is seeded into the table at `+2.75 dB`.
- `set recent.repeater <prefix> [snr_db]` seeds a missing prefix or adds another weighted sample for an existing prefix.
- Successful `set recent.repeater` replies include the stored prefix and SNR, for example `OK - set A1B2C3 at 3.0 SNR`.
**Examples:**
```
get recent.repeater
get recent.repeater 2
set recent.repeater A1B2C3 8.5
set recent.repeater 71CE82 -3.25
set recent.repeater A1B2C3
clear recent.repeater
```
---
### GPS (When GPS support is compiled in)
+360
View File
@@ -0,0 +1,360 @@
# Halo and Keymind Branch Settings
This file covers only CLI settings and helper commands added by the Halo or
Keymind branches. Use `docs/cli_commands.md` for the general MeshCore CLI.
## Quick Start
```text
set retry.preset rooftop
set direct.retry.heard on
set flood.retry.advert off
set flood.retry.bridge off
set flood.retry.prefixes none
set flood.retry.ignore none
```
Then verify:
```text
get retry.preset
get direct.retry.heard
get flood.retry.advert
get flood.retry.prefixes
get flood.retry.ignore
```
Use prefixes from the analyzer or neighbors list or `get recent.repeater` after the repeater has been online for a few hours.
## Common Examples
Disable retrying advert packets:
```text
set flood.retry.advert off
get flood.retry.advert
```
Ignore a repeater as a successful flood retry echo:
Use this if you have a car repeater and a house repeater; have the house ignore the car.
```text
set flood.retry.ignore 71CE82,C7618C
get flood.retry.ignore
```
Only accept specific downstream relays as flood retry success:
You're in a hole and need to hit a mountain top repeater to get out; keep trying till one you see one of these send out your packet.
```text
set flood.retry.prefixes A58296,860CCA,425E5C
get flood.retry.prefixes
```
Bridge two groups of repeaters:
```text
set flood.retry.bridge on
set flood.retry.bucket 1 71CE82,C7618C
set flood.retry.bucket 2 BEEBB0,425E5C
get flood.retry.bucket.1
get flood.retry.bucket.2
```
Return to simple non-bridge flood retry:
```text
set flood.retry.bridge off
set flood.retry.prefixes none
set flood.retry.ignore none
```
## Added Settings
| Setting | What it does | How to use | Example |
| --- | --- | --- | --- |
| `battery.alert` | Sends opt-in low-battery warnings to `#repeaters`. | `get battery.alert`, `set battery.alert on/off` | `set battery.alert on` |
| `battery.alert.low` | Warning threshold percentage. Must be greater than `battery.alert.critical`. | `get battery.alert.low`, `set battery.alert.low <1-100>` | `set battery.alert.low 20` |
| `battery.alert.critical` | Critical threshold percentage. Critical warnings repeat more often. | `get battery.alert.critical`, `set battery.alert.critical <0-99>` | `set battery.alert.critical 10` |
| `recent.repeater` | Shows, seeds, or clears the recent repeater prefix/SNR table used by direct retry and bridge freshness checks. | `get recent.repeater`, `get recent.repeater <page>`, `set recent.repeater <prefix> <snr_db>`, `clear recent.repeater` | `set recent.repeater A1B2C3 -8.5` |
| `outpath` | Overrides the primary direct route used for replies to the current remote client. | `get outpath`, `set outpath <hops>`, `set outpath direct`, `set outpath clear`, `set outpath flood` | `set outpath A1B2C3,D4E5F6` |
## Other Keymind Commands
| Command | What it does | How to use | Example |
| --- | --- | --- | --- |
| `send text.flood` | Sends a `#repeaters` flood text message formatted as `<node_name>: <message>`, with `:` in the node name sent as `;`. | `send text.flood <message>` | `send text.flood checking ridge link` |
## Battery Alerts
Battery alerts are off by default. When enabled, the repeater checks once per
minute and sends a flood text warning to `#repeaters` when voltage is above
`1 V` and the estimated battery percent is below `battery.alert.low`.
Warnings repeat every `24` hours, or every `12` hours when the estimate is
below `battery.alert.critical`.
Defaults:
| Setting | Default |
| --- | ---: |
| `battery.alert` | `off` |
| `battery.alert.low` | `20` |
| `battery.alert.critical` | `10` |
Example:
```text
set battery.alert.low 20
set battery.alert.critical 10
set battery.alert on
get battery.alert
```
## Recent Repeater Table
Direct retry uses the recent repeater table when `direct.retry.heard` is `on`.
Bridge buckets also use this table: a configured bucket prefix is active only
when it was heard within the last hour.
Show learned rows:
```text
get recent.repeater
get recent.repeater 2
get recent.repeaters 2
get recent.repeater page 3
```
Seed or correct a prefix:
```text
set recent.repeater A1B2C3 8.5
```
Clear learned and manually seeded rows:
```text
clear recent.repeater
```
Rows are sorted by prefix width, then SNR. A full direct retry failure lowers
the matching row by `0.25 dB`.
Serial CLI pages contain up to `128` rows. Remote LoRa CLI pages contain up to
`7` rows.
## Direct Path Overrides
`outpath` applies to the current remote client ACL entry. It needs remote
client context, so it is not useful from the local serial CLI.
Set paths with comma-separated hop hashes. Each hop must be `2`, `4`, or `6`
hex characters, and all hops in one path must use the same width.
```text
get outpath
set outpath A1B2C3,D4E5F6
set outpath direct
set outpath clear
set outpath flood
```
`set outpath direct` sets a zero-hop direct route for a client reachable without
repeaters. `set outpath clear` forgets the override and lets normal path
discovery fill it again. `set outpath flood` forces replies to use flood packets
until the client logs in again.
## Direct Retry Settings
Direct retry applies to direct-routed packets. A queued resend is canceled when the next-hop echo is heard.
| Setting | What it does | How to use | Example |
| --- | --- | --- | --- |
| `retry.preset` | Applies shared direct and flood retry defaults. Values: `infra`, `rooftop`, `mobile` or `0`, `1`, `2`. | `get retry.preset`, `set retry.preset <value>` | `set retry.preset rooftop` |
| `direct.retry.heard` | Uses the recent repeater table as the direct retry eligibility gate. | `get direct.retry.heard`, `set direct.retry.heard on/off` | `set direct.retry.heard on` |
| `direct.retry.margin` | SNR margin in dB above the SF-specific receive floor. | `get direct.retry.margin`, `set direct.retry.margin <0-40>` | `set direct.retry.margin 5` |
| `direct.retry.count` | Maximum direct retry attempts after initial TX. | `get direct.retry.count`, `set direct.retry.count <1-15>` | `set direct.retry.count 15` |
| `direct.retry.base` | Base wait in milliseconds before retry; non-TRACE paths under 6 remaining hops scale by `hops / 6`, TRACE paths under 16 by `hops / 16`. | `get direct.retry.base`, `set direct.retry.base <10-5000>` | `set direct.retry.base 175` |
| `direct.retry.step` | Milliseconds added per retry attempt before the same short-path scaling. | `get direct.retry.step`, `set direct.retry.step <0-5000>` | `set direct.retry.step 100` |
| `direct.retry.cr` | Adaptive coding-rate thresholds for repeater direct retry packets. Repeaters use `CR4`, `CR5`, `CR7`, or `CR8`, then escalate by attempt: CR4, CR5, CR7, CR7, then CR8 from a CR4 start; CR5, CR7, CR7, then CR8 from a CR5 start. Non-repeaters start at the current radio CR and follow the same escalation pattern, clamped at CR8. | `get direct.retry.cr`, `set direct.retry.cr <cr4_min>,<cr5_min>,<cr7_min>,<cr8_max>`, `set direct.retry.cr off` | `set direct.retry.cr 10.0,7.5,2.5,0` |
The default adaptive coding-rate profile is `10.0,7.5,2.5,2.5`.
SNR `10.0 dB` and up uses `CR4`, `7.5 dB` and up uses `CR5`,
`2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. If no
recent repeater table entry is available, retry packets use `CR5`. Use
`set direct.retry.cr off` to disable adaptive coding-rate overrides. Repeater
attempts escalate from the adaptive starting CR: `CR4`, `CR5`, `CR7`, `CR7`,
then `CR8` from a `CR4` start; `CR5`, `CR7`, `CR7`, then `CR8` from a `CR5`
start. Non-repeaters use the current radio CR as the first retry CR and follow
the same pattern up to `CR8`.
Preset details:
| Preset | Base | Count | Step | SNR gate |
| --- | ---: | ---: | ---: | --- |
| `infra` | `275 ms` | `4` | `150 ms` | SF floor + `15 dB` |
| `rooftop` | `175 ms` | `15` | `100 ms` | SF floor + `5 dB` |
| `mobile` | `175 ms` | `15` | `50 ms` | SF floor |
Example for a quiet fixed repeater:
```text
set retry.preset rooftop
set direct.retry.heard on
set direct.retry.margin 5
```
Example for a moving or weak-link node:
```text
set retry.preset mobile
set direct.retry.margin 0
```
## Flood And Advert Settings
Flood retry applies to flood-routed packets. A queued retry is canceled when the
same packet is heard from a qualifying, non-ignored repeater. Bridge mode uses
the bucket rules below instead.
| Setting | What it does | How to use | Example |
| --- | --- | --- | --- |
| `flood.retry.count` | Base flood retry attempts after initial TX. Hop 1 doubles it, hop 2 uses 1.5x rounded up, and actual attempts cap at `15`; `0` disables flood retry. | `get flood.retry.count`, `set flood.retry.count <0-15>` | `set flood.retry.count 7` |
| `flood.retry.path` | Maximum path hash count eligible for flood retry, or `off` to disable the gate. | `get flood.retry.path`, `set flood.retry.path <0-63/off>` | `set flood.retry.path 1` |
| `flood.retry.advert` | Allows or blocks retry for node advert packets (`type=4`). Default is `off`. | `get flood.retry.advert`, `set flood.retry.advert on/off` | `set flood.retry.advert off` |
| `flood.retry.prefixes` | Target prefixes. If set, only same-packet echoes from matching last-hop prefixes cancel a retry. | `get flood.retry.prefixes`, `set flood.retry.prefixes <prefixes/none/off>` | `set flood.retry.prefixes BEEBB0,425E5C` |
| `flood.retry.ignore` | Ignored prefixes. In non-bridge retry, ignored last-hop echoes do not cancel retry. | `get flood.retry.ignore`, `set flood.retry.ignore <prefixes/none/off>` | `set flood.retry.ignore 71CE82,C7618C` |
| `flood.retry.bridge` | Enables bucket-based bridge retry logic. | `get flood.retry.bridge`, `set flood.retry.bridge on/off` | `set flood.retry.bridge on` |
| `flood.retry.bucket.<n>` | Shows one bridge bucket. Buckets are numbered `1`-`6`. | `get flood.retry.bucket.<n>` | `get flood.retry.bucket.1` |
| `flood.retry.bucket` | Sets bridge bucket prefixes. | `set flood.retry.bucket <1-6> <prefixes/none/off>` | `set flood.retry.bucket 1 71CE82,C7618C` |
The shared retry preset sets these flood defaults:
| Preset | Retry count | Path gate |
| --- | ---: | ---: |
| `infra` | `1` | `1` |
| `rooftop` | `3` | `2` |
| `mobile` | `15` | `1` |
Example for path-gated retry:
```text
set retry.preset rooftop
set flood.retry.path 1
set flood.retry.advert off
set flood.retry.ignore 71CE82,C7618C
```
## North South Buckets
Buckets describe groups of repeaters on different sides of this relay. Bucket
numbers do not have built-in meanings; this example uses bucket `1` for North
and bucket `2` for South.
```text
North bucket 1
+-----------------------+
| A1B2C3 D4E5F6 |
| North A North B |
+-----------+-----------+
|
v
+-----------+
| This node |
+-----------+
^
|
+-----------+-----------+
| 71CE82 C7618C |
| South A South B |
+-----------------------+
South bucket 2
```
Configure the buckets:
```text
set flood.retry.bridge on
set flood.retry.bucket 1 A1B2C3,D4E5F6
set flood.retry.bucket 2 71CE82,C7618C
set flood.retry.ignore none
```
Packet heard from the North:
```text
heard source
|
v
+--------------+ retry targets
| North bucket | -----> South bucket
| bucket 1 | -----> Other fresh/unbucketed relays
+--------------+
```
Packet heard from the South:
```text
heard source
|
v
+--------------+ retry targets
| South bucket | -----> North bucket
| bucket 2 | -----> Other fresh/unbucketed relays
+--------------+
```
Packet heard from an unbucketed or pathless source:
```text
heard source
|
v
+--------------+ retry targets
| Other bucket | -----> North bucket
| implicit | -----> South bucket
+--------------+
```
Bridge retry stays eligible until every target bucket has been heard or
`flood.retry.count` is exhausted. A configured bucket is a target only when at
least one of its prefixes is fresh in `recent.repeater`. Prefixes in
`flood.retry.ignore` never count as bucket hits.
## Troubleshooting
If advert packets are still retrying:
```text
get flood.retry.advert
set flood.retry.advert off
```
If ignored prefixes still appear in `flood retry good` logs:
```text
get flood.retry.ignore
set flood.retry.ignore <prefix>
```
The ignored prefix must match the last hop shown as `heard=<prefix>`. For example, this log needs `C7618C` in the ignore list:
```text
flood retry good (... path=7773D0>C7618C, heard=C7618C ...)
```
If retries are too aggressive:
```text
set flood.retry.count 1
set flood.retry.path 1
set direct.retry.count 4
```
If retries are too sparse:
```text
set flood.retry.count 7
set flood.retry.path 2
```
+2
View File
@@ -233,6 +233,7 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
file.read((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89
file.read((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
file.read((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
file.read((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
file.close();
}
@@ -273,6 +274,7 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
file.write((uint8_t *)&_prefs.rx_boosted_gain, sizeof(_prefs.rx_boosted_gain)); // 89
file.write((uint8_t *)_prefs.default_scope_name, sizeof(_prefs.default_scope_name)); // 90
file.write((uint8_t *)_prefs.default_scope_key, sizeof(_prefs.default_scope_key)); // 121
file.write((uint8_t *)&_prefs.radio_fem_rxgain, sizeof(_prefs.radio_fem_rxgain)); // 122
file.close();
}
+29
View File
@@ -62,6 +62,8 @@
#define CMD_SET_DEFAULT_FLOOD_SCOPE 63
#define CMD_GET_DEFAULT_FLOOD_SCOPE 64
#define CMD_SEND_RAW_PACKET 65
#define CMD_GET_RADIO_FEM_RXGAIN 66
#define CMD_SET_RADIO_FEM_RXGAIN 67
// Stats sub-types for CMD_GET_STATS
#define STATS_TYPE_CORE 0
@@ -889,6 +891,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
_prefs.rx_boosted_gain = 1; // enabled by default
#endif
#endif
_prefs.radio_fem_rxgain = 1;
}
void MyMesh::begin(bool has_display) {
@@ -938,6 +941,7 @@ void MyMesh::begin(bool has_display) {
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, -9, MAX_LORA_TX_POWER);
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
_prefs.radio_fem_rxgain = constrain(_prefs.radio_fem_rxgain, 0, 1);
#ifdef BLE_PIN_CODE // 123456 by default
if (_prefs.ble_pin == 0) {
@@ -967,6 +971,7 @@ void MyMesh::begin(bool has_display) {
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_driver.setTxPower(_prefs.tx_power_dbm);
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
}
@@ -1825,6 +1830,30 @@ void MyMesh::handleCmdFrame(size_t len) {
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else if (cmd_frame[0] == CMD_GET_RADIO_FEM_RXGAIN) {
if (!board.canControlLoRaFemLna()) {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
} else {
out_frame[0] = RESP_CODE_OK;
uint8_t value = board.isLoRaFemLnaEnabled() ? 1 : 0;
memcpy(&out_frame[1], &value, 1);
_serial->writeFrame(out_frame, 2);
}
} else if (cmd_frame[0] == CMD_SET_RADIO_FEM_RXGAIN && len >= 2) {
uint8_t value = cmd_frame[1];
if (!board.canControlLoRaFemLna()) {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
} else if (value <= 1) {
_prefs.radio_fem_rxgain = value;
if (board.setLoRaFemLnaEnabled(value != 0)) {
savePrefs();
writeOKFrame();
} else {
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
}
} else {
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else if (cmd_frame[0] == CMD_GET_ADVERT_PATH && len >= PUB_KEY_SIZE+2) {
// FUTURE use: uint8_t reserved = cmd_frame[1];
uint8_t *pub_key = &cmd_frame[2];
+2 -1
View File
@@ -109,6 +109,7 @@ protected:
int calcRxDelay(float score, uint32_t air_time) const override;
uint32_t getRetransmitDelay(const mesh::Packet *packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet *packet) override;
uint8_t getDefaultTxCodingRate() const override { return _prefs.cr; }
uint8_t getExtraAckTransmitCount() const override;
bool filterRecvFloodPacket(mesh::Packet* packet) override;
bool allowPacketForward(const mesh::Packet* packet) override;
@@ -166,7 +167,7 @@ protected:
public:
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
#if ENV_INCLUDE_GPS == 1
void applyGpsPrefs() {
sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0");
+2 -1
View File
@@ -29,9 +29,10 @@ struct NodePrefs { // persisted to file
uint32_t gps_interval; // GPS read interval in seconds
uint8_t autoadd_config; // bitmask for auto-add contacts config
uint8_t rx_boosted_gain; // SX126x RX boosted gain mode (0=power saving, 1=boosted)
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
uint8_t client_repeat;
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t autoadd_max_hops; // 0 = no limit, 1 = direct (0 hops), N = up to N-1 hops (max 64)
char default_scope_name[31];
uint8_t default_scope_key[16];
};
};
+40
View File
@@ -2,6 +2,11 @@
#include <Mesh.h>
#include "MyMesh.h"
#ifdef ESP32_PLATFORM
#include "esp_pm.h"
#include "esp_bt.h"
#endif
// Believe it or not, this std C function is busted on some platforms!
static uint32_t _atoi(const char* sp) {
uint32_t n = 0;
@@ -240,6 +245,37 @@ void setup() {
#endif
board.onBootComplete();
#ifdef ESP32_PLATFORM
#if !CONFIG_IDF_TARGET_ESP32C6
// Enable BLE sleep
esp_err_t errBLESleep = esp_bt_sleep_enable();
if (errBLESleep == ESP_OK) {
Serial.println("Bluetooth sleep enabled successfully");
} else {
Serial.printf("Bluetooth sleep enable failed: %s\n", esp_err_to_name(errBLESleep));
}
#endif
#if CONFIG_IDF_TARGET_ESP32C3
esp_pm_config_esp32c3_t pm_config;
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config;
#elif CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config;
#elif CONFIG_IDF_TARGET_ESP32C6
esp_pm_config_t pm_config;
#endif
// Configure Power Management
pm_config = { .max_freq_mhz = 80, .min_freq_mhz = 40, .light_sleep_enable = true };
esp_err_t errPM = esp_pm_configure(&pm_config);
if (errPM == ESP_OK) {
Serial.println("Power Management configured successfully");
} else {
Serial.printf("Power Management failed to configure: %d\r\n", errPM);
}
#endif
}
void loop() {
@@ -253,6 +289,10 @@ void loop() {
if (!the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM)
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
#elif defined(ESP32_PLATFORM)
if (!serial_interface.isReadBusy() && !serial_interface.isWriteBusy()) { // BLE is not busy
vTaskDelay(pdMS_TO_TICKS(10)); // attempt to sleep
}
#endif
}
+1 -2
View File
@@ -697,8 +697,7 @@ void UITask::shutdown(bool restart){
if (restart) {
_board->reboot();
} else {
_display->turnOff();
radio_driver.powerOff();
// Power off board including radio, display, GPS and components
_board->powerOff();
}
}
+1 -1
View File
@@ -307,7 +307,7 @@ void UITask::shutdown(bool restart){
if (restart) {
_board->reboot();
} else {
radio_driver.powerOff();
// Power off board including radio, display, GPS and components
_board->powerOff();
}
}
+1 -2
View File
@@ -566,8 +566,7 @@ void UITask::shutdown(bool restart){
if (restart) {
_board->reboot();
} else {
_display->turnOff();
radio_driver.powerOff();
// Power off board including radio, display, GPS and components
_board->powerOff();
}
}
File diff suppressed because it is too large. Load diff
+113 -6
View File
@@ -5,6 +5,10 @@
#include <RTClib.h>
#include <target.h>
#ifndef MESH_ENABLE_RECENT_REPEATERS
#define MESH_ENABLE_RECENT_REPEATERS 1
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
#include <InternalFileSystem.h>
#elif defined(RP2040_PLATFORM)
@@ -80,11 +84,32 @@ struct NeighbourInfo {
#define PACKET_LOG_FILE "/packet_log"
#ifndef MAX_SCHEDULED_RADIO_SETTINGS_PER_TYPE
#define MAX_SCHEDULED_RADIO_SETTINGS_PER_TYPE 3
#endif
#define MAX_SCHEDULED_RADIO_SETTINGS (MAX_SCHEDULED_RADIO_SETTINGS_PER_TYPE * 2)
class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
struct ScheduledRadioSetting {
bool active;
bool temporary;
bool started;
float freq;
float bw;
uint8_t sf;
uint8_t cr;
uint32_t start_time;
uint32_t end_time;
};
FILESYSTEM* _fs;
uint32_t last_millis;
uint64_t uptime_millis;
unsigned long next_local_advert, next_flood_advert;
unsigned long next_battery_alert_check;
unsigned long last_battery_alert_sent;
bool battery_alert_sent;
bool _logging;
NodePrefs _prefs;
ClientACL acl;
@@ -99,6 +124,15 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
RegionEntry* recv_pkt_region;
TransportKey default_scope;
RateLimiter discover_limiter, anon_limiter;
struct FloodRetryBridgeState {
uint8_t key[MAX_HASH_SIZE];
uint8_t source_bucket;
uint8_t target_mask;
uint8_t heard_mask;
uint8_t progress_marker;
bool active;
};
mutable FloodRetryBridgeState flood_retry_bridge_states[MAX_FLOOD_RETRY_SLOTS];
uint32_t pending_discover_tag;
unsigned long pending_discover_until;
bool region_load_active;
@@ -107,11 +141,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
NeighbourInfo neighbours[MAX_NEIGHBOURS];
#endif
CayenneLPP telemetry;
unsigned long set_radio_at, revert_radio_at;
float pending_freq;
float pending_bw;
uint8_t pending_sf;
uint8_t pending_cr;
float active_bw; // live BW, including temporary radio overrides
uint8_t active_sf; // live SF, including temporary radio overrides
uint8_t active_cr; // live CR, including temporary radio overrides
ScheduledRadioSetting scheduled_radio_settings[MAX_SCHEDULED_RADIO_SETTINGS];
int matching_peer_indexes[MAX_CLIENTS];
#if defined(WITH_RS232_BRIDGE)
RS232Bridge bridge;
@@ -119,6 +152,30 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
ESPNowBridge bridge;
#endif
bool extractDirectRetryPrefix(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const;
int8_t getDirectRetryMinSNRX4() const;
uint8_t getDirectRetryCodingRateForSNR(int8_t snr_x4) const;
uint8_t getDirectRetryConfiguredMaxAttempts() const;
uint32_t getDirectRetryAttemptStepMillis() const;
bool hasFloodRetryPrefixes() const;
bool floodRetryPrefixMatches(const mesh::Packet* packet) const;
bool floodRetryLastHopMatches(const mesh::Packet* packet) const;
bool floodRetryPrefixIgnored(const uint8_t* prefix, uint8_t prefix_len) const;
uint8_t floodRetryEffectivePathLength(const mesh::Packet* packet, uint8_t max_hops = 0xFF) const;
bool floodRetryPrefixFresh(const uint8_t* prefix, uint8_t prefix_len) const;
int floodRetryBucketForPrefix(const uint8_t* prefix, uint8_t prefix_len, bool require_fresh,
bool include_other) const;
int floodRetryBucketForPathHop(const uint8_t* prefix, uint8_t prefix_len, uint8_t hop,
uint8_t progress_marker) const;
int floodRetrySourceBucket(const mesh::Packet* packet) const;
uint8_t floodRetryBridgeTargetMask(uint8_t source_bucket) const;
uint8_t floodRetryBridgeHeardMask(const mesh::Packet* packet, uint8_t source_bucket,
uint8_t progress_marker) const;
FloodRetryBridgeState* floodRetryBridgeStateFor(const mesh::Packet* packet, bool create) const;
void clearFloodRetryBridgeState(const mesh::Packet* packet);
void refreshFloodRetryHeardRecent(const mesh::Packet* packet);
void formatFloodRetryPath(char* dest, size_t dest_len, const mesh::Packet* packet) const;
bool formatFloodRetryHeard(char* dest, size_t dest_len, const mesh::Packet* packet) const;
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
@@ -126,9 +183,30 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
mesh::Packet* createSelfAdvert();
bool sendRepeatersFloodText(const char* text);
void checkBatteryAlert();
void printRecentRepeatersSerial();
File openAppend(const char* fname);
bool isLooped(const mesh::Packet* packet, const uint8_t max_counters[]);
void applyRadioParams(float freq, float bw, uint8_t sf, uint8_t cr);
void applySavedRadioParams();
void processScheduledRadioSettings();
bool isMillisTimerDue(unsigned long timestamp) const;
bool hasScheduledRadioWorkDue() const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const;
uint32_t limitSleepToRtcTime(uint32_t timestamp, uint32_t sleep_secs) const;
uint32_t limitSleepToScheduledRadioWork(uint32_t sleep_secs) const;
bool hasStartedScheduledTempRadio() const;
int findFreeScheduledRadioSlot() const;
int countScheduledRadioSettings(bool temporary) const;
int findScheduledRadioSettingByIndex(bool temporary, int wanted) const;
int getScheduledRadioSettingIndex(bool temporary, int slot_idx) const;
bool scheduledRadioConflicts(bool temporary, uint32_t start_time, uint32_t end_time) const;
void clearScheduledRadioSetting(int idx, bool restore_if_started);
void formatScheduledRadioDuration(char* dest, size_t dest_len, uint32_t target_time) const;
void formatRadioParamTuple(char* dest, size_t dest_len, const ScheduledRadioSetting& setting) const;
void formatScheduledRadioSetting(char* reply, int setting_idx, int display_idx) const;
protected:
float getAirtimeBudgetFactor() const override {
@@ -146,6 +224,24 @@ protected:
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
uint8_t getDefaultTxCodingRate() const override { return active_cr; }
bool allowDirectRetry(const mesh::Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const override;
bool maybeShortCircuitDirect(mesh::Packet* packet) override;
void configureDirectRetryPacket(mesh::Packet* retry, const mesh::Packet* original, uint8_t retry_attempt) override;
uint32_t getDirectRetryEchoDelay(const mesh::Packet* packet) const override;
uint8_t getDirectRetryMaxAttempts(const mesh::Packet* packet) const override;
uint32_t getDirectRetryAttemptDelay(const mesh::Packet* packet, uint8_t attempt_idx) override;
void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt,
const uint8_t* target_hash = NULL, uint8_t target_hash_len = 0,
int16_t payload_type = -1) override;
void onDirectRetryFailed(const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) override;
void onDirectRetrySucceeded(const uint8_t* next_hop_hash, uint8_t next_hop_hash_len, int8_t snr_x4) override;
bool allowFloodRetry(const mesh::Packet* packet) const override;
void onFloodRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) override;
bool hasFloodRetryTargetPrefix(const mesh::Packet* packet) const override;
uint8_t getFloodRetryMaxPathLength(const mesh::Packet* packet) const override;
uint8_t getFloodRetryMaxAttempts(const mesh::Packet* packet) const override;
bool isFloodRetryEchoTarget(const mesh::Packet* packet, uint8_t progress_marker) const override;
int getInterferenceThreshold() const override {
return _prefs.interference_threshold;
@@ -199,6 +295,10 @@ public:
// CommonCLICallbacks
void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) override;
void addScheduledRadioParams(bool temporary, float freq, float bw, uint8_t sf, uint8_t cr,
uint32_t start_time, uint32_t end_time, char* reply) override;
void formatScheduledRadioParams(bool temporary, const char* selector, char* reply) override;
void deleteScheduledRadioParams(bool temporary, const char* selector, char* reply) override;
bool formatFileSystem() override;
void sendSelfAdvertisement(int delay_millis, bool flood) override;
void updateAdvertTimer() override;
@@ -217,6 +317,9 @@ public:
void formatStatsReply(char *reply) override;
void formatRadioStatsReply(char *reply) override;
void formatPacketStatsReply(char *reply) override;
void formatRecentRepeatersReply(char *reply, int page) override;
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4) override;
void clearRecentRepeaters() override;
void startRegionsLoad() override;
bool saveRegions() override;
void onDefaultRegionChanged(const RegionEntry* r) override;
@@ -226,8 +329,12 @@ public:
void saveIdentity(const mesh::LocalIdentity& new_id) override;
void clearStats() override;
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void handleCommand(uint32_t sender_timestamp, ClientInfo* sender, char* command, char* reply);
void handleCommand(uint32_t sender_timestamp, char* command, char* reply) {
handleCommand(sender_timestamp, NULL, command, reply);
}
void loop();
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
#if defined(WITH_BRIDGE)
void setBridgeState(bool enable) override {
+13 -5
View File
@@ -122,7 +122,7 @@ void loop() {
Serial.print('\n');
command[len - 1] = 0; // replace newline with C string null terminator
char reply[160];
the_mesh.handleCommand(0, command, reply); // NOTE: there is no sender_timestamp via serial!
the_mesh.handleCommand(0, NULL, command, reply); // NOTE: there is no sender_timestamp via serial!
if (reply[0]) {
Serial.print(" -> "); Serial.println(reply);
}
@@ -152,13 +152,21 @@ void loop() {
#endif
rtc_clock.tick();
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
if (the_mesh.getNodePrefs()->powersaving_enabled && !board.isUsbDataConnected()) {
uint32_t sleep_secs = the_mesh.getPowerSaveSleepSeconds(30);
#if defined(NRF52_PLATFORM)
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
if (sleep_secs > 0) {
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
}
#else
if (the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
board.sleep(30); // Sleep. Wake up after a while or when receiving a LoRa packet
if (sleep_secs > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
board.sleep(sleep_secs); // Sleep. Wake up for scheduled jobs or when receiving a LoRa packet
}
#endif
}
if (the_mesh.getNodePrefs()->reboot_interval > 0 &&
the_mesh.millisHasNowPassed(the_mesh.getNodePrefs()->reboot_interval * 3600000)) {
board.reboot();
}
}
+46
View File
@@ -1031,3 +1031,49 @@ void MyMesh::loop() {
uptime_millis += now - last_millis;
last_millis = now;
}
bool MyMesh::isMillisTimerDue(unsigned long timestamp) const {
return timestamp && millisHasNowPassed(timestamp);
}
uint32_t MyMesh::limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const {
if (!timestamp || sleep_secs == 0) {
return sleep_secs;
}
unsigned long now = millis();
if ((long)(now - timestamp) >= 0) {
return 0;
}
unsigned long remaining_ms = timestamp - now;
uint32_t remaining_secs = (remaining_ms + 999UL) / 1000UL;
return remaining_secs < sleep_secs ? remaining_secs : sleep_secs;
}
uint32_t MyMesh::getPowerSaveSleepSeconds(uint32_t max_secs) const {
if (max_secs == 0 || hasPendingWork()) {
return 0;
}
uint32_t sleep_secs = max_secs;
if (acl.getNumClients() > 0) {
sleep_secs = limitSleepToMillisTimer(next_push, sleep_secs);
}
sleep_secs = limitSleepToMillisTimer(next_flood_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(next_local_advert, sleep_secs);
sleep_secs = limitSleepToMillisTimer(set_radio_at, sleep_secs);
sleep_secs = limitSleepToMillisTimer(revert_radio_at, sleep_secs);
sleep_secs = limitSleepToMillisTimer(dirty_contacts_expiry, sleep_secs);
return sleep_secs;
}
// To check if there is pending work
bool MyMesh::hasPendingWork() const {
#if defined(WITH_BRIDGE)
if (bridge.isRunning()) return true; // bridge needs WiFi radio, can't sleep
#endif
if (_mgr->getOutboundTotal() > 0) return true;
if (acl.getNumClients() > 0 && isMillisTimerDue(next_push)) return true;
if (isMillisTimerDue(next_flood_advert) || isMillisTimerDue(next_local_advert)) return true;
if (isMillisTimerDue(set_radio_at) || isMillisTimerDue(revert_radio_at)) return true;
return isMillisTimerDue(dirty_contacts_expiry);
}
+11
View File
@@ -153,6 +153,9 @@ protected:
uint8_t getExtraAckTransmitCount() const override {
return _prefs.multi_acks;
}
uint8_t getDefaultTxCodingRate() const override {
return set_radio_at == 0 && revert_radio_at != 0 ? pending_cr : _prefs.cr;
}
bool filterRecvFloodPacket(mesh::Packet* pkt) override;
@@ -225,4 +228,12 @@ public:
void clearStats() override;
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void loop();
uint32_t getPowerSaveSleepSeconds(uint32_t max_secs) const;
// To check if there is pending work
bool hasPendingWork() const;
private:
bool isMillisTimerDue(unsigned long timestamp) const;
uint32_t limitSleepToMillisTimer(unsigned long timestamp, uint32_t sleep_secs) const;
};
+16
View File
@@ -18,6 +18,9 @@ void halt() {
static char command[MAX_POST_TEXT_LEN+1];
// For power saving
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
void setup() {
Serial.begin(115200);
delay(1000);
@@ -115,4 +118,17 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();
if (the_mesh.getNodePrefs()->powersaving_enabled && !board.isUsbDataConnected()) {
uint32_t sleep_secs = the_mesh.getPowerSaveSleepSeconds(30);
#if defined(NRF52_PLATFORM)
if (sleep_secs > 0) {
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
}
#else
if (sleep_secs > 0 && the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
board.sleep(sleep_secs); // Sleep. Wake up for scheduled jobs or when receiving a LoRa packet
}
#endif
}
}
+4
View File
@@ -202,6 +202,10 @@ protected:
return true;
}
uint8_t getDefaultTxCodingRate() const override {
return LORA_CR;
}
void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) override {
// TODO: if not in favs, prompt to add as fav(?)
+3
View File
@@ -122,6 +122,9 @@ protected:
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int getAGCResetInterval() const override;
uint8_t getDefaultTxCodingRate() const override {
return set_radio_at == 0 && revert_radio_at != 0 ? pending_cr : _prefs.cr;
}
void onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, const mesh::Identity& sender, uint8_t* data, size_t len) override;
int searchPeersByHash(const uint8_t* hash) override;
void getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) override;
+64 -7
View File
@@ -52,6 +52,17 @@ void Dispatcher::updateTxBudget() {
}
}
void Dispatcher::restoreOutboundTxOverrides() {
if (outbound_restore_cr != 0) {
_radio->setCodingRate(outbound_restore_cr);
outbound_restore_cr = 0;
}
if (outbound_restore_preamble_len != 0) {
_radio->setPreambleLength(outbound_restore_preamble_len);
outbound_restore_preamble_len = 0;
}
}
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
return (int) ((pow(10, 0.85f - score) - 1.0) * air_time);
}
@@ -106,7 +117,9 @@ void Dispatcher::loop() {
}
_radio->onSendFinished();
restoreOutboundTxOverrides();
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
onSendComplete(outbound);
if (outbound->isRouteFlood()) {
n_sent_flood++;
} else {
@@ -118,7 +131,9 @@ void Dispatcher::loop() {
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime());
_radio->onSendFinished();
restoreOutboundTxOverrides();
logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
onSendFail(outbound);
releasePacket(outbound); // return to pool
outbound = NULL;
@@ -149,6 +164,7 @@ void Dispatcher::loop() {
bool Dispatcher::tryParsePacket(Packet* pkt, const uint8_t* raw, int len) {
int i = 0;
pkt->tx_cr = 0;
pkt->header = raw[i++];
if (pkt->getPayloadVer() > PAYLOAD_VER_1) {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkRecv(): unsupported packet version", getLogDateTime());
@@ -268,7 +284,10 @@ void Dispatcher::processRecvPacket(Packet* pkt) {
uint8_t priority = (action >> 24) - 1;
uint32_t _delay = action & 0xFFFFFF;
_mgr->queueOutbound(pkt, priority, futureMillis(_delay));
if (!queueOutboundPacket(pkt, priority, _delay)) {
onSendFail(pkt);
releasePacket(pkt);
}
}
}
@@ -319,18 +338,47 @@ void Dispatcher::checkSend() {
if (len + outbound->payload_len > MAX_TRANS_UNIT) {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): FATAL: Invalid packet queued... too long, len=%d", getLogDateTime(), len + outbound->payload_len);
_mgr->free(outbound);
onSendFail(outbound);
releasePacket(outbound);
outbound = NULL;
} else {
memcpy(&raw[len], outbound->payload, outbound->payload_len); len += outbound->payload_len;
uint32_t max_airtime = _radio->getEstAirtimeFor(len)*3/2;
outbound_restore_cr = 0;
outbound_restore_preamble_len = 0;
uint8_t default_cr = getDefaultTxCodingRate();
bool using_low_retry_cr = false;
if (outbound->tx_cr >= 4 && outbound->tx_cr <= 8 && default_cr >= 4 && default_cr <= 8
&& outbound->tx_cr != default_cr) {
if (_radio->setCodingRate(outbound->tx_cr)) {
outbound_restore_cr = default_cr;
using_low_retry_cr = outbound->tx_cr == 4 || outbound->tx_cr == 5;
max_airtime = _radio->getEstAirtimeFor(len)*3/2;
} else {
MESH_DEBUG_PRINTLN("%s Dispatcher::checkSend(): WARN: failed to set packet CR%d", getLogDateTime(), (uint32_t)outbound->tx_cr);
}
} else if (outbound->tx_cr == 4 || outbound->tx_cr == 5) {
using_low_retry_cr = outbound->tx_cr == default_cr;
}
bool has_direct_path = outbound->getPathHashCount() > 0
|| (outbound->getPayloadType() == PAYLOAD_TYPE_TRACE && outbound->payload_len > 9);
if (outbound->isRouteDirect() && has_direct_path
&& using_low_retry_cr) {
uint16_t default_preamble_len = _radio->getDefaultPreambleLength();
if (default_preamble_len > 16 && _radio->setPreambleLength(16)) {
outbound_restore_preamble_len = default_preamble_len;
max_airtime = _radio->getEstAirtimeFor(len)*3/2;
}
}
outbound_start = _ms->getMillis();
bool success = _radio->startSendRaw(raw, len);
if (!success) {
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime());
restoreOutboundTxOverrides();
logTxFail(outbound, outbound->getRawLength());
onSendFail(outbound);
releasePacket(outbound); // return to pool
outbound = NULL;
@@ -360,6 +408,7 @@ Packet* Dispatcher::obtainNewPacket() {
} else {
pkt->payload_len = pkt->path_len = 0;
pkt->_snr = 0;
pkt->tx_cr = 0;
}
return pkt;
}
@@ -368,13 +417,21 @@ void Dispatcher::releasePacket(Packet* packet) {
_mgr->free(packet);
}
void Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
bool Dispatcher::queueOutboundPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
if (!Packet::isValidPathLen(packet->path_len) || packet->payload_len > MAX_PACKET_PAYLOAD) {
MESH_DEBUG_PRINTLN("%s Dispatcher::sendPacket(): ERROR: invalid packet... path_len=%d, payload_len=%d", getLogDateTime(), (uint32_t) packet->path_len, (uint32_t) packet->payload_len);
_mgr->free(packet);
} else {
_mgr->queueOutbound(packet, priority, futureMillis(delay_millis));
return false;
}
return _mgr->queueOutbound(packet, priority, futureMillis(delay_millis));
}
bool Dispatcher::sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis) {
if (!queueOutboundPacket(packet, priority, delay_millis)) {
onSendFail(packet);
releasePacket(packet);
return false;
}
return true;
}
// Utility function -- handles the case where millis() wraps around back to zero
@@ -387,4 +444,4 @@ unsigned long Dispatcher::futureMillis(int millis_from_now) const {
return _ms->getMillis() + millis_from_now;
}
}
}
+21 -4
View File
@@ -46,6 +46,14 @@ public:
*/
virtual bool startSendRaw(const uint8_t* bytes, int len) = 0;
/**
* \brief Sets LoRa coding rate for subsequent transmits/receives.
* \returns true if the radio accepted the coding rate.
*/
virtual bool setCodingRate(uint8_t cr) { return false; }
virtual uint16_t getDefaultPreambleLength() const { return 0; }
virtual bool setPreambleLength(uint16_t len) { return false; }
/**
* \returns true if the previous 'startSendRaw()' completed successfully.
*/
@@ -89,7 +97,7 @@ public:
virtual Packet* allocNew() = 0;
virtual void free(Packet* packet) = 0;
virtual void queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
virtual bool queueOutbound(Packet* packet, uint8_t priority, uint32_t scheduled_for) = 0;
virtual Packet* getNextOutbound(uint32_t now) = 0; // by priority
virtual int getOutboundCount(uint32_t now) const = 0;
virtual int getOutboundTotal() const = 0;
@@ -118,6 +126,8 @@ typedef uint32_t DispatcherAction;
class Dispatcher {
Packet* outbound; // current outbound packet
unsigned long outbound_expiry, outbound_start, total_air_time, rx_air_time;
uint16_t outbound_restore_preamble_len;
uint8_t outbound_restore_cr;
unsigned long next_tx_time;
unsigned long cad_busy_start;
unsigned long radio_nonrx_start;
@@ -130,6 +140,7 @@ class Dispatcher {
unsigned long duty_cycle_window_ms;
void processRecvPacket(Packet* pkt);
void restoreOutboundTxOverrides();
void updateTxBudget();
protected:
@@ -142,6 +153,8 @@ protected:
: _radio(&radio), _ms(&ms), _mgr(&mgr)
{
outbound = NULL;
outbound_restore_preamble_len = 0;
outbound_restore_cr = 0;
total_air_time = rx_air_time = 0;
next_tx_time = ms.getMillis();
cad_busy_start = 0;
@@ -161,16 +174,22 @@ protected:
virtual void logRx(Packet* packet, int len, float score) { } // hooks for custom logging
virtual void logTx(Packet* packet, int len) { }
virtual void logTxFail(Packet* packet, int len) { }
virtual void onSendComplete(Packet* packet) { }
virtual void onSendFail(Packet* packet) { }
virtual const char* getLogDateTime() { return ""; }
virtual float getAirtimeBudgetFactor() const;
virtual int calcRxDelay(float score, uint32_t air_time) const;
virtual uint32_t getCADFailRetryDelay() const;
virtual uint32_t getCADFailMaxDuration() const;
virtual uint8_t getDefaultTxCodingRate() const { return 0; }
virtual int getInterferenceThreshold() const { return 0; } // disabled by default
virtual bool getCADEnabled() const { return false; } // hardware CAD disabled by default
virtual int getAGCResetInterval() const { return 0; } // disabled by default
virtual unsigned long getDutyCycleWindowMs() const { return 3600000; }
const Packet* getOutboundInFlight() const { return outbound; }
bool queueOutboundPacket(Packet* packet, uint8_t priority, uint32_t delay_millis);
bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len);
public:
void begin();
@@ -178,7 +197,7 @@ public:
Packet* obtainNewPacket();
void releasePacket(Packet* packet);
void sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
bool sendPacket(Packet* packet, uint8_t priority, uint32_t delay_millis=0);
unsigned long getTotalAirTime() const { return total_air_time; }
unsigned long getReceiveAirTime() const {return rx_air_time; }
@@ -196,8 +215,6 @@ public:
bool millisHasNowPassed(unsigned long timestamp) const;
unsigned long futureMillis(int millis_from_now) const;
bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len);
private:
void checkRecv();
void checkSend();
+958 -39
View File
File diff suppressed because it is too large. Load diff
+161 -5
View File
@@ -4,6 +4,18 @@
namespace mesh {
#ifndef MAX_DIRECT_RETRY_SLOTS
#define MAX_DIRECT_RETRY_SLOTS 6
#endif
#ifndef MAX_FLOOD_RETRY_SLOTS
#define MAX_FLOOD_RETRY_SLOTS 6
#endif
#ifndef FLOOD_RETRY_PATH_GATE_DISABLED
#define FLOOD_RETRY_PATH_GATE_DISABLED 0xFF
#endif
class GroupChannel {
public:
uint8_t hash[PATH_HASH_SIZE];
@@ -16,6 +28,7 @@ public:
class MeshTables {
public:
virtual bool hasSeen(const Packet* packet) = 0;
virtual void markSent(const Packet* packet) = 0;
virtual void clear(const Packet* packet) = 0; // remove this packet hash from table
};
@@ -24,17 +37,72 @@ public:
* and provides virtual methods for sub-classes on handling incoming, and also preparing outbound Packets.
*/
class Mesh : public Dispatcher {
struct DirectRetryEntry {
Packet* packet;
Packet* trigger_packet;
unsigned long retry_started_at;
unsigned long echo_wait_started_at;
unsigned long retry_at;
uint32_t retry_delay;
uint8_t retry_attempts_sent;
uint8_t retry_key[MAX_HASH_SIZE];
uint8_t next_hop_hash[MAX_HASH_SIZE];
uint8_t next_hop_hash_len;
uint8_t payload_type;
uint8_t priority;
uint8_t progress_marker;
bool expect_path_growth;
bool waiting_final_echo;
bool queued;
bool active;
};
struct FloodRetryEntry {
Packet* packet;
Packet* trigger_packet;
unsigned long retry_started_at;
unsigned long retry_at;
uint32_t retry_delay;
uint8_t retry_attempts_sent;
uint8_t retry_key[MAX_HASH_SIZE];
uint8_t priority;
uint8_t progress_marker;
bool waiting_final_echo;
bool queued;
bool active;
};
RTCClock* _rtc;
RNG* _rng;
MeshTables* _tables;
DirectRetryEntry _direct_retries[MAX_DIRECT_RETRY_SLOTS];
FloodRetryEntry _flood_retries[MAX_FLOOD_RETRY_SLOTS];
void removeSelfFromPath(Packet* packet);
void removePathPrefix(Packet* packet, uint8_t prefix_count);
void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis);
void clearDirectRetrySlot(int idx);
bool isDirectRetryQueued(const Packet* packet) const;
void calculateDirectRetryKey(const Packet* packet, uint8_t* dest_key) const;
bool cancelDirectRetryOnEcho(const Packet* packet);
void armDirectRetryOnSendComplete(const Packet* packet);
void clearPendingDirectRetryOnSendFail(const Packet* packet);
bool getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_hash, uint8_t& next_hop_hash_len,
uint8_t& progress_marker, bool& expect_path_growth) const;
bool canDecodeDirectPayloadForSelf(const Packet* packet);
void maybeScheduleDirectRetry(const Packet* packet, uint8_t priority);
void clearFloodRetrySlot(int idx);
bool isFloodRetryQueued(const Packet* packet) const;
bool cancelFloodRetryOnEcho(const Packet* packet);
void armFloodRetryOnSendComplete(const Packet* packet);
void clearPendingFloodRetryOnSendFail(const Packet* packet);
void maybeScheduleFloodRetry(const Packet* packet, uint8_t priority);
//void routeRecvAcks(Packet* packet, uint32_t delay_millis);
DispatcherAction forwardMultipartDirect(Packet* pkt);
protected:
DispatcherAction onRecvPacket(Packet* pkt) override;
void onSendComplete(Packet* packet) override;
void onSendFail(Packet* packet) override;
virtual uint32_t getCADFailRetryDelay() const override;
@@ -65,11 +133,99 @@ protected:
*/
virtual uint32_t getDirectRetransmitDelay(const Packet* packet);
/**
* \brief Decide whether a DIRECT packet should retry if the next hop echo is not overheard.
* Sub-classes can use recent repeater or other link-quality data to opt in selectively.
*/
virtual bool allowDirectRetry(const Packet* packet, const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) const;
/**
* \brief Allow subclasses to rewrite a non-TRACE DIRECT packet path when this node can safely skip ahead.
*/
virtual bool maybeShortCircuitDirect(Packet* packet) { return false; }
/**
* \returns milliseconds to wait for the next-hop echo before queueing a retry of the DIRECT packet.
*/
virtual uint32_t getDirectRetryEchoDelay(const Packet* packet) const;
/**
* \returns maximum number of retry transmissions after the initial direct TX.
*/
virtual uint8_t getDirectRetryMaxAttempts(const Packet* packet) const;
/**
* \returns delay before a specific retry attempt, where attempt_idx=0 is the first retry.
*/
virtual uint32_t getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx);
/**
* \brief Decide whether a FLOOD packet should retry when no downstream echo is overheard.
*/
virtual bool allowFloodRetry(const Packet* packet) const;
/**
* \brief Return true when this FLOOD packet already carries an application-defined target prefix.
*/
virtual bool hasFloodRetryTargetPrefix(const Packet* packet) const;
/**
* \returns maximum flood path hash count eligible for retry, or FLOOD_RETRY_PATH_GATE_DISABLED.
*/
virtual uint8_t getFloodRetryMaxPathLength(const Packet* packet) const;
/**
* \returns maximum number of FLOOD retry transmissions after the initial TX.
*/
virtual uint8_t getFloodRetryMaxAttempts(const Packet* packet) const;
/**
* \brief Return true when a received FLOOD echo is enough to cancel a pending retry.
*/
virtual bool isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker) const;
/**
* \returns delay before a specific flood retry attempt, where attempt_idx=0 is the first retry.
*/
virtual uint32_t getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx);
/**
* \brief Optional hook for logging flood-retry lifecycle events.
*/
virtual void onFloodRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) { }
/**
* \returns number of extra (Direct) ACK transmissions wanted.
*/
virtual uint8_t getExtraAckTransmitCount() const;
/**
* \brief Optional hook for logging direct-retry lifecycle events.
*/
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt,
const uint8_t* target_hash = NULL, uint8_t target_hash_len = 0,
int16_t payload_type = -1) { }
/**
* \brief Optional hook for link-quality feedback when all direct-retry attempts fail.
*/
virtual void onDirectRetryFailed(const uint8_t* next_hop_hash, uint8_t next_hop_hash_len) { }
/**
* \brief Optional hook for link-quality feedback when a direct-retry echo is heard.
*/
virtual void onDirectRetrySucceeded(const uint8_t* next_hop_hash, uint8_t next_hop_hash_len, int8_t snr_x4) { }
/**
* \returns Coding rate to use for a retry attempt, starting from the current/adaptive CR.
*/
static uint8_t getDirectRetryCodingRateForAttempt(uint8_t start_cr, uint8_t retry_attempt);
/**
* \brief Optional hook to set local-only transmit options on a retry packet before it is queued.
*/
virtual void configureDirectRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt);
/**
* \brief Perform search of local DB of peers/contacts.
* \returns Number of peers with matching hash
@@ -185,10 +341,10 @@ public:
Packet* createDatagram(uint8_t type, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t len);
Packet* createAnonDatagram(uint8_t type, const LocalIdentity& sender, const Identity& dest, const uint8_t* secret, const uint8_t* data, size_t data_len);
Packet* createGroupDatagram(uint8_t type, const GroupChannel& channel, const uint8_t* data, size_t data_len);
Packet* createAck(const uint8_t* ack, uint8_t len);
Packet* createAck(uint32_t ack_crc) { return createAck((uint8_t *) &ack_crc, 4); }
Packet* createMultiAck(const uint8_t* ack, uint8_t len, uint8_t remaining);
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining) { return createMultiAck((uint8_t *)&ack_crc, 4, remaining); }
Packet* createAck(const uint8_t* ack_hash, uint8_t ack_len);
Packet* createAck(uint32_t ack_crc);
Packet* createMultiAck(const uint8_t* ack_hash, uint8_t ack_len, uint8_t remaining);
Packet* createMultiAck(uint32_t ack_crc, uint8_t remaining);
Packet* createPathReturn(const uint8_t* dest_hash, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
Packet* createPathReturn(const Identity& dest, const uint8_t* secret, const uint8_t* path, uint8_t path_len, uint8_t extra_type, const uint8_t*extra, size_t extra_len);
Packet* createRawData(const uint8_t* data, size_t len);
+2
View File
@@ -64,12 +64,14 @@ public:
virtual uint8_t getStartupReason() const = 0;
virtual bool getBootloaderVersion(char* version, size_t max_len) { return false; }
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
virtual bool stopOTAUpdate(char reply[]) { return false; } // not supported
virtual bool setLoRaFemLnaEnabled(bool enable) { return false; }
virtual bool canControlLoRaFemLna() const { return false; }
virtual bool isLoRaFemLnaEnabled() const { return false; }
// Power management interface (boards with power management override these)
virtual bool isExternalPowered() { return false; }
virtual bool isUsbDataConnected() { return false; }
virtual uint16_t getBootVoltage() { return 0; }
virtual uint32_t getResetReason() const { return 0; }
virtual const char* getResetReasonString(uint32_t reason) { return "Not available"; }
+3 -1
View File
@@ -8,6 +8,7 @@ Packet::Packet() {
header = 0;
path_len = 0;
payload_len = 0;
tx_cr = 0;
}
bool Packet::isValidPathLen(uint8_t path_len) {
@@ -64,6 +65,7 @@ uint8_t Packet::writeTo(uint8_t dest[]) const {
bool Packet::readFrom(const uint8_t src[], uint8_t len) {
uint8_t i = 0;
tx_cr = 0;
header = src[i++];
if (hasTransportCodes()) {
memcpy(&transport_codes[0], &src[i], 2); i += 2;
@@ -84,4 +86,4 @@ bool Packet::readFrom(const uint8_t src[], uint8_t len) {
return true; // success
}
}
}
+1
View File
@@ -49,6 +49,7 @@ public:
uint8_t path[MAX_PATH_SIZE];
uint8_t payload[MAX_PACKET_PAYLOAD];
int8_t _snr;
uint8_t tx_cr; // volatile local-only TX coding-rate override; not serialized
/**
* \brief calculate the hash of payload + type
+6
View File
@@ -0,0 +1,6 @@
#include <stdint.h>
extern "C" {
__attribute__((section(".persistent_magic"))) uint32_t persistent_magic;
__attribute__((section(".persistent_data"))) uint32_t persistent_time;
}
+40 -2
View File
@@ -3,19 +3,57 @@
#include <Mesh.h>
#include <Arduino.h>
#ifdef NRF52_PLATFORM
#define CLOCK_MAGIC_NUM 0xAA55CC33
#define RTC_TIME_MIN 1772323200 // 1 Mar 2026
extern uint32_t persistent_magic;
extern uint32_t persistent_time;
#endif
class VolatileRTCClock : public mesh::RTCClock {
uint32_t base_time;
uint64_t accumulator;
unsigned long prev_millis;
public:
VolatileRTCClock() { base_time = 1715770351; accumulator = 0; prev_millis = millis(); } // 15 May 2024, 8:50pm
VolatileRTCClock() {
#ifdef NRF52_PLATFORM
if (persistent_magic == CLOCK_MAGIC_NUM && persistent_time >= RTC_TIME_MIN) {
base_time = persistent_time;
} else {
base_time = RTC_TIME_MIN;
}
#else
base_time = 1715770351;
#endif
accumulator = 0;
prev_millis = millis();
}
uint32_t getCurrentTime() override { return base_time + accumulator/1000; }
void setCurrentTime(uint32_t time) override { base_time = time; accumulator = 0; prev_millis = millis(); }
void setCurrentTime(uint32_t time) override {
base_time = time;
accumulator = 0;
prev_millis = millis();
#ifdef NRF52_PLATFORM
persistent_magic = CLOCK_MAGIC_NUM;
persistent_time = time;
#endif
}
void tick() override {
unsigned long now = millis();
accumulator += (now - prev_millis);
prev_millis = now;
#ifdef NRF52_PLATFORM
persistent_magic = CLOCK_MAGIC_NUM;
persistent_time = getCurrentTime();
#endif
}
};
+4
View File
@@ -17,6 +17,10 @@ bool ArduinoSerialInterface::isConnected() const {
return true; // no way of knowing, so assume yes
}
bool ArduinoSerialInterface::isReadBusy() const {
return false;
}
bool ArduinoSerialInterface::isWriteBusy() const {
return false;
}
+2 -1
View File
@@ -28,7 +28,8 @@ public:
bool isConnected() const override;
bool isReadBusy() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
};
};
+1
View File
@@ -15,6 +15,7 @@ public:
virtual bool isConnected() const = 0;
virtual bool isReadBusy() const = 0;
virtual bool isWriteBusy() const = 0;
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
virtual size_t checkRecvFrame(uint8_t dest[]) = 0;
+12 -1
View File
@@ -1,5 +1,7 @@
#include "ClientACL.h"
static const uint8_t CONTACT_RECORD_VERSION_ALT_PATH = 1;
static File openWrite(FILESYSTEM* _fs, const char* filename) {
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
_fs->remove(filename);
@@ -28,6 +30,7 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
uint8_t unused[2];
memset(&c, 0, sizeof(c));
c.alt_path_len = OUT_PATH_UNKNOWN;
bool success = (file.read(pub_key, 32) == 32);
success = success && (file.read((uint8_t *) &c.permissions, 1) == 1);
@@ -36,6 +39,10 @@ void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
success = success && (file.read(c.out_path, 64) == 64);
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); // will be recalculated below
if (success && unused[0] >= CONTACT_RECORD_VERSION_ALT_PATH) {
success = success && (file.read((uint8_t *)&c.alt_path_len, 1) == 1);
success = success && (file.read(c.alt_path, 64) == 64);
}
if (!success) break; // EOF
@@ -57,7 +64,8 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) {
File file = openWrite(_fs, "/s_contacts");
if (file) {
uint8_t unused[2];
memset(unused, 0, sizeof(unused));
unused[0] = CONTACT_RECORD_VERSION_ALT_PATH;
unused[1] = 0;
for (int i = 0; i < num_clients; i++) {
auto c = &clients[i];
@@ -70,6 +78,8 @@ void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) {
success = success && (file.write((uint8_t *)&c->out_path_len, 1) == 1);
success = success && (file.write(c->out_path, 64) == 64);
success = success && (file.write(c->shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
success = success && (file.write((uint8_t *)&c->alt_path_len, 1) == 1);
success = success && (file.write(c->alt_path, 64) == 64);
if (!success) break; // write failed
}
@@ -115,6 +125,7 @@ ClientInfo* ClientACL::putClient(const mesh::Identity& id, uint8_t init_perms) {
c->permissions = init_perms;
c->id = id;
c->out_path_len = OUT_PATH_UNKNOWN;
c->alt_path_len = OUT_PATH_UNKNOWN;
return c;
}
+4 -1
View File
@@ -10,13 +10,16 @@
#define PERM_ACL_READ_WRITE 2
#define PERM_ACL_ADMIN 3
#define OUT_PATH_UNKNOWN 0xFF
#define OUT_PATH_FORCE_FLOOD 0xFE
#define OUT_PATH_UNKNOWN 0xFF
struct ClientInfo {
mesh::Identity id;
uint8_t permissions;
uint8_t out_path_len;
uint8_t out_path[MAX_PATH_SIZE];
uint8_t alt_path_len;
uint8_t alt_path[MAX_PATH_SIZE];
uint8_t shared_secret[PUB_KEY_SIZE];
uint32_t last_timestamp; // by THEIR clock (transient)
uint32_t last_activity; // by OUR clock (transient)
+932 -13
View File
File diff suppressed because it is too large. Load diff
+126 -1
View File
@@ -19,6 +19,73 @@
#define LOOP_DETECT_MODERATE 2
#define LOOP_DETECT_STRICT 3
#define RETRY_PRESET_INFRA 0
#define RETRY_PRESET_ROOFTOP 1
#define RETRY_PRESET_MOBILE 2
#define RETRY_PRESET_CUSTOM 0xFF
#define DIRECT_RETRY_INFRA_BASE_MS 275
#define DIRECT_RETRY_INFRA_COUNT 4
#define DIRECT_RETRY_INFRA_STEP_MS 150
#define DIRECT_RETRY_INFRA_MARGIN_X4 60
#define DIRECT_RETRY_ROOFTOP_BASE_MS 175
#define DIRECT_RETRY_ROOFTOP_COUNT 15
#define DIRECT_RETRY_ROOFTOP_STEP_MS 100
#define DIRECT_RETRY_ROOFTOP_MARGIN_X4 20
#define DIRECT_RETRY_MOBILE_BASE_MS 175
#define DIRECT_RETRY_MOBILE_COUNT 15
#define DIRECT_RETRY_MOBILE_STEP_MS 50
#define DIRECT_RETRY_MOBILE_MARGIN_X4 0
#define DIRECT_RETRY_RECENT_DEFAULT 1
#define FLOOD_RETRY_INFRA_COUNT 1
#define FLOOD_RETRY_INFRA_MAX_PATH 1
#define FLOOD_RETRY_ROOFTOP_COUNT 3
#define FLOOD_RETRY_ROOFTOP_MAX_PATH 2
#define FLOOD_RETRY_MOBILE_COUNT 15
#define FLOOD_RETRY_MOBILE_MAX_PATH 1
#define FLOOD_RETRY_ADVERT_DEFAULT 0
#define BATTERY_ALERT_LOW_PERCENT_DEFAULT 20
#define BATTERY_ALERT_CRITICAL_PERCENT_DEFAULT 10
#ifndef FLOOD_RETRY_PREFIX_SLOTS
#define FLOOD_RETRY_PREFIX_SLOTS 8
#endif
#ifndef FLOOD_RETRY_PREFIX_LEN
#define FLOOD_RETRY_PREFIX_LEN 3
#endif
#ifndef FLOOD_RETRY_BRIDGE_BUCKETS
#define FLOOD_RETRY_BRIDGE_BUCKETS 6
#endif
#ifndef FLOOD_RETRY_BUCKET_PREFIXES
#define FLOOD_RETRY_BUCKET_PREFIXES 17
#endif
#ifndef FLOOD_RETRY_IGNORE_PREFIXES
#define FLOOD_RETRY_IGNORE_PREFIXES 8
#endif
#ifndef FLOOD_RETRY_LIST_PREFIXES
#define FLOOD_RETRY_LIST_PREFIXES ((FLOOD_RETRY_IGNORE_PREFIXES > FLOOD_RETRY_BUCKET_PREFIXES) ? FLOOD_RETRY_IGNORE_PREFIXES : FLOOD_RETRY_BUCKET_PREFIXES)
#endif
#ifndef FLOOD_RETRY_LIST_TEXT_MAX
#define FLOOD_RETRY_LIST_TEXT_MAX (FLOOD_RETRY_LIST_PREFIXES * FLOOD_RETRY_PREFIX_LEN * 2 + FLOOD_RETRY_LIST_PREFIXES)
#endif
#ifndef COMMON_CLI_TMP_LEN
#define COMMON_CLI_TMP_LEN ((FLOOD_RETRY_LIST_TEXT_MAX > (PRV_KEY_SIZE * 2 + 4)) ? FLOOD_RETRY_LIST_TEXT_MAX : (PRV_KEY_SIZE * 2 + 4))
#endif
#define DIRECT_RETRY_CR4_MIN_SNR_X4_DEFAULT 40
#define DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT 30
#define DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT 10
#define DIRECT_RETRY_CR8_MAX_SNR_X4_DEFAULT 10
#define DIRECT_RETRY_PREFS_MAGIC_0 0xD1
#define DIRECT_RETRY_PREFS_MAGIC_1 0x52
struct NodePrefs { // persisted to file
float airtime_factor;
char node_name[32];
@@ -53,6 +120,7 @@ struct NodePrefs { // persisted to file
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
// Power setting
uint8_t powersaving_enabled; // boolean
uint8_t reboot_interval; // hours, 0-255 (default 0=disable)
// Gps settings
uint8_t gps_enabled;
uint32_t gps_interval; // in seconds
@@ -65,6 +133,29 @@ struct NodePrefs { // persisted to file
uint8_t path_hash_mode; // which path mode to use when sending
uint8_t loop_detect;
uint8_t cad_enabled; // hardware Channel Activity Detection before TX (boolean)
uint8_t retry_preset;
uint8_t direct_retry_attempts;
uint16_t direct_retry_base_ms;
uint16_t direct_retry_step_ms;
uint16_t direct_retry_snr_margin_x4;
int8_t direct_retry_cr4_snr_x4;
int8_t direct_retry_cr5_snr_x4;
int8_t direct_retry_cr7_snr_x4;
int8_t direct_retry_cr8_snr_x4;
uint8_t direct_retry_enabled;
uint8_t direct_retry_cr_enabled;
uint8_t direct_retry_prefs_magic[2];
uint8_t flood_retry_attempts;
uint8_t flood_retry_max_path;
uint8_t flood_retry_prefixes[FLOOD_RETRY_PREFIX_SLOTS][FLOOD_RETRY_PREFIX_LEN];
uint8_t flood_retry_bridge_enabled;
uint8_t flood_retry_bridge_buckets[FLOOD_RETRY_BRIDGE_BUCKETS][FLOOD_RETRY_BUCKET_PREFIXES][FLOOD_RETRY_PREFIX_LEN];
uint8_t flood_retry_ignore_prefixes[FLOOD_RETRY_IGNORE_PREFIXES][FLOOD_RETRY_PREFIX_LEN];
uint8_t flood_retry_advert_enabled;
uint8_t battery_alert_enabled;
uint8_t battery_alert_low_percent;
uint8_t battery_alert_critical_percent;
uint8_t direct_retry_recent_enabled;
};
class CommonCLICallbacks {
@@ -88,10 +179,43 @@ public:
virtual void formatStatsReply(char *reply) = 0;
virtual void formatRadioStatsReply(char *reply) = 0;
virtual void formatPacketStatsReply(char *reply) = 0;
virtual void formatRecentRepeatersReply(char *reply, int page) {
(void)page;
if (reply != NULL) reply[0] = 0;
}
virtual bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4) {
(void)prefix;
(void)prefix_len;
(void)snr_x4;
return false;
}
virtual void clearRecentRepeaters() {
}
virtual mesh::LocalIdentity& getSelfId() = 0;
virtual void saveIdentity(const mesh::LocalIdentity& new_id) = 0;
virtual void clearStats() = 0;
virtual void applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) = 0;
virtual void addScheduledRadioParams(bool temporary, float freq, float bw, uint8_t sf, uint8_t cr,
uint32_t start_time, uint32_t end_time, char* reply) {
(void)temporary;
(void)freq;
(void)bw;
(void)sf;
(void)cr;
(void)start_time;
(void)end_time;
strcpy(reply, "Error: unsupported");
}
virtual void formatScheduledRadioParams(bool temporary, const char* selector, char* reply) {
(void)temporary;
(void)selector;
strcpy(reply, "Error: unsupported");
}
virtual void deleteScheduledRadioParams(bool temporary, const char* selector, char* reply) {
(void)temporary;
(void)selector;
strcpy(reply, "Error: unsupported");
}
virtual void startRegionsLoad() {
// no op by default
@@ -124,7 +248,7 @@ class CommonCLI {
SensorManager* _sensors;
RegionMap* _region_map;
ClientACL* _acl;
char tmp[PRV_KEY_SIZE*2 + 4];
char tmp[COMMON_CLI_TMP_LEN];
mesh::RTCClock* getRTCClock() { return _rtc; }
void savePrefs();
@@ -133,6 +257,7 @@ class CommonCLI {
void handleRegionCmd(char* command, char* reply);
void handleGetCmd(uint32_t sender_timestamp, char* command, char* reply);
void handleSetCmd(uint32_t sender_timestamp, char* command, char* reply);
void handleDelCmd(char* command, char* reply);
public:
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, RegionMap& region_map, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks)
+77 -5
View File
@@ -1,6 +1,7 @@
#ifdef ESP_PLATFORM
#include "ESP32Board.h"
#include <target.h>
#if defined(ADMIN_PASSWORD) && !defined(DISABLE_WIFI_OTA) // Repeater or Room Server only
#include <WiFi.h>
@@ -12,6 +13,12 @@
bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
inhibit_sleep = true; // prevent sleep during OTA
if (ota_server != nullptr) {
sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str());
return true;
}
WiFi.softAP("MeshCore-OTA", NULL);
sprintf(reply, "Started: http://%s/update", WiFi.softAPIP().toString().c_str());
@@ -22,18 +29,36 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
static char home_buf[90];
sprintf(home_buf, "<H2>Hi! I am a MeshCore Repeater. ID: %s</H2>", id);
AsyncWebServer* server = new AsyncWebServer(80);
ota_server = new AsyncWebServer(80);
server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
ota_server->on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(200, "text/html", home_buf);
});
server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) {
ota_server->on("/log", HTTP_GET, [](AsyncWebServerRequest *request) {
request->send(SPIFFS, "/packet_log", "text/plain");
});
AsyncElegantOTA.setID(id_buf);
AsyncElegantOTA.begin(server); // Start ElegantOTA
server->begin();
AsyncElegantOTA.begin(ota_server); // Start ElegantOTA
ota_server->begin();
return true;
}
bool ESP32Board::stopOTAUpdate(char reply[]) {
if (ota_server == nullptr) {
strcpy(reply, "OK - OTA not running");
return true;
}
ota_server->end();
delete ota_server;
ota_server = nullptr;
WiFi.softAPdisconnect(true);
inhibit_sleep = false;
strcpy(reply, "OK - OTA stopped");
MESH_DEBUG_PRINTLN("stopOTAUpdate: %s", reply);
return true;
}
@@ -42,6 +67,53 @@ bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
bool ESP32Board::startOTAUpdate(const char* id, char reply[]) {
return false; // not supported
}
bool ESP32Board::stopOTAUpdate(char reply[]) {
return false; // not supported
}
#endif
void ESP32Board::powerOff() {
enterDeepSleep(0); // Do not wakeup
}
void ESP32Board::enterDeepSleep(uint32_t secs) {
// Power off the display if any
#ifdef DISPLAY_CLASS
display.turnOff();
#endif
// Power off LoRa
radio_driver.powerOff();
#ifdef P_LORA_NSS
// Keep LoRa inactive during deepsleep
digitalWrite(P_LORA_NSS, HIGH);
#if CONFIG_IDF_TARGET_ESP32C3
gpio_hold_en((gpio_num_t)P_LORA_NSS);
#else
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
#endif
#endif
// Power off GPS if any
if (sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
// Flush serial buffers
Serial.flush();
delay(100);
// Clear stale wakeup sources to avoid ghost wakeup
// This is required when Power Management and automatic lightsleep are enabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000ULL);
}
// Finally set ESP32 into deepsleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
#endif
+93 -8
View File
@@ -14,11 +14,15 @@
#include <Wire.h>
#include "soc/rtc.h"
#include "esp_system.h"
#include <driver/rtc_io.h>
class AsyncWebServer;
class ESP32Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
bool inhibit_sleep = false;
AsyncWebServer* ota_server = nullptr;
static inline portMUX_TYPE sleepMux = portMUX_INITIALIZER_UNLOCKED;
public:
@@ -62,8 +66,15 @@ public:
return raw / 4;
}
virtual void powerOff() override;
void enterDeepSleep(uint32_t secs);
uint32_t getIRQGpio() override {
#ifdef P_LORA_DIO_1
return P_LORA_DIO_1; // default for SX1262
#else
return -1;
#endif
}
void sleep(uint32_t secs) override {
@@ -73,14 +84,22 @@ public:
return;
}
// Set GPIO wakeup
gpio_num_t wakeupPin = (gpio_num_t)getIRQGpio();
// Configure timer wakeup
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000ULL); // Wake up periodically to do scheduled jobs
}
uint32_t irqGpio = getIRQGpio();
if (irqGpio == static_cast<uint32_t>(-1)) {
if (secs > 0) {
esp_light_sleep_start();
}
return;
}
// Set GPIO wakeup
gpio_num_t wakeupPin = (gpio_num_t)irqGpio;
// Disable CPU interrupt servicing
portENTER_CRITICAL(&sleepMux);
@@ -151,25 +170,81 @@ public:
}
bool startOTAUpdate(const char* id, char reply[]) override;
bool stopOTAUpdate(char reply[]) override;
bool isUsbDataConnected() override {
#if defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT
return (bool)Serial;
#else
return false;
#endif
}
void setInhibitSleep(bool inhibit) {
inhibit_sleep = inhibit;
}
uint32_t getResetReason() const override {
return esp_reset_reason();
}
// https://docs.espressif.com/projects/esp-idf/en/v4.4.7/esp32/api-reference/system/system.html
const char *getResetReasonString(uint32_t reason) {
switch (reason) {
case ESP_RST_UNKNOWN:
return "Unknown or first boot";
case ESP_RST_POWERON:
return "Power-on reset";
case ESP_RST_EXT:
return "External reset";
case ESP_RST_SW:
return "Software reset";
case ESP_RST_PANIC:
return "Panic / exception reset";
case ESP_RST_INT_WDT:
return "Interrupt watchdog reset";
case ESP_RST_TASK_WDT:
return "Task watchdog reset";
case ESP_RST_WDT:
return "Other watchdog reset";
case ESP_RST_DEEPSLEEP:
return "Wake from deep sleep";
case ESP_RST_BROWNOUT:
return "Brownout (low voltage)";
case ESP_RST_SDIO:
return "SDIO reset";
default:
static char buf[40];
snprintf(buf, sizeof(buf), "Unknown reset reason (%d)", reason);
return buf;
}
}
};
static RTC_NOINIT_ATTR uint32_t _rtc_backup_time;
static RTC_NOINIT_ATTR uint32_t _rtc_backup_magic;
#define RTC_BACKUP_MAGIC 0xAA55CC33
#define RTC_TIME_MIN 1772323200 // 1 Mar 2026
class ESP32RTCClock : public mesh::RTCClock {
public:
ESP32RTCClock() { }
void begin() {
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_POWERON) {
// start with some date/time in the recent past
struct timeval tv;
tv.tv_sec = 1715770351; // 15 May 2024, 8:50pm
if (reason == ESP_RST_DEEPSLEEP) {
return; // ESP-IDF preserves system time across deep sleep
}
// All other resets (power-on, crash, WDT, brownout) lose system time.
// Restore from RTC backup if valid, otherwise use hardcoded seed.
struct timeval tv;
if (_rtc_backup_magic == RTC_BACKUP_MAGIC && _rtc_backup_time > RTC_TIME_MIN) {
tv.tv_sec = _rtc_backup_time;
} else {
tv.tv_sec = RTC_TIME_MIN;
}
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
}
uint32_t getCurrentTime() override {
time_t _now;
time(&_now);
@@ -180,6 +255,16 @@ public:
tv.tv_sec = time;
tv.tv_usec = 0;
settimeofday(&tv, NULL);
_rtc_backup_time = time;
_rtc_backup_magic = RTC_BACKUP_MAGIC;
}
void tick() override {
time_t now;
time(&now);
if (now > RTC_TIME_MIN && (uint32_t)now != _rtc_backup_time) {
_rtc_backup_time = (uint32_t)now;
_rtc_backup_magic = RTC_BACKUP_MAGIC;
}
}
};
-30
View File
@@ -15,8 +15,6 @@
#include "ESP32Board.h"
#include <driver/rtc_io.h>
class MeshadventurerBoard : public ESP32Board {
public:
@@ -35,34 +33,6 @@ public:
}
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are held on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void powerOff() override {
// TODO: re-enable this when there is a definite wake-up source pin:
// enterDeepSleep(0);
}
uint16_t getBattMilliVolts() override {
analogReadResolution(12);
+106 -15
View File
@@ -1,19 +1,38 @@
#if defined(NRF52_PLATFORM)
#include "NRF52Board.h"
#include <target.h>
#include <bluefruit.h>
#include "ble_gap.h"
#include "ble_hci.h"
#include <nrf_soc.h>
#ifdef USE_TINYUSB
#include <Adafruit_TinyUSB.h>
#endif
static BLEDfu bledfu;
static uint16_t ota_conn_handle = BLE_CONN_HANDLE_INVALID;
static bool ota_active = false;
static bool ota_ble_started = false;
static void format_ota_reply(char reply[]) {
uint8_t mac_addr[6];
memset(mac_addr, 0, sizeof(mac_addr));
Bluefruit.getAddr(mac_addr);
sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[5], mac_addr[4], mac_addr[3],
mac_addr[2], mac_addr[1], mac_addr[0]);
}
static void connect_callback(uint16_t conn_handle) {
(void)conn_handle;
ota_conn_handle = conn_handle;
MESH_DEBUG_PRINTLN("BLE client connected");
}
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void)conn_handle;
(void)reason;
if (ota_conn_handle == conn_handle) {
ota_conn_handle = BLE_CONN_HANDLE_INVALID;
}
MESH_DEBUG_PRINTLN("BLE client disconnected");
}
@@ -251,6 +270,18 @@ bool NRF52Board::isExternalPowered() {
}
}
bool NRF52Board::isUsbDataConnected() {
#if defined(USE_TINYUSB)
#if defined(CFG_TUD_CDC) && CFG_TUD_CDC
return tud_mounted() && tud_cdc_connected();
#else
return tud_mounted();
#endif
#else
return false;
#endif
}
void NRF52Board::sleep(uint32_t secs) {
// Clear FPU interrupt flags to avoid insomnia
// see errata 87 for details https://docs.nordicsemi.com/bundle/errata_nRF52840_Rev3/page/ERR/nRF52840/Rev3/latest/anomaly_840_87.html
@@ -297,6 +328,37 @@ float NRF52Board::getMCUTemperature() {
return temp * 0.25f; // Convert to *C
}
void NRF52Board::powerOff() {
// Power off the display if any
#ifdef DISPLAY_CLASS
display.turnOff();
#endif
// Power off LoRa
radio_driver.powerOff();
// Keep LoRa inactive during deepsleep
digitalWrite(P_LORA_NSS, HIGH);
// Power off GPS if any
if(sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->stop();
}
// Flush serial buffers
Serial.flush();
delay(100);
// Enter SYSTEMOFF
uint8_t sd_enabled = 0;
sd_softdevice_is_enabled(&sd_enabled);
if (sd_enabled) { // SoftDevice is enabled
sd_power_system_off();
} else { // SoftDevice is not enable
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
}
}
bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
static const char BOOTLOADER_MARKER[] = "UF2 Bootloader ";
const uint8_t* flash = (const uint8_t*)0x000FB000; // earliest known info.txt location is 0xFB90B, latest is 0xFCC4B
@@ -317,13 +379,27 @@ bool NRF52Board::getBootloaderVersion(char* out, size_t max_len) {
}
bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
(void)id;
if (ota_active) {
format_ota_reply(reply);
return true;
}
if (!ota_ble_started) {
// Config the peripheral connection with maximum bandwidth
// more SRAM required by SoftDevice
// Note: All config***() function must be called before begin()
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
Bluefruit.begin(1, 0);
ota_ble_started = true;
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
}
Bluefruit.begin(1, 0);
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
Bluefruit.setTxPower(4);
// Set the BLE device name
@@ -332,8 +408,8 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
// To be consistent OTA DFU should be added first if it exists
bledfu.begin();
Bluefruit.Advertising.clearData();
Bluefruit.ScanResponse.clearData();
// Set up and start advertising
// Advertising packet
@@ -355,12 +431,27 @@ bool NRF52Board::startOTAUpdate(const char *id, char reply[]) {
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
uint8_t mac_addr[6];
memset(mac_addr, 0, sizeof(mac_addr));
Bluefruit.getAddr(mac_addr);
sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[5], mac_addr[4], mac_addr[3],
mac_addr[2], mac_addr[1], mac_addr[0]);
ota_active = true;
format_ota_reply(reply);
return true;
}
bool NRF52Board::stopOTAUpdate(char reply[]) {
if (!ota_active) {
strcpy(reply, "OK - OTA not running");
return true;
}
Bluefruit.Advertising.restartOnDisconnect(false);
Bluefruit.Advertising.stop();
if (ota_conn_handle != BLE_CONN_HANDLE_INVALID) {
sd_ble_gap_disconnect(ota_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
ota_conn_handle = BLE_CONN_HANDLE_INVALID;
}
ota_active = false;
strcpy(reply, "OK - OTA stopped");
return true;
}
#endif
+4 -1
View File
@@ -50,10 +50,13 @@ public:
virtual uint8_t getStartupReason() const override { return startup_reason; }
virtual float getMCUTemperature() override;
virtual void reboot() override { NVIC_SystemReset(); }
virtual void powerOff() override;
virtual bool getBootloaderVersion(char* version, size_t max_len) override;
virtual bool startOTAUpdate(const char *id, char reply[]) override;
virtual bool stopOTAUpdate(char reply[]) override;
virtual void sleep(uint32_t secs) override;
bool isExternalPowered() override;
bool isUsbDataConnected() override;
#ifdef NRF52_POWER_MANAGEMENT
uint16_t getBootVoltage() override { return boot_voltage_mv; }
@@ -76,4 +79,4 @@ public:
NRF52BoardDCDC() {}
virtual void begin() override;
};
#endif
#endif
+2
View File
@@ -2,6 +2,7 @@
#include <CayenneLPP.h>
#include "sensors/LocationProvider.h"
#include <Wire.h>
#define TELEM_PERM_BASE 0x01 // 'base' permission includes battery
#define TELEM_PERM_LOCATION 0x02
@@ -15,6 +16,7 @@ public:
double node_altitude; // altitude in meters
SensorManager() { node_lat = 0; node_lon = 0; node_altitude = 0; }
virtual bool i2c_probe(TwoWire& wire, uint8_t addr) { return false; }
virtual bool begin() { return false; }
virtual bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { return false; }
virtual void loop() { }
+322 -11
View File
@@ -1,29 +1,164 @@
#pragma once
#include <Mesh.h>
#if ARDUINO
#include <Arduino.h>
#endif
#ifdef ESP32
#include <FS.h>
#endif
#define MAX_PACKET_HASHES (128+32)
#ifndef MESH_ENABLE_RECENT_REPEATERS
#define MESH_ENABLE_RECENT_REPEATERS 0
#endif
#ifndef MAX_RECENT_REPEATERS
// Recent repeater history is only needed by repeater firmware. Other roles
// use unconditional retry decisions and skip this RAM-heavy cache.
#if !MESH_ENABLE_RECENT_REPEATERS
#define MAX_RECENT_REPEATERS 0
#elif defined(ESP32) || defined(ESP32_PLATFORM)
#define MAX_RECENT_REPEATERS 2048
#elif defined(NRF52_PLATFORM)
#define MAX_RECENT_REPEATERS 512
#else
#define MAX_RECENT_REPEATERS 64
#endif
#endif
#define RECENT_REPEATER_STORAGE_SLOTS (MAX_RECENT_REPEATERS > 0 ? MAX_RECENT_REPEATERS : 1)
#define MAX_ROUTE_HASH_BYTES 3
class SimpleMeshTables : public mesh::MeshTables {
public:
struct RecentRepeaterInfo {
// Identity and link quality for a next-hop path prefix.
uint8_t prefix[MAX_ROUTE_HASH_BYTES];
uint8_t prefix_len;
int8_t snr_x4;
uint32_t last_heard_millis;
};
private:
uint8_t _hashes[MAX_PACKET_HASHES*MAX_HASH_SIZE];
int _next_idx;
uint32_t _direct_dups, _flood_dups;
RecentRepeaterInfo _recent_repeaters[RECENT_REPEATER_STORAGE_SLOTS];
bool hasSeenHash(const uint8_t* hash) const {
const uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
return true;
}
}
return false;
}
void storeHash(const uint8_t* hash) {
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES;
}
bool prefixesOverlap(const uint8_t* a, uint8_t a_len, const uint8_t* b, uint8_t b_len) const {
uint8_t n = a_len < b_len ? a_len : b_len;
return n > 0 && memcmp(a, b, n) == 0;
}
int8_t weightedSnrX4RoundUp(int8_t curr_snr_x4, int8_t new_snr_x4) const {
// Keep existing SNR heavier than a single new sample: 75% existing + 25% new.
int32_t weighted_sum = ((int32_t)curr_snr_x4 * 3) + (int32_t)new_snr_x4;
int32_t blended = weighted_sum / 4; // truncates toward zero
// "Round up" means ceil(), which only differs from truncation for positive remainders.
if (weighted_sum > 0 && (weighted_sum % 4) != 0) {
blended++;
}
if (blended > 127) {
blended = 127;
} else if (blended < -128) {
blended = -128;
}
return (int8_t)blended;
}
bool extractRecentRepeater(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
// Learn repeater prefixes only from packet shapes that expose a trustworthy repeater ID.
// For flood traffic, the last path entry is the repeater we directly heard.
if (packet->isRouteFlood() && packet->getPathHashCount() > 0) {
prefix_len = packet->getPathHashSize();
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
prefix_len = MAX_ROUTE_HASH_BYTES;
}
const uint8_t* last_hop = &packet->path[(packet->getPathHashCount() - 1) * packet->getPathHashSize()];
memcpy(prefix, last_hop, prefix_len);
return true;
}
// If there is no flood path to inspect, fall back to payload-derived identities.
if (packet->getPayloadType() == PAYLOAD_TYPE_ADVERT && packet->payload_len >= PUB_KEY_SIZE) {
memcpy(prefix, packet->payload, MAX_ROUTE_HASH_BYTES);
prefix_len = MAX_ROUTE_HASH_BYTES;
return true;
}
if (packet->getPayloadType() == PAYLOAD_TYPE_CONTROL
&& packet->isRouteDirect()
&& packet->getPathHashCount() == 0
&& packet->payload_len >= 6 + MAX_ROUTE_HASH_BYTES
&& (packet->payload[0] & 0xF0) == 0x90) {
memcpy(prefix, &packet->payload[6], MAX_ROUTE_HASH_BYTES);
prefix_len = MAX_ROUTE_HASH_BYTES;
return true;
}
return false;
}
bool recentRepeaterComesBefore(const RecentRepeaterInfo& a, int a_idx,
const RecentRepeaterInfo& b, int b_idx) const {
if (a.prefix_len != b.prefix_len) {
return a.prefix_len > b.prefix_len; // 3-byte prefixes, then 2-byte, then 1-byte.
}
if (a.snr_x4 != b.snr_x4) {
return a.snr_x4 > b.snr_x4; // Highest SNR first within each prefix length.
}
int cmp = memcmp(a.prefix, b.prefix, a.prefix_len);
if (cmp != 0) {
return cmp < 0;
}
return a_idx < b_idx;
}
void recordRecentRepeater(const mesh::Packet* packet) {
if (MAX_RECENT_REPEATERS == 0) {
return;
}
uint8_t prefix[MAX_ROUTE_HASH_BYTES] = {0};
uint8_t prefix_len = 0;
if (!extractRecentRepeater(packet, prefix, prefix_len) || prefix_len == 0) {
return;
}
setRecentRepeater(prefix, prefix_len, packet->_snr);
}
public:
SimpleMeshTables() {
memset(_hashes, 0, sizeof(_hashes));
_next_idx = 0;
_direct_dups = _flood_dups = 0;
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
}
#ifdef ESP32
void restoreFrom(File f) {
f.read(_hashes, sizeof(_hashes));
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
// Recent repeater entries are intentionally not restored across boots.
// This avoids struct-layout migration issues and keeps stale path quality
// stats from persisting indefinitely.
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
}
void saveTo(File f) {
f.write(_hashes, sizeof(_hashes));
@@ -35,23 +170,29 @@ public:
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
const uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
if (packet->isRouteDirect()) {
_direct_dups++; // keep some stats
} else {
_flood_dups++;
}
return true;
if (hasSeenHash(hash)) {
if (packet->isRouteDirect()) {
_direct_dups++; // keep some stats
} else {
_flood_dups++;
}
return true;
}
memcpy(&_hashes[_next_idx*MAX_HASH_SIZE], hash, MAX_HASH_SIZE);
_next_idx = (_next_idx + 1) % MAX_PACKET_HASHES; // cyclic table
storeHash(hash);
recordRecentRepeater(packet);
return false;
}
void markSent(const mesh::Packet* packet) override {
// Outbound packets must be marked as already-sent without teaching the recent-heard cache about ourselves.
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
if (!hasSeenHash(hash)) {
storeHash(hash);
}
}
void clear(const mesh::Packet* packet) override {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
@@ -68,5 +209,175 @@ public:
uint32_t getNumDirectDups() const { return _direct_dups; }
uint32_t getNumFloodDups() const { return _flood_dups; }
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4,
bool snr_locked = false, bool bypass_allow_filter = false) {
(void)snr_locked;
(void)bypass_allow_filter;
if (MAX_RECENT_REPEATERS == 0) {
return false;
}
if (prefix == NULL || prefix_len == 0) {
return false;
}
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
prefix_len = MAX_ROUTE_HASH_BYTES;
}
// Keep exact prefixes distinct so a 1-byte path prefix does not collapse
// independent 2/3-byte repeaters that share the same first byte.
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
RecentRepeaterInfo& existing = _recent_repeaters[i];
if (existing.prefix_len != prefix_len || memcmp(existing.prefix, prefix, prefix_len) != 0) {
continue;
}
existing.snr_x4 = weightedSnrX4RoundUp(existing.snr_x4, snr_x4);
#if ARDUINO
existing.last_heard_millis = millis();
#else
existing.last_heard_millis = 0;
#endif
return true;
}
int slot_idx = -1;
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
if (_recent_repeaters[i].prefix_len == 0) {
slot_idx = i;
break;
}
}
if (slot_idx < 0) {
// Table is full: evict the oldest heard entry.
slot_idx = 0;
#if ARDUINO
uint32_t now = millis();
uint32_t oldest_age = (uint32_t)(now - _recent_repeaters[0].last_heard_millis);
for (int i = 1; i < MAX_RECENT_REPEATERS; i++) {
uint32_t age = (uint32_t)(now - _recent_repeaters[i].last_heard_millis);
if (age > oldest_age) {
oldest_age = age;
slot_idx = i;
}
}
#endif
}
RecentRepeaterInfo& slot = _recent_repeaters[slot_idx];
memset(slot.prefix, 0, sizeof(slot.prefix));
memcpy(slot.prefix, prefix, prefix_len);
slot.prefix_len = prefix_len;
slot.snr_x4 = snr_x4;
#if ARDUINO
slot.last_heard_millis = millis();
#else
slot.last_heard_millis = 0;
#endif
return true;
}
bool decrementRecentRepeaterSnrX4(const uint8_t* prefix, uint8_t prefix_len, uint8_t amount_x4 = 1) {
if (MAX_RECENT_REPEATERS == 0) {
return false;
}
if (prefix == NULL || prefix_len == 0 || amount_x4 == 0) {
return false;
}
if (prefix_len > MAX_ROUTE_HASH_BYTES) {
prefix_len = MAX_ROUTE_HASH_BYTES;
}
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
RecentRepeaterInfo& existing = _recent_repeaters[i];
if (existing.prefix_len != prefix_len || memcmp(existing.prefix, prefix, prefix_len) != 0) {
continue;
}
int16_t lowered = (int16_t)existing.snr_x4 - (int16_t)amount_x4;
if (lowered < -128) {
lowered = -128;
}
existing.snr_x4 = (int8_t)lowered;
return true;
}
return false;
}
int getRecentRepeaterCount() const {
if (MAX_RECENT_REPEATERS == 0) {
return 0;
}
int count = 0;
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
if (_recent_repeaters[i].prefix_len > 0) {
count++;
}
}
return count;
}
const RecentRepeaterInfo* getRecentRepeaterBySortedIdx(int idx_wanted) const {
if (MAX_RECENT_REPEATERS == 0) {
return NULL;
}
if (idx_wanted < 0) {
return NULL;
}
const RecentRepeaterInfo* last = NULL;
int last_idx = -1;
for (int rank = 0; rank <= idx_wanted; rank++) {
const RecentRepeaterInfo* best = NULL;
int best_idx = -1;
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
const RecentRepeaterInfo* info = &_recent_repeaters[i];
if (info->prefix_len == 0) {
continue;
}
if (last != NULL && !recentRepeaterComesBefore(*last, last_idx, *info, i)) {
continue;
}
if (best == NULL || recentRepeaterComesBefore(*info, i, *best, best_idx)) {
best = info;
best_idx = i;
}
}
if (best == NULL) {
return NULL;
}
last = best;
last_idx = best_idx;
}
return last;
}
const RecentRepeaterInfo* findRecentRepeaterByHash(const uint8_t* hash, uint8_t hash_len) const {
if (MAX_RECENT_REPEATERS == 0) {
return NULL;
}
if (hash == NULL || hash_len == 0) {
return NULL;
}
// Prefer exact matches. If none exists, fall back to the longest overlapping
// prefix, using highest SNR to break ties.
const RecentRepeaterInfo* best = NULL;
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
const RecentRepeaterInfo* info = &_recent_repeaters[i];
if (info->prefix_len == 0) {
continue;
}
if (info->prefix_len == hash_len && memcmp(info->prefix, hash, hash_len) == 0) {
return info;
}
if (prefixesOverlap(info->prefix, info->prefix_len, hash, hash_len)) {
if (best == NULL || info->prefix_len > best->prefix_len
|| (info->prefix_len == best->prefix_len && info->snr_x4 > best->snr_x4)) {
best = info;
}
}
}
return best;
}
void clearRecentRepeaters() {
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
}
void resetStats() { _direct_dups = _flood_dups = 0; }
};
+3 -2
View File
@@ -83,11 +83,12 @@ void StaticPoolPacketManager::free(mesh::Packet* packet) {
unused.add(packet, 0, 0);
}
void StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
bool StaticPoolPacketManager::queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) {
if (!send_queue.add(packet, priority, scheduled_for)) {
MESH_DEBUG_PRINTLN("queueOutbound: send queue full, dropping packet");
free(packet);
return false;
}
return true;
}
mesh::Packet* StaticPoolPacketManager::getNextOutbound(uint32_t now) {
+2 -2
View File
@@ -26,7 +26,7 @@ public:
mesh::Packet* allocNew() override;
void free(mesh::Packet* packet) override;
void queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
bool queueOutbound(mesh::Packet* packet, uint8_t priority, uint32_t scheduled_for) override;
mesh::Packet* getNextOutbound(uint32_t now) override;
int getOutboundCount(uint32_t now) const override;
int getOutboundTotal() const override;
@@ -35,4 +35,4 @@ public:
mesh::Packet* removeOutboundByIdx(int i) override;
void queueInbound(mesh::Packet* packet, uint32_t scheduled_for) override;
mesh::Packet* getNextInbound(uint32_t now) override;
};
};
+11 -2
View File
@@ -9,11 +9,20 @@
ESPNowBridge *ESPNowBridge::_instance = nullptr;
// Static callback wrappers
void ESPNowBridge::recv_cb(const uint8_t *mac, const uint8_t *data, int32_t len) {
#if ESP_IDF_VERSION_MAJOR >= 5
void ESPNowBridge::recv_cb(const esp_now_recv_info_t *info, const uint8_t *data, int len) {
const uint8_t *mac = info != nullptr ? info->src_addr : nullptr;
if (_instance) {
_instance->onDataRecv(mac, data, len);
}
}
#else
void ESPNowBridge::recv_cb(const uint8_t *mac, const uint8_t *data, int len) {
if (_instance) {
_instance->onDataRecv(mac, data, len);
}
}
#endif
void ESPNowBridge::send_cb(const uint8_t *mac, esp_now_send_status_t status) {
if (_instance) {
@@ -100,7 +109,7 @@ void ESPNowBridge::xorCrypt(uint8_t *data, size_t len) {
}
}
void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len) {
void ESPNowBridge::onDataRecv(const uint8_t *mac, const uint8_t *data, int len) {
// Ignore packets that are too small to contain header + checksum
if (len < (BRIDGE_MAGIC_SIZE + BRIDGE_CHECKSUM_SIZE)) {
BRIDGE_DEBUG_PRINTLN("RX packet too small, len=%d\n", len);
+7 -2
View File
@@ -2,6 +2,7 @@
#include "MeshCore.h"
#include "esp_now.h"
#include "esp_idf_version.h"
#include "helpers/bridges/BridgeBase.h"
#ifdef WITH_ESPNOW_BRIDGE
@@ -42,7 +43,11 @@
class ESPNowBridge : public BridgeBase {
private:
static ESPNowBridge *_instance;
static void recv_cb(const uint8_t *mac, const uint8_t *data, int32_t len);
#if ESP_IDF_VERSION_MAJOR >= 5
static void recv_cb(const esp_now_recv_info_t *info, const uint8_t *data, int len);
#else
static void recv_cb(const uint8_t *mac, const uint8_t *data, int len);
#endif
static void send_cb(const uint8_t *mac, esp_now_send_status_t status);
/**
@@ -90,7 +95,7 @@ private:
* @param data Received data
* @param len Length of received data
*/
void onDataRecv(const uint8_t *mac, const uint8_t *data, int32_t len);
void onDataRecv(const uint8_t *mac, const uint8_t *data, int len);
/**
* ESP-NOW send callback
+4
View File
@@ -182,6 +182,10 @@ size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
#define BLE_WRITE_MIN_INTERVAL 60
bool SerialBLEInterface::isReadBusy() const {
return (recv_queue_len > 0);
}
bool SerialBLEInterface::isWriteBusy() const {
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?
}
+1
View File
@@ -76,6 +76,7 @@ public:
bool isConnected() const override;
bool isReadBusy() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
@@ -39,6 +39,10 @@ size_t SerialWifiInterface::writeFrame(const uint8_t src[], size_t len) {
return 0;
}
bool SerialWifiInterface::isReadBusy() const {
return false;
}
bool SerialWifiInterface::isWriteBusy() const {
return false;
}
+1
View File
@@ -52,6 +52,7 @@ public:
bool isEnabled() const override { return _isEnabled; }
bool isConnected() const override;
bool isReadBusy() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
-24
View File
@@ -86,7 +86,6 @@
#include <Arduino.h>
#include "XPowersLib.h"
#include "helpers/ESP32Board.h"
#include <driver/rtc_io.h>
class TBeamBoard : public ESP32Board {
XPowersLibInterface *PMU = NULL;
@@ -131,29 +130,6 @@ public:
}
#endif
void enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
uint16_t getBattMilliVolts(){
return PMU->getBattVoltage();
}
+4
View File
@@ -401,6 +401,10 @@ bool SerialBLEInterface::isConnected() const {
return _isDeviceConnected && Bluefruit.connected() > 0;
}
bool SerialBLEInterface::isReadBusy() const {
return (recv_queue_len > 0);
}
bool SerialBLEInterface::isWriteBusy() const {
return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3);
}
+1
View File
@@ -66,6 +66,7 @@ public:
void disable() override;
bool isEnabled() const override { return _isEnabled; }
bool isConnected() const override;
bool isReadBusy() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
@@ -16,6 +16,10 @@ public:
updatePreamble(sf);
}
bool setCodingRate(uint8_t cr) override {
return ((CustomLLCC68 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
bool isReceivingPacket() override {
return ((CustomLLCC68 *)_radio)->isReceiving();
}
@@ -4,6 +4,10 @@
#include "RadioLibWrappers.h"
#include "LR11x0Reset.h"
#ifndef USE_LR1110
#define USE_LR1110
#endif
class CustomLR1110Wrapper : public RadioLibWrapper {
public:
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
@@ -16,6 +20,10 @@ public:
updatePreamble(sf);
}
bool setCodingRate(uint8_t cr) override {
return ((CustomLR1110 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
bool isReceivingPacket() override {
return ((CustomLR1110 *)_radio)->isReceiving();
@@ -17,6 +17,10 @@ public:
updatePreamble(sf);
}
bool setCodingRate(uint8_t cr) override {
return ((CustomSTM32WLx *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
bool isReceivingPacket() override {
return ((CustomSTM32WLx *)_radio)->isReceiving();
}
@@ -20,6 +20,10 @@ public:
updatePreamble(sf);
}
bool setCodingRate(uint8_t cr) override {
return ((CustomSX1262 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
bool isReceivingPacket() override {
return ((CustomSX1262 *)_radio)->isReceiving();
}
@@ -20,6 +20,10 @@ public:
updatePreamble(sf);
}
bool setCodingRate(uint8_t cr) override {
return ((CustomSX1268 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
bool isReceivingPacket() override {
return ((CustomSX1268 *)_radio)->isReceiving();
}
@@ -19,6 +19,10 @@ public:
updatePreamble(sf);
}
bool setCodingRate(uint8_t cr) override {
return ((CustomSX1276 *)_radio)->setCodingRate(cr) == RADIOLIB_ERR_NONE;
}
bool isReceivingPacket() override {
return ((CustomSX1276 *)_radio)->isReceiving();
}
+2
View File
@@ -40,6 +40,8 @@ public:
}
virtual void setParams(float freq, float bw, uint8_t sf, uint8_t cr) = 0;
uint16_t getDefaultPreambleLength() const override { return preambleLengthForSF(_preamble_sf); }
bool setPreambleLength(uint16_t len) override { return _radio->setPreambleLength(len) == RADIOLIB_ERR_NONE; }
uint32_t getRngSeed();
void setTxPower(int8_t dbm);
@@ -8,6 +8,12 @@
#define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors
#endif
bool EnvironmentSensorManager::i2c_probe(TwoWire& wire, uint8_t addr) {
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return error == 0;
}
// ============================================================
// Sensor library includes and static driver instances
// ============================================================
@@ -15,6 +21,7 @@
#if ENV_INCLUDE_BME680_BSEC
#ifndef TELEM_BME680_ADDRESS
#define TELEM_BME680_ADDRESS 0x76
#define TELEM_BME680_ADDRESS_2 0x77
#endif
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
#include <bsec.h>
@@ -40,6 +47,7 @@ static uint32_t bsec_last_save_ms = 0;
#ifdef ENV_INCLUDE_BME680
#ifndef TELEM_BME680_ADDRESS
#define TELEM_BME680_ADDRESS 0x76
#define TELEM_BME680_ADDRESS_2 0x77
#endif
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
#include <Adafruit_BME680.h>
@@ -63,6 +71,7 @@ static Adafruit_AHTX0 AHTX0;
#if ENV_INCLUDE_BME280
#ifndef TELEM_BME280_ADDRESS
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
#define TELEM_BME280_ADDRESS_2 0x77
#endif
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Atmospheric pressure at sea level
#include <Adafruit_BME280.h>
@@ -72,6 +81,7 @@ static Adafruit_BME280 BME280;
#if ENV_INCLUDE_BMP280
#ifndef TELEM_BMP280_ADDRESS
#define TELEM_BMP280_ADDRESS 0x76 // BMP280 environmental sensor I2C address
#define TELEM_BMP280_ADDRESS_2 0x77
#endif
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Atmospheric pressure at sea level
#include <Adafruit_BMP280.h>
@@ -557,15 +567,27 @@ static const SensorDef SENSOR_TABLE[] = {
#endif
#ifdef ENV_INCLUDE_BME680
{ TELEM_BME680_ADDRESS, "BME680", init_bme680, query_bme680 },
#ifdef TELEM_BME680_ADDRESS_2
{ TELEM_BME680_ADDRESS_2, "BME680", init_bme680, query_bme680 },
#endif
#endif
#if ENV_INCLUDE_BME680_BSEC
{ TELEM_BME680_ADDRESS, "BME680+BSEC", init_bme680_bsec, query_bme680_bsec },
#ifdef TELEM_BME680_ADDRESS_2
{ TELEM_BME680_ADDRESS_2, "BME680+BSEC", init_bme680_bsec, query_bme680_bsec },
#endif
#endif
#if ENV_INCLUDE_BME280
{ TELEM_BME280_ADDRESS, "BME280", init_bme280, query_bme280 },
#ifdef TELEM_BME280_ADDRESS_2
{ TELEM_BME280_ADDRESS_2, "BME280", init_bme280, query_bme280 },
#endif
#endif
#if ENV_INCLUDE_BMP280
{ TELEM_BMP280_ADDRESS, "BMP280", init_bmp280, query_bmp280 },
#ifdef TELEM_BMP280_ADDRESS_2
{ TELEM_BMP280_ADDRESS_2, "BMP280", init_bmp280, query_bmp280 },
#endif
#endif
#if ENV_INCLUDE_SHTC3
{ 0x70, "SHTC3", init_shtc3, query_shtc3 },
@@ -41,6 +41,7 @@ public:
#else
EnvironmentSensorManager(){};
#endif
bool i2c_probe(TwoWire& wire, uint8_t addr) override;
bool begin() override;
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
#if ENV_INCLUDE_GPS || defined(ENV_INCLUDE_BME680_BSEC)
+9
View File
@@ -63,6 +63,15 @@ void ST7735Display::turnOff() {
#else
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
#endif
// Prevent back-powering to save 2.8 mA
pinMode(PIN_TFT_CS, INPUT);
pinMode(PIN_TFT_DC, INPUT);
pinMode(PIN_TFT_SDA, INPUT);
pinMode(PIN_TFT_SCL, INPUT);
pinMode(PIN_TFT_RST, INPUT);
pinMode(PIN_TFT_LEDA_CTL, INPUT);
_isOn = false;
if (_peripher_power) _peripher_power->release();
@@ -47,7 +47,7 @@ public:
uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off();
NRF52Board::powerOff();
}
};
+2 -1
View File
@@ -2,12 +2,13 @@
extends = nrf52_base
board = rak4631
board_check = true
board_build.ldscript = boards/nrf52840_s140_v6.ld
build_flags = ${nrf52_base.build_flags}
${sensor_base.build_flags}
-I variants/gat562_30s_mesh_kit
-D RAK_4631
-D RAK_BOARD
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_OLED_RESET=-1
@@ -47,7 +47,7 @@ public:
uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off();
NRF52Board::powerOff();
}
};
+1 -1
View File
@@ -5,7 +5,7 @@ board_check = true
build_flags = ${nrf52_base.build_flags}
${sensor_base.build_flags}
-I variants/gat562_mesh_evb_pro
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D RADIO_CLASS=CustomSX1262
@@ -47,7 +47,7 @@ public:
uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off();
NRF52Board::powerOff();
}
};
@@ -2,10 +2,11 @@
extends = nrf52_base
board = rak4631
board_check = true
board_build.ldscript = boards/nrf52840_s140_v6.ld
build_flags = ${nrf52_base.build_flags}
${sensor_base.build_flags}
-I variants/gat562_mesh_tracker_pro
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_OLED_RESET=-1
@@ -38,7 +38,7 @@ public:
uint32_t button_pin = PIN_BUTTON1;
nrf_gpio_cfg_input(button_pin, NRF_GPIO_PIN_PULLUP);
nrf_gpio_cfg_sense_set(button_pin, NRF_GPIO_PIN_SENSE_LOW);
sd_power_system_off();
NRF52Board::powerOff();
}
};
+1 -1
View File
@@ -8,7 +8,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/gat562_mesh_watch13
-D RAK_4631
-D RAK_BOARD
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D PIN_BOARD_SCL=14
-D PIN_BOARD_SDA=13
-D PIN_OLED_RESET=-1
+5
View File
@@ -99,6 +99,11 @@ lib_deps =
extends = Generic_E22
build_src_filter = ${Generic_E22.build_src_filter}
+<../examples/kiss_modem/>
build_flags =
${Generic_E22.build_flags}
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
[env:Generic_E22_sx1268_repeater]
extends = Generic_E22
+3
View File
@@ -130,6 +130,9 @@ lib_deps =
${esp32_ota.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_ct62_companion_radio_ble_ps]
extends = env:Heltec_ct62_companion_radio_ble
[env:Heltec_ct62_sensor]
extends = Heltec_ct62
build_flags =
-27
View File
@@ -20,33 +20,6 @@ void HeltecE213Board::begin() {
}
}
void HeltecE213Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void HeltecE213Board::powerOff() {
enterDeepSleep(0);
}
uint16_t HeltecE213Board::getBattMilliVolts() {
analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, HIGH);
-3
View File
@@ -3,7 +3,6 @@
#include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h>
#include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
class HeltecE213Board : public ESP32Board {
@@ -13,8 +12,6 @@ public:
HeltecE213Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
void begin();
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
};
-27
View File
@@ -20,33 +20,6 @@ void HeltecE290Board::begin() {
}
}
void HeltecE290Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void HeltecE290Board::powerOff() {
enterDeepSleep(0);
}
uint16_t HeltecE290Board::getBattMilliVolts() {
analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, HIGH);
-3
View File
@@ -3,7 +3,6 @@
#include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h>
#include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
class HeltecE290Board : public ESP32Board {
@@ -13,8 +12,6 @@ public:
HeltecE290Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { }
void begin();
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+1 -1
View File
@@ -17,6 +17,6 @@ class LoRaFEMControl
bool isLNAEnabled(void) const { return lna_enabled; }
private:
bool lna_enabled = false;
bool lna_enabled = true;
bool lna_can_control = false;
};
+7 -7
View File
@@ -112,13 +112,9 @@ void T096Board::variant_shutdown() {
}
void T096Board::powerOff() {
#if ENV_INCLUDE_GPS == 1
pinMode(PIN_GPS_EN, OUTPUT);
digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE);
#endif
loRaFEMControl.setSleepModeEnable();
variant_shutdown();
sd_power_system_off();
loRaFEMControl.setSleepModeEnable();
nrf_gpio_cfg_default(PIN_GPS_EN); // 363uA down to 39uA
NRF52Board::powerOff();
}
const char* T096Board::getManufacturerName() const {
@@ -126,6 +122,10 @@ const char* T096Board::getManufacturerName() const {
}
bool T096Board::setLoRaFemLnaEnabled(bool enable) {
#if defined(RADIO_FEM_RXGAIN) && (RADIO_FEM_RXGAIN == 0)
enable = false;
#endif
if (!loRaFEMControl.isLnaCanControl()) {
return false;
}
+11 -2
View File
@@ -9,7 +9,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/heltec_t096
-I src/helpers/ui
-D HELTEC_T096
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D P_LORA_DIO_1=21
-D P_LORA_NSS=5
-D P_LORA_RESET=16
@@ -120,7 +120,7 @@ build_src_filter = ${Heltec_t096.build_src_filter}
lib_deps =
${Heltec_t096.lib_deps}
[env:Heltec_t096_companion_radio_ble]
[env:Heltec_t096_companion_radio_ble_femon]
extends = Heltec_t096
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
board_upload.maximum_size = 712704
@@ -143,6 +143,15 @@ lib_deps =
${Heltec_t096.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_t096_companion_radio_ble]
extends = env:Heltec_t096_companion_radio_ble_femon
[env:Heltec_t096_companion_radio_ble_femoff]
extends = env:Heltec_t096_companion_radio_ble_femon
build_flags =
${env:Heltec_t096_companion_radio_ble_femon.build_flags}
-D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off
[env:Heltec_t096_companion_radio_usb]
extends = Heltec_t096
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
+1 -29
View File
@@ -81,34 +81,6 @@ uint16_t T1Board::getBattMilliVolts() {
}
void T1Board::variant_shutdown() {
nrf_gpio_cfg_default(PIN_TFT_CS);
nrf_gpio_cfg_default(PIN_TFT_DC);
nrf_gpio_cfg_default(PIN_TFT_SDA);
nrf_gpio_cfg_default(PIN_TFT_SCL);
nrf_gpio_cfg_default(PIN_TFT_RST);
nrf_gpio_cfg_default(PIN_TFT_LEDA_CTL);
nrf_gpio_cfg_default(PIN_TFT_VDD_CTL);
nrf_gpio_cfg_default(PIN_WIRE_SDA);
nrf_gpio_cfg_default(PIN_WIRE_SCL);
nrf_gpio_cfg_default(LORA_CS);
nrf_gpio_cfg_default(SX126X_DIO1);
nrf_gpio_cfg_default(SX126X_BUSY);
nrf_gpio_cfg_default(SX126X_RESET);
nrf_gpio_cfg_default(PIN_SPI_MISO);
nrf_gpio_cfg_default(PIN_SPI_MOSI);
nrf_gpio_cfg_default(PIN_SPI_SCK);
nrf_gpio_cfg_default(PIN_SPI1_MOSI);
nrf_gpio_cfg_default(PIN_SPI1_SCK);
nrf_gpio_cfg_default(PIN_GPS_RESET);
nrf_gpio_cfg_default(PIN_GPS_EN);
nrf_gpio_cfg_default(PIN_GPS_PPS);
nrf_gpio_cfg_default(PIN_GPS_RX);
nrf_gpio_cfg_default(PIN_GPS_TX);
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_1);
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_2);
@@ -127,7 +99,7 @@ void T1Board::variant_shutdown() {
void T1Board::powerOff() {
variant_shutdown();
sd_power_system_off();
NRF52Board::powerOff();
}
const char* T1Board::getManufacturerName() const {
+2 -5
View File
@@ -50,10 +50,7 @@ public:
#ifdef LED_PIN
digitalWrite(LED_PIN, HIGH);
#endif
#if ENV_INCLUDE_GPS == 1
pinMode(GPS_EN, OUTPUT);
digitalWrite(GPS_EN, LOW);
#endif
sd_power_system_off();
NRF52Board::powerOff();
}
};
+1 -1
View File
@@ -12,7 +12,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/heltec_t114
-I src/helpers/ui
-D HELTEC_T114
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D P_LORA_DIO_1=20
-D P_LORA_NSS=24
-D P_LORA_RESET=25
-27
View File
@@ -20,33 +20,6 @@ void HeltecT190Board::begin() {
}
}
void HeltecT190Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void HeltecT190Board::powerOff() {
enterDeepSleep(0);
}
uint16_t HeltecT190Board::getBattMilliVolts() {
analogReadResolution(10);
digitalWrite(PIN_ADC_CTRL, HIGH);
-3
View File
@@ -3,7 +3,6 @@
#include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h>
#include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
class HeltecT190Board : public ESP32Board {
@@ -13,8 +12,6 @@ public:
HeltecT190Board() : periph_power(PIN_VEXT_EN,PIN_VEXT_EN_ACTIVE) { }
void begin();
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+3
View File
@@ -99,6 +99,9 @@ lib_deps =
${Heltec_tracker_base.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_Wireless_Tracker_companion_radio_ble_ps]
extends = env:Heltec_Wireless_Tracker_companion_radio_ble
[env:Heltec_Wireless_Tracker_repeater]
extends = Heltec_tracker_base
build_flags =
@@ -35,33 +35,12 @@ void HeltecTrackerV2Board::begin() {
loRaFEMControl.setRxModeEnable();
}
void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
void HeltecTrackerV2Board::powerOff() {
// Turn off PA
digitalWrite(P_LORA_PA_POWER, LOW);
rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
loRaFEMControl.setRxModeEnableWhenMCUSleep();//It also needs to be enabled in receive mode
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
void HeltecTrackerV2Board::powerOff() {
enterDeepSleep(0);
ESP32Board::powerOff();
}
uint16_t HeltecTrackerV2Board::getBattMilliVolts() {
@@ -3,7 +3,6 @@
#include <Arduino.h>
#include <helpers/RefCountedDigitalPin.h>
#include <helpers/ESP32Board.h>
#include <driver/rtc_io.h>
#include "LoRaFEMControl.h"
class HeltecTrackerV2Board : public ESP32Board {
@@ -17,7 +16,6 @@ public:
void begin();
void onBeforeTransmit(void) override;
void onAfterTransmit(void) override;
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+1 -1
View File
@@ -17,6 +17,6 @@ class LoRaFEMControl
bool isLNAEnabled(void) const { return lna_enabled; }
private:
bool lna_enabled = false;
bool lna_enabled = true;
bool lna_can_control = false;
};
@@ -176,6 +176,9 @@ lib_deps =
${Heltec_tracker_v2.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:heltec_tracker_v2_companion_radio_ble_ps]
extends = env:heltec_tracker_v2_companion_radio_ble
[env:heltec_tracker_v2_companion_radio_wifi]
extends = Heltec_tracker_v2
build_flags =
-25
View File
@@ -7,8 +7,6 @@
#define PIN_VBAT_READ 37
#define PIN_LED_BUILTIN 25
#include <driver/rtc_io.h>
class HeltecV2Board : public ESP32Board {
public:
void begin() {
@@ -26,29 +24,6 @@ public:
}
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000);
}
// Finally set ESP32 into sleep
esp_deep_sleep_start(); // CPU halts here and never returns!
}
uint16_t getBattMilliVolts() override {
analogReadResolution(10);
+3
View File
@@ -172,6 +172,9 @@ lib_deps =
${Heltec_lora32_v2.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_v2_companion_radio_ble_ps]
extends = env:Heltec_v2_companion_radio_ble
[env:Heltec_v2_companion_radio_wifi]
extends = Heltec_lora32_v2
build_flags =
+4 -2
View File
@@ -17,8 +17,6 @@
#define PIN_ADC_CTRL_ACTIVE LOW
#define PIN_ADC_CTRL_INACTIVE HIGH
#include <driver/rtc_io.h>
class HeltecV3Board : public ESP32Board {
private:
bool adc_active_state;
@@ -53,6 +51,10 @@ public:
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
// Clear stale wakeup sources to avoid ghost wakeup
// This is required when Power Management and automatic lightsleep are enabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
Loaded 100 of 154 files, more files were not shown because too many files have changed in this diff. Show more