Compare commits

..
Author SHA1 Message Date
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 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
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
ripplebiz 1a7b3614a8 Merge pull request #2388 from OhYou-0/lilygo-teth-elite-board-support
Add LilyGo T-ETH Elite SX1262 board support
2026-04-28 14:34:11 +10:00
mikecarper 6c20a1062f Refine direct retry SNR handling and recent repeater controls 2026-04-27 16:33:39 -07:00
Kevin Le e608503aa0 Merged latest changes from "Add CLI control to LoRa's fem LNA" https://github.com/meshcore-dev/MeshCore/pull/2140 2026-04-27 12:15:46 +07:00
Kevin Le fa9f3bd935 Added Heltec_Wireless_Tracker_companion_radio_ble_ps 2026-04-27 11:07:36 +07:00
Kevin Le 181ddb2268 Added Heltec_WSL3_companion_radio_ble_ps 2026-04-27 10:18:55 +07:00
mikecarper 8fadd31159 Merge branch 'main' of https://github.com/IoTThinks/MeshCore into halo-direct-path-retries 2026-04-26 19:28:16 -07:00
Liam Cottle 03a13aed30 Merge pull request #2413 from keithtweed/patch-1
Update script link in FAQ 4.7 to the repo of the fork
2026-04-27 14:17:19 +12:00
ripplebiz fed8d36d03 Merge pull request #2412 from LitBomb/patch-25
Removed links to outdated resources and links
2026-04-27 12:14:23 +10:00
Keith Tweed b948369d71 Update script link in FAQ 4.7 2026-04-26 19:51:33 -06:00
uncle lit 34db93150a Removed links to outdated resources and links
Removed links to outdated resources and links
2026-04-26 18:35:02 -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
Liam Cottle 528bf3f61e add FUNDING.yml 2026-04-26 00:24:40 +12:00
OhYou-0 68360157ec Add LilyGo T-ETH Elite board support 2026-04-24 10:16:19 -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
liamcottle ecd0cfc1c1 update discord links 2026-04-24 15:57:34 +12:00
101 changed files with 4614 additions and 434 deletions

No files matched your search

+1
View File
@@ -0,0 +1 @@
github: meshcore-dev
+1 -1
View File
@@ -117,4 +117,4 @@ There are a number of fairly major features in the pipeline, with no particular
- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community.
- Join [MeshCore Discord](https://meshcore.gg) to chat with the developers and get help from the community.
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env bash
# ./build-iotthinks.sh
export FIRMWARE_VERSION="PowerSaving15"
############# Repeaters #############
# Commonly-used boards
## ESP32 - 12 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
## NRF52 - 13 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
## ESP32, SX1276 - 3 boards
./build.sh build-firmware \
Heltec_v2_repeater \
LilyGo_TLora_V2_1_1_6_repeater \
Tbeam_SX1276_repeater
## Ikoka - 3 boards
./build.sh build-firmware \
ikoka_nano_nrf_22dbm_repeater \
ikoka_nano_nrf_30dbm_repeater \
ikoka_nano_nrf_33dbm_repeater
############# Room Server #############
# ESP32
./build.sh build-firmware \
Heltec_v3_room_server \
heltec_v4_room_server
# NRF52
./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
############# Companions BLE #############
# ESP32
./build.sh build-firmware \
Heltec_v3_companion_radio_ble_ps \
heltec_v4_companion_radio_ble_ps \
heltec_v4_companion_radio_ble_ps_femoff \
Xiao_S3_WIO_companion_radio_ble \
Heltec_Wireless_Paper_companion_radio_ble
# NRF52
./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
############# Companions USB #############
./build.sh build-firmware \
Heltec_v3_companion_radio_usb
############# Companions BLE PS #############
./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
-1
View File
@@ -743,4 +743,3 @@ main() {
}
main "$@"
+188 -24
View File
@@ -118,24 +118,22 @@ This document provides an overview of CLI commands that can be sent to MeshCore
### Get or set recent repeater fallback prefix/SNR
**Usage:**
- `get recent.repeater`
- `get recent.repeater all`
- `get recent.repeater all <count> <offset>`
- `get recent.repeater first <count>`
- `get recent.repeater first <count> <offset>`
- `get recent.repeater last <count>`
- `get recent.repeater last <count> <offset>`
- `set recent.repeater <prefix_hex> <snr_db>`
- `get recent.repeater <page>`
- `get recent.repeater page <page>`
- `set recent.repeater <prefix_hex_6> <snr_db>`
**Parameters:**
- `prefix_hex`: 1-3 bytes of next-hop prefix (hex)
- `prefix_hex_6`: Exactly 3 bytes of next-hop prefix in hex (6 chars)
- `snr_db`: SNR in dB (supports decimals; stored at x4 precision)
- `count`: number of entries to print
- `offset`: zero-based row offset into the selected order
- `page`: 1-based page number
**Notes:**
- `set` is rejected when the prefix already exists in neighbors.
- `all` prints oldest to newest; `first` prints the oldest N; `last` prints the newest N.
- Over LoRa remote CLI, replies are packet-size limited; use `offset` to page through all rows.
- `set` stores or updates the prefix in the recent repeater table.
- Rows are sorted by prefix width (3-byte, 2-byte, 1-byte), then SNR descending.
- A full direct retry failure lowers the stored SNR by `0.25 dB`.
- If a full failure has no row yet, it first seeds the row at the active retry cutoff + `2.5 dB`, then applies the `0.25 dB` penalty.
- Serial CLI page size is fixed at `128` rows; choose page with `get recent.repeater <page>`.
- Over LoRa remote CLI, page size is fixed at `7` rows; choose page with `get recent.repeater <page>`.
---
@@ -301,6 +299,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
@@ -524,11 +536,13 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:**
- `value`: Direct transmit delay factor (0-2)
**Default:** `0.2`
**Default:** `0.3`
**Note:** Direct retry waits include the same airtime-based randomized delay calculation as direct retransmits, so this factor also controls retry echo windows.
---
#### View or change whether direct retries can fall back to the recently-heard repeater list
#### View or change whether direct retries use the recent repeater blacklist
**Usage:**
- `get direct.retry.heard`
- `set direct.retry.heard <state>`
@@ -536,9 +550,27 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:**
- `state`: `on`|`off`
**Default:** `off`
**Default:** `on`
**Note:** When enabled, a repeater can use recently-heard non-duplicate repeater prefixes as a fallback for direct retry eligibility when no suitable neighbor entry is available.
**Note:** When enabled, the recent repeater table is the only direct retry eligibility gate. Prefixes missing from the table are assumed reachable; prefixes in the table below the active SNR gate are blocked. Neighbor data is not used.
---
#### View or change adaptive coding rate for direct retry packets
**Usage:**
- `get direct.retry.cr`
- `set direct.retry.cr <cr4_min>,<cr5_min>,<cr7_min>,<cr8_max>`
- `set direct.retry.cr off`
**Parameters:**
- `cr4_min`: SNR in dB where retry packets use `CR4`
- `cr5_min`: SNR in dB where retry packets use `CR5`
- `cr7_min`: SNR in dB where retry packets use `CR7`
- `cr8_max`: SNR in dB where retry packets use `CR8`
**Default:** `10.0,7.5,2.5,2.5`
**Note:** DM retry packets use the next-hop SNR from a recent repeater table entry to pick a local transmit coding rate; if no recent entry is available, retry packets use `CR5`. With the default, SNR `10.0 dB` and up uses `CR4`, SNR `7.5 dB` and up uses `CR5`, SNR `2.5 dB` and down uses `CR8`, and the middle band uses `CR7`. `CR6` is never selected. Use `set direct.retry.cr off` to disable adaptive coding-rate overrides. If adaptive selection chooses `CR4`, retries after the third attempt use `CR5`.
---
@@ -548,11 +580,30 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- `set direct.retry.margin <value>`
**Parameters:**
- `value`: Margin in dB above the SF-specific receive floor (minimum `0`, default `5`)
- `value`: Rooftop preset margin in dB above the SF-specific receive floor (minimum `0`, maximum `40`, quarter-dB precision, default `5.0`)
**Default:** `5`
**Default:** `5.0`
**Note:** The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin.
**Note:** `get direct.retry.margin` returns the active preset's effective margin. The retry gate uses the active SF floor of `SF5=-2.5`, `SF6=-5`, `SF7=-7.5`, `SF8=-10`, `SF9=-12.5`, `SF10=-15`, `SF11=-17.5`, `SF12=-20`, then adds this margin.
---
#### View or change the retry preset
**Usage:**
- `get retry.preset`
- `set retry.preset <value>`
**Parameters:**
- `value`: `infra`|`rooftop`|`mobile` or `0`|`1`|`2`
**Default:** `rooftop` (`1`)
**Presets:**
- `infra` (`0`): `275 ms` direct base wait, `4` direct retries, `150 ms` added per direct retry, SNR gate is SF floor + `15 dB`; flood retry defaults to `1` retry and path gate `1`
- `rooftop` (`1`): `175 ms` direct base wait, `15` direct retries, `100 ms` added per direct retry, SNR gate is SF floor + `5 dB`; flood retry defaults to `3` retries and path gate `2`
- `mobile` (`2`): `175 ms` direct base wait, `15` direct retries, `50 ms` added per direct retry, SNR gate is the SF floor; flood retry defaults to `3` retries and path gate `1`
**Note:** Selecting a preset copies those values into the direct retry settings and resets flood retry defaults. You can refine `direct.retry.margin`, `direct.retry.count`, `direct.retry.base`, `direct.retry.step`, `flood.retry.count`, or `flood.retry.path` afterward. Retry delay is `direct.txdelay` jitter + base wait + packet-length airtime wait + per-attempt step.
---
@@ -562,9 +613,11 @@ This document provides an overview of CLI commands that can be sent to MeshCore
- `set direct.retry.count <value>`
**Parameters:**
- `value`: Retry attempts after initial TX (`1`-`15`)
- `value`: Maximum retry attempts after initial TX (`1`-`15`)
**Default:** `3`
**Default:** `15`
**Note:** The effective value is capped by total direct path length: paths of `3` hops or less use at most `8` retries, `4` hops use at most `12`, and `5+` hops use at most `15`. A queued resend is canceled early when the next-hop echo is heard.
---
@@ -576,9 +629,23 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Parameters:**
- `value`: Base wait in milliseconds (`10`-`5000`)
**Default:** `200`
**Default:** `175`
**Note:** The actual first retry wait is `base + computed_echo_wait_from_live_phy`.
**Note:** The configured base is added to packet-length airtime and `direct.txdelay` jitter. Preset defaults are already reduced to account for the added `direct.txdelay` component.
---
#### View or change the direct retry per-attempt add time (milliseconds)
**Usage:**
- `get direct.retry.step`
- `set direct.retry.step <value>`
**Parameters:**
- `value`: Milliseconds added per retry attempt (`0`-`5000`)
**Default:** `100`
**Note:** This controls the linear add after the first retry wait. For example, `base=300` and `step=150` adds `0`, `150`, `300`, ... ms across retry attempts.
---
@@ -705,6 +772,103 @@ This document provides an overview of CLI commands that can be sent to MeshCore
---
#### View or change the number of flood retry attempts
**Usage:**
- `get flood.retry.count`
- `set flood.retry.count <value>`
**Parameters:**
- `value`: Maximum retry attempts after initial flood TX (`0`-`15`)
**Default:** `3` for `rooftop` and `mobile`, `1` for `infra`
**Note:** `0` disables flood retry.
---
#### View or change the flood retry path gate
**Usage:**
- `get flood.retry.path`
- `set flood.retry.path <value>`
**Parameters:**
- `value`: Maximum flood path length eligible for retry (`0`-`63`), or `off` to disable the gate
**Default:** `2` for `rooftop`, `1` for `infra` and `mobile`
---
#### View or change whether advert packets are flood-retried
**Usage:**
- `get flood.retry.advert`
- `set flood.retry.advert <state>`
**Parameters:**
- `state`: `on` or `off`
**Default:** `off`
**Note:** When this is `off`, node advert packets (`PAYLOAD_TYPE_ADVERT`, type `4`) are not queued for flood retry.
---
#### View or change flood retry target prefixes
**Usage:**
- `get flood.retry.prefixes`
- `set flood.retry.prefixes <prefixes>`
**Parameters:**
- `prefixes`: Comma-separated 3-byte hex prefixes, such as `A1B2C3,D4E5F6`; use `none` or `off` to clear
**Default:** `none`
**Note:** Prefixes are stored as 3 bytes. Flood retry skips packets whose path already contains a matching target prefix. When prefixes are configured, only a downstream echo from one of those target prefixes cancels a queued retry; when no prefixes are configured, any downstream echo cancels it. Matching works with 3-byte, 2-byte, or 1-byte flood paths by comparing the matching leading bytes.
---
#### View or change flood retry bridge mode
**Usage:**
- `get flood.retry.bridge`
- `set flood.retry.bridge <state>`
**Parameters:**
- `state`: `on` or `off`
**Default:** `off`
**Note:** Bridge mode uses bucket definitions instead of the single `flood.retry.prefixes` target list. It also has an implicit unconfigured catch-all bucket. If a flood comes from one fresh configured bucket, retry continues until every other fresh configured bucket plus the catch-all bucket has been heard or `flood.retry.count` is exhausted. If a flood comes from an unconfigured or pathless source, retry targets every fresh configured bucket. This means one configured bucket bridges between that bucket and everything else. Prefixes in `flood.retry.ignore` never count as heard bridge targets.
---
#### View or change flood retry bridge buckets
**Usage:**
- `get flood.retry.bucket.<bucket>`
- `set flood.retry.bucket <bucket> <prefixes>`
**Parameters:**
- `bucket`: Bucket number (`1`-`6`)
- `prefixes`: Up to 17 comma-separated 3-byte hex prefixes, such as `AABBCC,223344`; use `none` or `off` to clear
**Default:** all buckets empty
**Note:** Prefixes are stored as 3 bytes but match 3-byte, 2-byte, and 1-byte flood paths by comparing leading bytes. Bucket prefixes are included in bridge retry logic only if they were heard in the recent repeater table within the last hour.
---
#### View or change flood retry ignored prefixes
**Usage:**
- `get flood.retry.ignore`
- `set flood.retry.ignore <prefixes>`
**Parameters:**
- `prefixes`: Up to 8 comma-separated 3-byte hex prefixes, such as `AABBCC,223344`; use `none` or `off` to clear
**Default:** empty
**Note:** In non-bridge retry, an echo whose last hop matches an ignored prefix does not cancel a queued retry as successful. In bridge mode, ignored prefixes do not count as a heard bridge bucket or as the implicit catch-all bucket when bridge retry decides whether every target has repeated the flood.
---
### ACL
#### Add, update or remove permissions for a companion
+3 -11
View File
@@ -111,7 +111,6 @@ Anyone is able to build anything they like on top of MeshCore without paying any
- MeshCore Firmware on GitHub: [https://github.com/meshcore-dev/MeshCore](https://github.com/meshcore-dev/MeshCore)
- MeshCore Companion Web App: [https://app.meshcore.nz](https://app.meshcore.nz)
- MeshCore Map: [https://map.meshcore.io](https://map.meshcore.io)
- Andy Kirby's [MeshCore Intro Video](https://www.youtube.com/watch?v=t1qne8uJBAc)
- Liam Cottle's [MeshCore Technical Presentation](https://www.youtube.com/watch?v=OwmkVkZQTf4)
You need LoRa hardware devices to run MeshCore firmware as clients or server (repeater and room server).
@@ -194,7 +193,7 @@ Recently, as of October 2025, many regions have moved to the "narrow" setting, a
After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions.
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://discord.gg/cYtQNYCCRK) to let Liam Cottle know.
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://meshcore.gg) to let Liam Cottle know.
@@ -402,10 +401,7 @@ Another way to download map tiles is to use this Python script to get the tiles
<https://github.com/fistulareffigy/MTD-Script>
There is also a modified script that adds additional error handling and parallel downloads:
<https://discord.com/channels/826570251612323860/1330643963501351004/1338775811548905572>
UK map tiles are available separately from Andy Kirby on his discord server:
<https://discord.com/channels/826570251612323860/1330643963501351004/1331346597367386224>
<https://github.com/TheBestJohn/MTD-Script>
### 4.8. Q: Where do the map tiles go?
Once you have the tiles downloaded, copy the `\tiles` folder to the root of your T-Deck's SD card.
@@ -526,7 +522,7 @@ The third character is the capital letter 'O', not zero `0`
- Firmware repo: https://github.com/meshcore-dev/MeshCore
### 5.8. Q: How can I support MeshCore?
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://meshcore.gg). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase
@@ -563,10 +559,6 @@ pio run -e RAK_4631_Repeater
```
then you'll find `firmware.zip` in `.pio/build/RAK_4631_Repeater`
Andy also has a video on how to build using VS Code:
*How to build and flash Meshcore repeater firmware | Heltec V3*
<https://www.youtube.com/watch?v=WJvg6dt13hk> *(Link referenced in the Discord post)*
### 5.10. Q: Are there other MeshCore related open source projects?
**A:** [Liam Cottle](https://liamcottle.net)'s MeshCore web client and MeshCore Javascript library are open source under MIT license.
+290
View File
@@ -0,0 +1,290 @@
# Halo and Keymind Branch Settings
This file covers only CLI settings 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 |
| --- | --- | --- | --- |
| `recent.repeater` | Shows or seeds 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>` | `set recent.repeater A1B2C3 -8.5` |
| `radio.fem.rxgain` | Controls the external LoRa FEM receive-path LNA where the board supports it. | `get radio.fem.rxgain`, `set radio.fem.rxgain on/off` | `set radio.fem.rxgain on` |
## 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
```
Seed or correct a prefix:
```text
set recent.repeater A1B2C3 8.5
```
Rows are sorted by prefix width, then SNR. A full direct retry failure lowers
the matching row by `0.25 dB`.
## 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. | `get direct.retry.base`, `set direct.retry.base <10-5000>` | `set direct.retry.base 175` |
| `direct.retry.step` | Milliseconds added per retry attempt. | `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 a qualifying downstream echo is heard.
| Setting | What it does | How to use | Example |
| --- | --- | --- | --- |
| `flood.retry.count` | Maximum flood retry attempts after initial TX. `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 matching downstream echoes 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` | `3` | `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();
}
+37
View File
@@ -61,6 +61,8 @@
#define CMD_SEND_CHANNEL_DATA 62
#define CMD_SET_DEFAULT_FLOOD_SCOPE 63
#define CMD_GET_DEFAULT_FLOOD_SCOPE 64
#define CMD_GET_RADIO_FEM_RXGAIN 65
#define CMD_SET_RADIO_FEM_RXGAIN 66
// Stats sub-types for CMD_GET_STATS
#define STATS_TYPE_CORE 0
@@ -876,6 +878,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) {
@@ -925,6 +928,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) {
@@ -954,6 +958,7 @@ void MyMesh::begin(bool has_display) {
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_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");
}
@@ -1798,6 +1803,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];
@@ -2191,3 +2220,11 @@ bool MyMesh::advert() {
return false;
}
}
// 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
return _mgr->getOutboundTotal() > 0;
}
+4 -1
View File
@@ -165,7 +165,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");
@@ -177,6 +177,9 @@ public:
}
#endif
// To check if there is pending work
bool hasPendingWork() const;
private:
void writeOKFrame();
void writeErrFrame(uint8_t err_code);
+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];
};
};
+44 -2
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;
@@ -42,8 +47,8 @@ static uint32_t _atoi(const char* sp) {
#define TCP_PORT 5000
#endif
#elif defined(BLE_PIN_CODE)
#include <helpers/esp32/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#include <helpers/esp32/SerialBLEInterface.h>
SerialBLEInterface serial_interface;
#elif defined(SERIAL_RX)
#include <helpers/ArduinoSerialInterface.h>
ArduinoSerialInterface serial_interface;
@@ -220,6 +225,33 @@ void setup() {
#ifdef DISPLAY_CLASS
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs()); // still want to pass this in as dependency, as prefs might be moved
#endif
#ifdef ESP32_PLATFORM
// 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));
}
#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;
#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() {
@@ -229,4 +261,14 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();
if (!the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM)
board.sleep(0); // nrf ignores seconds param, sleeps whenever possible
#else if defined(ESP32_PLATFORM)
if (!serial_interface.isReadBusy() && !serial_interface.isWriteBusy()) { // BLE is not busy
vTaskDelay(pdMS_TO_TICKS(10)); // attempt to sleep
}
#endif
}
}
File diff suppressed because it is too large. Load diff
+42 -3
View File
@@ -99,6 +99,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;
@@ -122,9 +131,30 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
ESPNowBridge bridge;
#endif
const NeighbourInfo* findNeighbourByHash(const uint8_t* hash, uint8_t hash_len) const;
static bool allowRecentRepeaterPrefixStore(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
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 getDirectRetryPreset() 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;
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);
@@ -152,10 +182,19 @@ 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;
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;
void onDirectRetryEvent(const char* event, const mesh::Packet* packet, uint32_t delay_millis) 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) 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;
+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() {
+6 -14
View File
@@ -20,8 +20,7 @@ void halt() {
static char command[160];
// For power saving
unsigned long lastActive = 0; // mark last active time
unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot
unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot
#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
static unsigned long userBtnDownAt = 0;
@@ -40,9 +39,6 @@ void setup() {
delay(5000);
#endif
// For power saving
lastActive = millis(); // mark last active time since boot
#ifdef DISPLAY_CLASS
if (display.begin()) {
display.startFrame();
@@ -155,16 +151,12 @@ void loop() {
rtc_clock.tick();
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM)
#if defined(NRF52_PLATFORM)
board.sleep(1800); // nrf ignores seconds param, sleeps whenever possible
#else
if (the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep
board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet
lastActive = millis();
nextSleepinSecs = 5; // Default: To work for 5s and sleep again
} else {
nextSleepinSecs += 5; // When there is pending work, to work another 5s
#else
if (the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep
board.sleep(30); // Sleep. Wake up after some seconds or when receiving a LoRa packet
}
#endif
#endif
}
}
+10
View File
@@ -652,6 +652,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;
@@ -693,6 +694,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
@@ -1022,3 +1024,11 @@ void MyMesh::loop() {
uptime_millis += now - last_millis;
last_millis = now;
}
// 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
return _mgr->getOutboundTotal() > 0;
}
+3
View File
@@ -222,4 +222,7 @@ public:
void clearStats() override;
void handleCommand(uint32_t sender_timestamp, char* command, char* reply);
void loop();
// To check if there is pending work
bool hasPendingWork() 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() {
+13
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);
@@ -113,4 +116,14 @@ void loop() {
ui_task.loop();
#endif
rtc_clock.tick();
if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) {
#if defined(NRF52_PLATFORM)
board.sleep(1800); // 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(1800); // Sleep. Wake up after 30 minutes or when receiving a LoRa packet
}
#endif
}
}
+2
View File
@@ -731,6 +731,7 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise
_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 +767,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm);
board.setLoRaFemLnaEnabled(_prefs.radio_fem_rxgain);
updateAdvertTimer();
updateFloodAdvertTimer();
+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() {
+42 -6
View File
@@ -52,6 +52,13 @@ void Dispatcher::updateTxBudget() {
}
}
void Dispatcher::restoreOutboundCodingRate() {
if (outbound_restore_cr != 0) {
_radio->setCodingRate(outbound_restore_cr);
outbound_restore_cr = 0;
}
}
int Dispatcher::calcRxDelay(float score, uint32_t air_time) const {
return (int) ((pow(10, 0.85f - score) - 1.0) * air_time);
}
@@ -105,6 +112,7 @@ void Dispatcher::loop() {
}
_radio->onSendFinished();
restoreOutboundCodingRate();
logTx(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
onSendComplete(outbound);
if (outbound->isRouteFlood()) {
@@ -118,6 +126,7 @@ void Dispatcher::loop() {
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): WARNING: outbound packed send timed out!", getLogDateTime());
_radio->onSendFinished();
restoreOutboundCodingRate();
logTxFail(outbound, 2 + outbound->getPathByteLen() + outbound->payload_len);
onSendFail(outbound);
@@ -150,6 +159,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());
@@ -269,7 +279,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);
}
}
}
@@ -320,18 +333,32 @@ 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;
uint8_t default_cr = getDefaultTxCodingRate();
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;
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);
}
}
outbound_start = _ms->getMillis();
bool success = _radio->startSendRaw(raw, len);
if (!success) {
MESH_DEBUG_PRINTLN("%s Dispatcher::loop(): ERROR: send start failed!", getLogDateTime());
restoreOutboundCodingRate();
logTxFail(outbound, outbound->getRawLength());
onSendFail(outbound);
releasePacket(outbound); // return to pool
outbound = NULL;
@@ -361,6 +388,7 @@ Packet* Dispatcher::obtainNewPacket() {
} else {
pkt->payload_len = pkt->path_len = 0;
pkt->_snr = 0;
pkt->tx_cr = 0;
}
return pkt;
}
@@ -369,13 +397,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
+13 -2
View File
@@ -46,6 +46,12 @@ 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; }
/**
* \returns true if the previous 'startSendRaw()' completed successfully.
*/
@@ -87,7 +93,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 +122,7 @@ typedef uint32_t DispatcherAction;
class Dispatcher {
Packet* outbound; // current outbound packet
unsigned long outbound_expiry, outbound_start, total_air_time, rx_air_time;
uint8_t outbound_restore_cr;
unsigned long next_tx_time;
unsigned long cad_busy_start;
unsigned long radio_nonrx_start;
@@ -128,6 +135,7 @@ class Dispatcher {
unsigned long duty_cycle_window_ms;
void processRecvPacket(Packet* pkt);
void restoreOutboundCodingRate();
void updateTxBudget();
protected:
@@ -140,6 +148,7 @@ protected:
: _radio(&radio), _ms(&ms), _mgr(&mgr)
{
outbound = NULL;
outbound_restore_cr = 0;
total_air_time = rx_air_time = 0;
next_tx_time = ms.getMillis();
cad_busy_start = 0;
@@ -167,10 +176,12 @@ protected:
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 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);
public:
void begin();
@@ -178,7 +189,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; }
+446 -43
View File
@@ -3,22 +3,60 @@
namespace mesh {
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT = 3;
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT = 15;
static const uint8_t DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX = 15;
static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT = 3;
static const uint8_t FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX = 15;
static uint8_t decodeTraceHashSize(uint8_t flags, uint8_t route_bytes) {
uint8_t code = flags & 0x03;
uint8_t size_pow2 = (uint8_t)(1U << code); // legacy TRACE interpretation
uint8_t size_linear = (uint8_t)(code + 1U); // packed-size interpretation (1..4)
bool pow2_ok = size_pow2 > 0 && (route_bytes % size_pow2) == 0;
bool linear_ok = size_linear > 0 && (route_bytes % size_linear) == 0;
if (pow2_ok && !linear_ok) {
return size_pow2;
}
if (linear_ok && !pow2_ok) {
return size_linear;
}
if (pow2_ok) {
return size_pow2;
}
return size_linear;
}
void Mesh::begin() {
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
_direct_retries[i].packet = NULL;
_direct_retries[i].trigger_packet = NULL;
_direct_retries[i].retry_started_at = 0;
_direct_retries[i].echo_wait_started_at = 0;
_direct_retries[i].retry_at = 0;
_direct_retries[i].retry_delay = 0;
_direct_retries[i].retry_attempts_sent = 0;
_direct_retries[i].priority = 0;
_direct_retries[i].progress_marker = 0;
_direct_retries[i].expect_path_growth = false;
_direct_retries[i].waiting_final_echo = false;
_direct_retries[i].queued = false;
_direct_retries[i].active = false;
}
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
_flood_retries[i].packet = NULL;
_flood_retries[i].trigger_packet = NULL;
_flood_retries[i].retry_started_at = 0;
_flood_retries[i].retry_at = 0;
_flood_retries[i].retry_delay = 0;
_flood_retries[i].retry_attempts_sent = 0;
_flood_retries[i].priority = 0;
_flood_retries[i].progress_marker = 0;
_flood_retries[i].waiting_final_echo = false;
_flood_retries[i].queued = false;
_flood_retries[i].active = false;
}
Dispatcher::begin();
}
@@ -26,7 +64,25 @@ void Mesh::loop() {
Dispatcher::loop();
for (int i = 0; i < MAX_DIRECT_RETRY_SLOTS; i++) {
if (!_direct_retries[i].active || !_direct_retries[i].queued || !millisHasNowPassed(_direct_retries[i].retry_at)) {
if (!_direct_retries[i].active) {
continue;
}
if (_direct_retries[i].waiting_final_echo) {
if (!millisHasNowPassed(_direct_retries[i].retry_at)) {
continue;
}
uint32_t elapsed_millis = _direct_retries[i].retry_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _direct_retries[i].retry_started_at);
onDirectRetryEvent("failed_all_tries", _direct_retries[i].packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
onDirectRetryEvent("failure", _direct_retries[i].packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
clearDirectRetrySlot(i);
continue;
}
if (!_direct_retries[i].queued || !millisHasNowPassed(_direct_retries[i].retry_at)) {
continue;
}
@@ -37,6 +93,37 @@ void Mesh::loop() {
clearDirectRetrySlot(i);
}
}
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
if (!_flood_retries[i].active) {
continue;
}
if (_flood_retries[i].waiting_final_echo) {
if (!millisHasNowPassed(_flood_retries[i].retry_at)) {
continue;
}
uint32_t elapsed_millis = _flood_retries[i].retry_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at);
onFloodRetryEvent("failed_all_tries", _flood_retries[i].packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
onFloodRetryEvent("failure", _flood_retries[i].packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
clearFloodRetrySlot(i);
continue;
}
if (!_flood_retries[i].queued || !millisHasNowPassed(_flood_retries[i].retry_at)) {
continue;
}
if (!isFloodRetryQueued(_flood_retries[i].packet)) {
if (_flood_retries[i].packet == getOutboundInFlight()) {
continue;
}
clearFloodRetrySlot(i);
}
}
}
bool Mesh::allowPacketForward(const mesh::Packet* packet) {
@@ -60,21 +147,44 @@ uint32_t Mesh::getDirectRetryEchoDelay(const Packet* packet) const {
uint8_t Mesh::getDirectRetryMaxAttempts(const Packet* packet) const {
return DIRECT_RETRY_MAX_ATTEMPTS_DEFAULT;
}
uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) const {
uint32_t Mesh::getDirectRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) {
uint32_t base = getDirectRetryEchoDelay(packet);
// Keep the historical linear spacing while allowing the base wait to vary by platform/profile.
return base + ((uint32_t)attempt_idx * 100UL);
}
bool Mesh::allowFloodRetry(const Packet* packet) const {
return true;
}
bool Mesh::hasFloodRetryTargetPrefix(const Packet* packet) const {
return false;
}
uint8_t Mesh::getFloodRetryMaxPathLength(const Packet* packet) const {
return 2;
}
uint8_t Mesh::getFloodRetryMaxAttempts(const Packet* packet) const {
return FLOOD_RETRY_MAX_ATTEMPTS_DEFAULT;
}
uint32_t Mesh::getFloodRetryAttemptDelay(const Packet* packet, uint8_t attempt_idx) {
if (packet == NULL) {
return _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
}
uint32_t max_packet_airtime = _radio->getEstAirtimeFor(MAX_TRANS_UNIT);
uint32_t packet_airtime = _radio->getEstAirtimeFor(packet->getRawLength());
return max_packet_airtime + (20UL * packet_airtime);
}
uint8_t Mesh::getExtraAckTransmitCount() const {
return 0;
}
void Mesh::onSendComplete(Packet* packet) {
armDirectRetryOnSendComplete(packet);
armFloodRetryOnSendComplete(packet);
}
void Mesh::onSendFail(Packet* packet) {
clearPendingDirectRetryOnSendFail(packet);
clearPendingFloodRetryOnSendFail(packet);
}
uint32_t Mesh::getCADFailRetryDelay() const {
@@ -92,6 +202,8 @@ int Mesh::searchChannelsByHash(const uint8_t* hash, GroupChannel channels[], int
DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
if (pkt->isRouteDirect()) {
cancelDirectRetryOnEcho(pkt);
} else if (pkt->isRouteFlood()) {
cancelFloodRetryOnEcho(pkt);
}
if (pkt->isRouteDirect() && pkt->getPayloadType() == PAYLOAD_TYPE_TRACE) {
@@ -102,13 +214,14 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
uint32_t auth_code;
memcpy(&auth_code, &pkt->payload[i], 4); i += 4;
uint8_t flags = pkt->payload[i++];
uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size
uint8_t len = pkt->payload_len - i;
uint8_t offset = pkt->path_len << path_sz;
uint8_t hash_size = decodeTraceHashSize(flags, len);
uint16_t offset = (uint16_t)pkt->path_len * (uint16_t)hash_size;
if (offset >= len) { // TRACE has reached end of given path
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
} else if (hash_size > 0 && offset + hash_size <= len
&& self_id.isHashMatch(&pkt->payload[i + offset], hash_size)
&& allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
// append SNR (Not hash!)
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
@@ -391,8 +504,10 @@ DispatcherAction Mesh::routeRecvPacket(Packet* packet) {
packet->setPathHashCount(n + 1);
uint32_t d = getRetransmitDelay(packet);
uint8_t priority = packet->getPathHashCount();
maybeScheduleFloodRetry(packet, priority);
// as this propagates outwards, give it lower and lower priority
return ACTION_RETRANSMIT_DELAYED(packet->getPathHashCount(), d); // give priority to closer sources, than ones further away
return ACTION_RETRANSMIT_DELAYED(priority, d); // give priority to closer sources, than ones further away
}
return ACTION_RELEASE;
}
@@ -447,14 +562,20 @@ void Mesh::routeDirectRecvAcks(Packet* packet, uint32_t delay_millis) {
}
void Mesh::clearDirectRetrySlot(int idx) {
if (_direct_retries[idx].waiting_final_echo && _direct_retries[idx].packet != NULL) {
releasePacket(_direct_retries[idx].packet);
}
_direct_retries[idx].packet = NULL;
_direct_retries[idx].trigger_packet = NULL;
_direct_retries[idx].retry_started_at = 0;
_direct_retries[idx].echo_wait_started_at = 0;
_direct_retries[idx].retry_at = 0;
_direct_retries[idx].retry_delay = 0;
_direct_retries[idx].retry_attempts_sent = 0;
_direct_retries[idx].priority = 0;
_direct_retries[idx].progress_marker = 0;
_direct_retries[idx].expect_path_growth = false;
_direct_retries[idx].waiting_final_echo = false;
_direct_retries[idx].queued = false;
_direct_retries[idx].active = false;
}
@@ -490,22 +611,39 @@ bool Mesh::cancelDirectRetryOnEcho(const Packet* packet) {
continue;
}
if (_direct_retries[i].queued) {
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
if (_mgr->getOutboundByIdx(j) == _direct_retries[i].packet) {
Packet* pending = _mgr->removeOutboundByIdx(j);
if (pending) {
releasePacket(pending);
int8_t echo_snr_x4 = packet->_snr;
if (_direct_retries[i].queued || _direct_retries[i].waiting_final_echo) {
if (_direct_retries[i].packet != NULL) {
// Success quality comes from the received downstream echo, not the original upstream RX.
_direct_retries[i].packet->_snr = echo_snr_x4;
}
uint32_t echo_millis = _direct_retries[i].echo_wait_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _direct_retries[i].echo_wait_started_at);
uint8_t retry_attempt = _direct_retries[i].waiting_final_echo
? _direct_retries[i].retry_attempts_sent
: _direct_retries[i].retry_attempts_sent + 1;
onDirectRetryEvent("good", _direct_retries[i].packet, echo_millis, retry_attempt);
if (_direct_retries[i].queued) {
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
if (_mgr->getOutboundByIdx(j) == _direct_retries[i].packet) {
Packet* pending = _mgr->removeOutboundByIdx(j);
if (pending) {
releasePacket(pending);
}
break;
}
break;
}
}
onDirectRetryEvent("canceled_echo", _direct_retries[i].packet, 0);
onDirectRetryEvent("good", _direct_retries[i].packet, 0);
clearDirectRetrySlot(i);
} else {
onDirectRetryEvent("canceled_echo", _direct_retries[i].trigger_packet, 0);
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, 0);
if (_direct_retries[i].trigger_packet != NULL) {
_direct_retries[i].trigger_packet->_snr = echo_snr_x4;
}
uint32_t echo_millis = _direct_retries[i].echo_wait_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _direct_retries[i].echo_wait_started_at);
onDirectRetryEvent("good", _direct_retries[i].trigger_packet, echo_millis, _direct_retries[i].retry_attempts_sent + 1);
clearDirectRetrySlot(i);
}
cleared = true;
@@ -523,7 +661,11 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
if (_direct_retries[i].queued) {
if (_direct_retries[i].packet == packet) {
// The retry packet itself just finished transmitting; Dispatcher will release it after this hook.
onDirectRetryEvent("resent", packet, 0);
uint32_t elapsed_millis = _direct_retries[i].retry_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _direct_retries[i].retry_started_at);
onDirectRetryEvent("resent", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
_direct_retries[i].echo_wait_started_at = _ms->getMillis();
_direct_retries[i].retry_attempts_sent++;
uint8_t max_attempts = getDirectRetryMaxAttempts(packet);
if (max_attempts < 1) {
@@ -532,30 +674,45 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
max_attempts = DIRECT_RETRY_MAX_ATTEMPTS_HARD_MAX;
}
if (_direct_retries[i].retry_attempts_sent >= max_attempts) {
onDirectRetryEvent("failure", packet, 0);
clearDirectRetrySlot(i);
Packet* final_wait = obtainNewPacket();
if (final_wait == NULL) {
onDirectRetryEvent("dropped_no_packet", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
onDirectRetryEvent("failure", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent);
clearDirectRetrySlot(i);
continue;
}
*final_wait = *packet;
_direct_retries[i].packet = final_wait;
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
_direct_retries[i].waiting_final_echo = true;
_direct_retries[i].queued = false;
continue;
}
Packet* retry = obtainNewPacket();
if (retry == NULL) {
onDirectRetryEvent("dropped_no_packet", packet, 0);
onDirectRetryEvent("failure", packet, 0);
onDirectRetryEvent("dropped_no_packet", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
onDirectRetryEvent("failure", packet, elapsed_millis, _direct_retries[i].retry_attempts_sent + 1);
clearDirectRetrySlot(i);
continue;
}
*retry = *packet;
retry->tx_cr = 0;
uint8_t retry_attempt = _direct_retries[i].retry_attempts_sent + 1;
configureDirectRetryPacket(retry, packet, retry_attempt);
uint32_t retry_delay = getDirectRetryAttemptDelay(packet, _direct_retries[i].retry_attempts_sent);
sendPacket(retry, _direct_retries[i].priority, retry_delay);
if (isDirectRetryQueued(retry)) {
if (queueOutboundPacket(retry, _direct_retries[i].priority, retry_delay)) {
_direct_retries[i].packet = retry;
_direct_retries[i].retry_delay = retry_delay;
_direct_retries[i].retry_at = futureMillis(retry_delay);
onDirectRetryEvent("queued", retry, retry_delay);
_direct_retries[i].waiting_final_echo = false;
onDirectRetryEvent("queued", retry, retry_delay, retry_attempt);
} else {
onDirectRetryEvent("dropped_queue_full", retry, retry_delay);
onDirectRetryEvent("failure", retry, 0);
onDirectRetryEvent("dropped_queue_full", retry, retry_delay, retry_attempt);
onDirectRetryEvent("failure", retry, elapsed_millis, retry_attempt);
releasePacket(retry);
clearDirectRetrySlot(i);
}
}
@@ -569,25 +726,31 @@ void Mesh::armDirectRetryOnSendComplete(const Packet* packet) {
// Allocate the retry packet only after TX-complete so busy repeaters do not reserve pool slots early.
Packet* retry = obtainNewPacket();
if (retry == NULL) {
onDirectRetryEvent("dropped_no_packet", packet, _direct_retries[i].retry_delay);
onDirectRetryEvent("failure", packet, 0);
onDirectRetryEvent("dropped_no_packet", packet, _direct_retries[i].retry_delay, 1);
onDirectRetryEvent("failure", packet, 0, 1);
clearDirectRetrySlot(i);
continue;
}
*retry = *packet;
retry->tx_cr = 0;
configureDirectRetryPacket(retry, packet, 1);
// Start the echo wait only after the initial direct transmission actually completed.
sendPacket(retry, _direct_retries[i].priority, _direct_retries[i].retry_delay);
if (isDirectRetryQueued(retry)) {
if (queueOutboundPacket(retry, _direct_retries[i].priority, _direct_retries[i].retry_delay)) {
unsigned long now = _ms->getMillis();
_direct_retries[i].packet = retry;
_direct_retries[i].trigger_packet = NULL;
_direct_retries[i].queued = true;
_direct_retries[i].waiting_final_echo = false;
_direct_retries[i].retry_at = futureMillis(_direct_retries[i].retry_delay);
onDirectRetryEvent("queued", retry, _direct_retries[i].retry_delay);
_direct_retries[i].retry_started_at = now;
_direct_retries[i].echo_wait_started_at = now;
onDirectRetryEvent("queued", retry, _direct_retries[i].retry_delay, 1);
} else {
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay);
onDirectRetryEvent("failure", retry, 0);
onDirectRetryEvent("dropped_queue_full", retry, _direct_retries[i].retry_delay, 1);
onDirectRetryEvent("failure", retry, 0, 1);
releasePacket(retry);
clearDirectRetrySlot(i);
}
}
@@ -602,16 +765,16 @@ void Mesh::clearPendingDirectRetryOnSendFail(const Packet* packet) {
if (_direct_retries[i].queued) {
if (_direct_retries[i].packet == packet) {
// The queued retry itself failed; Dispatcher will release it after this hook.
onDirectRetryEvent("dropped_send_fail", packet, 0);
onDirectRetryEvent("failure", packet, 0);
onDirectRetryEvent("dropped_send_fail", packet, 0, _direct_retries[i].retry_attempts_sent + 1);
onDirectRetryEvent("failure", packet, 0, _direct_retries[i].retry_attempts_sent + 1);
clearDirectRetrySlot(i);
}
continue;
}
if (_direct_retries[i].trigger_packet == packet) {
onDirectRetryEvent("dropped_send_fail", packet, 0);
onDirectRetryEvent("failure", packet, 0);
onDirectRetryEvent("dropped_send_fail", packet, 0, 1);
onDirectRetryEvent("failure", packet, 0, 1);
clearDirectRetrySlot(i);
}
}
@@ -652,9 +815,9 @@ bool Mesh::getDirectRetryTarget(const Packet* packet, const uint8_t*& next_hop_h
return false;
}
uint8_t hash_size = 1 << (packet->payload[8] & 0x03);
uint8_t route_bytes = packet->payload_len - 9;
uint8_t offset = packet->path_len * hash_size;
uint8_t hash_size = decodeTraceHashSize(packet->payload[8], route_bytes);
uint16_t offset = (uint16_t)packet->path_len * (uint16_t)hash_size;
if (offset + hash_size > route_bytes) {
return false;
}
@@ -692,6 +855,8 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
}
}
if (slot_idx < 0) {
onDirectRetryEvent("dropped_no_slot", packet, 0, 0);
onDirectRetryEvent("failure", packet, 0, 0);
return;
}
@@ -700,15 +865,253 @@ void Mesh::maybeScheduleDirectRetry(const Packet* packet, uint8_t priority) {
calculateDirectRetryKey(packet, _direct_retries[slot_idx].retry_key);
_direct_retries[slot_idx].packet = NULL;
_direct_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
_direct_retries[slot_idx].retry_started_at = 0;
_direct_retries[slot_idx].echo_wait_started_at = 0;
_direct_retries[slot_idx].retry_at = 0;
_direct_retries[slot_idx].retry_delay = retry_delay;
_direct_retries[slot_idx].retry_attempts_sent = 0;
_direct_retries[slot_idx].priority = priority;
_direct_retries[slot_idx].progress_marker = progress_marker;
_direct_retries[slot_idx].expect_path_growth = expect_path_growth;
_direct_retries[slot_idx].waiting_final_echo = false;
_direct_retries[slot_idx].queued = false;
_direct_retries[slot_idx].active = true;
onDirectRetryEvent("armed", packet, retry_delay);
}
void Mesh::clearFloodRetrySlot(int idx) {
if (_flood_retries[idx].waiting_final_echo && _flood_retries[idx].packet != NULL) {
releasePacket(_flood_retries[idx].packet);
}
_flood_retries[idx].packet = NULL;
_flood_retries[idx].trigger_packet = NULL;
_flood_retries[idx].retry_started_at = 0;
_flood_retries[idx].retry_at = 0;
_flood_retries[idx].retry_delay = 0;
_flood_retries[idx].retry_attempts_sent = 0;
_flood_retries[idx].priority = 0;
_flood_retries[idx].progress_marker = 0;
_flood_retries[idx].waiting_final_echo = false;
_flood_retries[idx].queued = false;
_flood_retries[idx].active = false;
}
bool Mesh::isFloodRetryQueued(const Packet* packet) const {
for (int i = 0; i < _mgr->getOutboundTotal(); i++) {
if (_mgr->getOutboundByIdx(i) == packet) {
return true;
}
}
return false;
}
bool Mesh::isFloodRetryEchoTarget(const Packet* packet, uint8_t progress_marker) const {
return packet->isRouteFlood() && packet->getPathHashCount() > progress_marker;
}
bool Mesh::cancelFloodRetryOnEcho(const Packet* packet) {
uint8_t recv_key[MAX_HASH_SIZE];
packet->calculatePacketHash(recv_key);
bool cleared = false;
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
if (!_flood_retries[i].active || memcmp(recv_key, _flood_retries[i].retry_key, MAX_HASH_SIZE) != 0) {
continue;
}
if (!isFloodRetryEchoTarget(packet, _flood_retries[i].progress_marker)) {
continue;
}
uint32_t echo_millis = _flood_retries[i].retry_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at);
uint8_t retry_attempt = _flood_retries[i].waiting_final_echo
? _flood_retries[i].retry_attempts_sent
: _flood_retries[i].retry_attempts_sent + 1;
onFloodRetryEvent("good", packet, echo_millis, retry_attempt);
if (_flood_retries[i].queued) {
for (int j = 0; j < _mgr->getOutboundTotal(); j++) {
if (_mgr->getOutboundByIdx(j) == _flood_retries[i].packet) {
Packet* pending = _mgr->removeOutboundByIdx(j);
if (pending) {
releasePacket(pending);
}
break;
}
}
}
clearFloodRetrySlot(i);
cleared = true;
}
return cleared;
}
void Mesh::armFloodRetryOnSendComplete(const Packet* packet) {
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
if (!_flood_retries[i].active) {
continue;
}
if (_flood_retries[i].queued) {
if (_flood_retries[i].packet != packet) {
continue;
}
uint32_t elapsed_millis = _flood_retries[i].retry_started_at == 0
? 0
: (uint32_t)(_ms->getMillis() - _flood_retries[i].retry_started_at);
onFloodRetryEvent("resent", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
_flood_retries[i].retry_attempts_sent++;
uint8_t max_attempts = getFloodRetryMaxAttempts(packet);
if (max_attempts < 1) {
max_attempts = 1;
} else if (max_attempts > FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX) {
max_attempts = FLOOD_RETRY_MAX_ATTEMPTS_HARD_MAX;
}
if (_flood_retries[i].retry_attempts_sent >= max_attempts) {
Packet* final_wait = obtainNewPacket();
if (final_wait == NULL) {
onFloodRetryEvent("dropped_no_packet", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
onFloodRetryEvent("failure", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent);
clearFloodRetrySlot(i);
continue;
}
*final_wait = *packet;
_flood_retries[i].packet = final_wait;
_flood_retries[i].retry_at = futureMillis(_flood_retries[i].retry_delay);
_flood_retries[i].waiting_final_echo = true;
_flood_retries[i].queued = false;
continue;
}
Packet* retry = obtainNewPacket();
if (retry == NULL) {
onFloodRetryEvent("dropped_no_packet", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
onFloodRetryEvent("failure", packet, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
clearFloodRetrySlot(i);
continue;
}
*retry = *packet;
uint32_t retry_delay = getFloodRetryAttemptDelay(packet, _flood_retries[i].retry_attempts_sent);
if (queueOutboundPacket(retry, _flood_retries[i].priority, retry_delay)) {
_flood_retries[i].packet = retry;
_flood_retries[i].retry_delay = retry_delay;
_flood_retries[i].retry_at = futureMillis(retry_delay);
_flood_retries[i].retry_started_at = _ms->getMillis();
_flood_retries[i].waiting_final_echo = false;
onFloodRetryEvent("queued", retry, retry_delay, _flood_retries[i].retry_attempts_sent + 1);
} else {
onFloodRetryEvent("dropped_queue_full", retry, retry_delay, _flood_retries[i].retry_attempts_sent + 1);
onFloodRetryEvent("failure", retry, elapsed_millis, _flood_retries[i].retry_attempts_sent + 1);
releasePacket(retry);
clearFloodRetrySlot(i);
}
continue;
}
if (_flood_retries[i].trigger_packet != packet) {
continue;
}
Packet* retry = obtainNewPacket();
if (retry == NULL) {
onFloodRetryEvent("dropped_no_packet", packet, _flood_retries[i].retry_delay, 1);
onFloodRetryEvent("failure", packet, 0, 1);
clearFloodRetrySlot(i);
continue;
}
*retry = *packet;
if (queueOutboundPacket(retry, _flood_retries[i].priority, _flood_retries[i].retry_delay)) {
unsigned long now = _ms->getMillis();
_flood_retries[i].packet = retry;
_flood_retries[i].trigger_packet = NULL;
_flood_retries[i].queued = true;
_flood_retries[i].waiting_final_echo = false;
_flood_retries[i].retry_at = futureMillis(_flood_retries[i].retry_delay);
_flood_retries[i].retry_started_at = now;
onFloodRetryEvent("queued", retry, _flood_retries[i].retry_delay, 1);
} else {
onFloodRetryEvent("dropped_queue_full", retry, _flood_retries[i].retry_delay, 1);
onFloodRetryEvent("failure", retry, 0, 1);
releasePacket(retry);
clearFloodRetrySlot(i);
}
}
}
void Mesh::clearPendingFloodRetryOnSendFail(const Packet* packet) {
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
if (!_flood_retries[i].active) {
continue;
}
if (_flood_retries[i].queued) {
if (_flood_retries[i].packet == packet) {
onFloodRetryEvent("dropped_send_fail", packet, 0, _flood_retries[i].retry_attempts_sent + 1);
onFloodRetryEvent("failure", packet, 0, _flood_retries[i].retry_attempts_sent + 1);
clearFloodRetrySlot(i);
}
continue;
}
if (_flood_retries[i].trigger_packet == packet) {
onFloodRetryEvent("dropped_send_fail", packet, 0, 1);
onFloodRetryEvent("failure", packet, 0, 1);
clearFloodRetrySlot(i);
}
}
}
void Mesh::maybeScheduleFloodRetry(const Packet* packet, uint8_t priority) {
if (packet == NULL || !packet->isRouteFlood() || hasFloodRetryTargetPrefix(packet)) {
return;
}
uint8_t max_path_len = getFloodRetryMaxPathLength(packet);
if (max_path_len != FLOOD_RETRY_PATH_GATE_DISABLED && packet->getPathHashCount() > max_path_len) {
return;
}
uint8_t max_attempts = getFloodRetryMaxAttempts(packet);
if (max_attempts == 0) {
return;
}
int slot_idx = -1;
for (int i = 0; i < MAX_FLOOD_RETRY_SLOTS; i++) {
if (!_flood_retries[i].active) {
slot_idx = i;
break;
}
}
if (slot_idx < 0) {
onFloodRetryEvent("dropped_no_slot", packet, 0, 0);
onFloodRetryEvent("failure", packet, 0, 0);
return;
}
if (!allowFloodRetry(packet)) {
return;
}
uint32_t retry_delay = getFloodRetryAttemptDelay(packet, 0);
packet->calculatePacketHash(_flood_retries[slot_idx].retry_key);
_flood_retries[slot_idx].packet = NULL;
_flood_retries[slot_idx].trigger_packet = const_cast<Packet*>(packet);
_flood_retries[slot_idx].retry_started_at = 0;
_flood_retries[slot_idx].retry_at = 0;
_flood_retries[slot_idx].retry_delay = retry_delay;
_flood_retries[slot_idx].retry_attempts_sent = 0;
_flood_retries[slot_idx].priority = priority;
_flood_retries[slot_idx].progress_marker = packet->getPathHashCount();
_flood_retries[slot_idx].waiting_final_echo = false;
_flood_retries[slot_idx].queued = false;
_flood_retries[slot_idx].active = true;
}
Packet* Mesh::createAdvert(const LocalIdentity& id, const uint8_t* app_data, size_t app_data_len) {
+75 -2
View File
@@ -8,6 +8,14 @@ namespace mesh {
#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];
@@ -32,6 +40,8 @@ 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;
@@ -39,6 +49,22 @@ class Mesh : public Dispatcher {
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;
};
@@ -47,6 +73,7 @@ class Mesh : public Dispatcher {
RNG* _rng;
MeshTables* _tables;
DirectRetryEntry _direct_retries[MAX_DIRECT_RETRY_SLOTS];
FloodRetryEntry _flood_retries[MAX_FLOOD_RETRY_SLOTS];
void removeSelfFromPath(Packet* packet);
void routeDirectRecvAcks(Packet* packet, uint32_t delay_millis);
@@ -59,6 +86,12 @@ class Mesh : public Dispatcher {
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;
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);
@@ -115,7 +148,42 @@ protected:
/**
* \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) const;
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.
@@ -125,7 +193,12 @@ protected:
/**
* \brief Optional hook for logging direct-retry lifecycle events.
*/
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis) { }
virtual void onDirectRetryEvent(const char* event, const Packet* packet, uint32_t delay_millis, uint8_t retry_attempt) { }
/**
* \brief Optional hook to set local-only transmit options on a retry packet before it is queued.
*/
virtual void configureDirectRetryPacket(Packet* retry, const Packet* original, uint8_t retry_attempt) { }
/**
* \brief Perform search of local DB of peers/contacts.
+4
View File
@@ -52,12 +52,16 @@ public:
virtual void onAfterTransmit() { }
virtual void reboot() = 0;
virtual void powerOff() { /* no op */ }
virtual uint32_t getIRQGpio() { return -1; } // not supported. Returns DIO1 (SX1262) and DIO0 (SX127x)
virtual void sleep(uint32_t secs) { /* no op */ }
virtual uint32_t getGpio() { return 0; }
virtual void setGpio(uint32_t values) {}
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 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; }
+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
+4
View File
@@ -17,6 +17,10 @@ bool ArduinoSerialInterface::isConnected() const {
return true; // no way of knowing, so assume yes
}
bool ArduinoSerialInterface::isReadBusy() const {
return false;
}
bool ArduinoSerialInterface::isWriteBusy() const {
return false;
}
+2 -1
View File
@@ -28,7 +28,8 @@ public:
bool isConnected() const override;
bool isReadBusy() const override;
bool isWriteBusy() const override;
size_t writeFrame(const uint8_t src[], size_t len) override;
size_t checkRecvFrame(uint8_t dest[]) override;
};
};
+1
View File
@@ -15,6 +15,7 @@ public:
virtual bool isConnected() const = 0;
virtual bool isReadBusy() const = 0;
virtual bool isWriteBusy() const = 0;
virtual size_t writeFrame(const uint8_t src[], size_t len) = 0;
virtual size_t checkRecvFrame(uint8_t dest[]) = 0;
+910 -24
View File
File diff suppressed because it is too large. Load diff
+68 -2
View File
@@ -19,6 +19,57 @@
#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 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
#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 // 10.0 dB and up => CR4
#define DIRECT_RETRY_CR5_MIN_SNR_X4_DEFAULT 30 // 7.5 dB and up => CR5
#define DIRECT_RETRY_CR7_MIN_SNR_X4_DEFAULT 10 // 2.5 dB and up => CR7
#define DIRECT_RETRY_CR8_MAX_SNR_X4_DEFAULT 10 // 2.5 dB and down => CR8
#define DIRECT_RETRY_CR_SNR_X4_MIN -128
#define DIRECT_RETRY_CR_SNR_X4_MAX 127
struct NodePrefs { // persisted to file
float airtime_factor;
char node_name[32];
@@ -34,7 +85,7 @@ struct NodePrefs { // persisted to file
char guest_password[16];
float direct_tx_delay_factor;
uint8_t direct_retry_recent_enabled;
uint8_t direct_retry_snr_margin_db;
uint8_t direct_retry_snr_margin_db; // stored in quarter-dB units (x4)
uint8_t direct_retry_prefs_magic[2];
uint8_t sf;
uint8_t cr;
@@ -61,11 +112,26 @@ 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 direct_retry_attempts;
uint16_t direct_retry_base_ms;
uint8_t direct_retry_timing_magic[2];
uint8_t retry_preset;
uint16_t direct_retry_step_ms;
uint8_t flood_retry_attempts;
uint8_t flood_retry_path_gate;
uint8_t flood_retry_prefs_magic[2];
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;
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;
};
class CommonCLICallbacks {
@@ -125,7 +191,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();
+106 -24
View File
@@ -12,12 +12,14 @@
#include <rom/rtc.h>
#include <sys/time.h>
#include <Wire.h>
#include "driver/rtc_io.h"
#include "soc/rtc.h"
#include "esp_system.h"
class ESP32Board : public mesh::MainBoard {
protected:
uint8_t startup_reason;
bool inhibit_sleep = false;
static inline portMUX_TYPE sleepMux = portMUX_INITIALIZER_UNLOCKED;
public:
void begin() {
@@ -60,25 +62,48 @@ public:
return raw / 4;
}
void enterLightSleep(uint32_t secs) {
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet
if (secs > 0) {
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
}
esp_light_sleep_start(); // CPU enters light sleep
}
#endif
uint32_t getIRQGpio() {
return P_LORA_DIO_1; // default for SX1262
}
void sleep(uint32_t secs) override {
if (!inhibit_sleep) {
enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet
// Skip if not allow to sleep
if (inhibit_sleep) {
delay(1); // Give MCU to OTA to run
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
}
// Disable CPU interrupt servicing
portENTER_CRITICAL(&sleepMux);
// Skip sleep if there is a LoRa packet
if (gpio_get_level(wakeupPin) == HIGH) {
portEXIT_CRITICAL(&sleepMux);
delay(1);
return;
}
// Configure GPIO wakeup
esp_sleep_enable_gpio_wakeup();
gpio_wakeup_enable((gpio_num_t)wakeupPin, GPIO_INTR_HIGH_LEVEL); // Wake up when receiving a LoRa packet
// MCU enters light sleep
esp_light_sleep_start();
// Avoid ISR flood during wakeup due to HIGH LEVEL interrupt
gpio_wakeup_disable(wakeupPin);
gpio_set_intr_type(wakeupPin, GPIO_INTR_POSEDGE);
// Enable CPU interrupt servicing
portEXIT_CRITICAL(&sleepMux);
}
uint8_t getStartupReason() const override { return startup_reason; }
@@ -102,7 +127,7 @@ public:
#endif
uint16_t getBattMilliVolts() override {
#ifdef PIN_VBAT_READ
#ifdef PIN_VBAT_READ
analogReadResolution(12);
uint32_t raw = 0;
@@ -130,31 +155,88 @@ public:
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
tv.tv_usec = 0;
settimeofday(&tv, NULL);
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 = 1772323200; // 1 Mar 2026
}
tv.tv_usec = 0;
settimeofday(&tv, NULL);
}
uint32_t getCurrentTime() override {
time_t _now;
time(&_now);
return _now;
}
void setCurrentTime(uint32_t time) override {
void setCurrentTime(uint32_t time) override {
struct timeval tv;
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;
}
}
};
+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() { }
+112 -23
View File
@@ -1,6 +1,9 @@
#pragma once
#include <Mesh.h>
#if ARDUINO
#include <Arduino.h>
#endif
#ifdef ESP32
#include <FS.h>
@@ -8,7 +11,16 @@
#define MAX_PACKET_HASHES 128
#define MAX_PACKET_ACKS 64
#define MAX_RECENT_REPEATERS 64
#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 {
@@ -16,10 +28,12 @@ public:
typedef bool (*RecentRepeaterAllowFn)(const uint8_t* prefix, uint8_t prefix_len, void* ctx);
struct RecentRepeaterInfo {
// Just enough identity to match a next-hop path prefix plus the SNR that heard it.
// 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;
uint8_t snr_locked;
uint32_t last_heard_millis;
};
private:
@@ -68,19 +82,20 @@ private:
return n > 0 && memcmp(a, b, n) == 0;
}
int8_t avgSnrX4RoundUp(int8_t curr_snr_x4, int8_t new_snr_x4) const {
int16_t sum = (int16_t)curr_snr_x4 + (int16_t)new_snr_x4;
int16_t avg = sum / 2; // truncates toward zero
// "Round up" means ceil(), which only differs from truncation for positive odd sums.
if (sum > 0 && (sum & 1)) {
avg++;
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 (avg > 127) {
avg = 127;
} else if (avg < -128) {
avg = -128;
if (blended > 127) {
blended = 127;
} else if (blended < -128) {
blended = -128;
}
return (int8_t)avg;
return (int8_t)blended;
}
bool extractRecentRepeater(const mesh::Packet* packet, uint8_t* prefix, uint8_t& prefix_len) const {
@@ -149,21 +164,24 @@ public:
f.read((uint8_t *) &_next_idx, sizeof(_next_idx));
f.read((uint8_t *) &_acks[0], sizeof(_acks));
f.read((uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
f.read((uint8_t *) &_recent_repeaters[0], sizeof(_recent_repeaters));
f.read((uint8_t *) &_next_recent_repeater_idx, sizeof(_next_recent_repeater_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));
_next_recent_repeater_idx = 0;
}
void saveTo(File f) {
f.write(_hashes, sizeof(_hashes));
f.write((const uint8_t *) &_next_idx, sizeof(_next_idx));
f.write((const uint8_t *) &_acks[0], sizeof(_acks));
f.write((const uint8_t *) &_next_ack_idx, sizeof(_next_ack_idx));
f.write((const uint8_t *) &_recent_repeaters[0], sizeof(_recent_repeaters));
f.write((const uint8_t *) &_next_recent_repeater_idx, sizeof(_next_recent_repeater_idx));
}
#endif
bool hasSeen(const mesh::Packet* packet) override {
if (packet->getPayloadType() == PAYLOAD_TYPE_ACK) {
recordRecentRepeater(packet);
uint32_t ack;
memcpy(&ack, packet->payload, 4);
@@ -249,7 +267,8 @@ public:
_recent_repeater_allow_fn = fn;
_recent_repeater_allow_ctx = ctx;
}
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4) {
bool setRecentRepeater(const uint8_t* prefix, uint8_t prefix_len, int8_t snr_x4, bool snr_locked = false,
bool bypass_allow_filter = false) {
if (prefix == NULL || prefix_len == 0) {
return false;
}
@@ -258,7 +277,8 @@ public:
prefix_len = MAX_ROUTE_HASH_BYTES;
}
if (_recent_repeater_allow_fn != NULL && !_recent_repeater_allow_fn(prefix, prefix_len, _recent_repeater_allow_ctx)) {
if (!bypass_allow_filter && _recent_repeater_allow_fn != NULL
&& !_recent_repeater_allow_fn(prefix, prefix_len, _recent_repeater_allow_ctx)) {
return false;
}
@@ -273,19 +293,84 @@ public:
memcpy(existing.prefix, prefix, prefix_len);
existing.prefix_len = prefix_len;
}
existing.snr_x4 = avgSnrX4RoundUp(existing.snr_x4, snr_x4);
if (snr_locked) {
existing.snr_x4 = snr_x4;
existing.snr_locked = 1;
} else if (!existing.snr_locked) {
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;
}
// Ring buffer is enough here; retry fallback only needs a recent prefix->SNR observation.
RecentRepeaterInfo& slot = _recent_repeaters[_next_recent_repeater_idx];
int slot_idx = -1;
// Prefer empty slots first while preserving newest-order iteration.
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
int idx = (_next_recent_repeater_idx + i) % MAX_RECENT_REPEATERS;
if (_recent_repeaters[idx].prefix_len == 0) {
slot_idx = idx;
break;
}
}
if (slot_idx < 0) {
// Table is full: evict the weakest observed SNR entry.
slot_idx = 0;
int8_t min_snr_x4 = _recent_repeaters[0].snr_x4;
for (int i = 1; i < MAX_RECENT_REPEATERS; i++) {
if (_recent_repeaters[i].snr_x4 < min_snr_x4) {
min_snr_x4 = _recent_repeaters[i].snr_x4;
slot_idx = i;
}
}
}
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;
_next_recent_repeater_idx = (_next_recent_repeater_idx + 1) % MAX_RECENT_REPEATERS;
slot.snr_locked = snr_locked ? 1 : 0;
#if ARDUINO
slot.last_heard_millis = millis();
#else
slot.last_heard_millis = 0;
#endif
_next_recent_repeater_idx = (slot_idx + 1) % MAX_RECENT_REPEATERS;
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 == 0 || !prefixesOverlap(existing.prefix, existing.prefix_len, prefix, prefix_len)) {
continue;
}
if (prefix_len > existing.prefix_len) {
memset(existing.prefix, 0, sizeof(existing.prefix));
memcpy(existing.prefix, prefix, prefix_len);
existing.prefix_len = prefix_len;
}
if (!existing.snr_locked) {
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;
}
const RecentRepeaterInfo* getLatestRecentRepeater() const {
for (int i = 0; i < MAX_RECENT_REPEATERS; i++) {
int idx = (_next_recent_repeater_idx - 1 - i + MAX_RECENT_REPEATERS) % MAX_RECENT_REPEATERS;
@@ -360,6 +445,10 @@ public:
}
return NULL;
}
void clearRecentRepeaters() {
memset(_recent_repeaters, 0, sizeof(_recent_repeaters));
_next_recent_repeater_idx = 0;
}
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;
};
};
+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;
+7 -7
View File
@@ -59,13 +59,13 @@
// uint32_t P_LORA_BUSY = 0; //shared, so define at run
// uint32_t P_LORA_DIO_2 = 0; //SX1276 only, so define at run
#define P_LORA_DIO_0 26
#define P_LORA_DIO_1 33
#define P_LORA_NSS 18
#define P_LORA_RESET 23
#define P_LORA_SCLK 5
#define P_LORA_MISO 19
#define P_LORA_MOSI 27
// #define P_LORA_DIO_0 26
// #define P_LORA_DIO_1 33
// #define P_LORA_NSS 18
// #define P_LORA_RESET 23
// #define P_LORA_SCLK 5
// #define P_LORA_MISO 19
// #define P_LORA_MOSI 27
// #define PIN_GPS_RX 34
// #define PIN_GPS_TX 12
+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;
@@ -20,6 +20,15 @@ public:
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
}
bool setCodingRate(uint8_t cr) override {
idle();
int err = ((CustomLLCC68 *)_radio)->setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("CustomLLCC68Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
return false;
}
return true;
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
@@ -25,6 +25,16 @@ public:
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
bool setCodingRate(uint8_t cr) override {
idle();
int err = ((CustomLR1110 *)_radio)->setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("CustomLR1110Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
return false;
}
return true;
}
void setRxBoostedGainMode(bool en) override {
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
}
@@ -21,6 +21,15 @@ public:
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
}
bool setCodingRate(uint8_t cr) override {
idle();
int err = ((CustomSTM32WLx *)_radio)->setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("CustomSTM32WLxWrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
return false;
}
return true;
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
};
@@ -24,6 +24,15 @@ public:
int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
}
bool setCodingRate(uint8_t cr) override {
idle();
int err = ((CustomSX1262 *)_radio)->setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("CustomSX1262Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
return false;
}
return true;
}
virtual void powerOff() override {
((CustomSX1262 *)_radio)->sleep(false);
}
@@ -24,6 +24,15 @@ public:
int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
}
bool setCodingRate(uint8_t cr) override {
idle();
int err = ((CustomSX1268 *)_radio)->setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("CustomSX1268Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
return false;
}
return true;
}
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
@@ -23,4 +23,13 @@ public:
int sf = ((CustomSX1276 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len);
}
bool setCodingRate(uint8_t cr) override {
idle();
int err = ((CustomSX1276 *)_radio)->setCodingRate(cr);
if (err != RADIOLIB_ERR_NONE) {
MESH_DEBUG_PRINTLN("CustomSX1276Wrapper: error: setCodingRate(%d)=%d", (uint32_t)cr, err);
return false;
}
return true;
}
};
@@ -7,8 +7,10 @@
#endif
#ifdef ENV_INCLUDE_BME680
#ifndef TELEM_BME680_ADDRESS
#define TELEM_BME680_ADDRESS 0x76
#if defined(TELEM_BME680_ADDRESS)
uint8_t TELEM_BME680_ADDRESSES[] = { TELEM_BME680_ADDRESS };
#else
uint8_t TELEM_BME680_ADDRESSES[] = { 0x76, 0x77 }; // Known I2C addresses for BME680
#endif
#define TELEM_BME680_SEALEVELPRESSURE_HPA (1013.25)
#include <Adafruit_BME680.h>
@@ -28,8 +30,10 @@ static Adafruit_AHTX0 AHTX0;
#endif
#if ENV_INCLUDE_BME280
#ifndef TELEM_BME280_ADDRESS
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
#if defined(TELEM_BME280_ADDRESS)
uint8_t TELEM_BME280_ADDRESSES[] = { TELEM_BME280_ADDRESS };
#else
uint8_t TELEM_BME280_ADDRESSES[] = { 0x76, 0x77 }; // Known I2C addresses for BME280
#endif
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
#include <Adafruit_BME280.h>
@@ -37,8 +41,10 @@ static Adafruit_BME280 BME280;
#endif
#if ENV_INCLUDE_BMP280
#ifndef TELEM_BMP280_ADDRESS
#define TELEM_BMP280_ADDRESS 0x76 // BMP280 environmental sensor I2C address
#if defined(TELEM_BMP280_ADDRESS)
uint8_t TELEM_BMP280_ADDRESSES[] = { TELEM_BMP280_ADDRESS };
#else
uint8_t TELEM_BMP280_ADDRESSES[] = { 0x76, 0x77 }; // Known I2C addresses for BMP280
#endif
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
#include <Adafruit_BMP280.h>
@@ -161,6 +167,12 @@ public:
static RAK12500LocationProvider RAK12500_provider;
#endif
bool EnvironmentSensorManager::i2c_probe(TwoWire &wire, uint8_t addr) {
wire.beginTransmission(addr);
uint8_t error = wire.endTransmission();
return (error == 0); // If found i2c device, the error is 0
}
bool EnvironmentSensorManager::begin() {
#if ENV_INCLUDE_GPS
#ifdef RAK_WISBLOCK_GPS
@@ -182,7 +194,7 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_AHTX0
if (AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) {
if (i2c_probe(*TELEM_WIRE, TELEM_AHTX_ADDRESS) && AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) {
MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
AHTX0_initialized = true;
} else {
@@ -192,46 +204,55 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_BME680
if (BME680.begin(TELEM_BME680_ADDRESS)) {
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
BME680_initialized = true;
} else {
BME680_initialized = false;
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
for (size_t i = 0; i < sizeof(TELEM_BME680_ADDRESSES) / sizeof(TELEM_BME680_ADDRESSES[0]); i++) {
if (i2c_probe(*TELEM_WIRE, TELEM_BME680_ADDRESSES[i]) && BME680.begin(TELEM_BME680_ADDRESSES[i], TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESSES[i]);
BME680_initialized = true;
break;
} else {
BME680_initialized = false;
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESSES[i]);
}
}
#endif
#if ENV_INCLUDE_BME280
if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS);
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
// Reduce self-heating: single-shot conversions, light oversampling, long standby.
BME280.setSampling(Adafruit_BME280::MODE_FORCED,
for (size_t i = 0; i < sizeof(TELEM_BME280_ADDRESSES) / sizeof(TELEM_BME280_ADDRESSES[0]); i++) {
if (i2c_probe(*TELEM_WIRE, TELEM_BME280_ADDRESSES[i]) && BME280.begin(TELEM_BME280_ADDRESSES[i], TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESSES[i]);
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
// Reduce self-heating: single-shot conversions, light oversampling, long standby.
BME280.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // temperature
Adafruit_BME280::SAMPLING_X1, // pressure
Adafruit_BME280::SAMPLING_X1, // humidity
Adafruit_BME280::FILTER_OFF,
Adafruit_BME280::STANDBY_MS_1000);
BME280_initialized = true;
} else {
BME280_initialized = false;
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS);
BME280_initialized = true;
break;
} else {
BME280_initialized = false;
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESSES[i]);
}
}
#endif
#endif
#if ENV_INCLUDE_BMP280
if (BMP280.begin(TELEM_BMP280_ADDRESS)) {
MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESS);
MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID());
BMP280_initialized = true;
} else {
BMP280_initialized = false;
MESH_DEBUG_PRINTLN("BMP280 was not found at I2C address %02X", TELEM_BMP280_ADDRESS);
}
#endif
for (size_t i = 0; i < sizeof(TELEM_BMP280_ADDRESSES) / sizeof(TELEM_BMP280_ADDRESSES[0]); i++) {
if (i2c_probe(*TELEM_WIRE, TELEM_BMP280_ADDRESSES[i]) && BMP280.begin(TELEM_BMP280_ADDRESSES[i])) {
MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESSES[i]);
MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID());
BMP280_initialized = true;
break;
} else {
BMP280_initialized = false;
MESH_DEBUG_PRINTLN("BMP280 was not found at I2C address %02X", TELEM_BMP280_ADDRESSES[i]);
}
}
#endif
#if ENV_INCLUDE_SHTC3
if (SHTC3.begin(TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, 0x70) && SHTC3.begin(TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
SHTC3_initialized = true;
} else {
@@ -242,21 +263,23 @@ bool EnvironmentSensorManager::begin() {
#if ENV_INCLUDE_SHT4X
SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS);
uint32_t serialNumber = 0;
int16_t sht4x_error;
sht4x_error = SHT4X.serialNumber(serialNumber);
if (sht4x_error == 0) {
MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS);
SHT4X_initialized = true;
} else {
SHT4X_initialized = false;
MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS);
if (i2c_probe(*TELEM_WIRE, TELEM_SHT4X_ADDRESS)) {
SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS);
uint32_t serialNumber = 0;
int16_t sht4x_error;
sht4x_error = SHT4X.serialNumber(serialNumber);
if (sht4x_error == 0) {
MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS);
SHT4X_initialized = true;
} else {
SHT4X_initialized = false;
MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS);
}
}
#endif
#if ENV_INCLUDE_LPS22HB
if (LPS22HB.begin()) {
if (i2c_probe(*TELEM_WIRE, 0x5C) && LPS22HB.begin()) {
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
LPS22HB_initialized = true;
} else {
@@ -266,7 +289,7 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_INA3221
if (INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, TELEM_INA3221_ADDRESS) && INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
@@ -281,7 +304,7 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_INA219
if (INA219.begin(TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, TELEM_INA219_ADDRESS) && INA219.begin(TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
INA219_initialized = true;
} else {
@@ -291,17 +314,17 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_INA260
if (INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, TELEM_INA260_ADDRESS) && INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found INA260 at address: %02X", TELEM_INA260_ADDRESS);
INA260_initialized = true;
} else {
INA260_initialized = false;
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS);
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
}
#endif
#if ENV_INCLUDE_INA226
if (INA226.begin()) {
if (i2c_probe(*TELEM_WIRE, TELEM_INA226_ADDRESS) && INA226.begin()) {
MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS);
INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE);
INA226_initialized = true;
@@ -312,7 +335,7 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_MLX90614
if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, TELEM_MLX90614_ADDRESS) && MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS);
MLX90614_initialized = true;
} else {
@@ -322,7 +345,7 @@ bool EnvironmentSensorManager::begin() {
#endif
#if ENV_INCLUDE_VL53L0X
if (VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, TELEM_VL53L0X_ADDRESS) && VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found VL53L0X at address: %02X", TELEM_VL53L0X_ADDRESS);
VL53L0X_initialized = true;
} else {
@@ -334,7 +357,7 @@ bool EnvironmentSensorManager::begin() {
#if ENV_INCLUDE_BMP085
// First argument is MODE (aka oversampling)
// choose ULTRALOWPOWER
if (BMP085.begin(0, TELEM_WIRE)) {
if (i2c_probe(*TELEM_WIRE, 0x77) && BMP085.begin(0, TELEM_WIRE)) {
MESH_DEBUG_PRINTLN("Found sensor BMP085");
BMP085_initialized = true;
} else {
@@ -345,7 +368,7 @@ bool EnvironmentSensorManager::begin() {
#if ENV_INCLUDE_RAK12035
RAK12035.setup(*TELEM_WIRE);
if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
if (i2c_probe(*TELEM_WIRE, TELEM_RAK12035_ADDRESS) && RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS);
RAK12035_initialized = true;
} else {
@@ -716,7 +739,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
gps_detected = true;
return true;
}
pinMode(ioPin, INPUT);
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
return false;
@@ -759,7 +782,7 @@ void EnvironmentSensorManager::loop() {
#if ENV_INCLUDE_GPS
if (gps_active) {
_location->loop();
_location->loop();
}
if (millis() > next_gps_update) {
@@ -47,6 +47,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
+1 -1
View File
@@ -7,7 +7,7 @@ build_flags = ${nrf52_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
+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
@@ -5,7 +5,7 @@ board_check = true
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
+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
+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;
};
+19 -1
View File
@@ -123,4 +123,22 @@ void T096Board::powerOff() {
const char* T096Board::getManufacturerName() const {
return "Heltec T096";
}
}
bool T096Board::setLoRaFemLnaEnabled(bool enable) {
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;
};
+25 -1
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
@@ -126,6 +126,30 @@ lib_deps =
${Heltec_t096.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_t096_companion_radio_ble_femoff]
extends = Heltec_t096
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
board_upload.maximum_size = 712704
build_flags =
${Heltec_t096.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D ENV_INCLUDE_GPS=1 ; enable the GPS page in UI
; -D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
-D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off
build_src_filter = ${Heltec_t096.build_src_filter}
+<helpers/nrf52/SerialBLEInterface.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Heltec_t096.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_t096_companion_radio_usb]
extends = Heltec_t096
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
+1 -1
View File
@@ -12,7 +12,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/heltec_t114
-I src/helpers/ui
-D HELTEC_T114
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D P_LORA_DIO_1=20
-D P_LORA_NSS=24
-D P_LORA_RESET=25
+27
View File
@@ -99,6 +99,33 @@ lib_deps =
${Heltec_tracker_base.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_Wireless_Tracker_companion_radio_ble_ps]
extends = Heltec_tracker_base
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Heltec_tracker_base.build_flags}
-I src/helpers/ui
-I examples/companion_radio/ui-new
-D DISPLAY_ROTATION=1
-D DISPLAY_CLASS=ST7735Display
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456 ; HWT will use display for pin
-D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_tracker_base.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
+<helpers/ui/ST7735Display.cpp>
lib_deps =
${Heltec_tracker_base.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_Wireless_Tracker_repeater]
extends = Heltec_tracker_base
build_flags =
@@ -82,3 +82,21 @@ void HeltecTrackerV2Board::begin() {
const char* HeltecTrackerV2Board::getManufacturerName() const {
return "Heltec Tracker V2";
}
bool HeltecTrackerV2Board::setLoRaFemLnaEnabled(bool enable) {
if (!loRaFEMControl.isLnaCanControl()) {
return false;
}
loRaFEMControl.setLNAEnable(enable);
loRaFEMControl.setRxModeEnable();
return true;
}
bool HeltecTrackerV2Board::canControlLoRaFemLna() const {
return loRaFEMControl.isLnaCanControl();
}
bool HeltecTrackerV2Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
@@ -21,5 +21,8 @@ public:
void powerOff() override;
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() 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;
};
+27
View File
@@ -176,6 +176,33 @@ lib_deps =
${Heltec_tracker_v2.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:heltec_tracker_v2_companion_radio_ble_ps]
extends = Heltec_tracker_v2
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Heltec_tracker_v2.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=ST7735Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
+<helpers/ui/ST7735Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Heltec_tracker_v2.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:heltec_tracker_v2_companion_radio_wifi]
extends = Heltec_tracker_v2
build_flags =
+10 -6
View File
@@ -17,12 +17,12 @@ public:
esp_reset_reason_t reason = esp_reset_reason();
if (reason == ESP_RST_DEEPSLEEP) {
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
if (wakeup_source & (1 << P_LORA_DIO_0)) { // received a LoRa packet (while in deep sleep)
startup_reason = BD_STARTUP_RX_PACKET;
}
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_0);
}
}
@@ -30,15 +30,15 @@ public:
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_set_direction((gpio_num_t)P_LORA_DIO_0, RTC_GPIO_MODE_INPUT_ONLY);
rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_0);
rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS);
if (pin_wake_btn < 0) {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet
} else {
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_0) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn
}
if (secs > 0) {
@@ -64,4 +64,8 @@ public:
const char* getManufacturerName() const override {
return "Heltec V2";
}
uint32_t getIRQGpio() override {
return P_LORA_DIO_0; // default for SX1276
}
};
+28 -3
View File
@@ -7,10 +7,10 @@ build_flags =
-D HELTEC_LORA_V2
-D RADIO_CLASS=CustomSX1276
-D WRAPPER_CLASS=CustomSX1276Wrapper
-D P_LORA_DIO_1=26
-D P_LORA_DIO_0=26
-D P_LORA_DIO_1=35
-D P_LORA_NSS=18
-D P_LORA_RESET=RADIOLIB_NC
-D P_LORA_BUSY=RADIOLIB_NC
-D P_LORA_RESET=14
-D P_LORA_SCLK=5
-D P_LORA_MISO=19
-D P_LORA_MOSI=27
@@ -172,6 +172,31 @@ lib_deps =
${Heltec_lora32_v2.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_v2_companion_radio_ble_ps]
extends = Heltec_lora32_v2
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Heltec_lora32_v2.build_flags}
-I examples/companion_radio/ui-new
-D DISPLAY_CLASS=SSD1306Display
-D MAX_CONTACTS=160
-D MAX_GROUP_CHANNELS=8
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v2.build_src_filter}
+<helpers/esp32/*.cpp>
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Heltec_lora32_v2.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_v2_companion_radio_wifi]
extends = Heltec_lora32_v2
build_flags =
+2 -2
View File
@@ -5,9 +5,9 @@ HeltecV2Board board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_0, P_LORA_RESET, P_LORA_DIO_1);
#endif
WRAPPER_CLASS radio_driver(radio, board);
+4
View File
@@ -53,6 +53,10 @@ public:
}
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
// Clear stale wakeup sources to avoid ghost wakeup
// This is required when Power Management and automatic lightsleep are enabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
+46
View File
@@ -179,6 +179,32 @@ lib_deps =
${Heltec_lora32_v3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_v3_companion_radio_ble_ps]
extends = Heltec_lora32_v3
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Heltec_lora32_v3.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=SSD1306Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Heltec_lora32_v3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_v3_companion_radio_wifi]
extends = Heltec_lora32_v3
build_flags =
@@ -320,6 +346,26 @@ lib_deps =
${Heltec_lora32_v3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_WSL3_companion_radio_ble_ps]
extends = Heltec_lora32_v3
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Heltec_lora32_v3.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
lib_deps =
${Heltec_lora32_v3.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Heltec_WSL3_companion_radio_usb]
extends = Heltec_lora32_v3
build_flags =
+26
View File
@@ -33,6 +33,10 @@ void HeltecV4Board::begin() {
}
void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {
// Clear stale wakeup sources to avoid ghost wakeup
// This is required when Power Management and automatic lightsleep are enabled
esp_sleep_disable_wakeup_source(ESP_SLEEP_WAKEUP_ALL);
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
@@ -83,3 +87,25 @@ void HeltecV4Board::begin() {
return loRaFEMControl.getFEMType() == KCT8103L_PA ? "Heltec V4.3 OLED" : "Heltec V4 OLED";
#endif
}
bool HeltecV4Board::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 HeltecV4Board::canControlLoRaFemLna() const {
return loRaFEMControl.isLnaCanControl();
}
bool HeltecV4Board::isLoRaFemLnaEnabled() const {
return loRaFEMControl.isLNAEnabled();
}
+3
View File
@@ -17,6 +17,9 @@ public:
void onAfterTransmit(void) override;
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1);
void powerOff() override;
bool setLoRaFemLnaEnabled(bool enable) override;
bool canControlLoRaFemLna() const override;
bool isLoRaFemLnaEnabled() const override;
uint16_t getBattMilliVolts() override;
const char* getManufacturerName() const override ;
+2 -1
View File
@@ -18,8 +18,9 @@ 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; }
LoRaFEMType getFEMType(void) const { return fem_type; }
private:
LoRaFEMType fem_type=OTHER_FEM_TYPES;
+75 -1
View File
@@ -102,6 +102,28 @@ lib_deps =
${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:heltec_v4_expansionkit_repeater]
extends = heltec_v4_oled
build_flags =
${heltec_v4_oled.build_flags}
-D DISPLAY_CLASS=SSD1306Display
-D ADVERT_NAME='"Heltec Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
-D ENV_PIN_SDA=4
-D ENV_PIN_SCL=3
build_src_filter = ${heltec_v4_oled.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<../examples/simple_repeater>
lib_deps =
${heltec_v4_oled.lib_deps}
${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:heltec_v4_repeater_bridge_espnow]
extends = heltec_v4_oled
build_flags =
@@ -200,6 +222,59 @@ lib_deps =
${heltec_v4_oled.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:heltec_v4_companion_radio_ble_ps]
extends = heltec_v4_oled
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${heltec_v4_oled.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=SSD1306Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${heltec_v4_oled.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${heltec_v4_oled.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:heltec_v4_3_companion_radio_ble_ps_femoff]
extends = heltec_v4_oled
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${heltec_v4_oled.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=SSD1306Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
-D RADIO_FEM_RXGAIN=0 ; undefined (default on), 1=on, 0=off
build_src_filter = ${heltec_v4_oled.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${heltec_v4_oled.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:heltec_v4_companion_radio_wifi]
extends = heltec_v4_oled
build_flags =
@@ -207,7 +282,6 @@ build_flags =
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
-D DISPLAY_CLASS=SSD1306Display
-D WIFI_DEBUG_LOGGING=1
-D WIFI_SSID='"myssid"'
@@ -64,6 +64,29 @@ lib_deps =
densaugeo/base64 @ ~1.4.0
bakercp/CRC32 @ ^2.0.0
[env:Heltec_Wireless_Paper_companion_radio_ble_ps]
extends = Heltec_Wireless_Paper_base
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Heltec_Wireless_Paper_base.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=E213Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
build_src_filter = ${Heltec_Wireless_Paper_base.build_src_filter}
+<helpers/ui/E213Display.cpp>
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Heltec_Wireless_Paper_base.lib_deps}
densaugeo/base64 @ ~1.4.0
bakercp/CRC32 @ ^2.0.0
[env:Heltec_Wireless_Paper_companion_radio_usb]
extends = Heltec_Wireless_Paper_base
build_flags =
@@ -0,0 +1,10 @@
#pragma once
#include <helpers/ESP32Board.h>
class LilygoT3S3SX1276Board : public ESP32Board {
public:
uint32_t getIRQGpio() override {
return P_LORA_DIO_0; // default for SX1276
}
};
+1 -1
View File
@@ -1,7 +1,7 @@
#include <Arduino.h>
#include "target.h"
ESP32Board board;
LilygoT3S3SX1276Board board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
+2 -2
View File
@@ -3,7 +3,7 @@
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/ESP32Board.h>
#include <LilygoT3S3SX1276Board.h>
#include <helpers/radiolib/CustomSX1276Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
@@ -12,7 +12,7 @@
#include <helpers/ui/MomentaryButton.h>
#endif
extern ESP32Board board;
extern LilygoT3S3SX1276Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
+25
View File
@@ -146,6 +146,31 @@ lib_deps =
${LilyGo_TBeam_1W.lib_deps}
densaugeo/base64 @ ~1.4.0
; === LILYGO T-Beam 1W Companion Radio PS (BLE PS) ===
[env:LilyGo_TBeam_1W_companion_radio_ble_ps]
extends = LilyGo_TBeam_1W
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${LilyGo_TBeam_1W.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D OFFLINE_QUEUE_SIZE=256
-D PERSISTANT_GPS=1
-D ENV_SKIP_GPS_DETECT=1
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TBeam_1W.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${LilyGo_TBeam_1W.lib_deps}
densaugeo/base64 @ ~1.4.0
; === LILYGO T-Beam 1W Companion Radio (WiFi) ===
[env:LilyGo_TBeam_1W_companion_radio_wifi]
extends = LilyGo_TBeam_1W
@@ -5,6 +5,13 @@ build_flags =
${esp32_base.build_flags}
-I variants/lilygo_tbeam_SX1262
-D TBEAM_SX1262
-D P_LORA_DIO_0=26
-D P_LORA_DIO_1=33
-D P_LORA_NSS=18
-D P_LORA_RESET=23
-D P_LORA_SCLK=5
-D P_LORA_MISO=19
-D P_LORA_MOSI=27
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
@@ -0,0 +1,10 @@
#pragma once
#include <helpers/esp32/TBeamBoard.h>
class LilygoTBeamSX1276Board : public TBeamBoard {
public:
uint32_t getIRQGpio() override {
return P_LORA_DIO_0; // default for SX1276
}
};
@@ -5,6 +5,13 @@ build_flags =
${esp32_base.build_flags}
-I variants/lilygo_tbeam_SX1276
-D TBEAM_SX1276
-D P_LORA_DIO_0=26
-D P_LORA_DIO_1=33
-D P_LORA_NSS=18
-D P_LORA_RESET=23
-D P_LORA_SCLK=5
-D P_LORA_MISO=19
-D P_LORA_MOSI=27
-D SX127X_CURRENT_LIMIT=120
-D RADIO_CLASS=CustomSX1276
-D WRAPPER_CLASS=CustomSX1276Wrapper
+1 -1
View File
@@ -1,7 +1,7 @@
#include <Arduino.h>
#include "target.h"
TBeamBoard board;
LilygoTBeamSX1276Board board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
+2 -2
View File
@@ -3,7 +3,7 @@
#define RADIOLIB_STATIC_ONLY 1
//#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/esp32/TBeamBoard.h>
#include <LilygoTBeamSX1276Board.h>
#include <helpers/radiolib/CustomSX1276Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
@@ -12,7 +12,7 @@
#include <helpers/ui/MomentaryButton.h>
#endif
extern TBeamBoard board;
extern LilygoTBeamSX1276Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors;
@@ -5,6 +5,13 @@ build_flags =
${esp32_base.build_flags}
-I variants/lilygo_tbeam_supreme_SX1262
-D TBEAM_SUPREME_SX1262
-D P_LORA_DIO_0=26
-D P_LORA_DIO_1=33
-D P_LORA_NSS=18
-D P_LORA_RESET=23
-D P_LORA_SCLK=5
-D P_LORA_MISO=19
-D P_LORA_MOSI=27
-D SX126X_CURRENT_LIMIT=140
-D SX126X_RX_BOOSTED_GAIN=1
-D USE_SX1262
@@ -0,0 +1,10 @@
#pragma once
#include <helpers/ESP32Board.h>
class TETHEliteBoard : public ESP32Board {
public:
const char* getManufacturerName() const override {
return "LilyGO T-ETH Elite";
}
};
+99
View File
@@ -0,0 +1,99 @@
[LilyGo_TETH_Elite_sx1262]
extends = esp32_base
board = esp32s3box
board_build.partitions = default_16MB.csv
board_upload.flash_size = 16MB
build_flags =
${esp32_base.build_flags}
-I variants/lilygo_teth_elite
-D BOARD_HAS_PSRAM
-D LILYGO_TETH_ELITE
-D LILYGO_T_ETH_ELITE_ESP32S3
-D ARDUINO_USB_CDC_ON_BOOT=1
-D P_LORA_DIO_1=8
-D P_LORA_NSS=40
-D P_LORA_RESET=46
-D P_LORA_BUSY=16
-D P_LORA_SCLK=10
-D P_LORA_MISO=9
-D P_LORA_MOSI=11
-D P_LORA_TX_LED=38
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
-D USE_SX1262
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=8
-D SX126X_RX_BOOSTED_GAIN=1
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/lilygo_teth_elite>
lib_deps =
${esp32_base.lib_deps}
[env:LilyGo_TETH_Elite_sx1262_repeater]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D ADVERT_NAME='"T-ETH Elite Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<../examples/simple_repeater>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
${esp32_ota.lib_deps}
[env:LilyGo_TETH_Elite_sx1262_room_server]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D ADVERT_NAME='"T-ETH Elite Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<../examples/simple_room_server>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
${esp32_ota.lib_deps}
[env:LilyGo_TETH_Elite_sx1262_companion_radio_usb]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<../examples/companion_radio/*.cpp>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:LilyGo_TETH_Elite_sx1262_companion_radio_ble]
extends = LilyGo_TETH_Elite_sx1262
build_flags =
${LilyGo_TETH_Elite_sx1262.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${LilyGo_TETH_Elite_sx1262.build_src_filter}
+<helpers/esp32/*.cpp>
+<../examples/companion_radio/*.cpp>
lib_deps =
${LilyGo_TETH_Elite_sx1262.lib_deps}
densaugeo/base64 @ ~1.4.0
+43
View File
@@ -0,0 +1,43 @@
#include <Arduino.h>
#include "target.h"
TETHEliteBoard board;
static SPIClass spi(HSPI);
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
SensorManager sensors;
#ifndef LORA_CR
#define LORA_CR 5
#endif
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
return radio.std_init(&spi);
}
uint32_t radio_get_rng_seed() {
return radio.random(0x7FFFFFFF);
}
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setFrequency(freq);
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
}
void radio_set_tx_power(int8_t dbm) {
radio.setOutputPower(dbm);
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng);
}
+20
View File
@@ -0,0 +1,20 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/SensorManager.h>
#include "TETHEliteBoard.h"
extern TETHEliteBoard board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors;
bool radio_init();
uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(int8_t dbm);
mesh::LocalIdentity radio_new_identity();
@@ -21,4 +21,8 @@ public:
return (2 * raw);
}
uint32_t getIRQGpio() override {
return P_LORA_DIO_0; // default for SX1276
}
};
+1 -1
View File
@@ -6,7 +6,7 @@ build_flags = ${nrf52_base.build_flags}
${sensor_base.build_flags}
-I variants/rak3401
-D RAK_3401
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
+1 -1
View File
@@ -7,7 +7,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/rak4631
-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_GPS_TX=PIN_SERIAL1_RX
+1 -1
View File
@@ -10,7 +10,7 @@ build_flags = ${nrf52_base.build_flags}
-I src/helpers/nrf52
-D NRF52_PLATFORM=1
-D USE_SX1262
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D P_LORA_TX_LED=11
+22
View File
@@ -90,6 +90,28 @@ lib_deps =
${esp32_ota.lib_deps}
densaugeo/base64 @ ~1.4.0
[env:Xiao_C3_companion_radio_ble_ps]
extends = Xiao_esp32_C3
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
+<../examples/companion_radio/*.cpp>
+<helpers/esp32/*.cpp>
build_flags =
${Xiao_esp32_C3.build_flags}
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Xiao_esp32_C3.lib_deps}
${esp32_ota.lib_deps}
densaugeo/base64 @ ~1.4.0
board_build.partitions = min_spiffs.csv ; get around 4mb flash limit
[env:Xiao_C3_companion_radio_usb]
extends = Xiao_esp32_C3
build_src_filter = ${Xiao_esp32_C3.build_src_filter}
+1 -1
View File
@@ -9,7 +9,7 @@ build_flags = ${nrf52_base.build_flags}
-I variants/xiao_nrf52
-UENV_INCLUDE_GPS
-D NRF52_PLATFORM
-D NRF52_POWER_MANAGEMENT
; -D NRF52_POWER_MANAGEMENT
-D XIAO_NRF52
-D USE_SX1262
-D RADIO_CLASS=CustomSX1262
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <Arduino.h>
#include <helpers/ESP32Board.h>
class XiaoS3Board : public ESP32Board {
public:
XiaoS3Board() { }
const char* getManufacturerName() const override {
return "Xiao S3";
}
};
+180
View File
@@ -0,0 +1,180 @@
[Xiao_S3]
extends = esp32_base
board = seeed_xiao_esp32s3
board_check = true
board_build.mcu = esp32s3
build_flags = ${esp32_base.build_flags}
${sensor_base.build_flags}
-I variants/xiao_s3
-UENV_INCLUDE_GPS
-D SEEED_XIAO_S3
-D PIN_VBAT_READ=1 ; D0
-D P_LORA_DIO_1=2 ; D1
-D P_LORA_NSS=5 ; D4
-D P_LORA_RESET=3 ; D2
-D P_LORA_BUSY=4 ; D3
-D P_LORA_SCLK=7 ; D8
-D P_LORA_MISO=8 ; D0
-D P_LORA_MOSI=9 ; D10
-D PIN_USER_BTN=-1 ; NC
-D PIN_STATUS_LED=21 ; Orange user led, LOW=On
-D PIN_BOARD_SDA=D6 ; D6=43
-D PIN_BOARD_SCL=D7 ; D7=44
-D SX126X_RXEN=6 ; D5
-D SX126X_TXEN=RADIOLIB_NC
-D SX126X_DIO2_AS_RF_SWITCH=true
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
-D SX126X_CURRENT_LIMIT=140
-D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22
-D SX126X_RX_BOOSTED_GAIN=1
build_src_filter = ${esp32_base.build_src_filter}
+<../variants/xiao_s3>
+<helpers/sensors>
lib_deps =
${esp32_base.lib_deps}
${sensor_base.lib_deps}
[env:Xiao_S3_repeater]
extends = Xiao_S3
build_src_filter = ${Xiao_S3.build_src_filter}
+<../examples/simple_repeater/*.cpp>
build_flags =
${Xiao_S3.build_flags}
-D ADVERT_NAME='"Xiao S3 Repeater"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Xiao_S3.lib_deps}
${esp32_ota.lib_deps}
[env:Xiao_S3_repeater_bridge_espnow]
extends = Xiao_S3
build_src_filter = ${Xiao_S3.build_src_filter}
+<helpers/bridges/ESPNowBridge.cpp>
+<../examples/simple_repeater/*.cpp>
build_flags =
${Xiao_S3.build_flags}
-D ADVERT_NAME='"ESPNow Bridge"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D MAX_NEIGHBOURS=50
-D WITH_ESPNOW_BRIDGE=1
; -D BRIDGE_DEBUG=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Xiao_S3.lib_deps}
${esp32_ota.lib_deps}
[env:Xiao_S3_room_server]
extends = Xiao_S3
build_src_filter = ${Xiao_S3.build_src_filter}
+<../examples/simple_room_server>
build_flags =
${Xiao_S3.build_flags}
-D ADVERT_NAME='"Xiao S3 Room"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
-D ROOM_PASSWORD='"hello"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Xiao_S3.lib_deps}
${esp32_ota.lib_deps}
[env:Xiao_S3_companion_radio_ble]
extends = Xiao_S3
build_flags =
${Xiao_S3.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D DISPLAY_CLASS=SSD1306Display
-D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Xiao_S3.lib_deps}
densaugeo/base64 @ ~1.4.0
adafruit/Adafruit SSD1306 @ ^2.5.13
[env:Xiao_S3_companion_radio_ble_ps]
extends = Xiao_S3
platform_packages =
framework-arduinoespressif32 @ symlink://D:/esp32-2.0.17
build_flags =
${Xiao_S3.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D BLE_PIN_CODE=123456
-D DISPLAY_CLASS=SSD1306Display
-D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Xiao_S3.lib_deps}
densaugeo/base64 @ ~1.4.0
adafruit/Adafruit SSD1306 @ ^2.5.13
[env:Xiao_S3_companion_radio_usb]
extends = Xiao_S3
build_flags =
${Xiao_S3.build_flags}
-I examples/companion_radio/ui-new
-D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40
-D DISPLAY_CLASS=SSD1306Display
-D OFFLINE_QUEUE_SIZE=256
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Xiao_S3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp>
+<helpers/esp32/*.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio/*.cpp>
+<../examples/companion_radio/ui-new/*.cpp>
lib_deps =
${Xiao_S3.lib_deps}
densaugeo/base64 @ ~1.4.0
adafruit/Adafruit SSD1306 @ ^2.5.13
[env:Xiao_S3_sensor]
extends = Xiao_S3
build_src_filter = ${Xiao_S3.build_src_filter}
+<../examples/simple_sensor>
build_flags =
${Xiao_S3.build_flags}
-D ADVERT_NAME='"Xiao S3 Sensor"'
-D ADVERT_LAT=0.0
-D ADVERT_LON=0.0
-D ADMIN_PASSWORD='"password"'
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
lib_deps =
${Xiao_S3.lib_deps}
${esp32_ota.lib_deps}
+56
View File
@@ -0,0 +1,56 @@
#include <Arduino.h>
#include "target.h"
XiaoS3Board board;
#if defined(P_LORA_SCLK)
static SPIClass spi;
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
#else
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
#endif
WRAPPER_CLASS radio_driver(radio, board);
ESP32RTCClock fallback_clock;
AutoDiscoverRTCClock rtc_clock(fallback_clock);
EnvironmentSensorManager sensors;
#ifdef DISPLAY_CLASS
DISPLAY_CLASS display;
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
#endif
bool radio_init() {
fallback_clock.begin();
rtc_clock.begin(Wire);
pinMode(21, INPUT);
pinMode(48, OUTPUT);
#if defined(P_LORA_SCLK)
spi.begin(P_LORA_SCLK, P_LORA_MISO, P_LORA_MOSI);
return radio.std_init(&spi);
#else
return radio.std_init();
#endif
}
uint32_t radio_get_rng_seed() {
return radio.random(0x7FFFFFFF);
}
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
radio.setFrequency(freq);
radio.setSpreadingFactor(sf);
radio.setBandwidth(bw);
radio.setCodingRate(cr);
}
void radio_set_tx_power(int8_t dbm) {
radio.setOutputPower(dbm);
}
mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity
}
+30
View File
@@ -0,0 +1,30 @@
#pragma once
#define RADIOLIB_STATIC_ONLY 1
#include <RadioLib.h>
#include <helpers/radiolib/RadioLibWrappers.h>
#include <helpers/ESP32Board.h>
#include <helpers/radiolib/CustomSX1262Wrapper.h>
#include <helpers/AutoDiscoverRTCClock.h>
#include <helpers/sensors/EnvironmentSensorManager.h>
#ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h>
#include <helpers/ui/MomentaryButton.h>
#endif
#include "XiaoS3Board.h"
extern XiaoS3Board board;
extern WRAPPER_CLASS radio_driver;
extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors;
#ifdef DISPLAY_CLASS
extern DISPLAY_CLASS display;
extern MomentaryButton user_btn;
#endif
bool radio_init();
uint32_t radio_get_rng_seed();
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
void radio_set_tx_power(int8_t dbm);
mesh::LocalIdentity radio_new_identity();
Loaded 100 of 101 files, more files were not shown because too many files have changed in this diff. Show more