Compare commits
36 Commits
pio-ini-ad
...
region_via
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0805a47f35 | ||
|
|
7ae164217c | ||
|
|
a5f3766016 | ||
|
|
f0269c9bff | ||
|
|
153bcdc6a3 | ||
|
|
96ef5e5efe | ||
|
|
988287bfd7 | ||
|
|
6336bd5b72 | ||
|
|
f46f0d0ed1 | ||
|
|
c7b3d34963 | ||
|
|
e744adfa39 | ||
|
|
b853c7ced5 | ||
|
|
266f6ee856 | ||
|
|
e7c72c5c6a | ||
|
|
9dd52bd0cc | ||
|
|
1f59e52880 | ||
|
|
3c27132914 | ||
|
|
fc61018d4d | ||
|
|
616eb57b16 | ||
|
|
537acd7ea1 | ||
|
|
32230f6167 | ||
|
|
bccefd6e37 | ||
|
|
36f230d074 | ||
|
|
ea85486dca | ||
|
|
b09ddfc5e1 | ||
|
|
d68bc74514 | ||
|
|
a7cadc8e44 | ||
|
|
e51a2d1ba0 | ||
|
|
56ab59ded2 | ||
|
|
bf0777845a | ||
|
|
ed5d2909fc | ||
|
|
5e4b33a1a0 | ||
|
|
5c7b28f110 | ||
|
|
b919119faf | ||
|
|
c61fde9328 | ||
|
|
06c4ca19ab |
72
boards/rak3401.json
Normal file
72
boards/rak3401.json
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"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": "WisCore RAK3401 Board",
|
||||||
|
"mcu": "nrf52840",
|
||||||
|
"variant": "WisCore_RAK3401_Board",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"frameworks": [
|
||||||
|
"arduino"
|
||||||
|
],
|
||||||
|
"name": "WisCore RAK3401 Board",
|
||||||
|
"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://www.rakwireless.com",
|
||||||
|
"vendor": "RAKwireless"
|
||||||
|
}
|
||||||
213
docs/nrf52_power_management.md
Normal file
213
docs/nrf52_power_management.md
Normal file
@@ -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)
|
||||||
@@ -323,18 +323,17 @@ void MyMesh::onContactsFull() {
|
|||||||
|
|
||||||
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
||||||
if (_serial->isConnected()) {
|
if (_serial->isConnected()) {
|
||||||
if (!shouldAutoAddContactType(contact.type) && is_new) {
|
if (is_new) {
|
||||||
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
|
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
|
||||||
} else {
|
} else {
|
||||||
out_frame[0] = PUSH_CODE_ADVERT;
|
out_frame[0] = PUSH_CODE_ADVERT;
|
||||||
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);
|
||||||
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
#ifdef DISPLAY_CLASS
|
|
||||||
if (_ui) _ui->notify(UIEventType::newContactMessage);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
if (_ui && !_prefs.buzzer_quiet) _ui->notify(UIEventType::newContactMessage); //buzz if enabled
|
||||||
|
#endif
|
||||||
|
|
||||||
// add inbound-path to mem cache
|
// add inbound-path to mem cache
|
||||||
if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid
|
if (path && path_len <= sizeof(AdvertPath::path)) { // check path is valid
|
||||||
@@ -358,7 +357,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
|
|||||||
memcpy(p->path, path, p->path_len);
|
memcpy(p->path, path, p->path_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
if (!is_new) dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); // only schedule lazy write for contacts that are in contacts[]
|
||||||
}
|
}
|
||||||
|
|
||||||
static int sort_by_recent(const void *a, const void *b) {
|
static int sort_by_recent(const void *a, const void *b) {
|
||||||
@@ -441,9 +440,7 @@ void MyMesh::queueMessage(const ContactInfo &from, uint8_t txt_type, mesh::Packe
|
|||||||
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
|
bool should_display = txt_type == TXT_TYPE_PLAIN || txt_type == TXT_TYPE_SIGNED_PLAIN;
|
||||||
if (should_display && _ui) {
|
if (should_display && _ui) {
|
||||||
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
_ui->newMsg(path_len, from.name, text, offline_queue_len);
|
||||||
if (!_serial->isConnected()) {
|
if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::contactMessage); //buzz if enabled
|
||||||
_ui->notify(UIEventType::contactMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -528,11 +525,8 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
|||||||
uint8_t frame[1];
|
uint8_t frame[1];
|
||||||
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
frame[0] = PUSH_CODE_MSG_WAITING; // send push 'tickle'
|
||||||
_serial->writeFrame(frame, 1);
|
_serial->writeFrame(frame, 1);
|
||||||
} else {
|
|
||||||
#ifdef DISPLAY_CLASS
|
|
||||||
if (_ui) _ui->notify(UIEventType::channelMessage);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
// Get the channel name from the channel index
|
// Get the channel name from the channel index
|
||||||
const char *channel_name = "Unknown";
|
const char *channel_name = "Unknown";
|
||||||
@@ -540,7 +534,10 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
|
|||||||
if (getChannel(channel_idx, channel_details)) {
|
if (getChannel(channel_idx, channel_details)) {
|
||||||
channel_name = channel_details.name;
|
channel_name = channel_details.name;
|
||||||
}
|
}
|
||||||
if (_ui) _ui->newMsg(path_len, channel_name, text, offline_queue_len);
|
if (_ui) {
|
||||||
|
_ui->newMsg(path_len, channel_name, text, offline_queue_len);
|
||||||
|
if (!_prefs.buzzer_quiet) _ui->notify(UIEventType::channelMessage); //buzz if enabled
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -799,6 +796,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
|||||||
_prefs.bw = LORA_BW;
|
_prefs.bw = LORA_BW;
|
||||||
_prefs.cr = LORA_CR;
|
_prefs.cr = LORA_CR;
|
||||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||||
|
_prefs.buzzer_quiet = 0;
|
||||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||||
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
||||||
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
||||||
@@ -817,14 +815,14 @@ void MyMesh::begin(bool has_display) {
|
|||||||
_store->saveMainIdentity(self_id);
|
_store->saveMainIdentity(self_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if name is provided as a build flag, use that as default node name instead
|
||||||
|
#ifdef ADVERT_NAME
|
||||||
|
strcpy(_prefs.node_name, ADVERT_NAME);
|
||||||
|
#else
|
||||||
// use hex of first 4 bytes of identity public key as default node name
|
// use hex of first 4 bytes of identity public key as default node name
|
||||||
char pub_key_hex[10];
|
char pub_key_hex[10];
|
||||||
mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4);
|
mesh::Utils::toHex(pub_key_hex, self_id.pub_key, 4);
|
||||||
strcpy(_prefs.node_name, pub_key_hex);
|
strcpy(_prefs.node_name, pub_key_hex);
|
||||||
|
|
||||||
// if name is provided as a build flag, use that as default node name instead
|
|
||||||
#ifdef ADVERT_NAME
|
|
||||||
strcpy(_prefs.node_name, ADVERT_NAME);
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
@@ -838,6 +836,7 @@ void MyMesh::begin(bool has_display) {
|
|||||||
_prefs.sf = constrain(_prefs.sf, 5, 12);
|
_prefs.sf = constrain(_prefs.sf, 5, 12);
|
||||||
_prefs.cr = constrain(_prefs.cr, 5, 8);
|
_prefs.cr = constrain(_prefs.cr, 5, 8);
|
||||||
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
|
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
|
||||||
|
_prefs.buzzer_quiet = constrain(_prefs.buzzer_quiet, 0, 1); // Ensure boolean 0 or 1
|
||||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
_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.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||||
|
|
||||||
@@ -1295,6 +1294,9 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
#endif
|
#endif
|
||||||
} else if (cmd_frame[0] == CMD_IMPORT_PRIVATE_KEY && len >= 65) {
|
} else if (cmd_frame[0] == CMD_IMPORT_PRIVATE_KEY && len >= 65) {
|
||||||
#if ENABLE_PRIVATE_KEY_IMPORT
|
#if ENABLE_PRIVATE_KEY_IMPORT
|
||||||
|
if (!mesh::LocalIdentity::validatePrivateKey(&cmd_frame[1])) {
|
||||||
|
writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid key
|
||||||
|
} else {
|
||||||
mesh::LocalIdentity identity;
|
mesh::LocalIdentity identity;
|
||||||
identity.readFrom(&cmd_frame[1], 64);
|
identity.readFrom(&cmd_frame[1], 64);
|
||||||
if (_store->saveMainIdentity(identity)) {
|
if (_store->saveMainIdentity(identity)) {
|
||||||
@@ -1306,6 +1308,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
|||||||
} else {
|
} else {
|
||||||
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
writeErrFrame(ERR_CODE_FILE_IO_ERROR);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
writeDisabledFrame();
|
writeDisabledFrame();
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -151,9 +151,7 @@ void setup() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
#ifdef BLE_PIN_CODE
|
#ifdef BLE_PIN_CODE
|
||||||
char dev_name[32+16];
|
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
|
||||||
sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
|
|
||||||
serial_interface.begin(dev_name, the_mesh.getBLEPin());
|
|
||||||
#else
|
#else
|
||||||
serial_interface.begin(Serial);
|
serial_interface.begin(Serial);
|
||||||
#endif
|
#endif
|
||||||
@@ -199,9 +197,7 @@ void setup() {
|
|||||||
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
||||||
serial_interface.begin(TCP_PORT);
|
serial_interface.begin(TCP_PORT);
|
||||||
#elif defined(BLE_PIN_CODE)
|
#elif defined(BLE_PIN_CODE)
|
||||||
char dev_name[32+16];
|
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
|
||||||
sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
|
|
||||||
serial_interface.begin(dev_name, the_mesh.getBLEPin());
|
|
||||||
#elif defined(SERIAL_RX)
|
#elif defined(SERIAL_RX)
|
||||||
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
||||||
companion_serial.begin(115200);
|
companion_serial.begin(115200);
|
||||||
|
|||||||
@@ -744,7 +744,7 @@ void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
|||||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store),
|
_cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4), region_map(key_store), temp_map(key_store),
|
||||||
discover_limiter(4, 120), // max 4 every 2 minutes
|
discover_limiter(4, 120), // max 4 every 2 minutes
|
||||||
anon_limiter(4, 180) // max 4 every 3 minutes
|
anon_limiter(4, 180) // max 4 every 3 minutes
|
||||||
#if defined(WITH_RS232_BRIDGE)
|
#if defined(WITH_RS232_BRIDGE)
|
||||||
@@ -808,7 +808,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
_fs = fs;
|
_fs = fs;
|
||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
acl.load(_fs);
|
acl.load(_fs, self_id);
|
||||||
// TODO: key_store.begin();
|
// TODO: key_store.begin();
|
||||||
region_map.load(_fs);
|
region_map.load(_fs);
|
||||||
|
|
||||||
@@ -968,7 +968,6 @@ void MyMesh::formatPacketStatsReply(char *reply) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
||||||
self_id = new_id;
|
|
||||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
IdentityStore store(*_fs, "");
|
IdentityStore store(*_fs, "");
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
@@ -978,7 +977,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
|||||||
#else
|
#else
|
||||||
#error "need to define saveIdentity()"
|
#error "need to define saveIdentity()"
|
||||||
#endif
|
#endif
|
||||||
store.save("_main", self_id);
|
store.save("_main", new_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::clearStats() {
|
void MyMesh::clearStats() {
|
||||||
@@ -1069,8 +1068,8 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|||||||
|
|
||||||
const char* parts[4];
|
const char* parts[4];
|
||||||
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
int n = mesh::Utils::parseTextParts(command, parts, 4, ' ');
|
||||||
if (n == 1 && sender_timestamp == 0) {
|
if (n == 1) {
|
||||||
region_map.exportTo(Serial);
|
region_map.exportTo(reply, 160);
|
||||||
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
} else if (n >= 2 && strcmp(parts[1], "load") == 0) {
|
||||||
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
temp_map.resetFrom(region_map); // rebuild regions in a temp instance
|
||||||
memset(load_stack, 0, sizeof(load_stack));
|
memset(load_stack, 0, sizeof(load_stack));
|
||||||
@@ -1143,6 +1142,25 @@ void MyMesh::handleCommand(uint32_t sender_timestamp, char *command, char *reply
|
|||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Err - not found");
|
strcpy(reply, "Err - not found");
|
||||||
}
|
}
|
||||||
|
} else if (n >= 3 && strcmp(parts[1], "list") == 0) {
|
||||||
|
uint8_t mask = 0;
|
||||||
|
bool invert = false;
|
||||||
|
|
||||||
|
if (strcmp(parts[2], "allowed") == 0) {
|
||||||
|
mask = REGION_DENY_FLOOD;
|
||||||
|
invert = false; // list regions that DON'T have DENY flag
|
||||||
|
} else if (strcmp(parts[2], "denied") == 0) {
|
||||||
|
mask = REGION_DENY_FLOOD;
|
||||||
|
invert = true; // list regions that DO have DENY flag
|
||||||
|
} else {
|
||||||
|
strcpy(reply, "Err - use 'allowed' or 'denied'");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int len = region_map.exportNamesTo(reply, 160, mask, invert);
|
||||||
|
if (len == 0) {
|
||||||
|
strcpy(reply, "-none-");
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Err - ??");
|
strcpy(reply, "Err - ??");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,11 +86,11 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
unsigned long next_local_advert, next_flood_advert;
|
unsigned long next_local_advert, next_flood_advert;
|
||||||
bool _logging;
|
bool _logging;
|
||||||
NodePrefs _prefs;
|
NodePrefs _prefs;
|
||||||
|
ClientACL acl;
|
||||||
CommonCLI _cli;
|
CommonCLI _cli;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
uint8_t reply_path[MAX_PATH_SIZE];
|
uint8_t reply_path[MAX_PATH_SIZE];
|
||||||
int8_t reply_path_len;
|
int8_t reply_path_len;
|
||||||
ClientACL acl;
|
|
||||||
TransportKeyStore key_store;
|
TransportKeyStore key_store;
|
||||||
RegionMap region_map, temp_map;
|
RegionMap region_map, temp_map;
|
||||||
RegionEntry* load_stack[8];
|
RegionEntry* load_stack[8];
|
||||||
|
|||||||
@@ -587,7 +587,7 @@ void MyMesh::onAckRecv(mesh::Packet *packet, uint32_t ack_crc) {
|
|||||||
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondClock &ms, mesh::RNG &rng,
|
||||||
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
mesh::RTCClock &rtc, mesh::MeshTables &tables)
|
||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
_cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4) {
|
||||||
last_millis = 0;
|
last_millis = 0;
|
||||||
uptime_millis = 0;
|
uptime_millis = 0;
|
||||||
next_local_advert = next_flood_advert = 0;
|
next_local_advert = next_flood_advert = 0;
|
||||||
@@ -637,7 +637,7 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
|||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
|
|
||||||
acl.load(_fs);
|
acl.load(_fs, self_id);
|
||||||
|
|
||||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||||
@@ -720,7 +720,6 @@ void MyMesh::setTxPower(uint8_t power_dbm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
||||||
self_id = new_id;
|
|
||||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
IdentityStore store(*_fs, "");
|
IdentityStore store(*_fs, "");
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
@@ -730,7 +729,7 @@ void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
|||||||
#else
|
#else
|
||||||
#error "need to define saveIdentity()"
|
#error "need to define saveIdentity()"
|
||||||
#endif
|
#endif
|
||||||
store.save("_main", self_id);
|
store.save("_main", new_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyMesh::clearStats() {
|
void MyMesh::clearStats() {
|
||||||
@@ -815,7 +814,7 @@ void MyMesh::loop() {
|
|||||||
if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) {
|
if (c->extra.room.pending_ack && millisHasNowPassed(c->extra.room.ack_timeout)) {
|
||||||
c->extra.room.push_failures++;
|
c->extra.room.push_failures++;
|
||||||
c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry)
|
c->extra.room.pending_ack = 0; // reset (TODO: keep prev expected_ack's in a list, incase they arrive LATER, after we retry)
|
||||||
MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->push_failures);
|
MESH_DEBUG_PRINTLN("pending ACK timed out: push_failures: %d", (uint32_t)c->extra.room.push_failures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check next Round-Robin client, and sync next new post
|
// check next Round-Robin client, and sync next new post
|
||||||
|
|||||||
@@ -94,8 +94,8 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
|||||||
unsigned long next_local_advert, next_flood_advert;
|
unsigned long next_local_advert, next_flood_advert;
|
||||||
bool _logging;
|
bool _logging;
|
||||||
NodePrefs _prefs;
|
NodePrefs _prefs;
|
||||||
CommonCLI _cli;
|
|
||||||
ClientACL acl;
|
ClientACL acl;
|
||||||
|
CommonCLI _cli;
|
||||||
unsigned long dirty_contacts_expiry;
|
unsigned long dirty_contacts_expiry;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
unsigned long next_push;
|
unsigned long next_push;
|
||||||
|
|||||||
@@ -695,7 +695,7 @@ void SensorMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
|||||||
|
|
||||||
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::MeshTables& tables)
|
||||||
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
: mesh::Mesh(radio, ms, rng, rtc, *new StaticPoolPacketManager(32), tables),
|
||||||
_cli(board, rtc, sensors, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
_cli(board, rtc, sensors, acl, &_prefs, this), telemetry(MAX_PACKET_PAYLOAD - 4)
|
||||||
{
|
{
|
||||||
next_local_advert = next_flood_advert = 0;
|
next_local_advert = next_flood_advert = 0;
|
||||||
dirty_contacts_expiry = 0;
|
dirty_contacts_expiry = 0;
|
||||||
@@ -736,7 +736,7 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
|||||||
// load persisted prefs
|
// load persisted prefs
|
||||||
_cli.loadPrefs(_fs);
|
_cli.loadPrefs(_fs);
|
||||||
|
|
||||||
acl.load(_fs);
|
acl.load(_fs, self_id);
|
||||||
|
|
||||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||||
@@ -765,7 +765,6 @@ bool SensorMesh::formatFileSystem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) {
|
void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) {
|
||||||
self_id = new_id;
|
|
||||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||||
IdentityStore store(*_fs, "");
|
IdentityStore store(*_fs, "");
|
||||||
#elif defined(ESP32)
|
#elif defined(ESP32)
|
||||||
@@ -775,7 +774,7 @@ void SensorMesh::saveIdentity(const mesh::LocalIdentity& new_id) {
|
|||||||
#else
|
#else
|
||||||
#error "need to define saveIdentity()"
|
#error "need to define saveIdentity()"
|
||||||
#endif
|
#endif
|
||||||
store.save("_main", self_id);
|
store.save("_main", new_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
void SensorMesh::applyTempRadioParams(float freq, float bw, uint8_t sf, uint8_t cr, int timeout_mins) {
|
||||||
|
|||||||
@@ -133,9 +133,9 @@ private:
|
|||||||
FILESYSTEM* _fs;
|
FILESYSTEM* _fs;
|
||||||
unsigned long next_local_advert, next_flood_advert;
|
unsigned long next_local_advert, next_flood_advert;
|
||||||
NodePrefs _prefs;
|
NodePrefs _prefs;
|
||||||
|
ClientACL acl;
|
||||||
CommonCLI _cli;
|
CommonCLI _cli;
|
||||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||||
ClientACL acl;
|
|
||||||
unsigned long dirty_contacts_expiry;
|
unsigned long dirty_contacts_expiry;
|
||||||
CayenneLPP telemetry;
|
CayenneLPP telemetry;
|
||||||
uint32_t last_read_time;
|
uint32_t last_read_time;
|
||||||
|
|||||||
@@ -48,6 +48,50 @@ LocalIdentity::LocalIdentity(RNG* rng) {
|
|||||||
ed25519_create_keypair(pub_key, prv_key, seed);
|
ed25519_create_keypair(pub_key, prv_key, seed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LocalIdentity::validatePrivateKey(const uint8_t prv[64]) {
|
||||||
|
uint8_t pub[32];
|
||||||
|
ed25519_derive_pub(pub, prv); // derive public key from given private key
|
||||||
|
|
||||||
|
// disallow 00 or FF prefixed public keys
|
||||||
|
if (pub[0] == 0x00 || pub[0] == 0xFF) return false;
|
||||||
|
|
||||||
|
// known good test client keypair
|
||||||
|
const uint8_t test_client_prv[64] = {
|
||||||
|
0x70, 0x65, 0xe1, 0x8f, 0xd9, 0xfa, 0xbb, 0x70,
|
||||||
|
0xc1, 0xed, 0x90, 0xdc, 0xa1, 0x99, 0x07, 0xde,
|
||||||
|
0x69, 0x8c, 0x88, 0xb7, 0x09, 0xea, 0x14, 0x6e,
|
||||||
|
0xaf, 0xd9, 0x3d, 0x9b, 0x83, 0x0c, 0x7b, 0x60,
|
||||||
|
0xc4, 0x68, 0x11, 0x93, 0xc7, 0x9b, 0xbc, 0x39,
|
||||||
|
0x94, 0x5b, 0xa8, 0x06, 0x41, 0x04, 0xbb, 0x61,
|
||||||
|
0x8f, 0x8f, 0xd7, 0xa8, 0x4a, 0x0a, 0xf6, 0xf5,
|
||||||
|
0x70, 0x33, 0xd6, 0xe8, 0xdd, 0xcd, 0x64, 0x71
|
||||||
|
};
|
||||||
|
const uint8_t test_client_pub[32] = {
|
||||||
|
0x1e, 0xc7, 0x71, 0x75, 0xb0, 0x91, 0x8e, 0xd2,
|
||||||
|
0x06, 0xf9, 0xae, 0x04, 0xec, 0x13, 0x6d, 0x6d,
|
||||||
|
0x5d, 0x43, 0x15, 0xbb, 0x26, 0x30, 0x54, 0x27,
|
||||||
|
0xf6, 0x45, 0xb4, 0x92, 0xe9, 0x35, 0x0c, 0x10
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t ss1[32], ss2[32];
|
||||||
|
|
||||||
|
// shared secret we calculte from test client pubkey and given private key
|
||||||
|
ed25519_key_exchange(ss1, test_client_pub, prv);
|
||||||
|
|
||||||
|
// shared secret they calculate from our derived public key and test client private key
|
||||||
|
ed25519_key_exchange(ss2, pub, test_client_prv);
|
||||||
|
|
||||||
|
// check that both shared secrets match
|
||||||
|
if (memcmp(ss1, ss2, 32) != 0) return false;
|
||||||
|
|
||||||
|
// reject all-zero shared secret
|
||||||
|
for (int i = 0; i < 32; i++) {
|
||||||
|
if (ss1[i] != 0) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
bool LocalIdentity::readFrom(Stream& s) {
|
bool LocalIdentity::readFrom(Stream& s) {
|
||||||
bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
bool success = (s.readBytes(pub_key, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
||||||
success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
|
success = success && (s.readBytes(prv_key, PRV_KEY_SIZE) == PRV_KEY_SIZE);
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const;
|
void calcSharedSecret(uint8_t* secret, const uint8_t* other_pub_key) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Validates that a given private key can be used for ECDH / shared-secret operations.
|
||||||
|
* \param prv IN - the private key to validate (must be PRV_KEY_SIZE bytes)
|
||||||
|
* \returns true, if the private key is valid for login.
|
||||||
|
*/
|
||||||
|
static bool validatePrivateKey(const uint8_t prv[64]);
|
||||||
|
|
||||||
bool readFrom(Stream& s);
|
bool readFrom(Stream& s);
|
||||||
bool writeTo(Stream& s) const;
|
bool writeTo(Stream& s) const;
|
||||||
void printTo(Stream& s) const;
|
void printTo(Stream& s) const;
|
||||||
|
|||||||
@@ -56,6 +56,14 @@ public:
|
|||||||
virtual void setGpio(uint32_t values) {}
|
virtual void setGpio(uint32_t values) {}
|
||||||
virtual uint8_t getStartupReason() const = 0;
|
virtual uint8_t getStartupReason() const = 0;
|
||||||
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
|
virtual bool startOTAUpdate(const char* id, char reply[]) { return false; } // not supported
|
||||||
|
|
||||||
|
// Power management interface (boards with power management override these)
|
||||||
|
virtual bool isExternalPowered() { 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"; }
|
||||||
|
virtual uint8_t getShutdownReason() const { return 0; }
|
||||||
|
virtual const char* getShutdownReasonString(uint8_t reason) { return "Not available"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
|||||||
}
|
}
|
||||||
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
putBlobByKey(id.pub_key, PUB_KEY_SIZE, temp_buf, plen);
|
||||||
|
|
||||||
bool is_new = false;
|
bool is_new = false; // true = not in contacts[], false = exists in contacts[]
|
||||||
if (from == NULL) {
|
if (from == NULL) {
|
||||||
if (!shouldAutoAddContactType(parser.getType())) {
|
if (!shouldAutoAddContactType(parser.getType())) {
|
||||||
ContactInfo ci;
|
ContactInfo ci;
|
||||||
@@ -142,7 +142,6 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
is_new = true;
|
|
||||||
from = allocateContactSlot();
|
from = allocateContactSlot();
|
||||||
if (from == NULL) {
|
if (from == NULL) {
|
||||||
ContactInfo ci;
|
ContactInfo ci;
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientACL::load(FILESYSTEM* _fs) {
|
void ClientACL::load(FILESYSTEM* fs, const mesh::LocalIdentity& self_id) {
|
||||||
|
_fs = fs;
|
||||||
num_clients = 0;
|
num_clients = 0;
|
||||||
if (_fs->exists("/s_contacts")) {
|
if (_fs->exists("/s_contacts")) {
|
||||||
#if defined(RP2040_PLATFORM)
|
#if defined(RP2040_PLATFORM)
|
||||||
@@ -34,11 +35,12 @@ void ClientACL::load(FILESYSTEM* _fs) {
|
|||||||
success = success && (file.read(unused, 2) == 2);
|
success = success && (file.read(unused, 2) == 2);
|
||||||
success = success && (file.read((uint8_t *)&c.out_path_len, 1) == 1);
|
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.out_path, 64) == 64);
|
||||||
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE);
|
success = success && (file.read(c.shared_secret, PUB_KEY_SIZE) == PUB_KEY_SIZE); // will be recalculated below
|
||||||
|
|
||||||
if (!success) break; // EOF
|
if (!success) break; // EOF
|
||||||
|
|
||||||
c.id = mesh::Identity(pub_key);
|
c.id = mesh::Identity(pub_key);
|
||||||
|
self_id.calcSharedSecret(c.shared_secret, pub_key); // recalculate shared secrets in case our private key changed
|
||||||
if (num_clients < MAX_CLIENTS) {
|
if (num_clients < MAX_CLIENTS) {
|
||||||
clients[num_clients++] = c;
|
clients[num_clients++] = c;
|
||||||
} else {
|
} else {
|
||||||
@@ -50,7 +52,8 @@ void ClientACL::load(FILESYSTEM* _fs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) {
|
void ClientACL::save(FILESYSTEM* fs, bool (*filter)(ClientInfo*)) {
|
||||||
|
_fs = fs;
|
||||||
File file = openWrite(_fs, "/s_contacts");
|
File file = openWrite(_fs, "/s_contacts");
|
||||||
if (file) {
|
if (file) {
|
||||||
uint8_t unused[2];
|
uint8_t unused[2];
|
||||||
@@ -74,6 +77,16 @@ void ClientACL::save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ClientACL::clear() {
|
||||||
|
if (!_fs) return false; // no filesystem, nothing to clear
|
||||||
|
if (_fs->exists("/s_contacts")) {
|
||||||
|
_fs->remove("/s_contacts");
|
||||||
|
}
|
||||||
|
memset(clients, 0, sizeof(clients));
|
||||||
|
num_clients = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) {
|
ClientInfo* ClientACL::getClient(const uint8_t* pubkey, int key_len) {
|
||||||
for (int i = 0; i < num_clients; i++) {
|
for (int i = 0; i < num_clients; i++) {
|
||||||
if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known
|
if (memcmp(pubkey, clients[i].id.pub_key, key_len) == 0) return &clients[i]; // already known
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ struct ClientInfo {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
class ClientACL {
|
class ClientACL {
|
||||||
|
FILESYSTEM* _fs;
|
||||||
ClientInfo clients[MAX_CLIENTS];
|
ClientInfo clients[MAX_CLIENTS];
|
||||||
int num_clients;
|
int num_clients;
|
||||||
|
|
||||||
@@ -44,8 +45,9 @@ public:
|
|||||||
memset(clients, 0, sizeof(clients));
|
memset(clients, 0, sizeof(clients));
|
||||||
num_clients = 0;
|
num_clients = 0;
|
||||||
}
|
}
|
||||||
void load(FILESYSTEM* _fs);
|
void load(FILESYSTEM* _fs, const mesh::LocalIdentity& self_id);
|
||||||
void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL);
|
void save(FILESYSTEM* _fs, bool (*filter)(ClientInfo*)=NULL);
|
||||||
|
bool clear();
|
||||||
|
|
||||||
ClientInfo* getClient(const uint8_t* pubkey, int key_len);
|
ClientInfo* getClient(const uint8_t* pubkey, int key_len);
|
||||||
ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms);
|
ClientInfo* putClient(const mesh::Identity& id, uint8_t init_perms);
|
||||||
|
|||||||
@@ -364,6 +364,33 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
|||||||
} else {
|
} else {
|
||||||
sprintf(reply, "> %.3f", adc_mult);
|
sprintf(reply, "> %.3f", adc_mult);
|
||||||
}
|
}
|
||||||
|
// Power management commands
|
||||||
|
} else if (memcmp(config, "pwrmgt.support", 14) == 0) {
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
strcpy(reply, "> supported");
|
||||||
|
#else
|
||||||
|
strcpy(reply, "> unsupported");
|
||||||
|
#endif
|
||||||
|
} else if (memcmp(config, "pwrmgt.source", 13) == 0) {
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
strcpy(reply, _board->isExternalPowered() ? "> external" : "> battery");
|
||||||
|
#else
|
||||||
|
strcpy(reply, "ERROR: Power management not supported");
|
||||||
|
#endif
|
||||||
|
} else if (memcmp(config, "pwrmgt.bootreason", 17) == 0) {
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
sprintf(reply, "> Reset: %s; Shutdown: %s",
|
||||||
|
_board->getResetReasonString(_board->getResetReason()),
|
||||||
|
_board->getShutdownReasonString(_board->getShutdownReason()));
|
||||||
|
#else
|
||||||
|
strcpy(reply, "ERROR: Power management not supported");
|
||||||
|
#endif
|
||||||
|
} else if (memcmp(config, "pwrmgt.bootmv", 13) == 0) {
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
sprintf(reply, "> %u mV", _board->getBootVoltage());
|
||||||
|
#else
|
||||||
|
strcpy(reply, "ERROR: Power management not supported");
|
||||||
|
#endif
|
||||||
} else {
|
} else {
|
||||||
sprintf(reply, "??: %s", config);
|
sprintf(reply, "??: %s", config);
|
||||||
}
|
}
|
||||||
@@ -416,17 +443,18 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
|||||||
StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
|
StrHelper::strncpy(_prefs->guest_password, &config[15], sizeof(_prefs->guest_password));
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else if (sender_timestamp == 0 &&
|
} else if (memcmp(config, "prv.key ", 8) == 0) {
|
||||||
memcmp(config, "prv.key ", 8) == 0) { // from serial command line only
|
|
||||||
uint8_t prv_key[PRV_KEY_SIZE];
|
uint8_t prv_key[PRV_KEY_SIZE];
|
||||||
bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
|
bool success = mesh::Utils::fromHex(prv_key, PRV_KEY_SIZE, &config[8]);
|
||||||
if (success) {
|
// only allow rekey if key is valid
|
||||||
|
if (success && mesh::LocalIdentity::validatePrivateKey(prv_key)) {
|
||||||
mesh::LocalIdentity new_id;
|
mesh::LocalIdentity new_id;
|
||||||
new_id.readFrom(prv_key, PRV_KEY_SIZE);
|
new_id.readFrom(prv_key, PRV_KEY_SIZE);
|
||||||
_callbacks->saveIdentity(new_id);
|
_callbacks->saveIdentity(new_id);
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK, reboot to apply! New pubkey: ");
|
||||||
|
mesh::Utils::toHex(&reply[33], new_id.pub_key, PUB_KEY_SIZE);
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Error, invalid key");
|
strcpy(reply, "Error, bad key");
|
||||||
}
|
}
|
||||||
} else if (memcmp(config, "name ", 5) == 0) {
|
} else if (memcmp(config, "name ", 5) == 0) {
|
||||||
if (isValidName(&config[5])) {
|
if (isValidName(&config[5])) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "Mesh.h"
|
#include "Mesh.h"
|
||||||
#include <helpers/IdentityStore.h>
|
#include <helpers/IdentityStore.h>
|
||||||
#include <helpers/SensorManager.h>
|
#include <helpers/SensorManager.h>
|
||||||
|
#include <helpers/ClientACL.h>
|
||||||
|
|
||||||
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
|
#if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE)
|
||||||
#define WITH_BRIDGE
|
#define WITH_BRIDGE
|
||||||
@@ -94,6 +95,7 @@ class CommonCLI {
|
|||||||
CommonCLICallbacks* _callbacks;
|
CommonCLICallbacks* _callbacks;
|
||||||
mesh::MainBoard* _board;
|
mesh::MainBoard* _board;
|
||||||
SensorManager* _sensors;
|
SensorManager* _sensors;
|
||||||
|
ClientACL* _acl;
|
||||||
char tmp[PRV_KEY_SIZE*2 + 4];
|
char tmp[PRV_KEY_SIZE*2 + 4];
|
||||||
|
|
||||||
mesh::RTCClock* getRTCClock() { return _rtc; }
|
mesh::RTCClock* getRTCClock() { return _rtc; }
|
||||||
@@ -101,8 +103,8 @@ class CommonCLI {
|
|||||||
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
void loadPrefsInt(FILESYSTEM* _fs, const char* filename);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
CommonCLI(mesh::MainBoard& board, mesh::RTCClock& rtc, SensorManager& sensors, ClientACL& acl, NodePrefs* prefs, CommonCLICallbacks* callbacks)
|
||||||
: _board(&board), _rtc(&rtc), _sensors(&sensors), _prefs(prefs), _callbacks(callbacks) { }
|
: _board(&board), _rtc(&rtc), _sensors(&sensors), _acl(&acl), _prefs(prefs), _callbacks(callbacks) { }
|
||||||
|
|
||||||
void loadPrefs(FILESYSTEM* _fs);
|
void loadPrefs(FILESYSTEM* _fs);
|
||||||
void savePrefs(FILESYSTEM* _fs);
|
void savePrefs(FILESYSTEM* _fs);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "NRF52Board.h"
|
#include "NRF52Board.h"
|
||||||
|
|
||||||
#include <bluefruit.h>
|
#include <bluefruit.h>
|
||||||
|
#include <nrf_soc.h>
|
||||||
|
|
||||||
static BLEDfu bledfu;
|
static BLEDfu bledfu;
|
||||||
|
|
||||||
@@ -21,6 +22,222 @@ void NRF52Board::begin() {
|
|||||||
startup_reason = BD_STARTUP_NORMAL;
|
startup_reason = BD_STARTUP_NORMAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
#include "nrf.h"
|
||||||
|
|
||||||
|
// Power Management global variables
|
||||||
|
uint32_t g_nrf52_reset_reason = 0; // Reset/Startup reason
|
||||||
|
uint8_t g_nrf52_shutdown_reason = 0; // Shutdown reason
|
||||||
|
|
||||||
|
// Early constructor - runs before SystemInit() clears the registers
|
||||||
|
// Priority 101 ensures this runs before SystemInit (102) and before
|
||||||
|
// any C++ static constructors (default 65535)
|
||||||
|
static void __attribute__((constructor(101))) nrf52_early_reset_capture() {
|
||||||
|
g_nrf52_reset_reason = NRF_POWER->RESETREAS;
|
||||||
|
g_nrf52_shutdown_reason = NRF_POWER->GPREGRET2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NRF52Board::initPowerMgr() {
|
||||||
|
// Copy early-captured register values
|
||||||
|
reset_reason = g_nrf52_reset_reason;
|
||||||
|
shutdown_reason = g_nrf52_shutdown_reason;
|
||||||
|
boot_voltage_mv = 0; // Will be set by checkBootVoltage()
|
||||||
|
|
||||||
|
// Clear registers for next boot
|
||||||
|
// Note: At this point SoftDevice may or may not be enabled
|
||||||
|
uint8_t sd_enabled = 0;
|
||||||
|
sd_softdevice_is_enabled(&sd_enabled);
|
||||||
|
if (sd_enabled) {
|
||||||
|
sd_power_reset_reason_clr(0xFFFFFFFF);
|
||||||
|
sd_power_gpregret_clr(1, 0xFF);
|
||||||
|
} else {
|
||||||
|
NRF_POWER->RESETREAS = 0xFFFFFFFF; // Write 1s to clear
|
||||||
|
NRF_POWER->GPREGRET2 = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log reset/shutdown info
|
||||||
|
if (shutdown_reason != SHUTDOWN_REASON_NONE) {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX); Shutdown = %s (0x%02X)",
|
||||||
|
getResetReasonString(reset_reason), (unsigned long)reset_reason,
|
||||||
|
getShutdownReasonString(shutdown_reason), shutdown_reason);
|
||||||
|
} else {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: Reset = %s (0x%lX)",
|
||||||
|
getResetReasonString(reset_reason), (unsigned long)reset_reason);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NRF52Board::isExternalPowered() {
|
||||||
|
// Check if SoftDevice is enabled before using its API
|
||||||
|
uint8_t sd_enabled = 0;
|
||||||
|
sd_softdevice_is_enabled(&sd_enabled);
|
||||||
|
|
||||||
|
if (sd_enabled) {
|
||||||
|
uint32_t usb_status;
|
||||||
|
sd_power_usbregstatus_get(&usb_status);
|
||||||
|
return (usb_status & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
|
||||||
|
} else {
|
||||||
|
return (NRF_POWER->USBREGSTATUS & POWER_USBREGSTATUS_VBUSDETECT_Msk) != 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* NRF52Board::getResetReasonString(uint32_t reason) {
|
||||||
|
if (reason & POWER_RESETREAS_RESETPIN_Msk) return "Reset Pin";
|
||||||
|
if (reason & POWER_RESETREAS_DOG_Msk) return "Watchdog";
|
||||||
|
if (reason & POWER_RESETREAS_SREQ_Msk) return "Soft Reset";
|
||||||
|
if (reason & POWER_RESETREAS_LOCKUP_Msk) return "CPU Lockup";
|
||||||
|
#ifdef POWER_RESETREAS_LPCOMP_Msk
|
||||||
|
if (reason & POWER_RESETREAS_LPCOMP_Msk) return "Wake from LPCOMP";
|
||||||
|
#endif
|
||||||
|
#ifdef POWER_RESETREAS_VBUS_Msk
|
||||||
|
if (reason & POWER_RESETREAS_VBUS_Msk) return "Wake from VBUS";
|
||||||
|
#endif
|
||||||
|
#ifdef POWER_RESETREAS_OFF_Msk
|
||||||
|
if (reason & POWER_RESETREAS_OFF_Msk) return "Wake from GPIO";
|
||||||
|
#endif
|
||||||
|
#ifdef POWER_RESETREAS_DIF_Msk
|
||||||
|
if (reason & POWER_RESETREAS_DIF_Msk) return "Debug Interface";
|
||||||
|
#endif
|
||||||
|
return "Cold Boot";
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* NRF52Board::getShutdownReasonString(uint8_t reason) {
|
||||||
|
switch (reason) {
|
||||||
|
case SHUTDOWN_REASON_LOW_VOLTAGE: return "Low Voltage";
|
||||||
|
case SHUTDOWN_REASON_USER: return "User Request";
|
||||||
|
case SHUTDOWN_REASON_BOOT_PROTECT: return "Boot Protection";
|
||||||
|
}
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool NRF52Board::checkBootVoltage(const PowerMgtConfig* config) {
|
||||||
|
initPowerMgr();
|
||||||
|
|
||||||
|
// Read boot voltage
|
||||||
|
boot_voltage_mv = getBattMilliVolts();
|
||||||
|
|
||||||
|
if (config->voltage_bootlock == 0) return true; // Protection disabled
|
||||||
|
|
||||||
|
// Skip check if externally powered
|
||||||
|
if (isExternalPowered()) {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: Boot check skipped (external power)");
|
||||||
|
boot_voltage_mv = getBattMilliVolts();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage = %u mV (threshold = %u mV)",
|
||||||
|
boot_voltage_mv, config->voltage_bootlock);
|
||||||
|
|
||||||
|
// Only trigger shutdown if reading is valid (>1000mV) AND below threshold
|
||||||
|
// This prevents spurious shutdowns on ADC glitches or uninitialized reads
|
||||||
|
if (boot_voltage_mv > 1000 && boot_voltage_mv < config->voltage_bootlock) {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: Boot voltage too low - entering protective shutdown");
|
||||||
|
|
||||||
|
initiateShutdown(SHUTDOWN_REASON_BOOT_PROTECT);
|
||||||
|
return false; // Should never reach this
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NRF52Board::initiateShutdown(uint8_t reason) {
|
||||||
|
enterSystemOff(reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NRF52Board::enterSystemOff(uint8_t reason) {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: Entering SYSTEMOFF (%s)", getShutdownReasonString(reason));
|
||||||
|
|
||||||
|
// Record shutdown reason in GPREGRET2
|
||||||
|
uint8_t sd_enabled = 0;
|
||||||
|
sd_softdevice_is_enabled(&sd_enabled);
|
||||||
|
if (sd_enabled) {
|
||||||
|
sd_power_gpregret_clr(1, 0xFF);
|
||||||
|
sd_power_gpregret_set(1, reason);
|
||||||
|
} else {
|
||||||
|
NRF_POWER->GPREGRET2 = reason;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush serial buffers
|
||||||
|
Serial.flush();
|
||||||
|
delay(100);
|
||||||
|
|
||||||
|
// Enter SYSTEMOFF
|
||||||
|
if (sd_enabled) {
|
||||||
|
uint32_t err = sd_power_system_off();
|
||||||
|
if (err == NRF_ERROR_SOFTDEVICE_NOT_ENABLED) { //SoftDevice not enabled
|
||||||
|
sd_enabled = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sd_enabled) {
|
||||||
|
// SoftDevice not available; write directly to POWER->SYSTEMOFF
|
||||||
|
NRF_POWER->SYSTEMOFF = POWER_SYSTEMOFF_SYSTEMOFF_Enter;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we get here, something went wrong. Reset to recover.
|
||||||
|
NVIC_SystemReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void NRF52Board::configureVoltageWake(uint8_t ain_channel, uint8_t refsel) {
|
||||||
|
// LPCOMP is not managed by SoftDevice - direct register access required
|
||||||
|
// Halt and disable before reconfiguration
|
||||||
|
NRF_LPCOMP->TASKS_STOP = 1;
|
||||||
|
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Disabled;
|
||||||
|
|
||||||
|
// Select analog input (AIN0-7 maps to PSEL 0-7)
|
||||||
|
NRF_LPCOMP->PSEL = ((uint32_t)ain_channel << LPCOMP_PSEL_PSEL_Pos) & LPCOMP_PSEL_PSEL_Msk;
|
||||||
|
|
||||||
|
// Reference: REFSEL (0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16)
|
||||||
|
NRF_LPCOMP->REFSEL = ((uint32_t)refsel << LPCOMP_REFSEL_REFSEL_Pos) & LPCOMP_REFSEL_REFSEL_Msk;
|
||||||
|
|
||||||
|
// Detect UP events (voltage rises above threshold for battery recovery)
|
||||||
|
NRF_LPCOMP->ANADETECT = LPCOMP_ANADETECT_ANADETECT_Up;
|
||||||
|
|
||||||
|
// Enable 50mV hysteresis for noise immunity
|
||||||
|
NRF_LPCOMP->HYST = LPCOMP_HYST_HYST_Hyst50mV;
|
||||||
|
|
||||||
|
// Clear stale events/interrupts before enabling wake
|
||||||
|
NRF_LPCOMP->EVENTS_READY = 0;
|
||||||
|
NRF_LPCOMP->EVENTS_DOWN = 0;
|
||||||
|
NRF_LPCOMP->EVENTS_UP = 0;
|
||||||
|
NRF_LPCOMP->EVENTS_CROSS = 0;
|
||||||
|
|
||||||
|
NRF_LPCOMP->INTENCLR = 0xFFFFFFFF;
|
||||||
|
NRF_LPCOMP->INTENSET = LPCOMP_INTENSET_UP_Msk;
|
||||||
|
|
||||||
|
// Enable LPCOMP
|
||||||
|
NRF_LPCOMP->ENABLE = LPCOMP_ENABLE_ENABLE_Enabled;
|
||||||
|
NRF_LPCOMP->TASKS_START = 1;
|
||||||
|
|
||||||
|
// Wait for comparator to settle before entering SYSTEMOFF
|
||||||
|
for (uint8_t i = 0; i < 20 && !NRF_LPCOMP->EVENTS_READY; i++) {
|
||||||
|
delayMicroseconds(50);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (refsel == 7) {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=ARef)", ain_channel);
|
||||||
|
} else if (refsel <= 6) {
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/8 VDD)",
|
||||||
|
ain_channel, refsel + 1);
|
||||||
|
} else {
|
||||||
|
uint8_t ref_num = (uint8_t)((refsel - 8) * 2 + 1);
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: LPCOMP wake configured (AIN%d, ref=%d/16 VDD)",
|
||||||
|
ain_channel, ref_num);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure VBUS (USB power) wake alongside LPCOMP
|
||||||
|
uint8_t sd_enabled = 0;
|
||||||
|
sd_softdevice_is_enabled(&sd_enabled);
|
||||||
|
if (sd_enabled) {
|
||||||
|
sd_power_usbdetected_enable(1);
|
||||||
|
} else {
|
||||||
|
NRF_POWER->EVENTS_USBDETECTED = 0;
|
||||||
|
NRF_POWER->INTENSET = POWER_INTENSET_USBDETECTED_Msk;
|
||||||
|
}
|
||||||
|
|
||||||
|
MESH_DEBUG_PRINTLN("PWRMGT: VBUS wake configured");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
void NRF52BoardDCDC::begin() {
|
void NRF52BoardDCDC::begin() {
|
||||||
NRF52Board::begin();
|
NRF52Board::begin();
|
||||||
|
|
||||||
|
|||||||
@@ -5,15 +5,58 @@
|
|||||||
|
|
||||||
#if defined(NRF52_PLATFORM)
|
#if defined(NRF52_PLATFORM)
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
// Shutdown Reason Codes (stored in GPREGRET before SYSTEMOFF)
|
||||||
|
#define SHUTDOWN_REASON_NONE 0x00
|
||||||
|
#define SHUTDOWN_REASON_LOW_VOLTAGE 0x4C // 'L' - Runtime low voltage threshold
|
||||||
|
#define SHUTDOWN_REASON_USER 0x55 // 'U' - User requested powerOff()
|
||||||
|
#define SHUTDOWN_REASON_BOOT_PROTECT 0x42 // 'B' - Boot voltage protection
|
||||||
|
|
||||||
|
// Boards provide this struct with their hardware-specific settings and callbacks.
|
||||||
|
struct PowerMgtConfig {
|
||||||
|
// LPCOMP wake configuration (for voltage recovery from SYSTEMOFF)
|
||||||
|
uint8_t lpcomp_ain_channel; // AIN0-7 for voltage sensing pin
|
||||||
|
uint8_t lpcomp_refsel; // REFSEL value: 0-6=1/8..7/8, 7=ARef, 8-15=1/16..15/16
|
||||||
|
|
||||||
|
// Boot protection voltage threshold (millivolts)
|
||||||
|
// Set to 0 to disable boot protection
|
||||||
|
uint16_t voltage_bootlock;
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
class NRF52Board : public mesh::MainBoard {
|
class NRF52Board : public mesh::MainBoard {
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
void initPowerMgr();
|
||||||
|
#endif
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
uint8_t startup_reason;
|
uint8_t startup_reason;
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
uint32_t reset_reason; // RESETREAS register value
|
||||||
|
uint8_t shutdown_reason; // GPREGRET value (why we entered last SYSTEMOFF)
|
||||||
|
uint16_t boot_voltage_mv; // Battery voltage at boot (millivolts)
|
||||||
|
|
||||||
|
bool checkBootVoltage(const PowerMgtConfig* config);
|
||||||
|
void enterSystemOff(uint8_t reason);
|
||||||
|
void configureVoltageWake(uint8_t ain_channel, uint8_t refsel);
|
||||||
|
virtual void initiateShutdown(uint8_t reason);
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual void begin();
|
virtual void begin();
|
||||||
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
||||||
virtual float getMCUTemperature() override;
|
virtual float getMCUTemperature() override;
|
||||||
virtual void reboot() override { NVIC_SystemReset(); }
|
virtual void reboot() override { NVIC_SystemReset(); }
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
bool isExternalPowered() override;
|
||||||
|
uint16_t getBootVoltage() override { return boot_voltage_mv; }
|
||||||
|
virtual uint32_t getResetReason() const override { return reset_reason; }
|
||||||
|
uint8_t getShutdownReason() const override { return shutdown_reason; }
|
||||||
|
const char* getResetReasonString(uint32_t reason) override;
|
||||||
|
const char* getShutdownReasonString(uint8_t reason) override;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -2,6 +2,45 @@
|
|||||||
#include <helpers/TxtDataHelpers.h>
|
#include <helpers/TxtDataHelpers.h>
|
||||||
#include <SHA256.h>
|
#include <SHA256.h>
|
||||||
|
|
||||||
|
// helper class for region map exporter, we emulate Stream with a safe buffer writer.
|
||||||
|
|
||||||
|
class BufStream : public Stream {
|
||||||
|
public:
|
||||||
|
BufStream(char *buf, size_t max)
|
||||||
|
: _buf(buf), _max(max), _pos(0) {
|
||||||
|
if (_max > 0) _buf[0] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(uint8_t c) override {
|
||||||
|
if (_pos + 1 >= _max) return 0;
|
||||||
|
_buf[_pos++] = c;
|
||||||
|
_buf[_pos] = 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write(const uint8_t *buffer, size_t size) override {
|
||||||
|
size_t written = 0;
|
||||||
|
while (written < size) {
|
||||||
|
if (!write(buffer[written])) break;
|
||||||
|
written++;
|
||||||
|
}
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
int available() override { return 0; }
|
||||||
|
int read() override { return -1; }
|
||||||
|
int peek() override { return -1; }
|
||||||
|
void flush() override {}
|
||||||
|
|
||||||
|
size_t length() const { return _pos; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
char *_buf;
|
||||||
|
size_t _max;
|
||||||
|
size_t _pos;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
||||||
next_id = 1; num_regions = 0; home_id = 0;
|
next_id = 1; num_regions = 0; home_id = 0;
|
||||||
wildcard.id = wildcard.parent = 0;
|
wildcard.id = wildcard.parent = 0;
|
||||||
@@ -11,7 +50,11 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
|||||||
|
|
||||||
bool RegionMap::is_name_char(uint8_t c) {
|
bool RegionMap::is_name_char(uint8_t c) {
|
||||||
// accept all alpha-num or accented characters, but exclude most punctuation chars
|
// accept all alpha-num or accented characters, but exclude most punctuation chars
|
||||||
return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A';
|
return c == '-' || c == '$' || c == '#' || (c >= '0' && c <= '9') || c >= 'A';
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* skip_hash(const char* name) {
|
||||||
|
return *name == '#' ? name + 1 : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||||
@@ -127,11 +170,17 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
|
|||||||
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
if ((region->flags & mask) == 0) { // does region allow this? (per 'mask' param)
|
||||||
TransportKey keys[4];
|
TransportKey keys[4];
|
||||||
int num;
|
int num;
|
||||||
if (region->name[0] == '#') { // auto hashtag region
|
if (region->name[0] == '$') { // private region
|
||||||
|
num = _store->loadKeysFor(region->id, keys, 4);
|
||||||
|
} else if (region->name[0] == '#') { // auto hashtag region
|
||||||
_store->getAutoKeyFor(region->id, region->name, keys[0]);
|
_store->getAutoKeyFor(region->id, region->name, keys[0]);
|
||||||
num = 1;
|
num = 1;
|
||||||
} else {
|
} else { // new: implicit auto hashtag region
|
||||||
num = _store->loadKeysFor(region->id, keys, 4);
|
char tmp[sizeof(region->name)];
|
||||||
|
tmp[0] = '#';
|
||||||
|
strcpy(&tmp[1], region->name);
|
||||||
|
_store->getAutoKeyFor(region->id, tmp, keys[0]);
|
||||||
|
num = 1;
|
||||||
}
|
}
|
||||||
for (int j = 0; j < num; j++) {
|
for (int j = 0; j < num; j++) {
|
||||||
uint16_t code = keys[j].calcTransportCode(packet);
|
uint16_t code = keys[j].calcTransportCode(packet);
|
||||||
@@ -147,9 +196,10 @@ RegionEntry* RegionMap::findMatch(mesh::Packet* packet, uint8_t mask) {
|
|||||||
RegionEntry* RegionMap::findByName(const char* name) {
|
RegionEntry* RegionMap::findByName(const char* name) {
|
||||||
if (strcmp(name, "*") == 0) return &wildcard;
|
if (strcmp(name, "*") == 0) return &wildcard;
|
||||||
|
|
||||||
|
if (*name == '#') { name++; } // ignore the '#' when matching by name
|
||||||
for (int i = 0; i < num_regions; i++) {
|
for (int i = 0; i < num_regions; i++) {
|
||||||
auto region = ®ions[i];
|
auto region = ®ions[i];
|
||||||
if (strcmp(name, region->name) == 0) return region;
|
if (strcmp(name, skip_hash(region->name)) == 0) return region;
|
||||||
}
|
}
|
||||||
return NULL; // not found
|
return NULL; // not found
|
||||||
}
|
}
|
||||||
@@ -157,11 +207,12 @@ RegionEntry* RegionMap::findByName(const char* name) {
|
|||||||
RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
|
RegionEntry* RegionMap::findByNamePrefix(const char* prefix) {
|
||||||
if (strcmp(prefix, "*") == 0) return &wildcard;
|
if (strcmp(prefix, "*") == 0) return &wildcard;
|
||||||
|
|
||||||
|
if (*prefix == '#') { prefix++; } // ignore the '#' when matching by name
|
||||||
RegionEntry* partial = NULL;
|
RegionEntry* partial = NULL;
|
||||||
for (int i = 0; i < num_regions; i++) {
|
for (int i = 0; i < num_regions; i++) {
|
||||||
auto region = ®ions[i];
|
auto region = ®ions[i];
|
||||||
if (strcmp(prefix, region->name) == 0) return region; // is a complete match, preference this one
|
if (strcmp(prefix, skip_hash(region->name)) == 0) return region; // is a complete match, preference this one
|
||||||
if (memcmp(prefix, region->name, strlen(prefix)) == 0) {
|
if (memcmp(prefix, skip_hash(region->name), strlen(prefix)) == 0) {
|
||||||
partial = region;
|
partial = region;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -220,9 +271,9 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream&
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (parent->flags & REGION_DENY_FLOOD) {
|
if (parent->flags & REGION_DENY_FLOOD) {
|
||||||
out.printf("%s%s\n", parent->name, parent->id == home_id ? "^" : "");
|
out.printf("%s%s\n", skip_hash(parent->name), parent->id == home_id ? "^" : "");
|
||||||
} else {
|
} else {
|
||||||
out.printf("%s%s F\n", parent->name, parent->id == home_id ? "^" : "");
|
out.printf("%s%s F\n", skip_hash(parent->name), parent->id == home_id ? "^" : "");
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < num_regions; i++) {
|
for (int i = 0; i < num_regions; i++) {
|
||||||
@@ -237,24 +288,40 @@ void RegionMap::exportTo(Stream& out) const {
|
|||||||
printChildRegions(0, &wildcard, out); // recursive
|
printChildRegions(0, &wildcard, out); // recursive
|
||||||
}
|
}
|
||||||
|
|
||||||
int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) {
|
size_t RegionMap::exportTo(char *dest, size_t max_len) const {
|
||||||
|
if (!dest || max_len == 0) return 0;
|
||||||
|
|
||||||
|
BufStream bs(dest, max_len);
|
||||||
|
exportTo(bs); // ← reuse existing logic
|
||||||
|
return bs.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert) {
|
||||||
char *dp = dest;
|
char *dp = dest;
|
||||||
if ((wildcard.flags & mask) == 0) {
|
|
||||||
|
// Check wildcard region
|
||||||
|
bool wildcard_matches = invert ? (wildcard.flags & mask) : !(wildcard.flags & mask);
|
||||||
|
if (wildcard_matches) {
|
||||||
*dp++ = '*';
|
*dp++ = '*';
|
||||||
*dp++ = ',';
|
*dp++ = ',';
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < num_regions; i++) {
|
for (int i = 0; i < num_regions; i++) {
|
||||||
auto region = ®ions[i];
|
auto region = ®ions[i];
|
||||||
if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param)
|
|
||||||
int len = strlen(region->name);
|
// Check if region matches the filter criteria
|
||||||
|
bool region_matches = invert ? (region->flags & mask) : !(region->flags & mask);
|
||||||
|
|
||||||
|
if (region_matches) {
|
||||||
|
int len = strlen(skip_hash(region->name));
|
||||||
if ((dp - dest) + len + 2 < max_len) { // only append if name will fit
|
if ((dp - dest) + len + 2 < max_len) { // only append if name will fit
|
||||||
memcpy(dp, region->name, len);
|
memcpy(dp, skip_hash(region->name), len);
|
||||||
dp += len;
|
dp += len;
|
||||||
*dp++ = ',';
|
*dp++ = ',';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dp > dest) { dp--; } // don't include trailing comma
|
if (dp > dest) { dp--; } // don't include trailing comma
|
||||||
|
|
||||||
*dp = 0; // set null terminator
|
*dp = 0; // set null terminator
|
||||||
|
|||||||
@@ -49,7 +49,9 @@ public:
|
|||||||
int getCount() const { return num_regions; }
|
int getCount() const { return num_regions; }
|
||||||
const RegionEntry* getByIdx(int i) const { return ®ions[i]; }
|
const RegionEntry* getByIdx(int i) const { return ®ions[i]; }
|
||||||
const RegionEntry* getRoot() const { return &wildcard; }
|
const RegionEntry* getRoot() const { return &wildcard; }
|
||||||
int exportNamesTo(char *dest, int max_len, uint8_t mask);
|
int exportNamesTo(char *dest, int max_len, uint8_t mask, bool invert = false);
|
||||||
|
|
||||||
void exportTo(Stream& out) const;
|
void exportTo(Stream& out) const;
|
||||||
|
size_t exportTo(char *dest, size_t max_len) const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,11 +9,21 @@
|
|||||||
|
|
||||||
#define ADVERT_RESTART_DELAY 1000 // millis
|
#define ADVERT_RESTART_DELAY 1000 // millis
|
||||||
|
|
||||||
void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
|
||||||
_pin_code = pin_code;
|
_pin_code = pin_code;
|
||||||
|
|
||||||
|
if (strcmp(name, "@@MAC") == 0) {
|
||||||
|
uint8_t addr[8];
|
||||||
|
memset(addr, 0, sizeof(addr));
|
||||||
|
esp_efuse_mac_get_default(addr);
|
||||||
|
sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param)
|
||||||
|
addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
|
||||||
|
}
|
||||||
|
char dev_name[32+16];
|
||||||
|
sprintf(dev_name, "%s%s", prefix, name);
|
||||||
|
|
||||||
// Create the BLE Device
|
// Create the BLE Device
|
||||||
BLEDevice::init(device_name);
|
BLEDevice::init(dev_name);
|
||||||
BLEDevice::setSecurityCallbacks(this);
|
BLEDevice::setSecurityCallbacks(this);
|
||||||
BLEDevice::setMTU(MAX_FRAME_SIZE);
|
BLEDevice::setMTU(MAX_FRAME_SIZE);
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,13 @@ public:
|
|||||||
send_queue_len = recv_queue_len = 0;
|
send_queue_len = recv_queue_len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void begin(const char* device_name, uint32_t pin_code);
|
/**
|
||||||
|
* init the BLE interface.
|
||||||
|
* @param prefix a prefix for the device name
|
||||||
|
* @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned
|
||||||
|
* @param pin_code the BLE security pin
|
||||||
|
*/
|
||||||
|
void begin(const char* prefix, char* name, uint32_t pin_code);
|
||||||
|
|
||||||
// BaseSerialInterface methods
|
// BaseSerialInterface methods
|
||||||
void enable() override;
|
void enable() override;
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code) {
|
||||||
instance = this;
|
instance = this;
|
||||||
|
|
||||||
char charpin[20];
|
char charpin[20];
|
||||||
@@ -134,6 +134,16 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
|||||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||||
Bluefruit.begin();
|
Bluefruit.begin();
|
||||||
|
|
||||||
|
char dev_name[32+16];
|
||||||
|
if (strcmp(name, "@@MAC") == 0) {
|
||||||
|
ble_gap_addr_t addr;
|
||||||
|
if (sd_ble_gap_addr_get(&addr) == NRF_SUCCESS) {
|
||||||
|
sprintf(name, "%02X%02X%02X%02X%02X%02X", // modify (IN-OUT param)
|
||||||
|
addr.addr[5], addr.addr[4], addr.addr[3], addr.addr[2], addr.addr[1], addr.addr[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sprintf(dev_name, "%s%s", prefix, name);
|
||||||
|
|
||||||
// Connection interval units: 1.25ms, supervision timeout units: 10ms
|
// Connection interval units: 1.25ms, supervision timeout units: 10ms
|
||||||
ble_gap_conn_params_t ppcp_params;
|
ble_gap_conn_params_t ppcp_params;
|
||||||
ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
|
ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
|
||||||
@@ -153,7 +163,7 @@ void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bluefruit.setTxPower(BLE_TX_POWER);
|
Bluefruit.setTxPower(BLE_TX_POWER);
|
||||||
Bluefruit.setName(device_name);
|
Bluefruit.setName(dev_name);
|
||||||
|
|
||||||
Bluefruit.Security.setMITM(true);
|
Bluefruit.Security.setMITM(true);
|
||||||
Bluefruit.Security.setPIN(charpin);
|
Bluefruit.Security.setPIN(charpin);
|
||||||
|
|||||||
@@ -52,7 +52,14 @@ public:
|
|||||||
recv_queue_len = 0;
|
recv_queue_len = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void begin(const char* device_name, uint32_t pin_code);
|
/**
|
||||||
|
* init the BLE interface.
|
||||||
|
* @param prefix a prefix for the device name
|
||||||
|
* @param name IN/OUT - a name for the device (combined with prefix). If "@@MAC", is modified and returned
|
||||||
|
* @param pin_code the BLE security pin
|
||||||
|
*/
|
||||||
|
void begin(const char* prefix, char* name, uint32_t pin_code);
|
||||||
|
|
||||||
void disconnect();
|
void disconnect();
|
||||||
void enable() override;
|
void enable() override;
|
||||||
void disable() override;
|
void disable() override;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ static Adafruit_BME280 BME280;
|
|||||||
#endif
|
#endif
|
||||||
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
||||||
#include <Adafruit_BMP280.h>
|
#include <Adafruit_BMP280.h>
|
||||||
static Adafruit_BMP280 BMP280;
|
static Adafruit_BMP280 BMP280(TELEM_WIRE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENV_INCLUDE_SHTC3
|
#if ENV_INCLUDE_SHTC3
|
||||||
@@ -58,6 +58,7 @@ static SensirionI2cSht4x SHT4X;
|
|||||||
|
|
||||||
#if ENV_INCLUDE_LPS22HB
|
#if ENV_INCLUDE_LPS22HB
|
||||||
#include <Arduino_LPS22HB.h>
|
#include <Arduino_LPS22HB.h>
|
||||||
|
LPS22HBClass LPS22HB(*TELEM_WIRE);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENV_INCLUDE_INA3221
|
#if ENV_INCLUDE_INA3221
|
||||||
@@ -218,7 +219,7 @@ bool EnvironmentSensorManager::begin() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENV_INCLUDE_SHTC3
|
#if ENV_INCLUDE_SHTC3
|
||||||
if (SHTC3.begin()) {
|
if (SHTC3.begin(TELEM_WIRE)) {
|
||||||
MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
|
MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
|
||||||
SHTC3_initialized = true;
|
SHTC3_initialized = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -243,7 +244,7 @@ bool EnvironmentSensorManager::begin() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENV_INCLUDE_LPS22HB
|
#if ENV_INCLUDE_LPS22HB
|
||||||
if (BARO.begin()) {
|
if (LPS22HB.begin()) {
|
||||||
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
|
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
|
||||||
LPS22HB_initialized = true;
|
LPS22HB_initialized = true;
|
||||||
} else {
|
} else {
|
||||||
@@ -407,8 +408,8 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
|||||||
|
|
||||||
#if ENV_INCLUDE_LPS22HB
|
#if ENV_INCLUDE_LPS22HB
|
||||||
if (LPS22HB_initialized) {
|
if (LPS22HB_initialized) {
|
||||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature());
|
telemetry.addTemperature(TELEM_CHANNEL_SELF, LPS22HB.readTemperature());
|
||||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa
|
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, LPS22HB.readPressure() * 10); // convert kPa to hPa
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,35 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <Wire.h>
|
#include <Wire.h>
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
// Static configuration for power management
|
||||||
|
// Values come from variant.h defines
|
||||||
|
const PowerMgtConfig power_config = {
|
||||||
|
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
|
||||||
|
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
|
||||||
|
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
|
||||||
|
};
|
||||||
|
|
||||||
|
void T114Board::initiateShutdown(uint8_t reason) {
|
||||||
|
#if ENV_INCLUDE_GPS == 1
|
||||||
|
pinMode(GPS_EN, OUTPUT);
|
||||||
|
digitalWrite(GPS_EN, LOW);
|
||||||
|
#endif
|
||||||
|
digitalWrite(SX126X_POWER_EN, LOW);
|
||||||
|
|
||||||
|
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
|
||||||
|
reason == SHUTDOWN_REASON_BOOT_PROTECT);
|
||||||
|
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||||
|
digitalWrite(PIN_BAT_CTL, enable_lpcomp ? HIGH : LOW);
|
||||||
|
|
||||||
|
if (enable_lpcomp) {
|
||||||
|
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
|
||||||
|
}
|
||||||
|
|
||||||
|
enterSystemOff(reason);
|
||||||
|
}
|
||||||
|
#endif // NRF52_POWER_MANAGEMENT
|
||||||
|
|
||||||
void T114Board::begin() {
|
void T114Board::begin() {
|
||||||
NRF52Board::begin();
|
NRF52Board::begin();
|
||||||
NRF_POWER->DCDCEN = 1;
|
NRF_POWER->DCDCEN = 1;
|
||||||
@@ -21,6 +50,11 @@ void T114Board::begin() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
// Boot voltage protection check (may not return if voltage too low)
|
||||||
|
// We need to call this after we configure SX126X_POWER_EN as output but before we pull high
|
||||||
|
checkBootVoltage(&power_config);
|
||||||
|
#endif
|
||||||
digitalWrite(SX126X_POWER_EN, HIGH);
|
digitalWrite(SX126X_POWER_EN, HIGH);
|
||||||
delay(10); // give sx1262 some time to power up
|
delay(10); // give sx1262 some time to power up
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,11 @@
|
|||||||
#define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range
|
#define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range
|
||||||
|
|
||||||
class T114Board : public NRF52BoardOTA {
|
class T114Board : public NRF52BoardOTA {
|
||||||
|
protected:
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
void initiateShutdown(uint8_t reason) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
T114Board() : NRF52BoardOTA("T114_OTA") {}
|
T114Board() : NRF52BoardOTA("T114_OTA") {}
|
||||||
void begin();
|
void begin();
|
||||||
@@ -42,13 +47,13 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
void powerOff() override {
|
void powerOff() override {
|
||||||
#ifdef LED_PIN
|
#ifdef LED_PIN
|
||||||
digitalWrite(LED_PIN, HIGH);
|
digitalWrite(LED_PIN, HIGH);
|
||||||
#endif
|
#endif
|
||||||
#if ENV_INCLUDE_GPS == 1
|
#if ENV_INCLUDE_GPS == 1
|
||||||
pinMode(GPS_EN, OUTPUT);
|
pinMode(GPS_EN, OUTPUT);
|
||||||
digitalWrite(GPS_EN, LOW);
|
digitalWrite(GPS_EN, LOW);
|
||||||
#endif
|
#endif
|
||||||
sd_power_system_off();
|
sd_power_system_off();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ build_flags = ${nrf52_base.build_flags}
|
|||||||
-I variants/heltec_t114
|
-I variants/heltec_t114
|
||||||
-I src/helpers/ui
|
-I src/helpers/ui
|
||||||
-D HELTEC_T114
|
-D HELTEC_T114
|
||||||
|
-D NRF52_POWER_MANAGEMENT
|
||||||
-D P_LORA_DIO_1=20
|
-D P_LORA_DIO_1=20
|
||||||
-D P_LORA_NSS=24
|
-D P_LORA_NSS=24
|
||||||
-D P_LORA_RESET=25
|
-D P_LORA_RESET=25
|
||||||
|
|||||||
@@ -30,6 +30,14 @@
|
|||||||
|
|
||||||
#define AREF_VOLTAGE (3.0)
|
#define AREF_VOLTAGE (3.0)
|
||||||
|
|
||||||
|
// Power management boot protection threshold (millivolts)
|
||||||
|
// Set to 0 to disable boot protection
|
||||||
|
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV)
|
||||||
|
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
|
||||||
|
// AIN2 = P0.04 = BATTERY_PIN / PIN_VBAT_READ
|
||||||
|
#define PWRMGT_LPCOMP_AIN 2
|
||||||
|
#define PWRMGT_LPCOMP_REFSEL 1 // 2/8 VDD (~3.68-4.04V)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// Number of pins
|
// Number of pins
|
||||||
|
|
||||||
@@ -50,8 +58,8 @@
|
|||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// I2C pin definition
|
// I2C pin definition
|
||||||
|
|
||||||
#define PIN_WIRE_SDA (26) // P0.26
|
#define PIN_WIRE_SDA (16) // P0.16
|
||||||
#define PIN_WIRE_SCL (27) // P0.27
|
#define PIN_WIRE_SCL (13) // P0.13
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
// SPI pin definition
|
// SPI pin definition
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ build_flags =
|
|||||||
${esp32_base.build_flags}
|
${esp32_base.build_flags}
|
||||||
-I variants/heltec_wireless_paper
|
-I variants/heltec_wireless_paper
|
||||||
-D HELTEC_WIRELESS_PAPER
|
-D HELTEC_WIRELESS_PAPER
|
||||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
;-D ARDUINO_USB_CDC_ON_BOOT=1 ; this breaks Serial
|
||||||
-D P_LORA_DIO_1=14
|
-D P_LORA_DIO_1=14
|
||||||
-D P_LORA_NSS=8
|
-D P_LORA_NSS=8
|
||||||
-D P_LORA_RESET=RADIOLIB_NC
|
-D P_LORA_RESET=RADIOLIB_NC
|
||||||
@@ -17,8 +17,8 @@ build_flags =
|
|||||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
-D LORA_TX_POWER=22
|
-D LORA_TX_POWER=22
|
||||||
-D P_LORA_TX_LED=18
|
-D P_LORA_TX_LED=18
|
||||||
-D PIN_BOARD_SDA=17
|
;-D PIN_BOARD_SDA=17
|
||||||
-D PIN_BOARD_SCL=18
|
;-D PIN_BOARD_SCL=18 ; same GPIO as P_LORA_TX_LED
|
||||||
-D PIN_USER_BTN=0
|
-D PIN_USER_BTN=0
|
||||||
-D PIN_VEXT_EN=45
|
-D PIN_VEXT_EN=45
|
||||||
-D PIN_VBAT_READ=20
|
-D PIN_VBAT_READ=20
|
||||||
|
|||||||
96
variants/rak3112/RAK3112Board.h
Normal file
96
variants/rak3112/RAK3112Board.h
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <helpers/RefCountedDigitalPin.h>
|
||||||
|
#include <helpers/ESP32Board.h>
|
||||||
|
|
||||||
|
// built-ins
|
||||||
|
#ifndef PIN_VBAT_READ
|
||||||
|
#define PIN_VBAT_READ 1
|
||||||
|
#endif
|
||||||
|
#ifndef PIN_ADC_CTRL
|
||||||
|
#define PIN_ADC_CTRL 36
|
||||||
|
#endif
|
||||||
|
#define PIN_ADC_CTRL_ACTIVE LOW
|
||||||
|
#define PIN_ADC_CTRL_INACTIVE HIGH
|
||||||
|
#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000)
|
||||||
|
#define BATTERY_SAMPLES 8
|
||||||
|
|
||||||
|
#include <driver/rtc_io.h>
|
||||||
|
|
||||||
|
class RAK3112Board : public ESP32Board {
|
||||||
|
private:
|
||||||
|
bool adc_active_state;
|
||||||
|
|
||||||
|
public:
|
||||||
|
RefCountedDigitalPin periph_power;
|
||||||
|
|
||||||
|
RAK3112Board() : periph_power(PIN_VEXT_EN) { }
|
||||||
|
|
||||||
|
void begin() {
|
||||||
|
ESP32Board::begin();
|
||||||
|
|
||||||
|
// Auto-detect correct ADC_CTRL pin polarity (different for boards >3.2)
|
||||||
|
pinMode(PIN_ADC_CTRL, INPUT);
|
||||||
|
adc_active_state = !digitalRead(PIN_ADC_CTRL);
|
||||||
|
|
||||||
|
pinMode(PIN_ADC_CTRL, OUTPUT);
|
||||||
|
digitalWrite(PIN_ADC_CTRL, !adc_active_state); // Initially inactive
|
||||||
|
|
||||||
|
periph_power.begin();
|
||||||
|
|
||||||
|
esp_reset_reason_t reason = esp_reset_reason();
|
||||||
|
if (reason == ESP_RST_DEEPSLEEP) {
|
||||||
|
long wakeup_source = esp_sleep_get_ext1_wakeup_status();
|
||||||
|
if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep)
|
||||||
|
startup_reason = BD_STARTUP_RX_PACKET;
|
||||||
|
}
|
||||||
|
|
||||||
|
rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS);
|
||||||
|
rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1) {
|
||||||
|
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||||
|
|
||||||
|
// Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep
|
||||||
|
rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_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 {
|
||||||
|
enterDeepSleep(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t getBattMilliVolts() override {
|
||||||
|
analogReadResolution(12);
|
||||||
|
|
||||||
|
uint32_t raw = 0;
|
||||||
|
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
||||||
|
raw += analogRead(PIN_VBAT_READ);
|
||||||
|
}
|
||||||
|
raw = raw / BATTERY_SAMPLES;
|
||||||
|
|
||||||
|
return (ADC_MULTIPLIER * raw) / 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getManufacturerName() const override {
|
||||||
|
return "RAK 3112";
|
||||||
|
}
|
||||||
|
};
|
||||||
223
variants/rak3112/platformio.ini
Normal file
223
variants/rak3112/platformio.ini
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
[rak3112]
|
||||||
|
extends = esp32_base
|
||||||
|
board = esp32-s3-devkitc-1
|
||||||
|
build_flags =
|
||||||
|
${esp32_base.build_flags}
|
||||||
|
${sensor_base.build_flags}
|
||||||
|
-I variants/rak3112
|
||||||
|
-D RAK_3112=1
|
||||||
|
-D ESP32_CPU_FREQ=80
|
||||||
|
-D P_LORA_DIO_1=47
|
||||||
|
-D P_LORA_NSS=7
|
||||||
|
-D P_LORA_RESET=8
|
||||||
|
-D P_LORA_BUSY=48
|
||||||
|
-D P_LORA_SCLK=5
|
||||||
|
-D P_LORA_MISO=3
|
||||||
|
-D P_LORA_MOSI=6
|
||||||
|
-D RADIO_CLASS=CustomSX1262
|
||||||
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
|
-D LORA_TX_POWER=22
|
||||||
|
-D P_LORA_TX_LED=46
|
||||||
|
-D PIN_BOARD_SDA=9
|
||||||
|
-D PIN_BOARD_SCL=40
|
||||||
|
-D PIN_USER_BTN=-1
|
||||||
|
-D PIN_VEXT_EN=14
|
||||||
|
-D SX126X_DIO2_AS_RF_SWITCH=true
|
||||||
|
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||||
|
-D SX126X_CURRENT_LIMIT=140
|
||||||
|
-D SX126X_RX_BOOSTED_GAIN=1
|
||||||
|
-D PIN_GPS_RX=43
|
||||||
|
-D PIN_GPS_TX=44
|
||||||
|
; -D PIN_GPS_EN=26
|
||||||
|
build_src_filter = ${esp32_base.build_src_filter}
|
||||||
|
+<../variants/rak3112>
|
||||||
|
+<helpers/sensors>
|
||||||
|
lib_deps =
|
||||||
|
${esp32_base.lib_deps}
|
||||||
|
${sensor_base.lib_deps}
|
||||||
|
|
||||||
|
[env:RAK3112_repeater]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"RAK3112 Repeater"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=50
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
bakercp/CRC32 @ ^2.0.0
|
||||||
|
|
||||||
|
[env:RAK3112_repeater_bridge_rs232]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"RS232 Bridge"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=50
|
||||||
|
-D WITH_RS232_BRIDGE=Serial2
|
||||||
|
-D WITH_RS232_BRIDGE_RX=5
|
||||||
|
-D WITH_RS232_BRIDGE_TX=6
|
||||||
|
; -D BRIDGE_DEBUG=1
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/bridges/RS232Bridge.cpp>
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:RAK3112_repeater_bridge_espnow]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"ESPNow Bridge"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=50
|
||||||
|
-D WITH_ESPNOW_BRIDGE=1
|
||||||
|
; -D BRIDGE_DEBUG=1
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/bridges/ESPNowBridge.cpp>
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:RAK3112_room_server]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"RAK3112 Room"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D ROOM_PASSWORD='"hello"'
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_room_server>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
|
|
||||||
|
[env:RAK3112_terminal_chat]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-D MAX_CONTACTS=350
|
||||||
|
-D MAX_GROUP_CHANNELS=1
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<../examples/simple_secure_chat/main.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK3112_companion_radio_usb]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D MAX_CONTACTS=350
|
||||||
|
-D MAX_GROUP_CHANNELS=40
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||||
|
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK3112_companion_radio_ble]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D MAX_CONTACTS=350
|
||||||
|
-D MAX_GROUP_CHANNELS=40
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||||
|
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
|
||||||
|
-D BLE_DEBUG_LOGGING=1
|
||||||
|
-D OFFLINE_QUEUE_SIZE=256
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
|
+<helpers/esp32/*.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK3112_companion_radio_wifi]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D MAX_CONTACTS=350
|
||||||
|
-D MAX_GROUP_CHANNELS=40
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D WIFI_DEBUG_LOGGING=1
|
||||||
|
-D WIFI_SSID='"myssid"'
|
||||||
|
-D WIFI_PWD='"mypwd"'
|
||||||
|
-D OFFLINE_QUEUE_SIZE=256
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
|
+<helpers/esp32/*.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK3112_sensor]
|
||||||
|
extends = rak3112
|
||||||
|
build_flags =
|
||||||
|
${rak3112.build_flags}
|
||||||
|
-D ADVERT_NAME='"RAK3112 v3 Sensor"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D ENV_PIN_SDA=33
|
||||||
|
-D ENV_PIN_SCL=34
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
; -D MESH_PACKET_LOGGING=1
|
||||||
|
; -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3112.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_sensor>
|
||||||
|
lib_deps =
|
||||||
|
${rak3112.lib_deps}
|
||||||
|
${esp32_ota.lib_deps}
|
||||||
60
variants/rak3112/target.cpp
Normal file
60
variants/rak3112/target.cpp
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "target.h"
|
||||||
|
|
||||||
|
RAK3112Board board;
|
||||||
|
|
||||||
|
#if defined(P_LORA_SCLK)
|
||||||
|
static SPIClass spi;
|
||||||
|
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi);
|
||||||
|
#else
|
||||||
|
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
WRAPPER_CLASS radio_driver(radio, board);
|
||||||
|
|
||||||
|
ESP32RTCClock fallback_clock;
|
||||||
|
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||||
|
|
||||||
|
#if ENV_INCLUDE_GPS
|
||||||
|
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||||
|
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
||||||
|
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||||
|
#else
|
||||||
|
EnvironmentSensorManager sensors;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
DISPLAY_CLASS display;
|
||||||
|
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init() {
|
||||||
|
fallback_clock.begin();
|
||||||
|
rtc_clock.begin(Wire);
|
||||||
|
|
||||||
|
#if defined(P_LORA_SCLK)
|
||||||
|
return radio.std_init(&spi);
|
||||||
|
#else
|
||||||
|
return radio.std_init();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t radio_get_rng_seed() {
|
||||||
|
return radio.random(0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||||
|
radio.setFrequency(freq);
|
||||||
|
radio.setSpreadingFactor(sf);
|
||||||
|
radio.setBandwidth(bw);
|
||||||
|
radio.setCodingRate(cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_tx_power(uint8_t dbm) {
|
||||||
|
radio.setOutputPower(dbm);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::LocalIdentity radio_new_identity() {
|
||||||
|
RadioNoiseListener rng(radio);
|
||||||
|
return mesh::LocalIdentity(&rng); // create new random identity
|
||||||
|
}
|
||||||
30
variants/rak3112/target.h
Normal file
30
variants/rak3112/target.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||||
|
#include <RAK3112Board.h>
|
||||||
|
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||||
|
#include <helpers/AutoDiscoverRTCClock.h>
|
||||||
|
#include <helpers/SensorManager.h>
|
||||||
|
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
#include <helpers/ui/SSD1306Display.h>
|
||||||
|
#include <helpers/ui/MomentaryButton.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern RAK3112Board board;
|
||||||
|
extern WRAPPER_CLASS radio_driver;
|
||||||
|
extern AutoDiscoverRTCClock rtc_clock;
|
||||||
|
extern EnvironmentSensorManager sensors;
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
extern DISPLAY_CLASS display;
|
||||||
|
extern MomentaryButton user_btn;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init();
|
||||||
|
uint32_t radio_get_rng_seed();
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||||
|
void radio_set_tx_power(uint8_t dbm);
|
||||||
|
mesh::LocalIdentity radio_new_identity();
|
||||||
32
variants/rak3401/RAK3401Board.cpp
Normal file
32
variants/rak3401/RAK3401Board.cpp
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include <Wire.h>
|
||||||
|
|
||||||
|
#include "RAK3401Board.h"
|
||||||
|
|
||||||
|
void RAK3401Board::begin() {
|
||||||
|
NRF52BoardDCDC::begin();
|
||||||
|
pinMode(PIN_VBAT_READ, INPUT);
|
||||||
|
#ifdef PIN_USER_BTN
|
||||||
|
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef PIN_USER_BTN_ANA
|
||||||
|
pinMode(PIN_USER_BTN_ANA, INPUT_PULLUP);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||||
|
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Wire.begin();
|
||||||
|
|
||||||
|
pinMode(PIN_3V3_EN, OUTPUT);
|
||||||
|
digitalWrite(PIN_3V3_EN, HIGH);
|
||||||
|
|
||||||
|
#ifdef P_LORA_PA_EN
|
||||||
|
// Initialize RAK13302 1W LoRa transceiver module PA control pin
|
||||||
|
pinMode(P_LORA_PA_EN, OUTPUT);
|
||||||
|
digitalWrite(P_LORA_PA_EN, LOW); // Start with PA disabled
|
||||||
|
delay(10); // Allow PA module to initialize
|
||||||
|
#endif
|
||||||
|
}
|
||||||
70
variants/rak3401/RAK3401Board.h
Normal file
70
variants/rak3401/RAK3401Board.h
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <MeshCore.h>
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <helpers/NRF52Board.h>
|
||||||
|
|
||||||
|
// LoRa radio module pins for RAK13302
|
||||||
|
#define P_LORA_SCLK 3
|
||||||
|
#define P_LORA_MISO 29
|
||||||
|
#define P_LORA_MOSI 30
|
||||||
|
#define P_LORA_NSS 26
|
||||||
|
#define P_LORA_DIO_1 10
|
||||||
|
#define P_LORA_BUSY 9
|
||||||
|
#define P_LORA_RESET 4
|
||||||
|
#ifndef P_LORA_PA_EN
|
||||||
|
#define P_LORA_PA_EN 31
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//#define PIN_GPS_SDA 13 //GPS SDA pin (output option)
|
||||||
|
//#define PIN_GPS_SCL 14 //GPS SCL pin (output option)
|
||||||
|
// #define PIN_GPS_TX 16 //GPS TX pin
|
||||||
|
// #define PIN_GPS_RX 15 //GPS RX pin
|
||||||
|
#define PIN_GPS_1PPS 17 //GPS PPS pin
|
||||||
|
#define GPS_BAUD_RATE 9600
|
||||||
|
#define GPS_ADDRESS 0x42 //i2c address for GPS
|
||||||
|
|
||||||
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
|
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||||
|
|
||||||
|
|
||||||
|
// built-ins
|
||||||
|
#define PIN_VBAT_READ 5
|
||||||
|
#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000)
|
||||||
|
|
||||||
|
#define PIN_3V3_EN (34)
|
||||||
|
#define WB_IO2 PIN_3V3_EN
|
||||||
|
|
||||||
|
class RAK3401Board : public NRF52BoardDCDC, public NRF52BoardOTA {
|
||||||
|
public:
|
||||||
|
RAK3401Board() : NRF52BoardOTA("RAK3401_OTA") {}
|
||||||
|
void begin();
|
||||||
|
|
||||||
|
#define BATTERY_SAMPLES 8
|
||||||
|
|
||||||
|
uint16_t getBattMilliVolts() override {
|
||||||
|
analogReadResolution(12);
|
||||||
|
|
||||||
|
uint32_t raw = 0;
|
||||||
|
for (int i = 0; i < BATTERY_SAMPLES; i++) {
|
||||||
|
raw += analogRead(PIN_VBAT_READ);
|
||||||
|
}
|
||||||
|
raw = raw / BATTERY_SAMPLES;
|
||||||
|
|
||||||
|
return (ADC_MULTIPLIER * raw) / 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* getManufacturerName() const override {
|
||||||
|
return "RAK 3401";
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef P_LORA_PA_EN
|
||||||
|
void onBeforeTransmit() override {
|
||||||
|
digitalWrite(P_LORA_PA_EN, HIGH); // Enable PA before transmission
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAfterTransmit() override {
|
||||||
|
digitalWrite(P_LORA_PA_EN, LOW); // Disable PA after transmission to save power
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
127
variants/rak3401/platformio.ini
Normal file
127
variants/rak3401/platformio.ini
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
[rak3401]
|
||||||
|
extends = nrf52_base
|
||||||
|
board = rak3401
|
||||||
|
board_check = true
|
||||||
|
build_flags = ${nrf52_base.build_flags}
|
||||||
|
${sensor_base.build_flags}
|
||||||
|
-I variants/rak3401
|
||||||
|
-D RAK_3401
|
||||||
|
-D RAK13302
|
||||||
|
-D RADIO_CLASS=CustomSX1262
|
||||||
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
|
-D LORA_TX_POWER=22
|
||||||
|
-D SX126X_CURRENT_LIMIT=140
|
||||||
|
-D SX126X_RX_BOOSTED_GAIN=1
|
||||||
|
build_src_filter = ${nrf52_base.build_src_filter}
|
||||||
|
+<../variants/rak3401>
|
||||||
|
+<helpers/sensors>
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<helpers/ui/MomentaryButton.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${nrf52_base.lib_deps}
|
||||||
|
${sensor_base.lib_deps}
|
||||||
|
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||||
|
sparkfun/SparkFun u-blox GNSS Arduino Library@^2.2.27
|
||||||
|
|
||||||
|
[env:RAK_3401_repeater]
|
||||||
|
extends = rak3401
|
||||||
|
build_flags =
|
||||||
|
${rak3401.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"RAK3401 1W Repeater"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D MAX_NEIGHBOURS=50
|
||||||
|
;-D MESH_PACKET_LOGGING=1
|
||||||
|
;-D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3401.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_repeater>
|
||||||
|
|
||||||
|
[env:RAK_3401_room_server]
|
||||||
|
extends = rak3401
|
||||||
|
build_flags =
|
||||||
|
${rak3401.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"Test Room"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
-D ROOM_PASSWORD='"hello"'
|
||||||
|
;-D MESH_PACKET_LOGGING=1
|
||||||
|
;-D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3401.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_room_server>
|
||||||
|
|
||||||
|
[env:RAK_3401_companion_radio_usb]
|
||||||
|
extends = rak3401
|
||||||
|
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||||
|
board_upload.maximum_size = 712704
|
||||||
|
build_flags =
|
||||||
|
${rak3401.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D MAX_CONTACTS=350
|
||||||
|
-D MAX_GROUP_CHANNELS=40
|
||||||
|
; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1
|
||||||
|
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3401.build_src_filter}
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3401.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK_3401_companion_radio_ble]
|
||||||
|
extends = rak3401
|
||||||
|
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||||
|
board_upload.maximum_size = 712704
|
||||||
|
build_flags =
|
||||||
|
${rak3401.build_flags}
|
||||||
|
-I examples/companion_radio/ui-new
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D MAX_CONTACTS=350
|
||||||
|
-D MAX_GROUP_CHANNELS=40
|
||||||
|
-D BLE_PIN_CODE=123456
|
||||||
|
-D BLE_DEBUG_LOGGING=1
|
||||||
|
-D OFFLINE_QUEUE_SIZE=256
|
||||||
|
;-D MESH_PACKET_LOGGING=1
|
||||||
|
;-D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3401.build_src_filter}
|
||||||
|
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||||
|
+<../examples/companion_radio/*.cpp>
|
||||||
|
+<../examples/companion_radio/ui-new/*.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3401.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK_3401_terminal_chat]
|
||||||
|
extends = rak3401
|
||||||
|
build_flags =
|
||||||
|
${rak3401.build_flags}
|
||||||
|
-D MAX_CONTACTS=100
|
||||||
|
-D MAX_GROUP_CHANNELS=1
|
||||||
|
;-D MESH_PACKET_LOGGING=1
|
||||||
|
;-D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3401.build_src_filter}
|
||||||
|
+<../examples/simple_secure_chat/main.cpp>
|
||||||
|
lib_deps =
|
||||||
|
${rak3401.lib_deps}
|
||||||
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
||||||
|
[env:RAK_3401_sensor]
|
||||||
|
extends = rak3401
|
||||||
|
build_flags =
|
||||||
|
${rak3401.build_flags}
|
||||||
|
-D DISPLAY_CLASS=SSD1306Display
|
||||||
|
-D ADVERT_NAME='"RAK3401 Sensor"'
|
||||||
|
-D ADVERT_LAT=0.0
|
||||||
|
-D ADVERT_LON=0.0
|
||||||
|
-D ADMIN_PASSWORD='"password"'
|
||||||
|
;-D MESH_PACKET_LOGGING=1
|
||||||
|
;-D MESH_DEBUG=1
|
||||||
|
build_src_filter = ${rak3401.build_src_filter}
|
||||||
|
+<helpers/ui/SSD1306Display.cpp>
|
||||||
|
+<../examples/simple_sensor>
|
||||||
58
variants/rak3401/target.cpp
Normal file
58
variants/rak3401/target.cpp
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "target.h"
|
||||||
|
#include <helpers/ArduinoHelpers.h>
|
||||||
|
|
||||||
|
RAK3401Board board;
|
||||||
|
|
||||||
|
#ifndef PIN_USER_BTN
|
||||||
|
#define PIN_USER_BTN (-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
DISPLAY_CLASS display;
|
||||||
|
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true);
|
||||||
|
|
||||||
|
#if defined(PIN_USER_BTN_ANA)
|
||||||
|
MomentaryButton analog_btn(PIN_USER_BTN_ANA, 1000, 20);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||||
|
|
||||||
|
WRAPPER_CLASS radio_driver(radio, board);
|
||||||
|
|
||||||
|
VolatileRTCClock fallback_clock;
|
||||||
|
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||||
|
|
||||||
|
#if ENV_INCLUDE_GPS
|
||||||
|
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||||
|
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
||||||
|
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||||
|
#else
|
||||||
|
EnvironmentSensorManager sensors;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool radio_init() {
|
||||||
|
rtc_clock.begin(Wire);
|
||||||
|
return radio.std_init(&SPI);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t radio_get_rng_seed() {
|
||||||
|
return radio.random(0x7FFFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||||
|
radio.setFrequency(freq);
|
||||||
|
radio.setSpreadingFactor(sf);
|
||||||
|
radio.setBandwidth(bw);
|
||||||
|
radio.setCodingRate(cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void radio_set_tx_power(uint8_t dbm) {
|
||||||
|
radio.setOutputPower(dbm);
|
||||||
|
}
|
||||||
|
|
||||||
|
mesh::LocalIdentity radio_new_identity() {
|
||||||
|
RadioNoiseListener rng(radio);
|
||||||
|
return mesh::LocalIdentity(&rng); // create new random identity
|
||||||
|
}
|
||||||
30
variants/rak3401/target.h
Normal file
30
variants/rak3401/target.h
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define RADIOLIB_STATIC_ONLY 1
|
||||||
|
#include <RadioLib.h>
|
||||||
|
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||||
|
#include <RAK3401Board.h>
|
||||||
|
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||||
|
#include <helpers/AutoDiscoverRTCClock.h>
|
||||||
|
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CLASS
|
||||||
|
#include <helpers/ui/SSD1306Display.h>
|
||||||
|
extern DISPLAY_CLASS display;
|
||||||
|
#include <helpers/ui/MomentaryButton.h>
|
||||||
|
extern MomentaryButton user_btn;
|
||||||
|
#if defined(PIN_USER_BTN_ANA)
|
||||||
|
extern MomentaryButton analog_btn;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern RAK3401Board board;
|
||||||
|
extern WRAPPER_CLASS radio_driver;
|
||||||
|
extern AutoDiscoverRTCClock rtc_clock;
|
||||||
|
extern EnvironmentSensorManager sensors;
|
||||||
|
|
||||||
|
bool radio_init();
|
||||||
|
uint32_t radio_get_rng_seed();
|
||||||
|
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||||
|
void radio_set_tx_power(uint8_t dbm);
|
||||||
|
mesh::LocalIdentity radio_new_identity();
|
||||||
52
variants/rak3401/variant.cpp
Normal file
52
variants/rak3401/variant.cpp
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||||
|
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||||
|
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Lesser General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "variant.h"
|
||||||
|
#include "wiring_constants.h"
|
||||||
|
#include "wiring_digital.h"
|
||||||
|
#include "nrf.h"
|
||||||
|
|
||||||
|
const uint32_t g_ADigitalPinMap[] =
|
||||||
|
{
|
||||||
|
// P0
|
||||||
|
0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 ,
|
||||||
|
8 , 9 , 10, 11, 12, 13, 14, 15,
|
||||||
|
16, 17, 18, 19, 20, 21, 22, 23,
|
||||||
|
24, 25, 26, 27, 28, 29, 30, 31,
|
||||||
|
|
||||||
|
// P1
|
||||||
|
32, 33, 34, 35, 36, 37, 38, 39,
|
||||||
|
40, 41, 42, 43, 44, 45, 46, 47
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
void initVariant()
|
||||||
|
{
|
||||||
|
// LED1 & LED2
|
||||||
|
pinMode(PIN_LED1, OUTPUT);
|
||||||
|
ledOff(PIN_LED1);
|
||||||
|
|
||||||
|
pinMode(PIN_LED2, OUTPUT);
|
||||||
|
ledOff(PIN_LED2);
|
||||||
|
|
||||||
|
// 3V3 Power Rail
|
||||||
|
pinMode(PIN_3V3_EN, OUTPUT);
|
||||||
|
digitalWrite(PIN_3V3_EN, HIGH);
|
||||||
|
}
|
||||||
199
variants/rak3401/variant.h
Normal file
199
variants/rak3401/variant.h
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2014-2015 Arduino LLC. All right reserved.
|
||||||
|
Copyright (c) 2016 Sandeep Mistry All right reserved.
|
||||||
|
Copyright (c) 2018, Adafruit Industries (adafruit.com)
|
||||||
|
|
||||||
|
This library is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU Lesser General Public
|
||||||
|
License as published by the Free Software Foundation; either
|
||||||
|
version 2.1 of the License, or (at your option) any later version.
|
||||||
|
This library is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||||
|
See the GNU Lesser General Public License for more details.
|
||||||
|
You should have received a copy of the GNU Lesser General Public
|
||||||
|
License along with this library; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _VARIANT_RAK3401_
|
||||||
|
#define _VARIANT_RAK3401_
|
||||||
|
|
||||||
|
#define RAK4630
|
||||||
|
|
||||||
|
/** Master clock frequency */
|
||||||
|
#define VARIANT_MCK (64000000ul)
|
||||||
|
|
||||||
|
#define USE_LFXO // Board uses 32khz crystal for LF
|
||||||
|
// define USE_LFRC // Board uses RC for LF
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------------
|
||||||
|
* Headers
|
||||||
|
*----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#include "WVariant.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
// Number of pins defined in PinDescription array
|
||||||
|
#define PINS_COUNT (48)
|
||||||
|
#define NUM_DIGITAL_PINS (48)
|
||||||
|
#define NUM_ANALOG_INPUTS (6)
|
||||||
|
#define NUM_ANALOG_OUTPUTS (0)
|
||||||
|
|
||||||
|
// LEDs
|
||||||
|
#define PIN_LED1 (35)
|
||||||
|
#define PIN_LED2 (36)
|
||||||
|
|
||||||
|
#define LED_BUILTIN PIN_LED1
|
||||||
|
#define LED_CONN PIN_LED2
|
||||||
|
|
||||||
|
#define LED_GREEN PIN_LED1
|
||||||
|
#define LED_BLUE PIN_LED2
|
||||||
|
|
||||||
|
#define LED_STATE_ON 1 // State when LED is litted
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Analog pins
|
||||||
|
*/
|
||||||
|
#define PIN_A0 (5) //(3)
|
||||||
|
#define PIN_A1 (31) //(4)
|
||||||
|
#define PIN_A2 (28)
|
||||||
|
#define PIN_A3 (29)
|
||||||
|
#define PIN_A4 (30)
|
||||||
|
#define PIN_A5 (31)
|
||||||
|
#define PIN_A6 (0xff)
|
||||||
|
#define PIN_A7 (0xff)
|
||||||
|
|
||||||
|
static const uint8_t A0 = PIN_A0;
|
||||||
|
static const uint8_t A1 = PIN_A1;
|
||||||
|
static const uint8_t A2 = PIN_A2;
|
||||||
|
static const uint8_t A3 = PIN_A3;
|
||||||
|
static const uint8_t A4 = PIN_A4;
|
||||||
|
static const uint8_t A5 = PIN_A5;
|
||||||
|
static const uint8_t A6 = PIN_A6;
|
||||||
|
static const uint8_t A7 = PIN_A7;
|
||||||
|
#define ADC_RESOLUTION 14
|
||||||
|
|
||||||
|
// Other pins
|
||||||
|
#define WB_I2C1_SDA (13) // SENSOR_SLOT IO_SLOT
|
||||||
|
#define WB_I2C1_SCL (14) // SENSOR_SLOT IO_SLOT
|
||||||
|
|
||||||
|
#define PIN_AREF (2)
|
||||||
|
#define PIN_NFC1 (9)
|
||||||
|
#define WB_IO5 PIN_NFC1
|
||||||
|
#define WB_IO4 (4)
|
||||||
|
#define PIN_NFC2 (10)
|
||||||
|
|
||||||
|
static const uint8_t AREF = PIN_AREF;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Serial interfaces
|
||||||
|
*/
|
||||||
|
// TXD1 RXD1 on Base Board
|
||||||
|
#define PIN_SERIAL1_RX (15)
|
||||||
|
#define PIN_SERIAL1_TX (16)
|
||||||
|
|
||||||
|
// Connected to Jlink CDC
|
||||||
|
#define PIN_SERIAL2_RX (8)
|
||||||
|
#define PIN_SERIAL2_TX (6)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SPI Interfaces
|
||||||
|
*/
|
||||||
|
#define SPI_INTERFACES_COUNT 2
|
||||||
|
|
||||||
|
#define PIN_SPI_MISO (45)
|
||||||
|
#define PIN_SPI_MOSI (44)
|
||||||
|
#define PIN_SPI_SCK (43)
|
||||||
|
|
||||||
|
#define PIN_SPI1_MISO (29)
|
||||||
|
#define PIN_SPI1_MOSI (30)
|
||||||
|
#define PIN_SPI1_SCK (3)
|
||||||
|
|
||||||
|
static const uint8_t SS = 42;
|
||||||
|
static const uint8_t MOSI = PIN_SPI_MOSI;
|
||||||
|
static const uint8_t MISO = PIN_SPI_MISO;
|
||||||
|
static const uint8_t SCK = PIN_SPI_SCK;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Wire Interfaces
|
||||||
|
*/
|
||||||
|
#define WIRE_INTERFACES_COUNT 1
|
||||||
|
|
||||||
|
#define PIN_WIRE_SDA (WB_I2C1_SDA)
|
||||||
|
#define PIN_WIRE_SCL (WB_I2C1_SCL)
|
||||||
|
|
||||||
|
// QSPI Pins
|
||||||
|
// QSPI occupied by GPIO's
|
||||||
|
#define PIN_QSPI_SCK 3
|
||||||
|
#define PIN_QSPI_CS 26
|
||||||
|
#define PIN_QSPI_IO0 30
|
||||||
|
#define PIN_QSPI_IO1 29
|
||||||
|
#define PIN_QSPI_IO2 28
|
||||||
|
#define PIN_QSPI_IO3 2
|
||||||
|
|
||||||
|
// On-board QSPI Flash
|
||||||
|
// No onboard flash
|
||||||
|
#define EXTERNAL_FLASH_DEVICES IS25LP080D
|
||||||
|
#define EXTERNAL_FLASH_USE_QSPI
|
||||||
|
|
||||||
|
#define P_LORA_SCK PIN_SPI1_SCK
|
||||||
|
#define P_LORA_MISO PIN_SPI1_MISO
|
||||||
|
#define P_LORA_MOSI PIN_SPI1_MOSI
|
||||||
|
#define P_LORA_CS 26
|
||||||
|
|
||||||
|
#define USE_SX1262
|
||||||
|
#define SX126X_CS (26)
|
||||||
|
#define SX126X_DIO1 (10)
|
||||||
|
#define SX126X_BUSY (9)
|
||||||
|
#define SX126X_RESET (4)
|
||||||
|
|
||||||
|
#define SX126X_POWER_EN (21)
|
||||||
|
// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
|
||||||
|
#define SX126X_DIO2_AS_RF_SWITCH
|
||||||
|
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||||
|
|
||||||
|
// enables 3.3V periphery like GPS or IO Module
|
||||||
|
// Do not toggle this for GPS power savings
|
||||||
|
#define PIN_3V3_EN (34)
|
||||||
|
#define WB_IO2 PIN_3V3_EN
|
||||||
|
|
||||||
|
// RAK1910 GPS module
|
||||||
|
// If using the wisblock GPS module and pluged into Port A on WisBlock base
|
||||||
|
// IO1 is hooked to PPS (pin 12 on header) = gpio 17
|
||||||
|
// IO2 is hooked to GPS RESET = gpio 34, but it can not be used to this because IO2 is ALSO used to control 3V3_S power (1 is on).
|
||||||
|
// Therefore must be 1 to keep peripherals powered
|
||||||
|
// Power is on the controllable 3V3_S rail
|
||||||
|
#define PIN_GPS_PPS (17) // Pulse per second input from the GPS
|
||||||
|
|
||||||
|
#define PIN_GPS_RX PIN_SERIAL1_RX
|
||||||
|
#define PIN_GPS_TX PIN_SERIAL1_TX
|
||||||
|
|
||||||
|
// Battery
|
||||||
|
// The battery sense is hooked to pin A0 (5)
|
||||||
|
#define BATTERY_PIN PIN_A0
|
||||||
|
// and has 12 bit resolution
|
||||||
|
#define BATTERY_SENSE_RESOLUTION_BITS 12
|
||||||
|
#define BATTERY_SENSE_RESOLUTION 4096.0
|
||||||
|
#undef AREF_VOLTAGE
|
||||||
|
#define AREF_VOLTAGE 3.0
|
||||||
|
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
|
||||||
|
#define ADC_MULTIPLIER 1.73
|
||||||
|
|
||||||
|
#define HAS_RTC 1
|
||||||
|
|
||||||
|
#define RAK_4631 1
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*----------------------------------------------------------------------------
|
||||||
|
* Arduino objects - C++ only
|
||||||
|
*----------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -3,6 +3,28 @@
|
|||||||
|
|
||||||
#include "RAK4631Board.h"
|
#include "RAK4631Board.h"
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
// Static configuration for power management
|
||||||
|
// Values set in variant.h defines
|
||||||
|
const PowerMgtConfig power_config = {
|
||||||
|
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
|
||||||
|
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
|
||||||
|
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
|
||||||
|
};
|
||||||
|
|
||||||
|
void RAK4631Board::initiateShutdown(uint8_t reason) {
|
||||||
|
// Disable LoRa module power before shutdown
|
||||||
|
digitalWrite(SX126X_POWER_EN, LOW);
|
||||||
|
|
||||||
|
if (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
|
||||||
|
reason == SHUTDOWN_REASON_BOOT_PROTECT) {
|
||||||
|
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
|
||||||
|
}
|
||||||
|
|
||||||
|
enterSystemOff(reason);
|
||||||
|
}
|
||||||
|
#endif // NRF52_POWER_MANAGEMENT
|
||||||
|
|
||||||
void RAK4631Board::begin() {
|
void RAK4631Board::begin() {
|
||||||
NRF52BoardDCDC::begin();
|
NRF52BoardDCDC::begin();
|
||||||
pinMode(PIN_VBAT_READ, INPUT);
|
pinMode(PIN_VBAT_READ, INPUT);
|
||||||
@@ -21,6 +43,11 @@ void RAK4631Board::begin() {
|
|||||||
Wire.begin();
|
Wire.begin();
|
||||||
|
|
||||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
// Boot voltage protection check (may not return if voltage too low)
|
||||||
|
// We need to call this after we configure SX126X_POWER_EN as output but before we pull high
|
||||||
|
checkBootVoltage(&power_config);
|
||||||
|
#endif
|
||||||
digitalWrite(SX126X_POWER_EN, HIGH);
|
digitalWrite(SX126X_POWER_EN, HIGH);
|
||||||
delay(10); // give sx1262 some time to power up
|
delay(10); // give sx1262 some time to power up
|
||||||
}
|
}
|
||||||
@@ -30,6 +30,11 @@
|
|||||||
#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000)
|
#define ADC_MULTIPLIER (3 * 1.73 * 1.187 * 1000)
|
||||||
|
|
||||||
class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA {
|
class RAK4631Board : public NRF52BoardDCDC, public NRF52BoardOTA {
|
||||||
|
protected:
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
void initiateShutdown(uint8_t reason) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {}
|
RAK4631Board() : NRF52BoardOTA("RAK4631_OTA") {}
|
||||||
void begin();
|
void begin();
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ build_flags = ${nrf52_base.build_flags}
|
|||||||
-I variants/rak4631
|
-I variants/rak4631
|
||||||
-D RAK_4631
|
-D RAK_4631
|
||||||
-D RAK_BOARD
|
-D RAK_BOARD
|
||||||
|
-D NRF52_POWER_MANAGEMENT
|
||||||
-D PIN_BOARD_SCL=14
|
-D PIN_BOARD_SCL=14
|
||||||
-D PIN_BOARD_SDA=13
|
-D PIN_BOARD_SDA=13
|
||||||
-D PIN_GPS_TX=PIN_SERIAL1_RX
|
-D PIN_GPS_TX=PIN_SERIAL1_RX
|
||||||
|
|||||||
@@ -104,6 +104,14 @@ extern "C"
|
|||||||
static const uint8_t A7 = PIN_A7;
|
static const uint8_t A7 = PIN_A7;
|
||||||
#define ADC_RESOLUTION 14
|
#define ADC_RESOLUTION 14
|
||||||
|
|
||||||
|
// Power management boot protection threshold (millivolts)
|
||||||
|
// Set to 0 to disable boot protection
|
||||||
|
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage (mV)
|
||||||
|
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
|
||||||
|
// AIN3 = P0.05 = PIN_A0 / PIN_VBAT_READ
|
||||||
|
#define PWRMGT_LPCOMP_AIN 3
|
||||||
|
#define PWRMGT_LPCOMP_REFSEL 4 // 5/8 VDD (~3.13-3.44V)
|
||||||
|
|
||||||
// Other pins
|
// Other pins
|
||||||
#define PIN_AREF (2)
|
#define PIN_AREF (2)
|
||||||
#define PIN_NFC1 (9)
|
#define PIN_NFC1 (9)
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ build_flags =
|
|||||||
-D P_LORA_SCLK=12
|
-D P_LORA_SCLK=12
|
||||||
-D P_LORA_MISO=14
|
-D P_LORA_MISO=14
|
||||||
-D P_LORA_MOSI=13
|
-D P_LORA_MOSI=13
|
||||||
-D LORA_TX_POWER=19
|
-D LORA_TX_POWER=7 ; configured as 7dbm, because the final output will be ~27dbm (~0.5w) if the PA is enabled.
|
||||||
|
-D MAX_LORA_TX_POWER=19 ; max output without burning out the PA
|
||||||
; -D P_LORA_TX_LED=35
|
; -D P_LORA_TX_LED=35
|
||||||
-D PIN_BOARD_SDA=5
|
-D PIN_BOARD_SDA=5
|
||||||
-D PIN_BOARD_SCL=6
|
-D PIN_BOARD_SCL=6
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ build_flags = ${t1000-e.build_flags}
|
|||||||
-D DISPLAY_CLASS=NullDisplayDriver
|
-D DISPLAY_CLASS=NullDisplayDriver
|
||||||
-D PIN_BUZZER=25
|
-D PIN_BUZZER=25
|
||||||
-D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E
|
-D PIN_BUZZER_EN=37 ; P1/5 - required for T1000-E
|
||||||
|
-D ADVERT_NAME='"@@MAC"'
|
||||||
build_src_filter = ${t1000-e.build_src_filter}
|
build_src_filter = ${t1000-e.build_src_filter}
|
||||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||||
+<helpers/ui/buzzer.cpp>
|
+<helpers/ui/buzzer.cpp>
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ build_flags =
|
|||||||
-D PIN_BUZZER=6
|
-D PIN_BUZZER=6
|
||||||
-D AUTO_SHUTDOWN_MILLIVOLTS=3300
|
-D AUTO_SHUTDOWN_MILLIVOLTS=3300
|
||||||
-D QSPIFLASH=1
|
-D QSPIFLASH=1
|
||||||
|
-D ENV_INCLUDE_GPS=1
|
||||||
; -D MESH_PACKET_LOGGING=1
|
; -D MESH_PACKET_LOGGING=1
|
||||||
; -D MESH_DEBUG=1
|
; -D MESH_DEBUG=1
|
||||||
build_src_filter = ${ThinkNode_M1.build_src_filter}
|
build_src_filter = ${ThinkNode_M1.build_src_filter}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ WRAPPER_CLASS radio_driver(radio, board);
|
|||||||
|
|
||||||
VolatileRTCClock fallback_clock;
|
VolatileRTCClock fallback_clock;
|
||||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1);
|
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
|
||||||
ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea);
|
ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea);
|
||||||
|
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ class ThinkNodeM1SensorManager : public SensorManager {
|
|||||||
void stop_gps();
|
void stop_gps();
|
||||||
public:
|
public:
|
||||||
ThinkNodeM1SensorManager(LocationProvider &location): _location(&location) { }
|
ThinkNodeM1SensorManager(LocationProvider &location): _location(&location) { }
|
||||||
|
LocationProvider* getLocationProvider() override { return _location; }
|
||||||
bool begin() override;
|
bool begin() override;
|
||||||
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override;
|
||||||
void loop() override;
|
void loop() override;
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ build_flags = ${WioTrackerL1.build_flags}
|
|||||||
-D UI_HAS_JOYSTICK=1
|
-D UI_HAS_JOYSTICK=1
|
||||||
-D PIN_BUZZER=12
|
-D PIN_BUZZER=12
|
||||||
-D QSPIFLASH=1
|
-D QSPIFLASH=1
|
||||||
|
-D ADVERT_NAME='"@@MAC"'
|
||||||
; -D MESH_PACKET_LOGGING=1
|
; -D MESH_PACKET_LOGGING=1
|
||||||
; -D MESH_DEBUG=1
|
; -D MESH_DEBUG=1
|
||||||
build_src_filter = ${WioTrackerL1.build_src_filter}
|
build_src_filter = ${WioTrackerL1.build_src_filter}
|
||||||
|
|||||||
@@ -5,12 +5,40 @@
|
|||||||
|
|
||||||
#include "XiaoNrf52Board.h"
|
#include "XiaoNrf52Board.h"
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
// Static configuration for power management
|
||||||
|
// Values set in variant.h defines
|
||||||
|
const PowerMgtConfig power_config = {
|
||||||
|
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
|
||||||
|
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
|
||||||
|
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
|
||||||
|
};
|
||||||
|
|
||||||
|
void XiaoNrf52Board::initiateShutdown(uint8_t reason) {
|
||||||
|
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
|
||||||
|
reason == SHUTDOWN_REASON_BOOT_PROTECT);
|
||||||
|
|
||||||
|
pinMode(VBAT_ENABLE, OUTPUT);
|
||||||
|
digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH);
|
||||||
|
|
||||||
|
if (enable_lpcomp) {
|
||||||
|
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
|
||||||
|
}
|
||||||
|
|
||||||
|
enterSystemOff(reason);
|
||||||
|
}
|
||||||
|
#endif // NRF52_POWER_MANAGEMENT
|
||||||
|
|
||||||
void XiaoNrf52Board::begin() {
|
void XiaoNrf52Board::begin() {
|
||||||
NRF52BoardDCDC::begin();
|
NRF52BoardDCDC::begin();
|
||||||
|
|
||||||
|
// Configure battery voltage ADC
|
||||||
pinMode(PIN_VBAT, INPUT);
|
pinMode(PIN_VBAT, INPUT);
|
||||||
pinMode(VBAT_ENABLE, OUTPUT);
|
pinMode(VBAT_ENABLE, OUTPUT);
|
||||||
digitalWrite(VBAT_ENABLE, HIGH);
|
digitalWrite(VBAT_ENABLE, LOW); // Enable VBAT divider for reading
|
||||||
|
analogReadResolution(12);
|
||||||
|
analogReference(AR_INTERNAL_3_0);
|
||||||
|
delay(50); // Allow ADC to settle
|
||||||
|
|
||||||
#ifdef PIN_USER_BTN
|
#ifdef PIN_USER_BTN
|
||||||
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||||
@@ -27,9 +55,20 @@ void XiaoNrf52Board::begin() {
|
|||||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// pinMode(SX126X_POWER_EN, OUTPUT);
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
// digitalWrite(SX126X_POWER_EN, HIGH);
|
// Boot voltage protection check (may not return if voltage too low)
|
||||||
delay(10); // give sx1262 some time to power up
|
checkBootVoltage(&power_config);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
delay(10); // Give sx1262 some time to power up
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t XiaoNrf52Board::getBattMilliVolts() {
|
||||||
|
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||||
|
// VBAT_ENABLE must be LOW to read battery voltage
|
||||||
|
digitalWrite(VBAT_ENABLE, LOW);
|
||||||
|
int adcvalue = analogRead(PIN_VBAT);
|
||||||
|
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
@@ -7,6 +7,11 @@
|
|||||||
#ifdef XIAO_NRF52
|
#ifdef XIAO_NRF52
|
||||||
|
|
||||||
class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA {
|
class XiaoNrf52Board : public NRF52BoardDCDC, public NRF52BoardOTA {
|
||||||
|
protected:
|
||||||
|
#if NRF52_POWER_MANAGEMENT
|
||||||
|
void initiateShutdown(uint8_t reason) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {}
|
XiaoNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {}
|
||||||
void begin();
|
void begin();
|
||||||
@@ -20,21 +25,7 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
uint16_t getBattMilliVolts() override {
|
uint16_t getBattMilliVolts() override;
|
||||||
// Please read befor going further ;)
|
|
||||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
|
||||||
|
|
||||||
// We can't drive VBAT_ENABLE to HIGH as long
|
|
||||||
// as we don't know wether we are charging or not ...
|
|
||||||
// this is a 3mA loss (4/1500)
|
|
||||||
digitalWrite(VBAT_ENABLE, LOW);
|
|
||||||
int adcvalue = 0;
|
|
||||||
analogReadResolution(12);
|
|
||||||
analogReference(AR_INTERNAL_3_0);
|
|
||||||
delay(10);
|
|
||||||
adcvalue = analogRead(PIN_VBAT);
|
|
||||||
return (adcvalue * ADC_MULTIPLIER * AREF_VOLTAGE) / 4.096;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* getManufacturerName() const override {
|
const char* getManufacturerName() const override {
|
||||||
return "Seeed Xiao-nrf52";
|
return "Seeed Xiao-nrf52";
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ build_flags = ${nrf52_base.build_flags}
|
|||||||
-I variants/xiao_nrf52
|
-I variants/xiao_nrf52
|
||||||
-UENV_INCLUDE_GPS
|
-UENV_INCLUDE_GPS
|
||||||
-D NRF52_PLATFORM
|
-D NRF52_PLATFORM
|
||||||
|
-D NRF52_POWER_MANAGEMENT
|
||||||
-D XIAO_NRF52
|
-D XIAO_NRF52
|
||||||
-D RADIO_CLASS=CustomSX1262
|
-D RADIO_CLASS=CustomSX1262
|
||||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
|
|||||||
@@ -75,6 +75,21 @@ static const uint8_t D10 = 10;
|
|||||||
#define AREF_VOLTAGE (3.0)
|
#define AREF_VOLTAGE (3.0)
|
||||||
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
||||||
|
|
||||||
|
// Power management boot protection threshold (millivolts)
|
||||||
|
// Set to 0 to disable boot protection
|
||||||
|
#define PWRMGT_VOLTAGE_BOOTLOCK 3300 // Won't boot below this voltage
|
||||||
|
|
||||||
|
// LPCOMP wake configuration (voltage recovery from SYSTEMOFF)
|
||||||
|
#define PWRMGT_LPCOMP_AIN 7 // AIN7 = P0.31 = PIN_VBAT
|
||||||
|
// IMPORTANT: The XIAO exposes battery via a resistor divider (ADC_MULTIPLIER = 3.0).
|
||||||
|
// LPCOMP measures the divided voltage, not the battery voltage directly.
|
||||||
|
// Vpin = VDD * (REFSEL fraction), and VBAT ≈ Vpin * ADC_MULTIPLIER.
|
||||||
|
//
|
||||||
|
// Using 3/8 VDD gives a wake threshold above the boot protection point:
|
||||||
|
// - If VDD ≈ 3.0V: VBAT ≈ (3.0 * 3/8) * 3 ≈ 3375mV
|
||||||
|
// - If VDD ≈ 3.3V: VBAT ≈ (3.3 * 3/8) * 3 ≈ 3712mV
|
||||||
|
#define PWRMGT_LPCOMP_REFSEL 2 // 3/8 VDD (~3.38-3.71V)
|
||||||
|
|
||||||
static const uint8_t A0 = PIN_A0;
|
static const uint8_t A0 = PIN_A0;
|
||||||
static const uint8_t A1 = PIN_A1;
|
static const uint8_t A1 = PIN_A1;
|
||||||
static const uint8_t A2 = PIN_A2;
|
static const uint8_t A2 = PIN_A2;
|
||||||
|
|||||||
Reference in New Issue
Block a user