mirror of https://github.com/meshcore-dev/MeshCore
committed by
GitHub
254 changed files with 5227 additions and 973 deletions
@ -0,0 +1,74 @@ |
|||||
|
{ |
||||
|
"build": { |
||||
|
"arduino": { |
||||
|
"ldscript": "nrf52840_s140_v6.ld" |
||||
|
}, |
||||
|
"core": "nRF5", |
||||
|
"cpu": "cortex-m4", |
||||
|
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA", |
||||
|
"f_cpu": "64000000L", |
||||
|
"hwids": [ |
||||
|
[ |
||||
|
"0x239A", |
||||
|
"0x8029" |
||||
|
], |
||||
|
[ |
||||
|
"0x239A", |
||||
|
"0x0029" |
||||
|
], |
||||
|
[ |
||||
|
"0x239A", |
||||
|
"0x002A" |
||||
|
], |
||||
|
[ |
||||
|
"0x239A", |
||||
|
"0x802A" |
||||
|
] |
||||
|
], |
||||
|
"usb_product": "Meshtiny", |
||||
|
"mcu": "nrf52840", |
||||
|
"variant": "meshtiny", |
||||
|
"bsp": { |
||||
|
"name": "adafruit" |
||||
|
}, |
||||
|
"softdevice": { |
||||
|
"sd_flags": "-DS140", |
||||
|
"sd_name": "s140", |
||||
|
"sd_version": "6.1.1", |
||||
|
"sd_fwid": "0x00B6" |
||||
|
}, |
||||
|
"bootloader": { |
||||
|
"settings_addr": "0xFF000" |
||||
|
} |
||||
|
}, |
||||
|
"connectivity": [ |
||||
|
"bluetooth" |
||||
|
], |
||||
|
"debug": { |
||||
|
"jlink_device": "nRF52840_xxAA", |
||||
|
"svd_path": "nrf52840.svd", |
||||
|
"openocd_target": "nrf52840-mdk-rs" |
||||
|
}, |
||||
|
"frameworks": [ |
||||
|
"arduino", |
||||
|
"freertos" |
||||
|
], |
||||
|
"name": "Meshtiny", |
||||
|
"upload": { |
||||
|
"maximum_ram_size": 248832, |
||||
|
"maximum_size": 815104, |
||||
|
"speed": 115200, |
||||
|
"protocol": "nrfutil", |
||||
|
"protocols": [ |
||||
|
"jlink", |
||||
|
"nrfjprog", |
||||
|
"nrfutil", |
||||
|
"stlink" |
||||
|
], |
||||
|
"use_1200bps_touch": true, |
||||
|
"require_upload_port": true, |
||||
|
"wait_for_upload_port": true |
||||
|
}, |
||||
|
"url": "https://shop.mtoolstec.com/product/meshtiny", |
||||
|
"vendor": "MTools Tec" |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
{ |
||||
|
"build": { |
||||
|
"arduino": { |
||||
|
"ldscript": "esp32s3_out.ld", |
||||
|
"memory_type": "qio_opi" |
||||
|
}, |
||||
|
"core": "esp32", |
||||
|
"extra_flags": [ |
||||
|
"-DBOARD_HAS_PSRAM", |
||||
|
"-DLILYGO_TBEAM_1W", |
||||
|
"-DARDUINO_USB_CDC_ON_BOOT=1", |
||||
|
"-DARDUINO_USB_MODE=0", |
||||
|
"-DARDUINO_RUNNING_CORE=1", |
||||
|
"-DARDUINO_EVENT_RUNNING_CORE=1" |
||||
|
], |
||||
|
"f_cpu": "240000000L", |
||||
|
"f_flash": "80000000L", |
||||
|
"flash_mode": "qio", |
||||
|
"psram_type": "opi", |
||||
|
"hwids": [ |
||||
|
[ |
||||
|
"0x303A", |
||||
|
"0x1001" |
||||
|
] |
||||
|
], |
||||
|
"mcu": "esp32s3", |
||||
|
"variant": "lilygo_tbeam_1w" |
||||
|
}, |
||||
|
"connectivity": [ |
||||
|
"wifi", |
||||
|
"bluetooth", |
||||
|
"lora" |
||||
|
], |
||||
|
"debug": { |
||||
|
"openocd_target": "esp32s3.cfg" |
||||
|
}, |
||||
|
"frameworks": [ |
||||
|
"arduino" |
||||
|
], |
||||
|
"name": "LilyGo TBeam-1W", |
||||
|
"upload": { |
||||
|
"flash_size": "16MB", |
||||
|
"maximum_ram_size": 327680, |
||||
|
"maximum_size": 16777216, |
||||
|
"require_upload_port": true, |
||||
|
"speed": 921600 |
||||
|
}, |
||||
|
"url": "http://www.lilygo.cn/", |
||||
|
"vendor": "LilyGo" |
||||
|
} |
||||
@ -0,0 +1,881 @@ |
|||||
|
# MeshCore Repeater & Room Server CLI Commands |
||||
|
|
||||
|
## Navigation |
||||
|
|
||||
|
- [Operational](#operational) |
||||
|
- [Neighbors](#neighbors-repeater-only) |
||||
|
- [Statistics](#statistics) |
||||
|
- [Logging](#logging) |
||||
|
- [Information](#info) |
||||
|
- [Configuration](#configuration) |
||||
|
- [Radio](#radio) |
||||
|
- [System](#system) |
||||
|
- [Routing](#routing) |
||||
|
- [ACL](#acl) |
||||
|
- [Region Management](#region-management-v110) |
||||
|
- [Region Examples](#region-examples) |
||||
|
- [GPS](#gps-when-gps-support-is-compiled-in) |
||||
|
- [Sensors](#sensors-when-sensor-support-is-compiled-in) |
||||
|
- [Bridge](#bridge-when-bridge-support-is-compiled-in) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Operational |
||||
|
|
||||
|
### Reboot the node |
||||
|
**Usage:** |
||||
|
- `reboot` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Reset the clock and reboot |
||||
|
**Usage:** |
||||
|
- `clkreboot` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Sync the clock with the remote device |
||||
|
**Usage:** |
||||
|
- `clock sync` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Display current time in UTC |
||||
|
**Usage:** |
||||
|
- `clock` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Set the time to a specific timestamp |
||||
|
**Usage:** |
||||
|
- `time <epoch_seconds>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `epoc_seconds`: Unix epoc time |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Send a flood advert |
||||
|
**Usage:** |
||||
|
- `advert` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Start an Over-The-Air (OTA) firmware update |
||||
|
**Usage:** |
||||
|
- `start ota` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Erase/Factory Reset |
||||
|
**Usage:** |
||||
|
- `erase` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
**Warning:** _**This is destructive!**_ |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Neighbors (Repeater Only) |
||||
|
|
||||
|
### List nearby neighbors |
||||
|
**Usage:** |
||||
|
- `neighbors` |
||||
|
|
||||
|
**Note:** The output of this command is limited to the 8 most recent adverts. |
||||
|
|
||||
|
**Note:** Each line is encoded as `{pubkey-prefix}:{timestamp}:{snr*4}` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Remove a neighbor |
||||
|
**Usage:** |
||||
|
- `neighbor.remove <pubkey_prefix>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `pubkey_prefix`: The public key of the node to remove from the neighbors list |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Statistics |
||||
|
|
||||
|
### Clear Stats |
||||
|
**Usage:** `clear stats` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### System Stats - Battery, Uptime, Queue Length and Debug Flags |
||||
|
**Usage:** |
||||
|
- `stats-core` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Radio Stats - Noise floor, Last RSSI/SNR, Airtime, Receive errors |
||||
|
**Usage:** `stats-radio` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Packet stats - Packet counters: Received, Sent |
||||
|
**Usage:** `stats-packets` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Logging |
||||
|
|
||||
|
### Begin capture of rx log to node storage |
||||
|
**Usage:** `log start` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### End capture of rx log to node sotrage |
||||
|
**Usage:** `log stop` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Erase captured log |
||||
|
**Usage:** `log erase` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Print the captured log to the serial terminal |
||||
|
**Usage:** `log` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Info |
||||
|
|
||||
|
### Get the Version |
||||
|
**Usage:** `ver` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Show the hardware name |
||||
|
**Usage:** `board` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
## Configuration |
||||
|
|
||||
|
### Radio |
||||
|
|
||||
|
#### View or change this node's radio parameters |
||||
|
**Usage:** |
||||
|
- `get radio` |
||||
|
- `set radio <freq>,<bw>,<sf>,<cr>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `freq`: Frequency in MHz |
||||
|
- `bw`: Bandwidth in kHz |
||||
|
- `sf`: Spreading factor (5-12) |
||||
|
- `cr`: Coding rate (5-8) |
||||
|
|
||||
|
**Set by build flag:** `LORA_FREQ`, `LORA_BW`, `LORA_SF`, `LORA_CR` |
||||
|
|
||||
|
**Default:** `869.525,250,11,5` |
||||
|
|
||||
|
**Note:** Requires reboot to apply |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's transmit power |
||||
|
**Usage:** |
||||
|
- `get tx` |
||||
|
- `set tx <dbm>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `dbm`: Power level in dBm (1-22) |
||||
|
|
||||
|
**Set by build flag:** `LORA_TX_POWER` |
||||
|
|
||||
|
**Default:** Varies by board |
||||
|
|
||||
|
**Notes:** This setting only controls the power level of the LoRa chip. Some nodes have an additional power amplifier stage which increases the total output. Referr to the node's manual for the correct setting to use. **Setting a value too high may violate the laws in your country.** |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Change the radio parameters for a set duration |
||||
|
**Usage:** |
||||
|
- `tempradio <freq>,<bw>,<sf>,<cr>,<timeout_mins>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `freq`: Frequency in MHz (300-2500) |
||||
|
- `bw`: Bandwidth in kHz (7.8-500) |
||||
|
- `sf`: Spreading factor (5-12) |
||||
|
- `cr`: Coding rate (5-8) |
||||
|
- `timeout_mins`: Duration in minutes (must be > 0) |
||||
|
|
||||
|
**Note:** This is not saved to preferences and will clear on reboot |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's frequency |
||||
|
**Usage:** |
||||
|
- `get freq` |
||||
|
- `set freq <frequency>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `frequency`: Frequency in MHz |
||||
|
|
||||
|
**Default:** `869.525` |
||||
|
|
||||
|
**Note:** Requires reboot to apply |
||||
|
|
||||
|
### System |
||||
|
|
||||
|
#### View or change this node's name |
||||
|
**Usage:** |
||||
|
- `get name` |
||||
|
- `set name <name>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Node name |
||||
|
|
||||
|
**Set by build flag:** `ADVERT_NAME` |
||||
|
|
||||
|
**Default:** Varies by board |
||||
|
|
||||
|
**Note:** Max length varies. If a location is set, the max length is 24 bytes; 32 otherwise. Emoji and unicode characters may take more than one byte. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's latitude |
||||
|
**Usage:** |
||||
|
- `get lat` |
||||
|
- `set lat <degrees>` |
||||
|
|
||||
|
**Set by build flag:** `ADVERT_LAT` |
||||
|
|
||||
|
**Default:** `0` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `degrees`: Latitude in degrees |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's longitude |
||||
|
**Usage:** |
||||
|
- `get lon` |
||||
|
- `set lon <degrees>` |
||||
|
|
||||
|
**Set by build flag:** `ADVERT_LON` |
||||
|
|
||||
|
**Default:** `0` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `degrees`: Longitude in degrees |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's identity (Private Key) |
||||
|
**Usage:** |
||||
|
- `get prv.key` |
||||
|
- `set prv.key <private_key>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `private_key`: Private key in hex format (64 hex characters) |
||||
|
|
||||
|
**Serial Only:** |
||||
|
- `get prv.key`: Yes |
||||
|
- `set prv.key`: No |
||||
|
|
||||
|
**Note:** Requires reboot to take effect after setting |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's admin password |
||||
|
**Usage:** |
||||
|
- `get password` |
||||
|
- `set password <password>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `password`: Admin password |
||||
|
|
||||
|
**Set by build flag:** `ADMIN_PASSWORD` |
||||
|
|
||||
|
**Default:** `password` |
||||
|
|
||||
|
**Note:** Echoed back for confirmation |
||||
|
|
||||
|
**Note:** Any node using this password will be added to the admin ACL list. |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's guest password |
||||
|
**Usage:** |
||||
|
- `get guest.password` |
||||
|
- `set guest.password <password>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `password`: Guest password |
||||
|
|
||||
|
**Set by build flag:** `ROOM_PASSWORD` (Room Server only) |
||||
|
|
||||
|
**Default:** `<blank>` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's owner info |
||||
|
**Usage:** |
||||
|
- `get owner.info` |
||||
|
- `set owner.info <text>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `text`: Owner information text |
||||
|
|
||||
|
**Default:** `<blank>` |
||||
|
|
||||
|
**Note:** `|` characters are translated to newlines |
||||
|
|
||||
|
**Note:** Requires firmware 1.12.+ |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Fine-tune the battery reading |
||||
|
**Usage:** |
||||
|
- `get adc.multiplier` |
||||
|
- `set adc.multiplier <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: ADC multiplier (0.0-10.0) |
||||
|
|
||||
|
**Default:** `0.0` (value defined by board) |
||||
|
|
||||
|
**Note:** Returns "Error: unsupported by this board" if hardware doesn't support it |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this node's power saving flag (Repeater Only) |
||||
|
**Usage:** |
||||
|
- `powersaving <state>` |
||||
|
- `powersaving` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `state`: `on`|`off` |
||||
|
|
||||
|
**Default:** `on` |
||||
|
|
||||
|
**Note:** When enabled, device enters sleep mode between radio transmissions |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Routing |
||||
|
|
||||
|
#### View or change this node's repeat flag |
||||
|
**Usage:** |
||||
|
- `get repeat` |
||||
|
- `set repeat <state>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `state`: `on`|`off` |
||||
|
|
||||
|
**Default:** `on` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the retransmit delay factor for flood traffic |
||||
|
**Usage:** |
||||
|
- `get txdelay` |
||||
|
- `set txdelay <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Transmit delay factor (0-2) |
||||
|
|
||||
|
**Default:** `0.5` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the retransmit delay factor for direct traffic |
||||
|
**Usage:** |
||||
|
- `get direct.txdelay` |
||||
|
- `set direct.txdelay <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Direct transmit delay factor (0-2) |
||||
|
|
||||
|
**Default:** `0.2` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### [Experimental] View or change the processing delay for received traffic |
||||
|
**Usage:** |
||||
|
- `get rxdelay` |
||||
|
- `set rxdelay <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Receive delay base (0-20) |
||||
|
|
||||
|
**Default:** `0.0` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the airtime factor (duty cycle limit) |
||||
|
**Usage:** |
||||
|
- `get af` |
||||
|
- `set af <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Airtime factor (0-9) |
||||
|
|
||||
|
**Default:** `1.0` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the local interference threshold |
||||
|
**Usage:** |
||||
|
- `get int.thresh` |
||||
|
- `set int.thresh <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Interference threshold value |
||||
|
|
||||
|
**Default:** `0.0` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the AGC Reset Interval |
||||
|
**Usage:** |
||||
|
- `get agc.reset.interval` |
||||
|
- `set agc.reset.interval <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Interval in seconds rounded down to a multiple of 4 (17 becomes 16) |
||||
|
|
||||
|
**Default:** `0.0` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Enable or disable Multi-Acks support |
||||
|
**Usage:** |
||||
|
- `get multi.acks` |
||||
|
- `set multi.acks <state>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `state`: `0` (disable) or `1` (enable) |
||||
|
|
||||
|
**Default:** `0` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the flood advert interval |
||||
|
**Usage:** |
||||
|
- `get flood.advert.interval` |
||||
|
- `set flood.advert.interval <hours>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `hours`: Interval in hours (3-168) |
||||
|
|
||||
|
**Default:** `12` (Repeater) - `0` (Sensor) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the zero-hop advert interval |
||||
|
**Usage:** |
||||
|
- `get advert.interval` |
||||
|
- `set advert.interval <minutes>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `minutes`: Interval in minutes rounded down to the nearest multiple of 2 (61 becomes 60) (60-240) |
||||
|
|
||||
|
**Default:** `0` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Limit the number of hops for a flood message |
||||
|
**Usage:** |
||||
|
- `get flood.max` |
||||
|
- `set flood.max <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `value`: Maximum flood hop count (0-64) |
||||
|
|
||||
|
**Default:** `64` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### ACL |
||||
|
|
||||
|
#### Add, update or remove permissions for a companion |
||||
|
**Usage:** |
||||
|
- `setperm <pubkey> <permissions>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `pubkey`: Companion public key |
||||
|
- `permissions`: |
||||
|
- `0`: Guest |
||||
|
- `1`: Read-only |
||||
|
- `2`: Read-write |
||||
|
- `3`: Admin |
||||
|
|
||||
|
**Note:** Removes the entry when `permissions` is omitted |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View the current ACL |
||||
|
**Usage:** |
||||
|
- `get acl` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change this room server's 'read-only' flag |
||||
|
**Usage:** |
||||
|
- `get allow.read.only` |
||||
|
- `set allow.read.only <state>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `state`: `on` (enable) or `off` (disable) |
||||
|
|
||||
|
**Default:** `off` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Region Management (v1.10.+) |
||||
|
|
||||
|
#### Bulk-load region lists |
||||
|
**Usage:** |
||||
|
- `region load` |
||||
|
- `region load <name> [flood_flag]` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: A name of a region. `*` represents the wildcard region |
||||
|
|
||||
|
**Note:** `flood_flag`: Optional `F` to allow flooding |
||||
|
|
||||
|
**Note:** Indentation creates parent-child relationships (max 8 levels) |
||||
|
|
||||
|
**Note:** `region load` with an empty name will not work remotely (it's interactive) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Save any changes to regions made since reboot |
||||
|
**Usage:** |
||||
|
- `region save` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Allow a region |
||||
|
**Usage:** |
||||
|
- `region allowf <name>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Region name (or `*` for wildcard) |
||||
|
|
||||
|
**Note:** Setting on wildcard `*` allows packets without region transport codes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Block a region |
||||
|
**Usage:** |
||||
|
- `region denyf <name>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Region name (or `*` for wildcard) |
||||
|
|
||||
|
**Note:** Setting on wildcard `*` drops packets without region transport codes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Show information for a region |
||||
|
**Usage:** |
||||
|
- `region get <name>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Region name (or `*` for wildcard) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the home region for this node |
||||
|
**Usage:** |
||||
|
- `region home` |
||||
|
- `region home <name>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Region name |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Create a new region |
||||
|
**Usage:** |
||||
|
- `region put <name> [parent_name]` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Region name |
||||
|
- `parent_name`: Parent region name (optional, defaults to wildcard) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Remove a region |
||||
|
**Usage:** |
||||
|
- `region remove <name>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `name`: Region name |
||||
|
|
||||
|
**Note:** Must remove all child regions before the region can be removed |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View all regions |
||||
|
**Usage:** |
||||
|
- `region list <filter>` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `filter`: `allowed`|`denied` |
||||
|
|
||||
|
**Note:** Requires firmware 1.12.+ |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Dump all defined regions and flood permissions |
||||
|
**Usage:** |
||||
|
- `region` |
||||
|
|
||||
|
**Serial Only:** Yes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Region Examples |
||||
|
|
||||
|
**Example 1: Using F Flag with Named Public Region** |
||||
|
``` |
||||
|
region load |
||||
|
#Europe F |
||||
|
<blank line to end region load> |
||||
|
region save |
||||
|
``` |
||||
|
|
||||
|
**Explanation:** |
||||
|
- Creates a region named `#Europe` with flooding enabled |
||||
|
- Packets from this region will be flooded to other nodes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Example 2: Using Wildcard with F Flag** |
||||
|
``` |
||||
|
region load |
||||
|
* F |
||||
|
<blank line to end region load> |
||||
|
region save |
||||
|
``` |
||||
|
|
||||
|
**Explanation:** |
||||
|
- Creates a wildcard region `*` with flooding enabled |
||||
|
- Enables flooding for all regions automatically |
||||
|
- Applies only to packets without transport codes |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Example 3: Using Wildcard Without F Flag** |
||||
|
``` |
||||
|
region load |
||||
|
* |
||||
|
<blank line to end region load> |
||||
|
region save |
||||
|
``` |
||||
|
**Explanation:** |
||||
|
- Creates a wildcard region `*` without flooding |
||||
|
- This region exists but doesn't affect packet distribution |
||||
|
- Used as a default/empty region |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Example 4: Nested Public Region with F Flag** |
||||
|
``` |
||||
|
region load |
||||
|
#Europe F |
||||
|
#UK |
||||
|
#London |
||||
|
#Manchester |
||||
|
#France |
||||
|
#Paris |
||||
|
#Lyon |
||||
|
<blank line to end region load> |
||||
|
region save |
||||
|
``` |
||||
|
|
||||
|
**Explanation:** |
||||
|
- Creates `#Europe` region with flooding enabled |
||||
|
- Adds nested child regions (`#UK`, `#France`) |
||||
|
- All nested regions inherit the flooding flag from parent |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
**Example 5: Wildcard with Nested Public Regions** |
||||
|
``` |
||||
|
region load |
||||
|
* F |
||||
|
#NorthAmerica |
||||
|
#USA |
||||
|
#NewYork |
||||
|
#California |
||||
|
#Canada |
||||
|
#Ontario |
||||
|
#Quebec |
||||
|
<blank line to end region load> |
||||
|
region save |
||||
|
``` |
||||
|
|
||||
|
**Explanation:** |
||||
|
- Creates wildcard region `*` with flooding enabled |
||||
|
- Adds nested `#NorthAmerica` hierarchy |
||||
|
- Enables flooding for all child regions automatically |
||||
|
- Useful for global networks with specific regional rules |
||||
|
|
||||
|
--- |
||||
|
### GPS (When GPS support is compiled in) |
||||
|
|
||||
|
#### View or change GPS state |
||||
|
**Usage:** |
||||
|
- `gps` |
||||
|
- `gps <state>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `state`: `on`|`off` |
||||
|
|
||||
|
**Default:** `off` |
||||
|
|
||||
|
**Note:** Output format: `{status}, {fix}, {sat count}` (when enabled) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Sync this node's clock with GPS time |
||||
|
**Usage:** |
||||
|
- `gps sync` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Set this node's location based on the GPS coordinates |
||||
|
**Usage:** |
||||
|
- `gps setloc` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the GPS advert policy |
||||
|
**Usage:** |
||||
|
- `gps advert` |
||||
|
- `gps advert <policy>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `policy`: `none`|`shared`|`prefs` |
||||
|
- `none`: don't include location in adverts |
||||
|
- `share`: share gps location (from SensorManager) |
||||
|
- `prefs`: location stored in node's lat and lon settings |
||||
|
|
||||
|
**Default:** `prefs` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Sensors (When sensor support is compiled in) |
||||
|
|
||||
|
#### View the list of sensors on this node |
||||
|
**Usage:** `sensor list [start]` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `start`: Optional starting index (defaults to 0) |
||||
|
|
||||
|
**Note:** Output format: `<var_name>=<value>\n` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change thevalue of a sensor |
||||
|
**Usage:** |
||||
|
- `sensor get <key>` |
||||
|
- `sensor set <key> <value>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `key`: Sensor setting name |
||||
|
- `value`: The value to set the sensor to |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
### Bridge (When bridge support is compiled in) |
||||
|
|
||||
|
#### View or change the bridge enabled flag |
||||
|
**Usage:** |
||||
|
- `get bridge.enabled` |
||||
|
- `set bridge.enabled <state>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `state`: `on`|`off` |
||||
|
|
||||
|
**Default:** `off` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View the bridge source |
||||
|
**Usage:** |
||||
|
- `get bridge.source` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Add a delay to packets routed through this bridge |
||||
|
**Usage:** |
||||
|
- `get bridge.delay` |
||||
|
- `set bridge.delay <ms>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `ms`: Delay in milliseconds (0-10000) |
||||
|
|
||||
|
**Default:** `500` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the source of packets bridged to the external interface |
||||
|
**Usage:** |
||||
|
- `get bridge.source` |
||||
|
- `set bridge.source <source>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `source`: |
||||
|
- `rx`: bridges received packets |
||||
|
- `tx`: bridges transmitted packets |
||||
|
|
||||
|
**Default:** `tx` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the speed of the bridge (RS-232 only) |
||||
|
**Usage:** |
||||
|
- `get bridge.baud` |
||||
|
- `set bridge.baud <rate>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `rate`: Baud rate (`9600`, `19200`, `38400`, `57600`, or `115200`) |
||||
|
|
||||
|
**Default:** `115200` |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### View or change the channel used for bridging (ESPNow only) |
||||
|
**Usage:** |
||||
|
- `get bridge.channel` |
||||
|
- `set bridge.channel <channel>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `channel`: Channel number (1-14) |
||||
|
|
||||
|
--- |
||||
|
|
||||
|
#### Set the ESP-Now secret |
||||
|
**Usage:** |
||||
|
- `get bridge.secret` |
||||
|
- `set bridge.secret <secret>` |
||||
|
|
||||
|
**Parameters:** |
||||
|
- `secret`: 16-character encryption secret |
||||
|
|
||||
|
**Default:** Varies by board |
||||
|
|
||||
|
--- |
||||
@ -0,0 +1,162 @@ |
|||||
|
# MeshCore KISS Modem Protocol |
||||
|
|
||||
|
Serial protocol for the KISS modem firmware. Enables sending/receiving MeshCore packets over LoRa and cryptographic operations using the modem's identity. |
||||
|
|
||||
|
## Serial Configuration |
||||
|
|
||||
|
115200 baud, 8N1, no flow control. |
||||
|
|
||||
|
## Frame Format |
||||
|
|
||||
|
Standard KISS framing with byte stuffing. |
||||
|
|
||||
|
| Byte | Name | Description | |
||||
|
|------|------|-------------| |
||||
|
| `0xC0` | FEND | Frame delimiter | |
||||
|
| `0xDB` | FESC | Escape character | |
||||
|
| `0xDC` | TFEND | Escaped FEND (FESC + TFEND = 0xC0) | |
||||
|
| `0xDD` | TFESC | Escaped FESC (FESC + TFESC = 0xDB) | |
||||
|
|
||||
|
``` |
||||
|
┌──────┬─────────┬──────────────┬──────┐ |
||||
|
│ FEND │ Command │ Data (escaped)│ FEND │ |
||||
|
│ 0xC0 │ 1 byte │ 0-510 bytes │ 0xC0 │ |
||||
|
└──────┴─────────┴──────────────┴──────┘ |
||||
|
``` |
||||
|
|
||||
|
Maximum unescaped frame size: 512 bytes. |
||||
|
|
||||
|
## Commands |
||||
|
|
||||
|
### Request Commands (Host → Modem) |
||||
|
|
||||
|
| Command | Value | Data | |
||||
|
|---------|-------|------| |
||||
|
| `CMD_DATA` | `0x00` | Packet (2-255 bytes) | |
||||
|
| `CMD_GET_IDENTITY` | `0x01` | - | |
||||
|
| `CMD_GET_RANDOM` | `0x02` | Length (1 byte, 1-64) | |
||||
|
| `CMD_VERIFY_SIGNATURE` | `0x03` | PubKey (32) + Signature (64) + Data | |
||||
|
| `CMD_SIGN_DATA` | `0x04` | Data to sign | |
||||
|
| `CMD_ENCRYPT_DATA` | `0x05` | Key (32) + Plaintext | |
||||
|
| `CMD_DECRYPT_DATA` | `0x06` | Key (32) + MAC (2) + Ciphertext | |
||||
|
| `CMD_KEY_EXCHANGE` | `0x07` | Remote PubKey (32) | |
||||
|
| `CMD_HASH` | `0x08` | Data to hash | |
||||
|
| `CMD_SET_RADIO` | `0x09` | Freq (4) + BW (4) + SF (1) + CR (1) | |
||||
|
| `CMD_SET_TX_POWER` | `0x0A` | Power dBm (1) | |
||||
|
| *reserved* | `0x0B` | *(not implemented)* | |
||||
|
| `CMD_GET_RADIO` | `0x0C` | - | |
||||
|
| `CMD_GET_TX_POWER` | `0x0D` | - | |
||||
|
| *reserved* | `0x0E` | *(not implemented)* | |
||||
|
| `CMD_GET_VERSION` | `0x0F` | - | |
||||
|
| `CMD_GET_CURRENT_RSSI` | `0x10` | - | |
||||
|
| `CMD_IS_CHANNEL_BUSY` | `0x11` | - | |
||||
|
| `CMD_GET_AIRTIME` | `0x12` | Packet length (1) | |
||||
|
| `CMD_GET_NOISE_FLOOR` | `0x13` | - | |
||||
|
| `CMD_GET_STATS` | `0x14` | - | |
||||
|
| `CMD_GET_BATTERY` | `0x15` | - | |
||||
|
| `CMD_PING` | `0x16` | - | |
||||
|
| `CMD_GET_SENSORS` | `0x17` | Permissions (1) | |
||||
|
|
||||
|
### Response Commands (Modem → Host) |
||||
|
|
||||
|
| Command | Value | Data | |
||||
|
|---------|-------|------| |
||||
|
| `CMD_DATA` | `0x00` | SNR (1) + RSSI (1) + Packet | |
||||
|
| `RESP_IDENTITY` | `0x21` | PubKey (32) | |
||||
|
| `RESP_RANDOM` | `0x22` | Random bytes (1-64) | |
||||
|
| `RESP_VERIFY` | `0x23` | Result (1): 0x00=invalid, 0x01=valid | |
||||
|
| `RESP_SIGNATURE` | `0x24` | Signature (64) | |
||||
|
| `RESP_ENCRYPTED` | `0x25` | MAC (2) + Ciphertext | |
||||
|
| `RESP_DECRYPTED` | `0x26` | Plaintext | |
||||
|
| `RESP_SHARED_SECRET` | `0x27` | Shared secret (32) | |
||||
|
| `RESP_HASH` | `0x28` | SHA-256 hash (32) | |
||||
|
| `RESP_OK` | `0x29` | - | |
||||
|
| `RESP_RADIO` | `0x2A` | Freq (4) + BW (4) + SF (1) + CR (1) | |
||||
|
| `RESP_TX_POWER` | `0x2B` | Power dBm (1) | |
||||
|
| *reserved* | `0x2C` | *(not implemented)* | |
||||
|
| `RESP_VERSION` | `0x2D` | Version (1) + Reserved (1) | |
||||
|
| `RESP_ERROR` | `0x2E` | Error code (1) | |
||||
|
| `RESP_TX_DONE` | `0x2F` | Result (1): 0x00=failed, 0x01=success | |
||||
|
| `RESP_CURRENT_RSSI` | `0x30` | RSSI dBm (1, signed) | |
||||
|
| `RESP_CHANNEL_BUSY` | `0x31` | Result (1): 0x00=clear, 0x01=busy | |
||||
|
| `RESP_AIRTIME` | `0x32` | Milliseconds (4) | |
||||
|
| `RESP_NOISE_FLOOR` | `0x33` | dBm (2, signed) | |
||||
|
| `RESP_STATS` | `0x34` | RX (4) + TX (4) + Errors (4) | |
||||
|
| `RESP_BATTERY` | `0x35` | Millivolts (2) | |
||||
|
| `RESP_PONG` | `0x36` | - | |
||||
|
| `RESP_SENSORS` | `0x37` | CayenneLPP payload | |
||||
|
|
||||
|
## Error Codes |
||||
|
|
||||
|
| Code | Value | Description | |
||||
|
|------|-------|-------------| |
||||
|
| `ERR_INVALID_LENGTH` | `0x01` | Request data too short | |
||||
|
| `ERR_INVALID_PARAM` | `0x02` | Invalid parameter value | |
||||
|
| `ERR_NO_CALLBACK` | `0x03` | Feature not available | |
||||
|
| `ERR_MAC_FAILED` | `0x04` | MAC verification failed | |
||||
|
| `ERR_UNKNOWN_CMD` | `0x05` | Unknown command | |
||||
|
| `ERR_ENCRYPT_FAILED` | `0x06` | Encryption failed | |
||||
|
| `ERR_TX_PENDING` | `0x07` | TX already pending | |
||||
|
|
||||
|
## Data Formats |
||||
|
|
||||
|
### Radio Parameters (CMD_SET_RADIO / RESP_RADIO) |
||||
|
|
||||
|
All values little-endian. |
||||
|
|
||||
|
| Field | Size | Description | |
||||
|
|-------|------|-------------| |
||||
|
| Frequency | 4 bytes | Hz (e.g., 869618000) | |
||||
|
| Bandwidth | 4 bytes | Hz (e.g., 62500) | |
||||
|
| SF | 1 byte | Spreading factor (5-12) | |
||||
|
| CR | 1 byte | Coding rate (5-8) | |
||||
|
|
||||
|
### Received Packet (CMD_DATA response) |
||||
|
|
||||
|
| Field | Size | Description | |
||||
|
|-------|------|-------------| |
||||
|
| SNR | 1 byte | Signal-to-noise × 4, signed | |
||||
|
| RSSI | 1 byte | Signal strength dBm, signed | |
||||
|
| Packet | variable | Raw MeshCore packet | |
||||
|
|
||||
|
### Noise Floor (RESP_NOISE_FLOOR) |
||||
|
|
||||
|
Response to `CMD_GET_NOISE_FLOOR` (0x13). Little-endian. |
||||
|
|
||||
|
| Field | Size | Description | |
||||
|
|--------------|------|--------------------------------| |
||||
|
| Noise floor | 2 | int16_t, dBm (signed), e.g. -120 | |
||||
|
|
||||
|
The modem recalibrates the noise floor every two seconds with an AGC reset every 30 seconds. |
||||
|
|
||||
|
### Stats (RESP_STATS) |
||||
|
|
||||
|
Response to `CMD_GET_STATS` (0x14). All values little-endian. |
||||
|
|
||||
|
| Field | Size | Description | |
||||
|
|-------|------|-------------| |
||||
|
| RX | 4 bytes | Packets received | |
||||
|
| TX | 4 bytes | Packets transmitted | |
||||
|
| Errors | 4 bytes | Receive errors | |
||||
|
|
||||
|
### Sensor Permissions (CMD_GET_SENSORS) |
||||
|
|
||||
|
| Bit | Value | Description | |
||||
|
|-----|-------|-------------| |
||||
|
| 0 | `0x01` | Base (battery) | |
||||
|
| 1 | `0x02` | Location (GPS) | |
||||
|
| 2 | `0x04` | Environment (temp, humidity, pressure) | |
||||
|
|
||||
|
Use `0x07` for all permissions. |
||||
|
|
||||
|
### Sensor Data (RESP_SENSORS) |
||||
|
|
||||
|
Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.mydevices.com/docs/lorawan/cayenne-lpp) for parsing. |
||||
|
|
||||
|
## Notes |
||||
|
|
||||
|
- Modem generates identity on first boot (stored in flash) |
||||
|
- SNR values multiplied by 4 for 0.25 dB precision |
||||
|
- Wait for `RESP_TX_DONE` before sending next packet |
||||
|
- Sending `CMD_DATA` while TX is pending returns `ERR_TX_PENDING` |
||||
|
- See [packet_structure.md](./packet_structure.md) for packet format |
||||
@ -0,0 +1,213 @@ |
|||||
|
# nRF52 Power Management |
||||
|
|
||||
|
## Overview |
||||
|
|
||||
|
The nRF52 Power Management module provides battery protection features to prevent over-discharge, minimise likelihood of brownout and flash corruption conditions existing, and enable safe voltage-based recovery. |
||||
|
|
||||
|
## Features |
||||
|
|
||||
|
### Boot Voltage Protection |
||||
|
- Checks battery voltage immediately after boot and before mesh operations commence |
||||
|
- If voltage is below a configurable threshold (e.g., 3300mV), the device configures voltage wake (LPCOMP + VBUS) and enters protective shutdown (SYSTEMOFF) |
||||
|
- Prevents boot loops when battery is critically low |
||||
|
- Skipped when external power (USB VBUS) is detected |
||||
|
|
||||
|
### Voltage Wake (LPCOMP + VBUS) |
||||
|
- Configures the nRF52's Low Power Comparator (LPCOMP) before entering SYSTEMOFF |
||||
|
- Enables USB VBUS detection so external power can wake the device |
||||
|
- Device automatically wakes when battery voltage rises above recovery threshold or when VBUS is detected |
||||
|
|
||||
|
### Early Boot Register Capture |
||||
|
- Captures RESETREAS (reset reason) and GPREGRET2 (shutdown reason) before SystemInit() clears them |
||||
|
- Allows firmware to determine why it booted (cold boot, watchdog, LPCOMP wake, etc.) |
||||
|
- Allows firmware to determine why it last shut down (user request, low voltage, boot protection) |
||||
|
|
||||
|
### Shutdown Reason Tracking |
||||
|
Shutdown reason codes (stored in GPREGRET2): |
||||
|
| Code | Name | Description | |
||||
|
|------|------|-------------| |
||||
|
| 0x00 | NONE | Normal boot / no previous shutdown | |
||||
|
| 0x4C | LOW_VOLTAGE | Runtime low voltage threshold reached | |
||||
|
| 0x55 | USER | User requested powerOff() | |
||||
|
| 0x42 | BOOT_PROTECT | Boot voltage protection triggered | |
||||
|
|
||||
|
## Supported Boards |
||||
|
|
||||
|
| Board | Implemented | LPCOMP wake | VBUS wake | |
||||
|
|-------|-------------|-------------|-----------| |
||||
|
| Seeed Studio XIAO nRF52840 (`xiao_nrf52`) | Yes | Yes | Yes | |
||||
|
| RAK4631 (`rak4631`) | Yes | Yes | Yes | |
||||
|
| Heltec T114 (`heltec_t114`) | Yes | Yes | Yes | |
||||
|
| Promicro nRF52840 | No | No | No | |
||||
|
| RAK WisMesh Tag | No | No | No | |
||||
|
| Heltec Mesh Solar | No | No | No | |
||||
|
| LilyGo T-Echo / T-Echo Lite | No | No | No | |
||||
|
| SenseCAP Solar | No | No | No | |
||||
|
| WIO Tracker L1 / L1 E-Ink | No | No | No | |
||||
|
| WIO WM1110 | No | No | No | |
||||
|
| Mesh Pocket | No | No | No | |
||||
|
| Nano G2 Ultra | No | No | No | |
||||
|
| ThinkNode M1/M3/M6 | No | No | No | |
||||
|
| T1000-E | No | No | No | |
||||
|
| Ikoka Nano/Stick/Handheld (nRF) | No | No | No | |
||||
|
| Keepteen LT1 | No | No | No | |
||||
|
| Minewsemi ME25LS01 | No | No | No | |
||||
|
|
||||
|
Notes: |
||||
|
- "Implemented" reflects Phase 1 (boot lockout + shutdown reason capture). |
||||
|
- User power-off on Heltec T114 does not enable LPCOMP wake. |
||||
|
- VBUS detection is used to skip boot lockout on external power, and VBUS wake is configured alongside LPCOMP when supported hardware exposes VBUS to the nRF52. |
||||
|
|
||||
|
## Technical Details |
||||
|
|
||||
|
### Architecture |
||||
|
|
||||
|
The power management functionality is integrated into the `NRF52Board` base class in `src/helpers/NRF52Board.cpp`. Board variants provide hardware-specific configuration via a `PowerMgtConfig` struct and override `initiateShutdown(uint8_t reason)` to perform board-specific power-down work and conditionally enable voltage wake (LPCOMP + VBUS). |
||||
|
|
||||
|
### Early Boot Capture |
||||
|
|
||||
|
A static constructor with priority 101 in `NRF52Board.cpp` captures the RESETREAS and GPREGRET2 registers before: |
||||
|
- SystemInit() (priority 102) - which clears RESETREAS |
||||
|
- Static C++ constructors (default priority 65535) |
||||
|
|
||||
|
This ensures we capture the true reset reason before any initialisation code runs. |
||||
|
|
||||
|
### Board Implementation |
||||
|
|
||||
|
To enable power management on a board variant: |
||||
|
|
||||
|
1. **Enable in platformio.ini**: |
||||
|
```ini |
||||
|
-D NRF52_POWER_MANAGEMENT |
||||
|
``` |
||||
|
|
||||
|
2. **Define configuration in variant.h**: |
||||
|
```c |
||||
|
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV) |
||||
|
#define PWRMGT_LPCOMP_AIN 7 // AIN channel for voltage sensing |
||||
|
#define PWRMGT_LPCOMP_REFSEL 2 // REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) |
||||
|
``` |
||||
|
|
||||
|
3. **Implement in board .cpp file**: |
||||
|
```cpp |
||||
|
#ifdef NRF52_POWER_MANAGEMENT |
||||
|
const PowerMgtConfig power_config = { |
||||
|
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN, |
||||
|
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL, |
||||
|
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK |
||||
|
}; |
||||
|
|
||||
|
void MyBoard::initiateShutdown(uint8_t reason) { |
||||
|
// Board-specific shutdown preparation (e.g., disable peripherals) |
||||
|
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE || |
||||
|
reason == SHUTDOWN_REASON_BOOT_PROTECT); |
||||
|
|
||||
|
if (enable_lpcomp) { |
||||
|
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel); |
||||
|
} |
||||
|
|
||||
|
enterSystemOff(reason); |
||||
|
} |
||||
|
#endif |
||||
|
|
||||
|
void MyBoard::begin() { |
||||
|
NRF52Board::begin(); // or NRF52BoardDCDC::begin() |
||||
|
// ... board setup ... |
||||
|
|
||||
|
#ifdef NRF52_POWER_MANAGEMENT |
||||
|
checkBootVoltage(&power_config); |
||||
|
#endif |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
For user-initiated shutdowns, `powerOff()` remains board-specific. Power management only arms LPCOMP for automated shutdown reasons (boot protection/low voltage). |
||||
|
|
||||
|
4. **Declare override in board .h file**: |
||||
|
```cpp |
||||
|
#ifdef NRF52_POWER_MANAGEMENT |
||||
|
void initiateShutdown(uint8_t reason) override; |
||||
|
#endif |
||||
|
``` |
||||
|
|
||||
|
### Voltage Wake Configuration |
||||
|
|
||||
|
The LPCOMP (Low Power Comparator) is configured to: |
||||
|
- Monitor the specified AIN channel (0-7 corresponding to P0.02-P0.05, P0.28-P0.31) |
||||
|
- Compare against VDD fraction reference (REFSEL: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16) |
||||
|
- Detect UP events (voltage rising above threshold) |
||||
|
- Use 50mV hysteresis for noise immunity |
||||
|
- Wake the device from SYSTEMOFF when triggered |
||||
|
|
||||
|
VBUS wake is enabled via the POWER peripheral USBDETECTED event whenever `configureVoltageWake()` is used. This requires USB VBUS to be routed to the nRF52 (typical on nRF52840 boards with native USB). |
||||
|
|
||||
|
**LPCOMP Reference Selection (PWRMGT_LPCOMP_REFSEL)**: |
||||
|
| REFSEL | Fraction | VBAT @ 1M/1M divider (VDD=3.0-3.3) | VBAT @ 1.5M/1M divider (VDD=3.0-3.3) | |
||||
|
|--------|----------|------------------------------------|--------------------------------------| |
||||
|
| 0 | 1/8 | 0.75-0.82 V | 0.94-1.03 V | |
||||
|
| 1 | 2/8 | 1.50-1.65 V | 1.88-2.06 V | |
||||
|
| 2 | 3/8 | 2.25-2.47 V | 2.81-3.09 V | |
||||
|
| 3 | 4/8 | 3.00-3.30 V | 3.75-4.12 V | |
||||
|
| 4 | 5/8 | 3.75-4.12 V | 4.69-5.16 V | |
||||
|
| 5 | 6/8 | 4.50-4.95 V | 5.62-6.19 V | |
||||
|
| 6 | 7/8 | 5.25-5.77 V | 6.56-7.22 V | |
||||
|
| 7 | ARef | - | - | |
||||
|
| 8 | 1/16 | 0.38-0.41 V | 0.47-0.52 V | |
||||
|
| 9 | 3/16 | 1.12-1.24 V | 1.41-1.55 V | |
||||
|
| 10 | 5/16 | 1.88-2.06 V | 2.34-2.58 V | |
||||
|
| 11 | 7/16 | 2.62-2.89 V | 3.28-3.61 V | |
||||
|
| 12 | 9/16 | 3.38-3.71 V | 4.22-4.64 V | |
||||
|
| 13 | 11/16 | 4.12-4.54 V | 5.16-5.67 V | |
||||
|
| 14 | 13/16 | 4.88-5.36 V | 6.09-6.70 V | |
||||
|
| 15 | 15/16 | 5.62-6.19 V | 7.03-7.73 V | |
||||
|
|
||||
|
**Important**: For boards with a voltage divider on the battery sense pin, LPCOMP measures the divided voltage. Use: |
||||
|
`VBAT_threshold ≈ (VDD * fraction) * divider_scale`, where `divider_scale = (Rtop + Rbottom) / Rbottom` (e.g., 2.0 for 1M/1M, 2.5 for 1.5M/1M, 3.0 for XIAO). |
||||
|
|
||||
|
### SoftDevice Compatibility |
||||
|
|
||||
|
The power management code checks whether SoftDevice is enabled and uses the appropriate API: |
||||
|
- When SD enabled: `sd_power_*` functions |
||||
|
- When SD disabled: Direct register access (NRF_POWER->*) |
||||
|
|
||||
|
This ensures compatibility regardless of BLE stack state. |
||||
|
|
||||
|
## CLI Commands |
||||
|
|
||||
|
Power management status can be queried via the CLI: |
||||
|
|
||||
|
| Command | Description | |
||||
|
|---------|-------------| |
||||
|
| `get pwrmgt.support` | Returns "supported" or "unsupported" | |
||||
|
| `get pwrmgt.source` | Returns current power source - "battery" or "external" (5V/USB power) | |
||||
|
| `get pwrmgt.bootreason` | Returns reset and shutdown reason strings | |
||||
|
| `get pwrmgt.bootmv` | Returns boot voltage in millivolts | |
||||
|
|
||||
|
On boards without power management enabled, all commands except `get pwrmgt.support` return: |
||||
|
``` |
||||
|
ERROR: Power management not supported |
||||
|
``` |
||||
|
|
||||
|
## Debug Output |
||||
|
|
||||
|
When `MESH_DEBUG=1` is enabled, the power management module outputs: |
||||
|
``` |
||||
|
DEBUG: PWRMGT: Reset = Wake from LPCOMP (0x20000); Shutdown = Low Voltage (0x4C) |
||||
|
DEBUG: PWRMGT: Boot voltage = 3450 mV (threshold = 3300 mV) |
||||
|
DEBUG: PWRMGT: LPCOMP wake configured (AIN7, ref=3/8 VDD) |
||||
|
``` |
||||
|
|
||||
|
## Phase 2 (Planned) |
||||
|
|
||||
|
- Runtime voltage monitoring |
||||
|
- Voltage state machine (Normal -> Warning -> Critical -> Shutdown) |
||||
|
- Configurable thresholds |
||||
|
- Load shedding callbacks for power reduction |
||||
|
- Deep sleep integration |
||||
|
- Scheduled wake-up |
||||
|
- Extended sleep with periodic monitoring |
||||
|
|
||||
|
## References |
||||
|
|
||||
|
- [nRF52840 Product Specification - POWER](https://infocenter.nordicsemi.com/topic/ps_nrf52840/power.html) |
||||
|
- [nRF52840 Product Specification - LPCOMP](https://infocenter.nordicsemi.com/topic/ps_nrf52840/lpcomp.html) |
||||
|
- [SoftDevice S140 API - Power Management](https://infocenter.nordicsemi.com/topic/sdk_nrf5_v17.1.0/group__nrf__sdm__api.html) |
||||
@ -0,0 +1,437 @@ |
|||||
|
#include "KissModem.h" |
||||
|
#include <CayenneLPP.h> |
||||
|
|
||||
|
KissModem::KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, |
||||
|
mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors) |
||||
|
: _serial(serial), _identity(identity), _rng(rng), _radio(radio), _board(board), _sensors(sensors) { |
||||
|
_rx_len = 0; |
||||
|
_rx_escaped = false; |
||||
|
_rx_active = false; |
||||
|
_has_pending_tx = false; |
||||
|
_pending_tx_len = 0; |
||||
|
_setRadioCallback = nullptr; |
||||
|
_setTxPowerCallback = nullptr; |
||||
|
_getCurrentRssiCallback = nullptr; |
||||
|
_getStatsCallback = nullptr; |
||||
|
_config = {0, 0, 0, 0, 0}; |
||||
|
} |
||||
|
|
||||
|
void KissModem::begin() { |
||||
|
_rx_len = 0; |
||||
|
_rx_escaped = false; |
||||
|
_rx_active = false; |
||||
|
_has_pending_tx = false; |
||||
|
} |
||||
|
|
||||
|
void KissModem::writeByte(uint8_t b) { |
||||
|
if (b == KISS_FEND) { |
||||
|
_serial.write(KISS_FESC); |
||||
|
_serial.write(KISS_TFEND); |
||||
|
} else if (b == KISS_FESC) { |
||||
|
_serial.write(KISS_FESC); |
||||
|
_serial.write(KISS_TFESC); |
||||
|
} else { |
||||
|
_serial.write(b); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void KissModem::writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len) { |
||||
|
_serial.write(KISS_FEND); |
||||
|
writeByte(cmd); |
||||
|
for (uint16_t i = 0; i < len; i++) { |
||||
|
writeByte(data[i]); |
||||
|
} |
||||
|
_serial.write(KISS_FEND); |
||||
|
} |
||||
|
|
||||
|
void KissModem::writeErrorFrame(uint8_t error_code) { |
||||
|
writeFrame(RESP_ERROR, &error_code, 1); |
||||
|
} |
||||
|
|
||||
|
void KissModem::loop() { |
||||
|
while (_serial.available()) { |
||||
|
uint8_t b = _serial.read(); |
||||
|
|
||||
|
if (b == KISS_FEND) { |
||||
|
if (_rx_active && _rx_len > 0) { |
||||
|
processFrame(); |
||||
|
} |
||||
|
_rx_len = 0; |
||||
|
_rx_escaped = false; |
||||
|
_rx_active = true; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (!_rx_active) continue; |
||||
|
|
||||
|
if (b == KISS_FESC) { |
||||
|
_rx_escaped = true; |
||||
|
continue; |
||||
|
} |
||||
|
|
||||
|
if (_rx_escaped) { |
||||
|
_rx_escaped = false; |
||||
|
if (b == KISS_TFEND) b = KISS_FEND; |
||||
|
else if (b == KISS_TFESC) b = KISS_FESC; |
||||
|
} |
||||
|
|
||||
|
if (_rx_len < KISS_MAX_FRAME_SIZE) { |
||||
|
_rx_buf[_rx_len++] = b; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void KissModem::processFrame() { |
||||
|
if (_rx_len < 1) return; |
||||
|
|
||||
|
uint8_t cmd = _rx_buf[0]; |
||||
|
const uint8_t* data = &_rx_buf[1]; |
||||
|
uint16_t data_len = _rx_len - 1; |
||||
|
|
||||
|
switch (cmd) { |
||||
|
case CMD_DATA: |
||||
|
if (data_len < 2) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
} else if (data_len > KISS_MAX_PACKET_SIZE) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
} else if (_has_pending_tx) { |
||||
|
writeErrorFrame(ERR_TX_PENDING); |
||||
|
} else { |
||||
|
memcpy(_pending_tx, data, data_len); |
||||
|
_pending_tx_len = data_len; |
||||
|
_has_pending_tx = true; |
||||
|
} |
||||
|
break; |
||||
|
case CMD_GET_IDENTITY: |
||||
|
handleGetIdentity(); |
||||
|
break; |
||||
|
case CMD_GET_RANDOM: |
||||
|
handleGetRandom(data, data_len); |
||||
|
break; |
||||
|
case CMD_VERIFY_SIGNATURE: |
||||
|
handleVerifySignature(data, data_len); |
||||
|
break; |
||||
|
case CMD_SIGN_DATA: |
||||
|
handleSignData(data, data_len); |
||||
|
break; |
||||
|
case CMD_ENCRYPT_DATA: |
||||
|
handleEncryptData(data, data_len); |
||||
|
break; |
||||
|
case CMD_DECRYPT_DATA: |
||||
|
handleDecryptData(data, data_len); |
||||
|
break; |
||||
|
case CMD_KEY_EXCHANGE: |
||||
|
handleKeyExchange(data, data_len); |
||||
|
break; |
||||
|
case CMD_HASH: |
||||
|
handleHash(data, data_len); |
||||
|
break; |
||||
|
case CMD_SET_RADIO: |
||||
|
handleSetRadio(data, data_len); |
||||
|
break; |
||||
|
case CMD_SET_TX_POWER: |
||||
|
handleSetTxPower(data, data_len); |
||||
|
break; |
||||
|
case CMD_GET_RADIO: |
||||
|
handleGetRadio(); |
||||
|
break; |
||||
|
case CMD_GET_TX_POWER: |
||||
|
handleGetTxPower(); |
||||
|
break; |
||||
|
case CMD_GET_VERSION: |
||||
|
handleGetVersion(); |
||||
|
break; |
||||
|
case CMD_GET_CURRENT_RSSI: |
||||
|
handleGetCurrentRssi(); |
||||
|
break; |
||||
|
case CMD_IS_CHANNEL_BUSY: |
||||
|
handleIsChannelBusy(); |
||||
|
break; |
||||
|
case CMD_GET_AIRTIME: |
||||
|
handleGetAirtime(data, data_len); |
||||
|
break; |
||||
|
case CMD_GET_NOISE_FLOOR: |
||||
|
handleGetNoiseFloor(); |
||||
|
break; |
||||
|
case CMD_GET_STATS: |
||||
|
handleGetStats(); |
||||
|
break; |
||||
|
case CMD_GET_BATTERY: |
||||
|
handleGetBattery(); |
||||
|
break; |
||||
|
case CMD_PING: |
||||
|
handlePing(); |
||||
|
break; |
||||
|
case CMD_GET_SENSORS: |
||||
|
handleGetSensors(data, data_len); |
||||
|
break; |
||||
|
default: |
||||
|
writeErrorFrame(ERR_UNKNOWN_CMD); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetIdentity() { |
||||
|
writeFrame(RESP_IDENTITY, _identity.pub_key, PUB_KEY_SIZE); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetRandom(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t requested = data[0]; |
||||
|
if (requested < 1 || requested > 64) { |
||||
|
writeErrorFrame(ERR_INVALID_PARAM); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t buf[64]; |
||||
|
_rng.random(buf, requested); |
||||
|
writeFrame(RESP_RANDOM, buf, requested); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleVerifySignature(const uint8_t* data, uint16_t len) { |
||||
|
if (len < PUB_KEY_SIZE + SIGNATURE_SIZE + 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
mesh::Identity signer(data); |
||||
|
const uint8_t* signature = data + PUB_KEY_SIZE; |
||||
|
const uint8_t* msg = data + PUB_KEY_SIZE + SIGNATURE_SIZE; |
||||
|
uint16_t msg_len = len - PUB_KEY_SIZE - SIGNATURE_SIZE; |
||||
|
|
||||
|
uint8_t result = signer.verify(signature, msg, msg_len) ? 0x01 : 0x00; |
||||
|
writeFrame(RESP_VERIFY, &result, 1); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleSignData(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t signature[SIGNATURE_SIZE]; |
||||
|
_identity.sign(signature, data, len); |
||||
|
writeFrame(RESP_SIGNATURE, signature, SIGNATURE_SIZE); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleEncryptData(const uint8_t* data, uint16_t len) { |
||||
|
if (len < PUB_KEY_SIZE + 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const uint8_t* key = data; |
||||
|
const uint8_t* plaintext = data + PUB_KEY_SIZE; |
||||
|
uint16_t plaintext_len = len - PUB_KEY_SIZE; |
||||
|
|
||||
|
uint8_t buf[KISS_MAX_FRAME_SIZE]; |
||||
|
int encrypted_len = mesh::Utils::encryptThenMAC(key, buf, plaintext, plaintext_len); |
||||
|
|
||||
|
if (encrypted_len > 0) { |
||||
|
writeFrame(RESP_ENCRYPTED, buf, encrypted_len); |
||||
|
} else { |
||||
|
writeErrorFrame(ERR_ENCRYPT_FAILED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleDecryptData(const uint8_t* data, uint16_t len) { |
||||
|
if (len < PUB_KEY_SIZE + CIPHER_MAC_SIZE + 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const uint8_t* key = data; |
||||
|
const uint8_t* ciphertext = data + PUB_KEY_SIZE; |
||||
|
uint16_t ciphertext_len = len - PUB_KEY_SIZE; |
||||
|
|
||||
|
uint8_t buf[KISS_MAX_FRAME_SIZE]; |
||||
|
int decrypted_len = mesh::Utils::MACThenDecrypt(key, buf, ciphertext, ciphertext_len); |
||||
|
|
||||
|
if (decrypted_len > 0) { |
||||
|
writeFrame(RESP_DECRYPTED, buf, decrypted_len); |
||||
|
} else { |
||||
|
writeErrorFrame(ERR_MAC_FAILED); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleKeyExchange(const uint8_t* data, uint16_t len) { |
||||
|
if (len < PUB_KEY_SIZE) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t shared_secret[PUB_KEY_SIZE]; |
||||
|
_identity.calcSharedSecret(shared_secret, data); |
||||
|
writeFrame(RESP_SHARED_SECRET, shared_secret, PUB_KEY_SIZE); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleHash(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t hash[32]; |
||||
|
mesh::Utils::sha256(hash, 32, data, len); |
||||
|
writeFrame(RESP_HASH, hash, 32); |
||||
|
} |
||||
|
|
||||
|
bool KissModem::getPacketToSend(uint8_t* packet, uint16_t* len) { |
||||
|
if (!_has_pending_tx) return false; |
||||
|
|
||||
|
memcpy(packet, _pending_tx, _pending_tx_len); |
||||
|
*len = _pending_tx_len; |
||||
|
_has_pending_tx = false; |
||||
|
return true; |
||||
|
} |
||||
|
|
||||
|
void KissModem::onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len) { |
||||
|
uint8_t buf[2 + KISS_MAX_PACKET_SIZE]; |
||||
|
buf[0] = (uint8_t)snr; |
||||
|
buf[1] = (uint8_t)rssi; |
||||
|
memcpy(&buf[2], packet, len); |
||||
|
writeFrame(CMD_DATA, buf, 2 + len); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleSetRadio(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 10) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
if (!_setRadioCallback) { |
||||
|
writeErrorFrame(ERR_NO_CALLBACK); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint32_t freq_hz, bw_hz; |
||||
|
memcpy(&freq_hz, data, 4); |
||||
|
memcpy(&bw_hz, data + 4, 4); |
||||
|
uint8_t sf = data[8]; |
||||
|
uint8_t cr = data[9]; |
||||
|
|
||||
|
_config.freq_hz = freq_hz; |
||||
|
_config.bw_hz = bw_hz; |
||||
|
_config.sf = sf; |
||||
|
_config.cr = cr; |
||||
|
|
||||
|
float freq = freq_hz / 1000000.0f; |
||||
|
float bw = bw_hz / 1000.0f; |
||||
|
|
||||
|
_setRadioCallback(freq, bw, sf, cr); |
||||
|
writeFrame(RESP_OK, nullptr, 0); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleSetTxPower(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
if (!_setTxPowerCallback) { |
||||
|
writeErrorFrame(ERR_NO_CALLBACK); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_config.tx_power = data[0]; |
||||
|
_setTxPowerCallback(data[0]); |
||||
|
writeFrame(RESP_OK, nullptr, 0); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetRadio() { |
||||
|
uint8_t buf[10]; |
||||
|
memcpy(buf, &_config.freq_hz, 4); |
||||
|
memcpy(buf + 4, &_config.bw_hz, 4); |
||||
|
buf[8] = _config.sf; |
||||
|
buf[9] = _config.cr; |
||||
|
writeFrame(RESP_RADIO, buf, 10); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetTxPower() { |
||||
|
writeFrame(RESP_TX_POWER, &_config.tx_power, 1); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetVersion() { |
||||
|
uint8_t buf[2]; |
||||
|
buf[0] = KISS_FIRMWARE_VERSION; |
||||
|
buf[1] = 0; |
||||
|
writeFrame(RESP_VERSION, buf, 2); |
||||
|
} |
||||
|
|
||||
|
void KissModem::onTxComplete(bool success) { |
||||
|
uint8_t result = success ? 0x01 : 0x00; |
||||
|
writeFrame(RESP_TX_DONE, &result, 1); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetCurrentRssi() { |
||||
|
if (!_getCurrentRssiCallback) { |
||||
|
writeErrorFrame(ERR_NO_CALLBACK); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
float rssi = _getCurrentRssiCallback(); |
||||
|
int8_t rssi_byte = (int8_t)rssi; |
||||
|
writeFrame(RESP_CURRENT_RSSI, (uint8_t*)&rssi_byte, 1); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleIsChannelBusy() { |
||||
|
uint8_t busy = _radio.isReceiving() ? 0x01 : 0x00; |
||||
|
writeFrame(RESP_CHANNEL_BUSY, &busy, 1); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetAirtime(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t packet_len = data[0]; |
||||
|
uint32_t airtime = _radio.getEstAirtimeFor(packet_len); |
||||
|
writeFrame(RESP_AIRTIME, (uint8_t*)&airtime, 4); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetNoiseFloor() { |
||||
|
int16_t noise_floor = _radio.getNoiseFloor(); |
||||
|
writeFrame(RESP_NOISE_FLOOR, (uint8_t*)&noise_floor, 2); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetStats() { |
||||
|
if (!_getStatsCallback) { |
||||
|
writeErrorFrame(ERR_NO_CALLBACK); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint32_t rx, tx, errors; |
||||
|
_getStatsCallback(&rx, &tx, &errors); |
||||
|
uint8_t buf[12]; |
||||
|
memcpy(buf, &rx, 4); |
||||
|
memcpy(buf + 4, &tx, 4); |
||||
|
memcpy(buf + 8, &errors, 4); |
||||
|
writeFrame(RESP_STATS, buf, 12); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetBattery() { |
||||
|
uint16_t mv = _board.getBattMilliVolts(); |
||||
|
writeFrame(RESP_BATTERY, (uint8_t*)&mv, 2); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handlePing() { |
||||
|
writeFrame(RESP_PONG, nullptr, 0); |
||||
|
} |
||||
|
|
||||
|
void KissModem::handleGetSensors(const uint8_t* data, uint16_t len) { |
||||
|
if (len < 1) { |
||||
|
writeErrorFrame(ERR_INVALID_LENGTH); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
uint8_t permissions = data[0]; |
||||
|
CayenneLPP telemetry(255); |
||||
|
if (_sensors.querySensors(permissions, telemetry)) { |
||||
|
writeFrame(RESP_SENSORS, telemetry.getBuffer(), telemetry.getSize()); |
||||
|
} else { |
||||
|
writeFrame(RESP_SENSORS, nullptr, 0); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,152 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <Arduino.h> |
||||
|
#include <Identity.h> |
||||
|
#include <Utils.h> |
||||
|
#include <Mesh.h> |
||||
|
#include <helpers/SensorManager.h> |
||||
|
|
||||
|
#define KISS_FEND 0xC0 |
||||
|
#define KISS_FESC 0xDB |
||||
|
#define KISS_TFEND 0xDC |
||||
|
#define KISS_TFESC 0xDD |
||||
|
|
||||
|
#define KISS_MAX_FRAME_SIZE 512 |
||||
|
#define KISS_MAX_PACKET_SIZE 255 |
||||
|
|
||||
|
#define CMD_DATA 0x00 |
||||
|
#define CMD_GET_IDENTITY 0x01 |
||||
|
#define CMD_GET_RANDOM 0x02 |
||||
|
#define CMD_VERIFY_SIGNATURE 0x03 |
||||
|
#define CMD_SIGN_DATA 0x04 |
||||
|
#define CMD_ENCRYPT_DATA 0x05 |
||||
|
#define CMD_DECRYPT_DATA 0x06 |
||||
|
#define CMD_KEY_EXCHANGE 0x07 |
||||
|
#define CMD_HASH 0x08 |
||||
|
#define CMD_SET_RADIO 0x09 |
||||
|
#define CMD_SET_TX_POWER 0x0A |
||||
|
#define CMD_GET_RADIO 0x0C |
||||
|
#define CMD_GET_TX_POWER 0x0D |
||||
|
#define CMD_GET_VERSION 0x0F |
||||
|
#define CMD_GET_CURRENT_RSSI 0x10 |
||||
|
#define CMD_IS_CHANNEL_BUSY 0x11 |
||||
|
#define CMD_GET_AIRTIME 0x12 |
||||
|
#define CMD_GET_NOISE_FLOOR 0x13 |
||||
|
#define CMD_GET_STATS 0x14 |
||||
|
#define CMD_GET_BATTERY 0x15 |
||||
|
#define CMD_PING 0x16 |
||||
|
#define CMD_GET_SENSORS 0x17 |
||||
|
|
||||
|
#define RESP_IDENTITY 0x21 |
||||
|
#define RESP_RANDOM 0x22 |
||||
|
#define RESP_VERIFY 0x23 |
||||
|
#define RESP_SIGNATURE 0x24 |
||||
|
#define RESP_ENCRYPTED 0x25 |
||||
|
#define RESP_DECRYPTED 0x26 |
||||
|
#define RESP_SHARED_SECRET 0x27 |
||||
|
#define RESP_HASH 0x28 |
||||
|
#define RESP_OK 0x29 |
||||
|
#define RESP_RADIO 0x2A |
||||
|
#define RESP_TX_POWER 0x2B |
||||
|
#define RESP_VERSION 0x2D |
||||
|
#define RESP_ERROR 0x2E |
||||
|
#define RESP_TX_DONE 0x2F |
||||
|
#define RESP_CURRENT_RSSI 0x30 |
||||
|
#define RESP_CHANNEL_BUSY 0x31 |
||||
|
#define RESP_AIRTIME 0x32 |
||||
|
#define RESP_NOISE_FLOOR 0x33 |
||||
|
#define RESP_STATS 0x34 |
||||
|
#define RESP_BATTERY 0x35 |
||||
|
#define RESP_PONG 0x36 |
||||
|
#define RESP_SENSORS 0x37 |
||||
|
|
||||
|
#define ERR_INVALID_LENGTH 0x01 |
||||
|
#define ERR_INVALID_PARAM 0x02 |
||||
|
#define ERR_NO_CALLBACK 0x03 |
||||
|
#define ERR_MAC_FAILED 0x04 |
||||
|
#define ERR_UNKNOWN_CMD 0x05 |
||||
|
#define ERR_ENCRYPT_FAILED 0x06 |
||||
|
#define ERR_TX_PENDING 0x07 |
||||
|
|
||||
|
#define KISS_FIRMWARE_VERSION 1 |
||||
|
|
||||
|
typedef void (*SetRadioCallback)(float freq, float bw, uint8_t sf, uint8_t cr); |
||||
|
typedef void (*SetTxPowerCallback)(uint8_t power); |
||||
|
typedef float (*GetCurrentRssiCallback)(); |
||||
|
typedef void (*GetStatsCallback)(uint32_t* rx, uint32_t* tx, uint32_t* errors); |
||||
|
|
||||
|
struct RadioConfig { |
||||
|
uint32_t freq_hz; |
||||
|
uint32_t bw_hz; |
||||
|
uint8_t sf; |
||||
|
uint8_t cr; |
||||
|
uint8_t tx_power; |
||||
|
}; |
||||
|
|
||||
|
class KissModem { |
||||
|
Stream& _serial; |
||||
|
mesh::LocalIdentity& _identity; |
||||
|
mesh::RNG& _rng; |
||||
|
mesh::Radio& _radio; |
||||
|
mesh::MainBoard& _board; |
||||
|
SensorManager& _sensors; |
||||
|
|
||||
|
uint8_t _rx_buf[KISS_MAX_FRAME_SIZE]; |
||||
|
uint16_t _rx_len; |
||||
|
bool _rx_escaped; |
||||
|
bool _rx_active; |
||||
|
|
||||
|
uint8_t _pending_tx[KISS_MAX_PACKET_SIZE]; |
||||
|
uint16_t _pending_tx_len; |
||||
|
bool _has_pending_tx; |
||||
|
|
||||
|
SetRadioCallback _setRadioCallback; |
||||
|
SetTxPowerCallback _setTxPowerCallback; |
||||
|
GetCurrentRssiCallback _getCurrentRssiCallback; |
||||
|
GetStatsCallback _getStatsCallback; |
||||
|
|
||||
|
RadioConfig _config; |
||||
|
|
||||
|
void writeByte(uint8_t b); |
||||
|
void writeFrame(uint8_t cmd, const uint8_t* data, uint16_t len); |
||||
|
void writeErrorFrame(uint8_t error_code); |
||||
|
void processFrame(); |
||||
|
|
||||
|
void handleGetIdentity(); |
||||
|
void handleGetRandom(const uint8_t* data, uint16_t len); |
||||
|
void handleVerifySignature(const uint8_t* data, uint16_t len); |
||||
|
void handleSignData(const uint8_t* data, uint16_t len); |
||||
|
void handleEncryptData(const uint8_t* data, uint16_t len); |
||||
|
void handleDecryptData(const uint8_t* data, uint16_t len); |
||||
|
void handleKeyExchange(const uint8_t* data, uint16_t len); |
||||
|
void handleHash(const uint8_t* data, uint16_t len); |
||||
|
void handleSetRadio(const uint8_t* data, uint16_t len); |
||||
|
void handleSetTxPower(const uint8_t* data, uint16_t len); |
||||
|
void handleGetRadio(); |
||||
|
void handleGetTxPower(); |
||||
|
void handleGetVersion(); |
||||
|
void handleGetCurrentRssi(); |
||||
|
void handleIsChannelBusy(); |
||||
|
void handleGetAirtime(const uint8_t* data, uint16_t len); |
||||
|
void handleGetNoiseFloor(); |
||||
|
void handleGetStats(); |
||||
|
void handleGetBattery(); |
||||
|
void handlePing(); |
||||
|
void handleGetSensors(const uint8_t* data, uint16_t len); |
||||
|
|
||||
|
public: |
||||
|
KissModem(Stream& serial, mesh::LocalIdentity& identity, mesh::RNG& rng, |
||||
|
mesh::Radio& radio, mesh::MainBoard& board, SensorManager& sensors); |
||||
|
|
||||
|
void begin(); |
||||
|
void loop(); |
||||
|
|
||||
|
void setRadioCallback(SetRadioCallback cb) { _setRadioCallback = cb; } |
||||
|
void setTxPowerCallback(SetTxPowerCallback cb) { _setTxPowerCallback = cb; } |
||||
|
void setGetCurrentRssiCallback(GetCurrentRssiCallback cb) { _getCurrentRssiCallback = cb; } |
||||
|
void setGetStatsCallback(GetStatsCallback cb) { _getStatsCallback = cb; } |
||||
|
|
||||
|
bool getPacketToSend(uint8_t* packet, uint16_t* len); |
||||
|
void onPacketReceived(int8_t snr, int8_t rssi, const uint8_t* packet, uint16_t len); |
||||
|
void onTxComplete(bool success); |
||||
|
}; |
||||
@ -0,0 +1,130 @@ |
|||||
|
#include <Arduino.h> |
||||
|
#include <target.h> |
||||
|
#include <helpers/ArduinoHelpers.h> |
||||
|
#include <helpers/IdentityStore.h> |
||||
|
#include "KissModem.h" |
||||
|
|
||||
|
#if defined(NRF52_PLATFORM) |
||||
|
#include <InternalFileSystem.h> |
||||
|
#elif defined(RP2040_PLATFORM) |
||||
|
#include <LittleFS.h> |
||||
|
#elif defined(ESP32) |
||||
|
#include <SPIFFS.h> |
||||
|
#endif |
||||
|
|
||||
|
#define NOISE_FLOOR_CALIB_INTERVAL_MS 2000 |
||||
|
#define AGC_RESET_INTERVAL_MS 30000 |
||||
|
|
||||
|
StdRNG rng; |
||||
|
mesh::LocalIdentity identity; |
||||
|
KissModem* modem; |
||||
|
static uint32_t next_noise_floor_calib_ms = 0; |
||||
|
static uint32_t next_agc_reset_ms = 0; |
||||
|
|
||||
|
void halt() { |
||||
|
while (1) ; |
||||
|
} |
||||
|
|
||||
|
void loadOrCreateIdentity() { |
||||
|
#if defined(NRF52_PLATFORM) |
||||
|
InternalFS.begin(); |
||||
|
IdentityStore store(InternalFS, ""); |
||||
|
#elif defined(ESP32) |
||||
|
SPIFFS.begin(true); |
||||
|
IdentityStore store(SPIFFS, "/identity"); |
||||
|
#elif defined(RP2040_PLATFORM) |
||||
|
LittleFS.begin(); |
||||
|
IdentityStore store(LittleFS, "/identity"); |
||||
|
store.begin(); |
||||
|
#else |
||||
|
#error "Filesystem not defined" |
||||
|
#endif |
||||
|
|
||||
|
if (!store.load("_main", identity)) { |
||||
|
identity = radio_new_identity(); |
||||
|
while (identity.pub_key[0] == 0x00 || identity.pub_key[0] == 0xFF) { |
||||
|
identity = radio_new_identity(); |
||||
|
} |
||||
|
store.save("_main", identity); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) { |
||||
|
radio_set_params(freq, bw, sf, cr); |
||||
|
} |
||||
|
|
||||
|
void onSetTxPower(uint8_t power) { |
||||
|
radio_set_tx_power(power); |
||||
|
} |
||||
|
|
||||
|
float onGetCurrentRssi() { |
||||
|
return radio_driver.getCurrentRSSI(); |
||||
|
} |
||||
|
|
||||
|
void onGetStats(uint32_t* rx, uint32_t* tx, uint32_t* errors) { |
||||
|
*rx = radio_driver.getPacketsRecv(); |
||||
|
*tx = radio_driver.getPacketsSent(); |
||||
|
*errors = radio_driver.getPacketsRecvErrors(); |
||||
|
} |
||||
|
|
||||
|
void setup() { |
||||
|
board.begin(); |
||||
|
|
||||
|
if (!radio_init()) { |
||||
|
halt(); |
||||
|
} |
||||
|
|
||||
|
radio_driver.begin(); |
||||
|
|
||||
|
rng.begin(radio_get_rng_seed()); |
||||
|
loadOrCreateIdentity(); |
||||
|
|
||||
|
Serial.begin(115200); |
||||
|
uint32_t start = millis(); |
||||
|
while (!Serial && millis() - start < 3000) delay(10); |
||||
|
delay(100); |
||||
|
|
||||
|
sensors.begin(); |
||||
|
|
||||
|
modem = new KissModem(Serial, identity, rng, radio_driver, board, sensors); |
||||
|
modem->setRadioCallback(onSetRadio); |
||||
|
modem->setTxPowerCallback(onSetTxPower); |
||||
|
modem->setGetCurrentRssiCallback(onGetCurrentRssi); |
||||
|
modem->setGetStatsCallback(onGetStats); |
||||
|
modem->begin(); |
||||
|
} |
||||
|
|
||||
|
void loop() { |
||||
|
modem->loop(); |
||||
|
|
||||
|
uint8_t packet[KISS_MAX_PACKET_SIZE]; |
||||
|
uint16_t len; |
||||
|
|
||||
|
// trigger noise floor calibration
|
||||
|
if ((uint32_t)(millis() - next_noise_floor_calib_ms) >= NOISE_FLOOR_CALIB_INTERVAL_MS) { |
||||
|
radio_driver.triggerNoiseFloorCalibrate(0); |
||||
|
next_noise_floor_calib_ms = millis(); |
||||
|
} |
||||
|
radio_driver.loop(); |
||||
|
|
||||
|
if (modem->getPacketToSend(packet, &len)) { |
||||
|
radio_driver.startSendRaw(packet, len); |
||||
|
while (!radio_driver.isSendComplete()) { |
||||
|
delay(1); |
||||
|
} |
||||
|
radio_driver.onSendFinished(); |
||||
|
modem->onTxComplete(true); |
||||
|
} |
||||
|
|
||||
|
if ((uint32_t)(millis() - next_agc_reset_ms) >= AGC_RESET_INTERVAL_MS) { |
||||
|
radio_driver.resetAGC(); |
||||
|
next_agc_reset_ms = millis(); |
||||
|
} |
||||
|
uint8_t rx_buf[256]; |
||||
|
int rx_len = radio_driver.recvRaw(rx_buf, sizeof(rx_buf)); |
||||
|
if (rx_len > 0) { |
||||
|
int8_t snr = (int8_t)(radio_driver.getLastSNR() * 4); |
||||
|
int8_t rssi = (int8_t)radio_driver.getLastRSSI(); |
||||
|
modem->onPacketReceived(snr, rssi, rx_buf, rx_len); |
||||
|
} |
||||
|
} |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue