Compare commits

...
Author SHA1 Message Date
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
Liam Cottle c2cfba4af3 Merge pull request #2810 from IoTThinks/MCDev-Fixed-GPS-Time-for-T114-202606
Fixed GPS time sync for Heltec T114
2026-06-22 14:19:09 +12:00
Kevin Le 62849ef114 Fixed GPS time sync for Heltec T114 2026-06-22 09:13:36 +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
ripplebiz 60ea4a91bf Merge pull request #1727 from weebl2000/use-hardware-channel-activity-detection
Use hardware channel activity detection for checking interference
2026-06-15 19:51:59 +10:00
Wessel Nieboer 7c8e092457 Have CAD be a separate toggle (set cad on/off) 2026-06-14 15:20:58 +02:00
Wessel Nieboer 099ef67348 Prevent packet errors from growing 2026-06-14 15:19:20 +02:00
Wessel Nieboer 04a6c7009a Just check for not channel free 2026-06-14 15:19:19 +02:00
Wessel Nieboer 813d108c7a Also return busy if preamble detected 2026-06-14 15:19:19 +02:00
Wessel Nieboer 4f9a091671 Use hardware channel activity detection for checking interference 2026-06-14 15:19:18 +02:00
Huw Duddy 29f0a7fc4f Merge pull request #2763 from meshcore-dev/anon-contacts-fix
* fix for anon contacts when full
2026-06-14 19:11:38 +10:00
Scott Powell 2e73fe948e * fix for anon lastmod 2026-06-14 18:54:51 +10:00
Scott Powell 538ac38e18 * fix for counter in RESP_CODE_CONTACTS_START 2026-06-14 18:39:34 +10:00
Scott Powell c2d223ff55 * now handle the case where onAdvertRecv() should _replace_ the anon contact slot 2026-06-14 18:12:08 +10:00
Scott Powell 07648e3344 * fix for anon contacts when full 2026-06-14 17:50:41 +10:00
ripplebiz a5cb0c25ca Merge pull request #2140 from Quency-D/cli-lna-command
Add CLI control to LoRa's fem LNA.
2026-06-13 22:00:08 +10:00
Quency-D 5300fa18c7 Restore companion NodePrefs file ending 2026-06-13 17:40:11 +08:00
Quency-D 3ee58fd2e3 Remove companion FEM RX gain command IDs 2026-06-13 17:31:54 +08:00
Scott Powell d5f74e93c5 * PAYLOAD_TYPE_PATH bad path_len now rejected 2026-06-13 18:19:41 +10: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
Quency-D 9100a58671 Merge branch 'dev' into cli-lna-command 2026-06-10 11:30:04 +08: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
ripplebiz 5f3b7f25d0 Merge pull request #2722 from liamcottle/fix/free-packet-on-parse-failure
Fix: CMD_SEND_RAW_PACKET not freeing packet on parse failure
2026-06-08 22:47:06 +10:00
liamcottle ae0bb7ee95 use releasePacket instead of _mgr->free 2026-06-09 00:42:58 +12:00
liamcottle 07bfe90695 free packet on parse failure 2026-06-09 00:31:48 +12: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
Quency-D 250f448c3f Merge branch 'dev' into cli-lna-command 2026-05-20 10:13:28 +08: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
Quency-D 12e6899580 Merge branch 'dev' into cli-lna-command 2026-05-09 18:19:31 +08: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
Quency-D 444dcfb8fd Change write to read for radio_fem_rxgain 2026-04-27 14:06:21 +08: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
Quency-D 62b0d82682 Restore WiFi operation command scope comments 2026-04-25 16:12:40 +08:00
Quency-D 9d26953398 Remove unnecessary blank line in MyMesh.cpp 2026-04-25 16:10:57 +08:00
Quency-DandCopilot c42f6db0eb Revise according to the review comments.
Co-authored-by: Copilot <copilot@github.com>
2026-04-25 16:01:26 +08:00
Quency-D 22f07b3e48 Revert "Merge branch 'meshcore-dev:main' into cli-lna-command"
This reverts commit ca047fe0c0, reversing
changes made to ddedb3c7a7.
2026-04-25 15:33:59 +08:00
Quency-D ca047fe0c0 Merge branch 'meshcore-dev:main' into cli-lna-command 2026-04-25 15:29:00 +08:00
Quency-D ddedb3c7a7 Merge branch 'dev' into cli-lna-command 2026-04-25 15:28:07 +08: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
Quency-D 9664305a87 Adapt LNA CLI control commands for heltec_t096. 2026-03-24 16:13:13 +08:00
Quency-D 2442e9a5bd Adapt LNA CLI control commands for heltec_tracker_v2. 2026-03-24 16:05:28 +08:00
Quency-D 65752fef72 Fix the memory leak issue in the strdup function. 2026-03-24 13:57:11 +08:00
Quency-D 8435464c84 Add v4 FEM LNA CLI control commands. 2026-03-24 10:48:15 +08:00
162 changed files with 8189 additions and 906 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
+1343 -183
View File
File diff suppressed because it is too large. Load diff
+555 -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`
@@ -263,6 +313,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### View or change the LoRa FEM receive-path gain state on supported boards
**Usage:**
- `get radio.fem.rxgain`
- `set radio.fem.rxgain <state>`
**Parameters:**
- `state`: `on`|`off`
**Notes:**
- This controls the external LoRa FEM receive-path LNA where the board supports it.
- This is separate from `radio.rxgain`, which controls the radio chip receive gain mode.
---
### System
#### View or change this node's name
@@ -386,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`
@@ -413,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.
---
@@ -564,6 +668,20 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### Enable or disable hardware Channel Activity Detection (CAD)
**Usage:**
- `get cad`
- `set cad <on|off>`
**Description:** When enabled, the radio performs a hardware Channel Activity Detection scan before transmitting and defers if the channel is busy. Runs independently of `int.thresh` — either, both, or none may be active.
**Parameters:**
- `on|off`: Enable or disable hardware CAD
**Default:** `off`
---
#### View or change the AGC Reset Interval
**Usage:**
- `get agc.reset.interval`
@@ -636,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`
---
@@ -922,6 +1051,427 @@ 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.
- CR6 is intentionally skipped.
- `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)
+358
View File
@@ -0,0 +1,358 @@
# 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 direct retry packets. Uses `CR4`, `CR5`, `CR7`, or `CR8`; `CR6` is never selected. | `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. If
adaptive selection chooses `CR4`, retries after the third attempt use
`CR5`.
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();
}
+36 -10
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
@@ -261,6 +263,9 @@ float MyMesh::getAirtimeBudgetFactor() const {
int MyMesh::getInterferenceThreshold() const {
return 0; // disabled for now, until currentRSSI() problem is resolved
}
bool MyMesh::getCADEnabled() const {
return true; // hardware CAD before TX (no CLI toggle on companion; enabled by default)
}
int MyMesh::calcRxDelay(float score, uint32_t air_time) const {
if (_prefs.rx_delay_base <= 0.0f) return 0;
@@ -854,7 +859,7 @@ void MyMesh::onSendTimeout() {}
MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui)
: BaseChatMesh(radio, *new ArduinoMillis(), rng, rtc, *new StaticPoolPacketManager(16), tables),
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui) {
_serial(NULL), telemetry(MAX_PACKET_PAYLOAD - 4), _store(&store), _ui(ui), _iter(0) {
_iter_started = false;
_cli_rescue = false;
offline_queue_len = 0;
@@ -886,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) {
@@ -935,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) {
@@ -964,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");
}
@@ -1542,6 +1550,7 @@ void MyMesh::handleCmdFrame(size_t len) {
memcpy(anon.id.pub_key, pub_key, PUB_KEY_SIZE);
anon.out_path_len = 0; // default to zero-hop direct
anon.type = ADV_TYPE_NONE; // unknown
anon.lastmod = getRTCClock()->getCurrentTime();
if (addContact(anon)) recipient = &anon;
}
@@ -1821,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];
@@ -1981,6 +2014,7 @@ void MyMesh::handleCmdFrame(size_t len) {
sendPacket(pkt, priority, 0);
writeOKFrame();
} else {
releasePacket(pkt);
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
}
} else {
@@ -2186,15 +2220,7 @@ void MyMesh::checkSerialInterface() {
&& !_serial->isWriteBusy() // don't spam the Serial Interface too quickly!
) {
ContactInfo contact;
bool found = false;
while (_iter.hasNext(this, contact)) {
if (contact.type != ADV_TYPE_NONE) {
found = true;
break;
}
}
if (found) {
if (_iter.hasNext(this, contact)) {
if (contact.lastmod > _iter_filter_since) { // apply the 'since' filter
writeContactRespFrame(RESP_CODE_CONTACT, contact);
if (contact.lastmod > _most_recent_lastmod) {
+2 -1
View File
@@ -105,6 +105,7 @@ public:
protected:
float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
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;
@@ -165,7 +166,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
+111 -6
View File
@@ -80,11 +80,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 +120,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 +137,9 @@ 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;
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 +147,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 +178,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,10 +219,31 @@ 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;
}
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -196,6 +290,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;
@@ -214,6 +312,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;
@@ -223,8 +324,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 {
+2 -1
View File
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
free(version);
}
void UITask::renderCurrScreen() {
+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();
}
}
+49
View File
@@ -650,6 +650,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.flood_max_unscoped = 64;
_prefs.flood_max_advert = 8;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
#ifdef ROOM_PASSWORD
StrHelper::strncpy(_prefs.guest_password, ROOM_PASSWORD, sizeof(_prefs.guest_password));
#endif
@@ -658,6 +659,7 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
_prefs.gps_enabled = 0;
_prefs.gps_interval = 0;
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
_prefs.radio_fem_rxgain = 1;
next_post_idx = 0;
next_client_idx = 0;
@@ -699,6 +701,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_driver.setTxPower(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
@@ -1028,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
@@ -144,6 +144,9 @@ protected:
int getInterferenceThreshold() const override {
return _prefs.interference_threshold;
}
bool getCADEnabled() const override {
return _prefs.cad_enabled;
}
int getAGCResetInterval() const override {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -222,4 +225,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;
};
+2 -1
View File
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
free(version);
}
void UITask::renderCurrScreen() {
+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
}
}
+1 -1
View File
@@ -135,7 +135,7 @@ class MyMesh : public BaseChatMesh, ContactVisitor {
File file = _fs->open("/contacts", "w", true);
#endif
if (file) {
ContactsIterator iter;
ContactsIterator iter = startContactsIterator();
ContactInfo c;
uint8_t unused = 0;
uint32_t reserved = 0;
+6
View File
@@ -323,6 +323,9 @@ uint32_t SensorMesh::getDirectRetransmitDelay(const mesh::Packet* packet) {
int SensorMesh::getInterferenceThreshold() const {
return _prefs.interference_threshold;
}
bool SensorMesh::getCADEnabled() const {
return _prefs.cad_enabled;
}
int SensorMesh::getAGCResetInterval() const {
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
}
@@ -726,11 +729,13 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_prefs.disable_fwd = true;
_prefs.flood_max = 64;
_prefs.interference_threshold = 0; // disabled
_prefs.cad_enabled = 0; // hardware CAD before TX (off by default; 'set cad on')
// GPS defaults
_prefs.gps_enabled = 0;
_prefs.gps_interval = 0;
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
_prefs.radio_fem_rxgain = 1;
memset(default_scope.key, 0, sizeof(default_scope.key));
}
@@ -766,6 +771,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_driver.setTxPower(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+1
View File
@@ -120,6 +120,7 @@ protected:
uint32_t getRetransmitDelay(const mesh::Packet* packet) override;
uint32_t getDirectRetransmitDelay(const mesh::Packet* packet) override;
int getInterferenceThreshold() const override;
bool getCADEnabled() const override;
int getAGCResetInterval() const override;
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;
+2 -1
View File
@@ -41,7 +41,8 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, build_date);
snprintf(_version_info, sizeof(_version_info), "%s (%s)", version, build_date);
free(version);
}
void UITask::renderCurrScreen() {
+65 -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);
}
@@ -66,6 +77,7 @@ uint32_t Dispatcher::getCADFailMaxDuration() const {
void Dispatcher::loop() {
if (millisHasNowPassed(next_floor_calib_time)) {
_radio->triggerNoiseFloorCalibrate(getInterferenceThreshold());
_radio->setCADEnabled(getCADEnabled());
next_floor_calib_time = futureMillis(NOISE_FLOOR_CALIB_INTERVAL);
}
_radio->loop();
@@ -105,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 {
@@ -117,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;
@@ -148,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());
@@ -267,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);
}
}
}
@@ -318,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;
@@ -359,6 +408,7 @@ Packet* Dispatcher::obtainNewPacket() {
} else {
pkt->payload_len = pkt->path_len = 0;
pkt->_snr = 0;
pkt->tx_cr = 0;
}
return pkt;
}
@@ -367,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
@@ -386,4 +444,4 @@ unsigned long Dispatcher::futureMillis(int millis_from_now) const {
return _ms->getMillis() + millis_from_now;
}
}
}
+24 -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.
*/
@@ -65,6 +73,8 @@ public:
virtual void triggerNoiseFloorCalibrate(int threshold) { }
virtual void setCADEnabled(bool enable) { }
virtual void resetAGC() { }
virtual bool isInRecvMode() const = 0;
@@ -87,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;
@@ -116,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;
@@ -128,6 +140,7 @@ class Dispatcher {
unsigned long duty_cycle_window_ms;
void processRecvPacket(Packet* pkt);
void restoreOutboundTxOverrides();
void updateTxBudget();
protected:
@@ -140,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;
@@ -159,15 +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();
@@ -175,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; }
@@ -193,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();
+911 -39
View File
File diff suppressed because it is too large. Load diff
+156 -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,94 @@ protected:
*/
virtual uint32_t getDirectRetransmitDelay(const Packet* packet);
/**
* \brief Decide whether a DIRECT packet should get one delayed 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 one 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) { }
/**
* \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 +336,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);
+5
View File
@@ -64,9 +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;
};
};
+32 -20
View File
@@ -68,29 +68,36 @@ void BaseChatMesh::bootstrapRTCfromContacts() {
}
ContactInfo* BaseChatMesh::allocateContactSlot(bool transient_only) {
if (num_contacts < MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (transient_only || shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
for (int i = 0; i < num_contacts; i++) {
if (transient_only) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
} else {
int oldest_idx = -1;
uint32_t oldest_lastmod = 0xFFFFFFFF;
if (transient_only) {
// only allocate from first N
for (int i = 0; i < MAX_ANON_CONTACTS; i++) {
if (contacts[i].type == ADV_TYPE_NONE && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
if (oldest_idx >= 0) {
// NOTE: do NOT call onContactOverwrite()
return &contacts[oldest_idx];
}
} else {
if (num_contacts < MAX_ANON_CONTACTS+MAX_CONTACTS) {
return &contacts[num_contacts++];
} else if (shouldOverwriteWhenFull()) {
// Find oldest non-favourite contact by oldest lastmod timestamp
for (int i = MAX_ANON_CONTACTS; i < num_contacts; i++) {
bool is_favourite = (contacts[i].flags & 0x01) != 0;
if (!is_favourite && contacts[i].lastmod < oldest_lastmod && contacts[i].type != ADV_TYPE_NONE) {
if (!is_favourite && contacts[i].lastmod < oldest_lastmod) {
oldest_lastmod = contacts[i].lastmod;
oldest_idx = i;
}
}
}
if (oldest_idx >= 0) {
onContactOverwrite(contacts[oldest_idx].id.pub_key);
return &contacts[oldest_idx];
if (oldest_idx >= 0) {
onContactOverwrite(contacts[oldest_idx].id.pub_key);
return &contacts[oldest_idx];
}
}
}
return NULL; // no space, no overwrite or all contacts are all favourites
@@ -139,6 +146,11 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
packet->header = save;
}
if (from && from->type == ADV_TYPE_NONE) { // already in contacts, but from a temporary ANON_REQ ?
memset(from, 0, sizeof(*from)); // clear the anon/temp slot
from = NULL; // do normal 'add' flow
}
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
if (from == NULL) {
if (!shouldAutoAddContactType(parser.getType())) {
@@ -930,11 +942,11 @@ bool BaseChatMesh::getContactByIdx(uint32_t idx, ContactInfo& contact) {
}
ContactsIterator BaseChatMesh::startContactsIterator() {
return ContactsIterator();
return ContactsIterator(MAX_ANON_CONTACTS); // start at offset, skip the anon entries
}
bool ContactsIterator::hasNext(const BaseChatMesh* mesh, ContactInfo& dest) {
if (next_idx >= mesh->getNumContacts()) return false;
if (next_idx >= mesh->getTotalContactSlots()) return false;
dest = mesh->contacts[next_idx++];
return true;
+12 -5
View File
@@ -28,8 +28,9 @@ public:
class BaseChatMesh;
class ContactsIterator {
int next_idx = 0;
int next_idx;
public:
ContactsIterator(int start) { next_idx = start; }
bool hasNext(const BaseChatMesh* mesh, ContactInfo& dest);
};
@@ -79,8 +80,9 @@ class BaseChatMesh : public mesh::Mesh {
protected:
BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables)
: mesh::Mesh(radio, ms, rng, rtc, mgr, tables)
{
num_contacts = 0;
{
resetContacts();
#ifdef MAX_GROUP_CHANNELS
memset(channels, 0, sizeof(channels));
num_channels = 0;
@@ -91,7 +93,11 @@ protected:
}
void bootstrapRTCfromContacts();
void resetContacts() { num_contacts = 0; }
void resetContacts() {
memset(contacts, 0, sizeof(contacts[0])*MAX_ANON_CONTACTS); // set all to have type = ADV_TYPE_NONE(0)
num_contacts = MAX_ANON_CONTACTS; // seed the first contacts for anon requests
}
void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp);
ContactInfo* allocateContactSlot(bool transient_only=false); // helper to find slot for new contact
@@ -166,7 +172,8 @@ public:
ContactInfo* lookupContactByPubKey(const uint8_t* pub_key, int prefix_len);
bool removeContact(ContactInfo& contact);
bool addContact(const ContactInfo& contact);
int getNumContacts() const { return num_contacts; }
int getTotalContactSlots() const { return num_contacts; }
int getNumContacts() const { return num_contacts - MAX_ANON_CONTACTS; } // don't include the reserved slots at start
bool getContactByIdx(uint32_t idx, ContactInfo& contact);
ContactsIterator startContactsIterator();
ChannelDetails* addChannel(const char* name, const char* psk_base64);
+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)
+983 -24
View File
File diff suppressed because it is too large. Load diff
+128 -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
@@ -61,8 +129,33 @@ struct NodePrefs { // persisted to file
float adc_multiplier;
char owner_info[120];
uint8_t rx_boosted_gain; // power settings
uint8_t radio_fem_rxgain; // LoRa FEM RX gain setting
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 {
@@ -86,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
@@ -122,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();
@@ -131,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() { }
+296 -11
View File
@@ -1,29 +1,153 @@
#pragma once
#include <Mesh.h>
#if ARDUINO
#include <Arduino.h>
#endif
#ifdef ESP32
#include <FS.h>
#endif
#define MAX_PACKET_HASHES (128+32)
#ifndef MAX_RECENT_REPEATERS
// Platform defaults. Can be overridden with -D MAX_RECENT_REPEATERS=<n>.
#if 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 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[MAX_RECENT_REPEATERS];
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) {
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 +159,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 +198,160 @@ 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 (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 (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 {
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 (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 (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();
}
+20 -3
View File
@@ -36,6 +36,7 @@ void RadioLibWrapper::begin() {
_noise_floor = 0;
_threshold = 0;
_cad_enabled = false;
// start average out some samples
_num_floor_samples = 0;
@@ -178,10 +179,26 @@ void RadioLibWrapper::onSendFinished() {
state = STATE_IDLE;
}
int16_t RadioLibWrapper::performChannelScan() {
return _radio->scanChannel();
}
bool RadioLibWrapper::isChannelActive() {
return _threshold == 0
? false // interference check is disabled
: getCurrentRSSI() > _noise_floor + _threshold;
// int.thresh: RSSI-based interference detection (relative to noise floor)
if (_threshold != 0 && getCurrentRSSI() > _noise_floor + _threshold) return true;
// cad: hardware channel activity detection
if (_cad_enabled) {
int16_t result = performChannelScan();
// scanChannel() triggers DIO interrupt (CAD done) which sets STATE_INT_READY
// via setFlag() ISR. Clear it before restarting RX so recvRaw() doesn't
// try to read a non-existent packet and count a spurious recv error.
state = STATE_IDLE;
startRecv();
if (result != RADIOLIB_CHANNEL_FREE) return true;
}
return false;
}
float RadioLibWrapper::getLastRSSI() const {
+6 -1
View File
@@ -9,6 +9,7 @@ protected:
mesh::MainBoard* _board;
uint32_t n_recv, n_sent, n_recv_errors;
int16_t _noise_floor, _threshold;
bool _cad_enabled;
uint16_t _num_floor_samples;
int32_t _floor_sample_sum;
uint8_t _preamble_sf;
@@ -32,13 +33,15 @@ public:
bool isInRecvMode() const override;
bool isChannelActive();
bool isReceiving() override {
bool isReceiving() override {
if (isReceivingPacket()) return true;
return isChannelActive();
}
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);
@@ -46,9 +49,11 @@ public:
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
virtual int16_t performChannelScan();
int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override;
void setCADEnabled(bool enable) override { _cad_enabled = enable; }
void resetAGC() override;
void loop() override;
@@ -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
+4
View File
@@ -130,6 +130,10 @@ 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
platform_packages = framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
[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 ;
+3 -2
View File
@@ -12,10 +12,11 @@ class LoRaFEMControl
void setRxModeEnable(void);
void setRxModeEnableWhenMCUSleep(void);
void setLNAEnable(bool enabled);
bool isLnaCanControl(void) { return lna_can_control; }
bool isLnaCanControl(void) const { return lna_can_control; }
void setLnaCanControl(bool can_control) { lna_can_control = can_control; }
bool isLNAEnabled(void) const { return lna_enabled; }
private:
bool lna_enabled = false;
bool lna_enabled = true;
bool lna_can_control = false;
};
+26 -8
View File
@@ -112,15 +112,33 @@ 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 {
return "Heltec T096";
}
}
bool T096Board::setLoRaFemLnaEnabled(bool enable) {
#if defined(RADIO_FEM_RXGAIN) && (RADIO_FEM_RXGAIN == 0)
enable = false;
#endif
if (!loRaFEMControl.isLnaCanControl()) {
return false;
}
loRaFEMControl.setLNAEnable(enable);
loRaFEMControl.setRxModeEnable();
return true;
}
bool T096Board::canControlLoRaFemLna() const {
return loRaFEMControl.isLnaCanControl();
}
bool T096Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
+3
View File
@@ -25,4 +25,7 @@ public:
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
void powerOff() override;
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
};
+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
+1 -1
View File
@@ -22,7 +22,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock);
#if ENV_INCLUDE_GPS
#include <helpers/sensors/MicroNMEALocationProvider.h>
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
#else
EnvironmentSensorManager sensors;
-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 ;
Loaded 100 of 162 files, more files were not shown because too many files have changed in this diff. Show more