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