mirror of
https://github.com/pelgraine/Meck.git
synced 2026-05-07 05:44:46 +02:00
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "MeshCore",
|
||||
"image": "mcr.microsoft.com/devcontainers/python:3-bookworm",
|
||||
"features": {
|
||||
"ghcr.io/rocker-org/devcontainer-features/apt-packages:1": {
|
||||
"packages": [
|
||||
"sudo"
|
||||
]
|
||||
}
|
||||
},
|
||||
"runArgs": [
|
||||
"--privileged",
|
||||
// arch tty* is owned by uucp (986)
|
||||
// debian tty* is owned by uucp (20) - no change needed
|
||||
"--group-add=986",
|
||||
"--network=host",
|
||||
"--volume=/dev/bus/usb:/dev/bus/usb:ro"
|
||||
],
|
||||
"postCreateCommand": {
|
||||
"platformio": "pipx install platformio"
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
"settings": {
|
||||
"platformio-ide.disablePIOHomeStartup": true,
|
||||
"editor.formatOnSave": false,
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.activeBackground": "#0d1a2b",
|
||||
"titleBar.activeForeground": "#ffffff",
|
||||
"titleBar.inactiveBackground": "#0d1a2b99",
|
||||
"titleBar.inactiveForeground": "#ffffff99"
|
||||
}
|
||||
},
|
||||
"extensions": [
|
||||
"platformio.platformio-ide",
|
||||
"github.vscode-github-actions",
|
||||
"GitHub.vscode-pull-request-github"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,3 +14,5 @@ cmake-*
|
||||
.cache
|
||||
.ccls
|
||||
compile_commands.json
|
||||
.venv/
|
||||
venv/
|
||||
|
||||
@@ -89,7 +89,7 @@ Please submit PR's using 'dev' as the base branch!
|
||||
For minor changes just submit your PR and I'll try to review it, but for anything more 'impactful' please open an Issue first and start a discussion. Is better to sound out what it is you want to achieve first, and try to come to a consensus on what the best approach is, especially when it impacts the structure or architecture of this codebase.
|
||||
|
||||
Here are some general principals you should try to adhere to:
|
||||
* Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unecessary layers.
|
||||
* Keep it simple. Please, don't think like a high-level lang programmer. Think embedded, and keep code concise, without any unnecessary layers.
|
||||
* No dynamic memory allocation, except during setup/begin functions.
|
||||
* Use the same brace and indenting style that's in the core source modules. (A .clang-format is prob going to be added soon, but please do NOT retroactively re-format existing code. This just creates unnecessary diffs that make finding problems harder)
|
||||
|
||||
@@ -106,7 +106,7 @@ There are a number of fairly major features in the pipeline, with no particular
|
||||
- [ ] Core + Apps: support for LZW message compression
|
||||
- [ ] Core: dynamic CR (Coding Rate) for weak vs strong hops
|
||||
- [ ] Core: new framework for hosting multiple virtual nodes on one physical device
|
||||
- [ ] V2 protocol spec: discussion and concensus around V2 packet protocol, including path hashes, new encryption specs, etc
|
||||
- [ ] V2 protocol spec: discussion and consensus around V2 packet protocol, including path hashes, new encryption specs, etc
|
||||
|
||||
## 📞 Get Support
|
||||
|
||||
|
||||
@@ -0,0 +1,198 @@
|
||||
"""
|
||||
Bluefruit BLE Patch Script
|
||||
|
||||
Patches Bluefruit library to fix semaphore leak bug that causes device lockup
|
||||
when BLE central disconnects unexpectedly (e.g., going out of range, supervision timeout).
|
||||
|
||||
Patches applied:
|
||||
1. BLEConnection.h: Add _hvn_qsize member to track semaphore queue size
|
||||
2. BLEConnection.cpp: Store hvn_qsize and restore semaphore on disconnect
|
||||
|
||||
Bug description:
|
||||
- When a BLE central disconnects unexpectedly (reason=8 supervision timeout),
|
||||
the BLE_GATTS_EVT_HVN_TX_COMPLETE event may never fire
|
||||
- This leaves the _hvn_sem counting semaphore in a decremented state
|
||||
- Since BLEConnection objects are reused (destructor never called), the
|
||||
semaphore count is never restored
|
||||
- Eventually all semaphore counts are exhausted and notify() blocks/fails
|
||||
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
Import("env") # pylint: disable=undefined-variable
|
||||
|
||||
|
||||
def _patch_ble_connection_header(source: Path) -> bool:
|
||||
"""
|
||||
Add _hvn_qsize member variable to BLEConnection class.
|
||||
|
||||
This is needed to restore the semaphore to its correct count on disconnect.
|
||||
|
||||
Returns True if patch was applied or already applied, False on error.
|
||||
"""
|
||||
try:
|
||||
content = source.read_text()
|
||||
|
||||
# Check if already patched
|
||||
if "_hvn_qsize" in content:
|
||||
return True # Already patched
|
||||
|
||||
# Find the location to insert - after _phy declaration
|
||||
original_pattern = ''' uint8_t _phy;
|
||||
|
||||
uint8_t _role;'''
|
||||
|
||||
patched_pattern = ''' uint8_t _phy;
|
||||
uint8_t _hvn_qsize;
|
||||
|
||||
uint8_t _role;'''
|
||||
|
||||
if original_pattern not in content:
|
||||
print("Bluefruit patch: WARNING - BLEConnection.h pattern not found")
|
||||
return False
|
||||
|
||||
content = content.replace(original_pattern, patched_pattern)
|
||||
source.write_text(content)
|
||||
|
||||
# Verify
|
||||
if "_hvn_qsize" not in source.read_text():
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Bluefruit patch: ERROR patching BLEConnection.h: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _patch_ble_connection_source(source: Path) -> bool:
|
||||
"""
|
||||
Patch BLEConnection.cpp to:
|
||||
1. Store hvn_qsize in constructor
|
||||
2. Restore _hvn_sem semaphore to full count on disconnect
|
||||
|
||||
Returns True if patch was applied or already applied, False on error.
|
||||
"""
|
||||
try:
|
||||
content = source.read_text()
|
||||
|
||||
# Check if already patched (look for the restore loop)
|
||||
if "uxSemaphoreGetCount(_hvn_sem)" in content:
|
||||
return True # Already patched
|
||||
|
||||
# Patch 1: Store queue size in constructor
|
||||
constructor_original = ''' _hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);'''
|
||||
|
||||
constructor_patched = ''' _hvn_qsize = hvn_qsize;
|
||||
_hvn_sem = xSemaphoreCreateCounting(hvn_qsize, hvn_qsize);'''
|
||||
|
||||
if constructor_original not in content:
|
||||
print("Bluefruit patch: WARNING - BLEConnection.cpp constructor pattern not found")
|
||||
return False
|
||||
|
||||
content = content.replace(constructor_original, constructor_patched)
|
||||
|
||||
# Patch 2: Restore semaphore on disconnect
|
||||
disconnect_original = ''' case BLE_GAP_EVT_DISCONNECTED:
|
||||
// mark as disconnected
|
||||
_connected = false;
|
||||
break;'''
|
||||
|
||||
disconnect_patched = ''' case BLE_GAP_EVT_DISCONNECTED:
|
||||
// Restore notification semaphore to full count
|
||||
// This fixes lockup when disconnect occurs with notifications in flight
|
||||
while (uxSemaphoreGetCount(_hvn_sem) < _hvn_qsize) {
|
||||
xSemaphoreGive(_hvn_sem);
|
||||
}
|
||||
// Release indication semaphore if waiting
|
||||
if (_hvc_sem) {
|
||||
_hvc_received = false;
|
||||
xSemaphoreGive(_hvc_sem);
|
||||
}
|
||||
// mark as disconnected
|
||||
_connected = false;
|
||||
break;'''
|
||||
|
||||
if disconnect_original not in content:
|
||||
print("Bluefruit patch: WARNING - BLEConnection.cpp disconnect pattern not found")
|
||||
return False
|
||||
|
||||
content = content.replace(disconnect_original, disconnect_patched)
|
||||
source.write_text(content)
|
||||
|
||||
# Verify
|
||||
verify_content = source.read_text()
|
||||
if "uxSemaphoreGetCount(_hvn_sem)" not in verify_content:
|
||||
return False
|
||||
if "_hvn_qsize = hvn_qsize" not in verify_content:
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Bluefruit patch: ERROR patching BLEConnection.cpp: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def _apply_bluefruit_patches(target, source, env): # pylint: disable=unused-argument
|
||||
framework_path = env.get("PLATFORMFW_DIR")
|
||||
if not framework_path:
|
||||
framework_path = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52")
|
||||
|
||||
if not framework_path:
|
||||
print("Bluefruit patch: ERROR - framework directory not found")
|
||||
env.Exit(1)
|
||||
return
|
||||
|
||||
framework_dir = Path(framework_path)
|
||||
bluefruit_lib = framework_dir / "libraries" / "Bluefruit52Lib" / "src"
|
||||
patch_failed = False
|
||||
|
||||
# Patch BLEConnection.h
|
||||
conn_header = bluefruit_lib / "BLEConnection.h"
|
||||
if conn_header.exists():
|
||||
before = conn_header.read_text()
|
||||
success = _patch_ble_connection_header(conn_header)
|
||||
after = conn_header.read_text()
|
||||
|
||||
if success:
|
||||
if before != after:
|
||||
print("Bluefruit patch: OK - Applied BLEConnection.h fix (added _hvn_qsize member)")
|
||||
else:
|
||||
print("Bluefruit patch: OK - BLEConnection.h already patched")
|
||||
else:
|
||||
print("Bluefruit patch: FAILED - BLEConnection.h")
|
||||
patch_failed = True
|
||||
else:
|
||||
print(f"Bluefruit patch: ERROR - BLEConnection.h not found at {conn_header}")
|
||||
patch_failed = True
|
||||
|
||||
# Patch BLEConnection.cpp
|
||||
conn_source = bluefruit_lib / "BLEConnection.cpp"
|
||||
if conn_source.exists():
|
||||
before = conn_source.read_text()
|
||||
success = _patch_ble_connection_source(conn_source)
|
||||
after = conn_source.read_text()
|
||||
|
||||
if success:
|
||||
if before != after:
|
||||
print("Bluefruit patch: OK - Applied BLEConnection.cpp fix (restore semaphore on disconnect)")
|
||||
else:
|
||||
print("Bluefruit patch: OK - BLEConnection.cpp already patched")
|
||||
else:
|
||||
print("Bluefruit patch: FAILED - BLEConnection.cpp")
|
||||
patch_failed = True
|
||||
else:
|
||||
print(f"Bluefruit patch: ERROR - BLEConnection.cpp not found at {conn_source}")
|
||||
patch_failed = True
|
||||
|
||||
if patch_failed:
|
||||
print("Bluefruit patch: CRITICAL - Patch failed! Build aborted.")
|
||||
env.Exit(1)
|
||||
|
||||
|
||||
# Register the patch to run before build
|
||||
bluefruit_action = env.VerboseAction(_apply_bluefruit_patches, "Applying Bluefruit BLE patches...")
|
||||
env.AddPreAction("$BUILD_DIR/${PROGNAME}.elf", bluefruit_action)
|
||||
|
||||
# Also run immediately to patch before any compilation
|
||||
_apply_bluefruit_patches(None, None, env)
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-D ARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-D ARDUINO_USB_MSC_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_DFU_ON_BOOT=0",
|
||||
"-D ARDUINO_USB_MODE=1",
|
||||
"-D ARDUINO_RUNNING_CORE=1",
|
||||
"-D ARDUINO_EVENT_RUNNING_CORE=1"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": ["wifi", "bluetooth"],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "ESP32-S3-Zero",
|
||||
"upload": {
|
||||
"flash_size": "4MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 4194304,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://www.espressif.com",
|
||||
"vendor": "Espressif"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x4405"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x0029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x002A"
|
||||
]
|
||||
],
|
||||
"usb_product": "elecrow_eink",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M3",
|
||||
"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",
|
||||
"onboard_tools": [
|
||||
"jlink"
|
||||
],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "elecrow nrf",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
]
|
||||
},
|
||||
"url": "https://github.com/Elecrow-RD",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_ELECROW_M6 -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x4405"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x0029"
|
||||
],
|
||||
[
|
||||
"0x239A",
|
||||
"0x002A"
|
||||
]
|
||||
],
|
||||
"usb_product": "elecrow_solar",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "ELECROW-ThinkNode-M6",
|
||||
"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",
|
||||
"onboard_tools": [
|
||||
"jlink"
|
||||
],
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "elecrow solar",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
]
|
||||
},
|
||||
"url": "https://github.com/Elecrow-RD",
|
||||
"vendor": "ELECROW"
|
||||
}
|
||||
@@ -29,6 +29,20 @@ $ sh build.sh build-repeater-firmwares
|
||||
|
||||
Build all chat room server firmwares
|
||||
$ sh build.sh build-room-server-firmwares
|
||||
|
||||
Environment Variables:
|
||||
DISABLE_DEBUG=1: Disables all debug logging flags (MESH_DEBUG, MESH_PACKET_LOGGING, etc.)
|
||||
If not set, debug flags from variant platformio.ini files are used.
|
||||
|
||||
Examples:
|
||||
Build without debug logging:
|
||||
$ export FIRMWARE_VERSION=v1.0.0
|
||||
$ export DISABLE_DEBUG=1
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
|
||||
Build with debug logging (default, uses flags from variant files):
|
||||
$ export FIRMWARE_VERSION=v1.0.0
|
||||
$ sh build.sh build-firmware RAK_4631_repeater
|
||||
EOF
|
||||
}
|
||||
|
||||
@@ -68,6 +82,13 @@ get_pio_envs_ending_with_string() {
|
||||
done
|
||||
}
|
||||
|
||||
# disable all debug logging flags if DISABLE_DEBUG=1 is set
|
||||
disable_debug_flags() {
|
||||
if [ "$DISABLE_DEBUG" == "1" ]; then
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -UMESH_DEBUG -UBLE_DEBUG_LOGGING -UWIFI_DEBUG_LOGGING -UBRIDGE_DEBUG -UGPS_NMEA_DEBUG -UCORE_DEBUG_LEVEL -UESPNOW_DEBUG_LOGGING -UDEBUG_RP2040_WIRE -UDEBUG_RP2040_SPI -UDEBUG_RP2040_CORE -UDEBUG_RP2040_PORT -URADIOLIB_DEBUG_SPI -UCFG_DEBUG -URADIOLIB_DEBUG_BASIC -URADIOLIB_DEBUG_PROTOCOL"
|
||||
fi
|
||||
}
|
||||
|
||||
# build firmware for the provided pio env in $1
|
||||
build_firmware() {
|
||||
|
||||
@@ -94,6 +115,9 @@ build_firmware() {
|
||||
# add firmware version info to end of existing platformio build flags in environment vars
|
||||
export PLATFORMIO_BUILD_FLAGS="${PLATFORMIO_BUILD_FLAGS} -DFIRMWARE_BUILD_DATE='\"${FIRMWARE_BUILD_DATE}\"' -DFIRMWARE_VERSION='\"${FIRMWARE_VERSION_STRING}\"'"
|
||||
|
||||
# disable debug flags if requested
|
||||
disable_debug_flags
|
||||
|
||||
# build firmware target
|
||||
pio run -e $1
|
||||
|
||||
|
||||
+52
-1
@@ -103,7 +103,9 @@ Request type
|
||||
| `0x02` | keepalive | (deprecated) |
|
||||
| `0x03` | get telemetry data | TODO |
|
||||
| `0x04` | get min,max,avg data | sensor nodes - get min, max, average for given time span |
|
||||
| `0x05` | get access list | get node's approved access list |
|
||||
| `0x05` | get access list | get node's approved access list |
|
||||
| `0x06` | get neighbors | get repeater node's neighbors |
|
||||
| `0x07` | get owner info | get repeater firmware-ver/name/owner info |
|
||||
|
||||
### Get stats
|
||||
|
||||
@@ -132,6 +134,27 @@ Gets information about the node, possibly including the following:
|
||||
|
||||
Request data about sensors on the node, including battery level.
|
||||
|
||||
### Get Telemetry
|
||||
|
||||
TODO
|
||||
|
||||
### Get Min/Max/Ave (Sensor nodes)
|
||||
|
||||
TODO
|
||||
|
||||
### Get Access List
|
||||
|
||||
TODO
|
||||
|
||||
### Get Neighors
|
||||
|
||||
TODO
|
||||
|
||||
### Get Owner Info
|
||||
|
||||
TODO
|
||||
|
||||
|
||||
## Response
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
@@ -179,6 +202,34 @@ txt_type
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| password | rest of message | password for repeater/sensor |
|
||||
|
||||
## Repeater - Regions request
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| req type | 1 | 0x01 (request sub type) |
|
||||
| reply path len | 1 | path len for reply |
|
||||
| reply path | (variable) | reply path |
|
||||
|
||||
## Repeater - Owner info request
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| req type | 1 | 0x02 (request sub type) |
|
||||
| reply path len | 1 | path len for reply |
|
||||
| reply path | (variable) | reply path |
|
||||
|
||||
## Repeater - Clock and status request
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|----------------|-----------------|-------------------------------------------------------------------------------|
|
||||
| timestamp | 4 | sender time (unix timestamp) |
|
||||
| req type | 1 | 0x03 (request sub type) |
|
||||
| reply path len | 1 | path len for reply |
|
||||
| reply path | (variable) | reply path |
|
||||
|
||||
|
||||
# Group text message / datagram
|
||||
|
||||
| Field | Size (bytes) | Description |
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,312 @@
|
||||
# Stats Binary Frame Structures
|
||||
|
||||
Binary frame structures for companion radio stats commands. All multi-byte integers use little-endian byte order.
|
||||
|
||||
## Command Codes
|
||||
|
||||
| Command | Code | Description |
|
||||
|---------|------|-------------|
|
||||
| `CMD_GET_STATS` | 56 | Get statistics (2-byte command: code + sub-type) |
|
||||
|
||||
### Stats Sub-Types
|
||||
|
||||
The `CMD_GET_STATS` command uses a 2-byte frame structure:
|
||||
- **Byte 0:** `CMD_GET_STATS` (56)
|
||||
- **Byte 1:** Stats sub-type:
|
||||
- `STATS_TYPE_CORE` (0) - Get core device statistics
|
||||
- `STATS_TYPE_RADIO` (1) - Get radio statistics
|
||||
- `STATS_TYPE_PACKETS` (2) - Get packet statistics
|
||||
|
||||
## Response Codes
|
||||
|
||||
| Response | Code | Description |
|
||||
|----------|------|-------------|
|
||||
| `RESP_CODE_STATS` | 24 | Statistics response (2-byte response: code + sub-type) |
|
||||
|
||||
### Stats Response Sub-Types
|
||||
|
||||
The `RESP_CODE_STATS` response uses a 2-byte header structure:
|
||||
- **Byte 0:** `RESP_CODE_STATS` (24)
|
||||
- **Byte 1:** Stats sub-type (matches command sub-type):
|
||||
- `STATS_TYPE_CORE` (0) - Core device statistics response
|
||||
- `STATS_TYPE_RADIO` (1) - Radio statistics response
|
||||
- `STATS_TYPE_PACKETS` (2) - Packet statistics response
|
||||
|
||||
---
|
||||
|
||||
## RESP_CODE_STATS + STATS_TYPE_CORE (24, 0)
|
||||
|
||||
**Total Frame Size:** 11 bytes
|
||||
|
||||
| Offset | Size | Type | Field Name | Description | Range/Notes |
|
||||
|--------|------|------|------------|-------------|-------------|
|
||||
| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
|
||||
| 1 | 1 | uint8_t | stats_type | Always `0x00` (STATS_TYPE_CORE) | - |
|
||||
| 2 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 |
|
||||
| 4 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 |
|
||||
| 8 | 2 | uint16_t | errors | Error flags bitmask | - |
|
||||
| 10 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 |
|
||||
|
||||
### Example Structure (C/C++)
|
||||
|
||||
```c
|
||||
struct StatsCore {
|
||||
uint8_t response_code; // 0x18
|
||||
uint8_t stats_type; // 0x00 (STATS_TYPE_CORE)
|
||||
uint16_t battery_mv;
|
||||
uint32_t uptime_secs;
|
||||
uint16_t errors;
|
||||
uint8_t queue_len;
|
||||
} __attribute__((packed));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RESP_CODE_STATS + STATS_TYPE_RADIO (24, 1)
|
||||
|
||||
**Total Frame Size:** 14 bytes
|
||||
|
||||
| Offset | Size | Type | Field Name | Description | Range/Notes |
|
||||
|--------|------|------|------------|-------------|-------------|
|
||||
| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
|
||||
| 1 | 1 | uint8_t | stats_type | Always `0x01` (STATS_TYPE_RADIO) | - |
|
||||
| 2 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 |
|
||||
| 4 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 |
|
||||
| 5 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB |
|
||||
| 6 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 |
|
||||
| 10 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 |
|
||||
|
||||
### Example Structure (C/C++)
|
||||
|
||||
```c
|
||||
struct StatsRadio {
|
||||
uint8_t response_code; // 0x18
|
||||
uint8_t stats_type; // 0x01 (STATS_TYPE_RADIO)
|
||||
int16_t noise_floor;
|
||||
int8_t last_rssi;
|
||||
int8_t last_snr; // Divide by 4.0 to get actual SNR in dB
|
||||
uint32_t tx_air_secs;
|
||||
uint32_t rx_air_secs;
|
||||
} __attribute__((packed));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)
|
||||
|
||||
**Total Frame Size:** 26 bytes
|
||||
|
||||
| Offset | Size | Type | Field Name | Description | Range/Notes |
|
||||
|--------|------|------|------------|-------------|-------------|
|
||||
| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
|
||||
| 1 | 1 | uint8_t | stats_type | Always `0x02` (STATS_TYPE_PACKETS) | - |
|
||||
| 2 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 |
|
||||
| 6 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 |
|
||||
| 10 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 |
|
||||
| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 |
|
||||
| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 |
|
||||
| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
|
||||
|
||||
### Notes
|
||||
|
||||
- Counters are cumulative from boot and may wrap.
|
||||
- `recv = flood_rx + direct_rx`
|
||||
- `sent = flood_tx + direct_tx`
|
||||
|
||||
### Example Structure (C/C++)
|
||||
|
||||
```c
|
||||
struct StatsPackets {
|
||||
uint8_t response_code; // 0x18
|
||||
uint8_t stats_type; // 0x02 (STATS_TYPE_PACKETS)
|
||||
uint32_t recv;
|
||||
uint32_t sent;
|
||||
uint32_t flood_tx;
|
||||
uint32_t direct_tx;
|
||||
uint32_t flood_rx;
|
||||
uint32_t direct_rx;
|
||||
} __attribute__((packed));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command Usage Example (Python)
|
||||
|
||||
```python
|
||||
# Send CMD_GET_STATS command
|
||||
def send_get_stats_core(serial_interface):
|
||||
"""Send command to get core stats"""
|
||||
cmd = bytes([56, 0]) # CMD_GET_STATS (56) + STATS_TYPE_CORE (0)
|
||||
serial_interface.write(cmd)
|
||||
|
||||
def send_get_stats_radio(serial_interface):
|
||||
"""Send command to get radio stats"""
|
||||
cmd = bytes([56, 1]) # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1)
|
||||
serial_interface.write(cmd)
|
||||
|
||||
def send_get_stats_packets(serial_interface):
|
||||
"""Send command to get packet stats"""
|
||||
cmd = bytes([56, 2]) # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2)
|
||||
serial_interface.write(cmd)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Response Parsing Example (Python)
|
||||
|
||||
```python
|
||||
import struct
|
||||
|
||||
def parse_stats_core(frame):
|
||||
"""Parse RESP_CODE_STATS + STATS_TYPE_CORE frame (11 bytes)"""
|
||||
response_code, stats_type, battery_mv, uptime_secs, errors, queue_len = \
|
||||
struct.unpack('<B B H I H B', frame)
|
||||
assert response_code == 24 and stats_type == 0, "Invalid response type"
|
||||
return {
|
||||
'battery_mv': battery_mv,
|
||||
'uptime_secs': uptime_secs,
|
||||
'errors': errors,
|
||||
'queue_len': queue_len
|
||||
}
|
||||
|
||||
def parse_stats_radio(frame):
|
||||
"""Parse RESP_CODE_STATS + STATS_TYPE_RADIO frame (14 bytes)"""
|
||||
response_code, stats_type, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \
|
||||
struct.unpack('<B B h b b I I', frame)
|
||||
assert response_code == 24 and stats_type == 1, "Invalid response type"
|
||||
return {
|
||||
'noise_floor': noise_floor,
|
||||
'last_rssi': last_rssi,
|
||||
'last_snr': last_snr / 4.0, # Unscale SNR
|
||||
'tx_air_secs': tx_air_secs,
|
||||
'rx_air_secs': rx_air_secs
|
||||
}
|
||||
|
||||
def parse_stats_packets(frame):
|
||||
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)"""
|
||||
response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \
|
||||
struct.unpack('<B B I I I I I I', frame)
|
||||
assert response_code == 24 and stats_type == 2, "Invalid response type"
|
||||
return {
|
||||
'recv': recv,
|
||||
'sent': sent,
|
||||
'flood_tx': flood_tx,
|
||||
'direct_tx': direct_tx,
|
||||
'flood_rx': flood_rx,
|
||||
'direct_rx': direct_rx
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Command Usage Example (JavaScript/TypeScript)
|
||||
|
||||
```typescript
|
||||
// Send CMD_GET_STATS command
|
||||
const CMD_GET_STATS = 56;
|
||||
const STATS_TYPE_CORE = 0;
|
||||
const STATS_TYPE_RADIO = 1;
|
||||
const STATS_TYPE_PACKETS = 2;
|
||||
|
||||
function sendGetStatsCore(serialInterface: SerialPort): void {
|
||||
const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_CORE]);
|
||||
serialInterface.write(cmd);
|
||||
}
|
||||
|
||||
function sendGetStatsRadio(serialInterface: SerialPort): void {
|
||||
const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_RADIO]);
|
||||
serialInterface.write(cmd);
|
||||
}
|
||||
|
||||
function sendGetStatsPackets(serialInterface: SerialPort): void {
|
||||
const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_PACKETS]);
|
||||
serialInterface.write(cmd);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Response Parsing Example (JavaScript/TypeScript)
|
||||
|
||||
```typescript
|
||||
interface StatsCore {
|
||||
battery_mv: number;
|
||||
uptime_secs: number;
|
||||
errors: number;
|
||||
queue_len: number;
|
||||
}
|
||||
|
||||
interface StatsRadio {
|
||||
noise_floor: number;
|
||||
last_rssi: number;
|
||||
last_snr: number;
|
||||
tx_air_secs: number;
|
||||
rx_air_secs: number;
|
||||
}
|
||||
|
||||
interface StatsPackets {
|
||||
recv: number;
|
||||
sent: number;
|
||||
flood_tx: number;
|
||||
direct_tx: number;
|
||||
flood_rx: number;
|
||||
direct_rx: number;
|
||||
}
|
||||
|
||||
function parseStatsCore(buffer: ArrayBuffer): StatsCore {
|
||||
const view = new DataView(buffer);
|
||||
const response_code = view.getUint8(0);
|
||||
const stats_type = view.getUint8(1);
|
||||
if (response_code !== 24 || stats_type !== 0) {
|
||||
throw new Error('Invalid response type');
|
||||
}
|
||||
return {
|
||||
battery_mv: view.getUint16(2, true),
|
||||
uptime_secs: view.getUint32(4, true),
|
||||
errors: view.getUint16(8, true),
|
||||
queue_len: view.getUint8(10)
|
||||
};
|
||||
}
|
||||
|
||||
function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {
|
||||
const view = new DataView(buffer);
|
||||
const response_code = view.getUint8(0);
|
||||
const stats_type = view.getUint8(1);
|
||||
if (response_code !== 24 || stats_type !== 1) {
|
||||
throw new Error('Invalid response type');
|
||||
}
|
||||
return {
|
||||
noise_floor: view.getInt16(2, true),
|
||||
last_rssi: view.getInt8(4),
|
||||
last_snr: view.getInt8(5) / 4.0, // Unscale SNR
|
||||
tx_air_secs: view.getUint32(6, true),
|
||||
rx_air_secs: view.getUint32(10, true)
|
||||
};
|
||||
}
|
||||
|
||||
function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
|
||||
const view = new DataView(buffer);
|
||||
const response_code = view.getUint8(0);
|
||||
const stats_type = view.getUint8(1);
|
||||
if (response_code !== 24 || stats_type !== 2) {
|
||||
throw new Error('Invalid response type');
|
||||
}
|
||||
return {
|
||||
recv: view.getUint32(2, true),
|
||||
sent: view.getUint32(6, true),
|
||||
flood_tx: view.getUint32(10, true),
|
||||
direct_tx: view.getUint32(14, true),
|
||||
flood_rx: view.getUint32(18, true),
|
||||
direct_rx: view.getUint32(22, true)
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Field Size Considerations
|
||||
|
||||
- Packet counters (uint32_t): May wrap after extended high-traffic operation.
|
||||
- Time fields (uint32_t): Max ~136 years.
|
||||
- SNR (int8_t, scaled by 4): Range -32 to +31.75 dB, 0.25 dB precision.
|
||||
|
||||
@@ -65,6 +65,7 @@ void DataStore::begin() {
|
||||
|
||||
#if defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#include <nvs_flash.h>
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
#include <LittleFS.h>
|
||||
#elif defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -172,7 +173,9 @@ bool DataStore::formatFileSystem() {
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
return LittleFS.format();
|
||||
#elif defined(ESP32)
|
||||
return ((fs::SPIFFSFS *)_fs)->format();
|
||||
bool fs_success = ((fs::SPIFFSFS *)_fs)->format();
|
||||
esp_err_t nvs_err = nvs_flash_erase(); // no need to reinit, will be done by reboot
|
||||
return fs_success && (nvs_err == ESP_OK);
|
||||
#else
|
||||
#error "need to implement format()"
|
||||
#endif
|
||||
@@ -221,6 +224,10 @@ void DataStore::loadPrefsInt(const char *filename, NodePrefs& _prefs, double& no
|
||||
file.read((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
|
||||
file.read(pad, 2); // 78
|
||||
file.read((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
file.read((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||
file.read((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
file.read((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||
file.read((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -252,6 +259,10 @@ void DataStore::savePrefs(const NodePrefs& _prefs, double node_lat, double node_
|
||||
file.write((uint8_t *)&_prefs.multi_acks, sizeof(_prefs.multi_acks)); // 77
|
||||
file.write(pad, 2); // 78
|
||||
file.write((uint8_t *)&_prefs.ble_pin, sizeof(_prefs.ble_pin)); // 80
|
||||
file.write((uint8_t *)&_prefs.buzzer_quiet, sizeof(_prefs.buzzer_quiet)); // 84
|
||||
file.write((uint8_t *)&_prefs.gps_enabled, sizeof(_prefs.gps_enabled)); // 85
|
||||
file.write((uint8_t *)&_prefs.gps_interval, sizeof(_prefs.gps_interval)); // 86
|
||||
file.write((uint8_t *)&_prefs.autoadd_config, sizeof(_prefs.autoadd_config)); // 87
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,15 @@
|
||||
#define CMD_SEND_PATH_DISCOVERY_REQ 52
|
||||
#define CMD_SET_FLOOD_SCOPE 54 // v8+
|
||||
#define CMD_SEND_CONTROL_DATA 55 // v8+
|
||||
#define CMD_GET_STATS 56 // v8+, second byte is stats type
|
||||
#define CMD_SEND_ANON_REQ 57
|
||||
#define CMD_SET_AUTOADD_CONFIG 58
|
||||
#define CMD_GET_AUTOADD_CONFIG 59
|
||||
|
||||
// Stats sub-types for CMD_GET_STATS
|
||||
#define STATS_TYPE_CORE 0
|
||||
#define STATS_TYPE_RADIO 1
|
||||
#define STATS_TYPE_PACKETS 2
|
||||
|
||||
#define RESP_CODE_OK 0
|
||||
#define RESP_CODE_ERR 1
|
||||
@@ -77,6 +86,8 @@
|
||||
#define RESP_CODE_CUSTOM_VARS 21
|
||||
#define RESP_CODE_ADVERT_PATH 22
|
||||
#define RESP_CODE_TUNING_PARAMS 23
|
||||
#define RESP_CODE_STATS 24 // v8+, second byte is stats type
|
||||
#define RESP_CODE_AUTOADD_CONFIG 25
|
||||
|
||||
#define SEND_TIMEOUT_BASE_MILLIS 500
|
||||
#define FLOOD_SEND_TIMEOUT_FACTOR 16.0f
|
||||
@@ -102,6 +113,8 @@
|
||||
#define PUSH_CODE_BINARY_RESPONSE 0x8C
|
||||
#define PUSH_CODE_PATH_DISCOVERY_RESPONSE 0x8D
|
||||
#define PUSH_CODE_CONTROL_DATA 0x8E // v8+
|
||||
#define PUSH_CODE_CONTACT_DELETED 0x8F // used to notify client app of deleted contact when overwriting oldest
|
||||
#define PUSH_CODE_CONTACTS_FULL 0x90 // used to notify client app that contacts storage is full
|
||||
|
||||
#define ERR_CODE_UNSUPPORTED_CMD 1
|
||||
#define ERR_CODE_NOT_FOUND 2
|
||||
@@ -112,6 +125,15 @@
|
||||
|
||||
#define MAX_SIGN_DATA_LEN (8 * 1024) // 8K
|
||||
|
||||
// Auto-add config bitmask
|
||||
// Bit 0: If set, overwrite oldest non-favourite contact when contacts file is full
|
||||
// Bits 1-4: these indicate which contact types to auto-add when manual_contact_mode = 0x01
|
||||
#define AUTO_ADD_OVERWRITE_OLDEST (1 << 0) // 0x01 - overwrite oldest non-favourite when full
|
||||
#define AUTO_ADD_CHAT (1 << 1) // 0x02 - auto-add Chat (Companion) (ADV_TYPE_CHAT)
|
||||
#define AUTO_ADD_REPEATER (1 << 2) // 0x04 - auto-add Repeater (ADV_TYPE_REPEATER)
|
||||
#define AUTO_ADD_ROOM_SERVER (1 << 3) // 0x08 - auto-add Room Server (ADV_TYPE_ROOM)
|
||||
#define AUTO_ADD_SENSOR (1 << 4) // 0x10 - auto-add Sensor (ADV_TYPE_SENSOR)
|
||||
|
||||
void MyMesh::writeOKFrame() {
|
||||
uint8_t buf[1];
|
||||
buf[0] = RESP_CODE_OK;
|
||||
@@ -254,9 +276,54 @@ bool MyMesh::isAutoAddEnabled() const {
|
||||
return (_prefs.manual_add_contacts & 1) == 0;
|
||||
}
|
||||
|
||||
bool MyMesh::shouldAutoAddContactType(uint8_t contact_type) const {
|
||||
if ((_prefs.manual_add_contacts & 1) == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t type_bit = 0;
|
||||
switch (contact_type) {
|
||||
case ADV_TYPE_CHAT:
|
||||
type_bit = AUTO_ADD_CHAT;
|
||||
break;
|
||||
case ADV_TYPE_REPEATER:
|
||||
type_bit = AUTO_ADD_REPEATER;
|
||||
break;
|
||||
case ADV_TYPE_ROOM:
|
||||
type_bit = AUTO_ADD_ROOM_SERVER;
|
||||
break;
|
||||
case ADV_TYPE_SENSOR:
|
||||
type_bit = AUTO_ADD_SENSOR;
|
||||
break;
|
||||
default:
|
||||
return false; // Unknown type, don't auto-add
|
||||
}
|
||||
|
||||
return (_prefs.autoadd_config & type_bit) != 0;
|
||||
}
|
||||
|
||||
bool MyMesh::shouldOverwriteWhenFull() const {
|
||||
return (_prefs.autoadd_config & AUTO_ADD_OVERWRITE_OLDEST) != 0;
|
||||
}
|
||||
|
||||
void MyMesh::onContactOverwrite(const uint8_t* pub_key) {
|
||||
if (_serial->isConnected()) {
|
||||
out_frame[0] = PUSH_CODE_CONTACT_DELETED;
|
||||
memcpy(&out_frame[1], pub_key, PUB_KEY_SIZE);
|
||||
_serial->writeFrame(out_frame, 1 + PUB_KEY_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onContactsFull() {
|
||||
if (_serial->isConnected()) {
|
||||
out_frame[0] = PUSH_CODE_CONTACTS_FULL;
|
||||
_serial->writeFrame(out_frame, 1);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) {
|
||||
if (_serial->isConnected()) {
|
||||
if (!isAutoAddEnabled() && is_new) {
|
||||
if (!shouldAutoAddContactType(contact.type) && is_new) {
|
||||
writeContactRespFrame(PUSH_CODE_NEW_ADVERT, contact);
|
||||
} else {
|
||||
out_frame[0] = PUSH_CODE_ADVERT;
|
||||
@@ -670,6 +737,11 @@ void MyMesh::onRawDataRecv(mesh::Packet *packet) {
|
||||
|
||||
void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code, uint8_t flags,
|
||||
const uint8_t *path_snrs, const uint8_t *path_hashes, uint8_t path_len) {
|
||||
uint8_t path_sz = flags & 0x03; // NEW v1.11+
|
||||
if (12 + path_len + (path_len >> path_sz) + 1 > sizeof(out_frame)) {
|
||||
MESH_DEBUG_PRINTLN("onTraceRecv(), path_len is too long: %d", (uint32_t)path_len);
|
||||
return;
|
||||
}
|
||||
int i = 0;
|
||||
out_frame[i++] = PUSH_CODE_TRACE_DATA;
|
||||
out_frame[i++] = 0; // reserved
|
||||
@@ -681,8 +753,9 @@ void MyMesh::onTraceRecv(mesh::Packet *packet, uint32_t tag, uint32_t auth_code,
|
||||
i += 4;
|
||||
memcpy(&out_frame[i], path_hashes, path_len);
|
||||
i += path_len;
|
||||
memcpy(&out_frame[i], path_snrs, path_len);
|
||||
i += path_len;
|
||||
|
||||
memcpy(&out_frame[i], path_snrs, path_len >> path_sz);
|
||||
i += path_len >> path_sz;
|
||||
out_frame[i++] = (int8_t)(packet->getSNR() * 4); // extra/final SNR (to this node)
|
||||
|
||||
if (_serial->isConnected()) {
|
||||
@@ -726,6 +799,8 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
_prefs.bw = LORA_BW;
|
||||
_prefs.cr = LORA_CR;
|
||||
_prefs.tx_power_dbm = LORA_TX_POWER;
|
||||
_prefs.gps_enabled = 0; // GPS disabled by default
|
||||
_prefs.gps_interval = 0; // No automatic GPS updates by default
|
||||
//_prefs.rx_delay_base = 10.0f; enable once new algo fixed
|
||||
}
|
||||
|
||||
@@ -763,6 +838,8 @@ void MyMesh::begin(bool has_display) {
|
||||
_prefs.sf = constrain(_prefs.sf, 5, 12);
|
||||
_prefs.cr = constrain(_prefs.cr, 5, 8);
|
||||
_prefs.tx_power_dbm = constrain(_prefs.tx_power_dbm, 1, MAX_LORA_TX_POWER);
|
||||
_prefs.gps_enabled = constrain(_prefs.gps_enabled, 0, 1); // Ensure boolean 0 or 1
|
||||
_prefs.gps_interval = constrain(_prefs.gps_interval, 0, 86400); // Max 24 hours
|
||||
|
||||
#ifdef BLE_PIN_CODE // 123456 by default
|
||||
if (_prefs.ble_pin == 0) {
|
||||
@@ -785,6 +862,7 @@ void MyMesh::begin(bool has_display) {
|
||||
|
||||
resetContacts();
|
||||
_store->loadContacts(this);
|
||||
bootstrapRTCfromContacts();
|
||||
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
|
||||
_store->loadChannels(this);
|
||||
|
||||
@@ -886,6 +964,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
int result;
|
||||
uint32_t expected_ack;
|
||||
if (txt_type == TXT_TYPE_CLI_DATA) {
|
||||
msg_timestamp = getRTCClock()->getCurrentTimeUnique(); // Use node's RTC instead of app timestamp to avoid tripping replay protection
|
||||
result = sendCommandData(*recipient, msg_timestamp, attempt, text, est_timeout);
|
||||
expected_ack = 0; // no Ack expected
|
||||
} else {
|
||||
@@ -1221,7 +1300,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
if (_store->saveMainIdentity(identity)) {
|
||||
self_id = identity;
|
||||
writeOKFrame();
|
||||
// re-load contacts, to recalc shared secrets
|
||||
// re-load contacts, to invalidate ecdh shared_secrets
|
||||
resetContacts();
|
||||
_store->loadContacts(this);
|
||||
} else {
|
||||
@@ -1268,6 +1347,27 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_ANON_REQ && len > 1 + PUB_KEY_SIZE) {
|
||||
uint8_t *pub_key = &cmd_frame[1];
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
uint8_t *data = &cmd_frame[1 + PUB_KEY_SIZE];
|
||||
if (recipient) {
|
||||
uint32_t tag, est_timeout;
|
||||
int result = sendAnonReq(*recipient, data, len - (1 + PUB_KEY_SIZE), tag, est_timeout);
|
||||
if (result == MSG_SEND_FAILED) {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
} else {
|
||||
clearPendingReqs();
|
||||
pending_req = tag; // match this to onContactResponse()
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = (result == MSG_SEND_SENT_FLOOD) ? 1 : 0;
|
||||
memcpy(&out_frame[2], &tag, 4);
|
||||
memcpy(&out_frame[6], &est_timeout, 4);
|
||||
_serial->writeFrame(out_frame, 10);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND); // contact not found
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_STATUS_REQ && len >= 1 + PUB_KEY_SIZE) {
|
||||
uint8_t *pub_key = &cmd_frame[1];
|
||||
ContactInfo *recipient = lookupContactByPubKey(pub_key, PUB_KEY_SIZE);
|
||||
@@ -1446,25 +1546,31 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_BAD_STATE);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PATH_SIZE) {
|
||||
uint32_t tag, auth;
|
||||
memcpy(&tag, &cmd_frame[1], 4);
|
||||
memcpy(&auth, &cmd_frame[5], 4);
|
||||
auto pkt = createTrace(tag, auth, cmd_frame[9]);
|
||||
if (pkt) {
|
||||
uint8_t path_len = len - 10;
|
||||
sendDirect(pkt, &cmd_frame[10], path_len);
|
||||
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
||||
uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len);
|
||||
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = 0;
|
||||
memcpy(&out_frame[2], &tag, 4);
|
||||
memcpy(&out_frame[6], &est_timeout, 4);
|
||||
_serial->writeFrame(out_frame, 10);
|
||||
} else if (cmd_frame[0] == CMD_SEND_TRACE_PATH && len > 10 && len - 10 < MAX_PACKET_PAYLOAD-5) {
|
||||
uint8_t path_len = len - 10;
|
||||
uint8_t flags = cmd_frame[9];
|
||||
uint8_t path_sz = flags & 0x03; // NEW v1.11+
|
||||
if ((path_len >> path_sz) > MAX_PATH_SIZE || (path_len % (1 << path_sz)) != 0) { // make sure is multiple of path_sz
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
uint32_t tag, auth;
|
||||
memcpy(&tag, &cmd_frame[1], 4);
|
||||
memcpy(&auth, &cmd_frame[5], 4);
|
||||
auto pkt = createTrace(tag, auth, flags);
|
||||
if (pkt) {
|
||||
sendDirect(pkt, &cmd_frame[10], path_len);
|
||||
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->payload_len + pkt->path_len + 2);
|
||||
uint32_t est_timeout = calcDirectTimeoutMillisFor(t, path_len);
|
||||
|
||||
out_frame[0] = RESP_CODE_SENT;
|
||||
out_frame[1] = 0;
|
||||
memcpy(&out_frame[2], &tag, 4);
|
||||
memcpy(&out_frame[6], &est_timeout, 4);
|
||||
_serial->writeFrame(out_frame, 10);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_DEVICE_PIN && len >= 5) {
|
||||
|
||||
@@ -1502,6 +1608,17 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
*np++ = 0; // modify 'cmd_frame', replace ':' with null
|
||||
bool success = sensors.setSettingValue(sp, np);
|
||||
if (success) {
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
// Update node preferences for GPS settings
|
||||
if (strcmp(sp, "gps") == 0) {
|
||||
_prefs.gps_enabled = (np[0] == '1') ? 1 : 0;
|
||||
savePrefs();
|
||||
} else if (strcmp(sp, "gps_interval") == 0) {
|
||||
uint32_t interval_seconds = atoi(np);
|
||||
_prefs.gps_interval = constrain(interval_seconds, 0, 86400);
|
||||
savePrefs();
|
||||
}
|
||||
#endif
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
@@ -1529,7 +1646,60 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_NOT_FOUND);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_GET_STATS && len >= 2) {
|
||||
uint8_t stats_type = cmd_frame[1];
|
||||
if (stats_type == STATS_TYPE_CORE) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_STATS;
|
||||
out_frame[i++] = STATS_TYPE_CORE;
|
||||
uint16_t battery_mv = board.getBattMilliVolts();
|
||||
uint32_t uptime_secs = _ms->getMillis() / 1000;
|
||||
uint8_t queue_len = (uint8_t)_mgr->getOutboundCount(0xFFFFFFFF);
|
||||
memcpy(&out_frame[i], &battery_mv, 2); i += 2;
|
||||
memcpy(&out_frame[i], &uptime_secs, 4); i += 4;
|
||||
memcpy(&out_frame[i], &_err_flags, 2); i += 2;
|
||||
out_frame[i++] = queue_len;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (stats_type == STATS_TYPE_RADIO) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_STATS;
|
||||
out_frame[i++] = STATS_TYPE_RADIO;
|
||||
int16_t noise_floor = (int16_t)_radio->getNoiseFloor();
|
||||
int8_t last_rssi = (int8_t)radio_driver.getLastRSSI();
|
||||
int8_t last_snr = (int8_t)(radio_driver.getLastSNR() * 4); // scaled by 4 for 0.25 dB precision
|
||||
uint32_t tx_air_secs = getTotalAirTime() / 1000;
|
||||
uint32_t rx_air_secs = getReceiveAirTime() / 1000;
|
||||
memcpy(&out_frame[i], &noise_floor, 2); i += 2;
|
||||
out_frame[i++] = last_rssi;
|
||||
out_frame[i++] = last_snr;
|
||||
memcpy(&out_frame[i], &tx_air_secs, 4); i += 4;
|
||||
memcpy(&out_frame[i], &rx_air_secs, 4); i += 4;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (stats_type == STATS_TYPE_PACKETS) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_STATS;
|
||||
out_frame[i++] = STATS_TYPE_PACKETS;
|
||||
uint32_t recv = radio_driver.getPacketsRecv();
|
||||
uint32_t sent = radio_driver.getPacketsSent();
|
||||
uint32_t n_sent_flood = getNumSentFlood();
|
||||
uint32_t n_sent_direct = getNumSentDirect();
|
||||
uint32_t n_recv_flood = getNumRecvFlood();
|
||||
uint32_t n_recv_direct = getNumRecvDirect();
|
||||
memcpy(&out_frame[i], &recv, 4); i += 4;
|
||||
memcpy(&out_frame[i], &sent, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_sent_flood, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_sent_direct, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_recv_flood, 4); i += 4;
|
||||
memcpy(&out_frame[i], &n_recv_direct, 4); i += 4;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG); // invalid stats sub-type
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_FACTORY_RESET && memcmp(&cmd_frame[1], "reset", 5) == 0) {
|
||||
if (_serial) {
|
||||
MESH_DEBUG_PRINTLN("Factory reset: disabling serial interface to prevent reconnects (BLE/WiFi)");
|
||||
_serial->disable(); // Phone app disconnects before we can send OK frame so it's safe here
|
||||
}
|
||||
bool success = _store->formatFileSystem();
|
||||
if (success) {
|
||||
writeOKFrame();
|
||||
@@ -1553,6 +1723,15 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_AUTOADD_CONFIG) {
|
||||
_prefs.autoadd_config = cmd_frame[1];
|
||||
savePrefs();
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_GET_AUTOADD_CONFIG) {
|
||||
int i = 0;
|
||||
out_frame[i++] = RESP_CODE_AUTOADD_CONFIG;
|
||||
out_frame[i++] = _prefs.autoadd_config;
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
@@ -1797,4 +1976,4 @@ bool MyMesh::advert() {
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#define FIRMWARE_VER_CODE 8
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#define FIRMWARE_BUILD_DATE "30 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#define FIRMWARE_VERSION "v1.11.0"
|
||||
#endif
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
@@ -114,6 +114,10 @@ protected:
|
||||
|
||||
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
|
||||
bool isAutoAddEnabled() const override;
|
||||
bool shouldAutoAddContactType(uint8_t type) const override;
|
||||
bool shouldOverwriteWhenFull() const override;
|
||||
void onContactsFull() override;
|
||||
void onContactOverwrite(const uint8_t* pub_key) override;
|
||||
bool onContactPathRecv(ContactInfo& from, uint8_t* in_path, uint8_t in_path_len, uint8_t* out_path, uint8_t out_path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path_len, const uint8_t* path) override;
|
||||
void onContactPathUpdated(const ContactInfo &contact) override;
|
||||
@@ -152,6 +156,9 @@ protected:
|
||||
pending_login = pending_status = pending_telemetry = pending_discovery = pending_req = 0;
|
||||
}
|
||||
|
||||
public:
|
||||
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
|
||||
|
||||
private:
|
||||
void writeOKFrame();
|
||||
void writeErrFrame(uint8_t err_code);
|
||||
@@ -171,11 +178,9 @@ private:
|
||||
void checkSerialInterface();
|
||||
|
||||
// helpers, short-cuts
|
||||
void savePrefs() { _store->savePrefs(_prefs, sensors.node_lat, sensors.node_lon); }
|
||||
void saveChannels() { _store->saveChannels(this); }
|
||||
void saveContacts() { _store->saveContacts(this); }
|
||||
|
||||
private:
|
||||
DataStore* _store;
|
||||
NodePrefs _prefs;
|
||||
uint32_t pending_login;
|
||||
|
||||
@@ -24,4 +24,8 @@ struct NodePrefs { // persisted to file
|
||||
float rx_delay_base;
|
||||
uint32_t ble_pin;
|
||||
uint8_t advert_loc_policy;
|
||||
uint8_t buzzer_quiet;
|
||||
uint8_t gps_enabled; // GPS enabled flag (0=disabled, 1=enabled)
|
||||
uint32_t gps_interval; // GPS read interval in seconds
|
||||
uint8_t autoadd_config; // bitmask for auto-add contacts config
|
||||
};
|
||||
@@ -2,6 +2,9 @@
|
||||
#include <helpers/TxtDataHelpers.h>
|
||||
#include "../MyMesh.h"
|
||||
#include "target.h"
|
||||
#ifdef WIFI_SSID
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
|
||||
#ifndef AUTO_OFF_MILLIS
|
||||
#define AUTO_OFF_MILLIS 15000 // 15 seconds
|
||||
@@ -129,7 +132,7 @@ class HomeScreen : public UIScreen {
|
||||
bool sensors_scroll = false;
|
||||
int sensors_scroll_offset = 0;
|
||||
int next_sensors_refresh = 0;
|
||||
|
||||
|
||||
void refresh_sensors() {
|
||||
if (millis() > next_sensors_refresh) {
|
||||
sensors_lpp.reset();
|
||||
@@ -192,10 +195,17 @@ public:
|
||||
sprintf(tmp, "MSG: %d", _task->getMsgCount());
|
||||
display.drawTextCentered(display.width() / 2, 20, tmp);
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
IPAddress ip = WiFi.localIP();
|
||||
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width() / 2, 54, tmp);
|
||||
#endif
|
||||
if (_task->hasConnection()) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width() / 2, 43, "< Connected >");
|
||||
|
||||
} else if (the_mesh.getBLEPin() != 0) { // BT pin
|
||||
display.setColor(DisplayDriver::RED);
|
||||
display.setTextSize(2);
|
||||
@@ -260,13 +270,24 @@ public:
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
} else if (_page == HomePage::GPS) {
|
||||
LocationProvider* nmea = sensors.getLocationProvider();
|
||||
char buf[50];
|
||||
int y = 18;
|
||||
display.drawTextLeftAlign(0, y, _task->getGPSState() ? "gps on" : "gps off");
|
||||
bool gps_state = _task->getGPSState();
|
||||
#ifdef PIN_GPS_SWITCH
|
||||
bool hw_gps_state = digitalRead(PIN_GPS_SWITCH);
|
||||
if (gps_state != hw_gps_state) {
|
||||
strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)");
|
||||
} else {
|
||||
strcpy(buf, gps_state ? "gps on" : "gps off");
|
||||
}
|
||||
#else
|
||||
strcpy(buf, gps_state ? "gps on" : "gps off");
|
||||
#endif
|
||||
display.drawTextLeftAlign(0, y, buf);
|
||||
if (nmea == NULL) {
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "Can't access GPS");
|
||||
} else {
|
||||
char buf[50];
|
||||
strcpy(buf, nmea->isValid()?"fix":"no fix");
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
@@ -526,12 +547,26 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#endif
|
||||
|
||||
_node_prefs = node_prefs;
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
// Apply GPS preferences from stored prefs
|
||||
if (_sensors != NULL && _node_prefs != NULL) {
|
||||
_sensors->setSettingValue("gps", _node_prefs->gps_enabled ? "1" : "0");
|
||||
if (_node_prefs->gps_interval > 0) {
|
||||
char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null)
|
||||
sprintf(interval_str, "%u", _node_prefs->gps_interval);
|
||||
_sensors->setSettingValue("gps_interval", interval_str);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (_display != NULL) {
|
||||
_display->turnOn();
|
||||
}
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
buzzer.begin();
|
||||
buzzer.quiet(_node_prefs->buzzer_quiet);
|
||||
#endif
|
||||
|
||||
#ifdef PIN_VIBRATION
|
||||
@@ -596,9 +631,13 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
setCurrScreen(msg_preview);
|
||||
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn()) _display->turnOn();
|
||||
if (!_display->isOn() && !hasConnection()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
if (_display->isOn()) {
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
|
||||
_next_refresh = 100; // trigger refresh
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,7 +657,7 @@ void UITask::userLedHandler() {
|
||||
led_state = 0;
|
||||
next_led_change = cur_time + LED_CYCLE_MILLIS - last_led_increment;
|
||||
}
|
||||
digitalWrite(PIN_STATUS_LED, led_state);
|
||||
digitalWrite(PIN_STATUS_LED, led_state == LED_STATE_ON);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -650,6 +689,7 @@ void UITask::shutdown(bool restart){
|
||||
_board->reboot();
|
||||
} else {
|
||||
_display->turnOff();
|
||||
radio_driver.powerOff();
|
||||
_board->powerOff();
|
||||
}
|
||||
}
|
||||
@@ -714,10 +754,14 @@ void UITask::loop() {
|
||||
_analogue_pin_read_millis = millis();
|
||||
}
|
||||
#endif
|
||||
#if defined(DISP_BACKLIGHT) && defined(BACKLIGHT_BTN)
|
||||
#if defined(BACKLIGHT_BTN)
|
||||
if (millis() > next_backlight_btn_check) {
|
||||
bool touch_state = digitalRead(PIN_BUTTON2);
|
||||
#if defined(DISP_BACKLIGHT)
|
||||
digitalWrite(DISP_BACKLIGHT, !touch_state);
|
||||
#elif defined(EXP_PIN_BACKLIGHT)
|
||||
expander.digitalWrite(EXP_PIN_BACKLIGHT, !touch_state);
|
||||
#endif
|
||||
next_backlight_btn_check = millis() + 300;
|
||||
}
|
||||
#endif
|
||||
@@ -846,13 +890,15 @@ void UITask::toggleGPS() {
|
||||
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
|
||||
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
|
||||
_sensors->setSettingValue("gps", "0");
|
||||
_node_prefs->gps_enabled = 0;
|
||||
notify(UIEventType::ack);
|
||||
showAlert("GPS: Disabled", 800);
|
||||
} else {
|
||||
_sensors->setSettingValue("gps", "1");
|
||||
_node_prefs->gps_enabled = 1;
|
||||
notify(UIEventType::ack);
|
||||
showAlert("GPS: Enabled", 800);
|
||||
}
|
||||
the_mesh.savePrefs();
|
||||
showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800);
|
||||
_next_refresh = 0;
|
||||
break;
|
||||
}
|
||||
@@ -866,11 +912,12 @@ void UITask::toggleBuzzer() {
|
||||
if (buzzer.isQuiet()) {
|
||||
buzzer.quiet(false);
|
||||
notify(UIEventType::ack);
|
||||
showAlert("Buzzer: ON", 800);
|
||||
} else {
|
||||
buzzer.quiet(true);
|
||||
showAlert("Buzzer: OFF", 800);
|
||||
}
|
||||
_node_prefs->buzzer_quiet = buzzer.isQuiet();
|
||||
the_mesh.savePrefs();
|
||||
showAlert(buzzer.isQuiet() ? "Buzzer: OFF" : "Buzzer: ON", 800);
|
||||
_next_refresh = 0; // trigger refresh
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include <Arduino.h>
|
||||
#include <helpers/sensors/LPPDataHelpers.h>
|
||||
|
||||
#ifndef LED_STATE_ON
|
||||
#define LED_STATE_ON 1
|
||||
#endif
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
#include <helpers/ui/buzzer.h>
|
||||
#endif
|
||||
@@ -50,7 +54,7 @@ class UITask : public AbstractUITask {
|
||||
UIScreen* curr;
|
||||
|
||||
void userLedHandler();
|
||||
|
||||
|
||||
// Button action handlers
|
||||
char checkDisplayOn(char c);
|
||||
char handleLongPress(char c);
|
||||
|
||||
@@ -56,6 +56,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
|
||||
#ifdef PIN_BUZZER
|
||||
buzzer.begin();
|
||||
buzzer.quiet(_node_prefs->buzzer_quiet);
|
||||
#endif
|
||||
|
||||
// Initialize digital button if available
|
||||
@@ -136,9 +137,13 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i
|
||||
StrHelper::strncpy(_msg, text, sizeof(_msg));
|
||||
|
||||
if (_display != NULL) {
|
||||
if (!_display->isOn()) _display->turnOn();
|
||||
if (!_display->isOn() && !hasConnection()) {
|
||||
_display->turnOn();
|
||||
}
|
||||
if (_display->isOn()) {
|
||||
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
|
||||
_need_refresh = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,7 +274,7 @@ void UITask::userLedHandler() {
|
||||
state = 0;
|
||||
next_change = cur_time + LED_CYCLE_MILLIS - last_increment;
|
||||
}
|
||||
digitalWrite(PIN_STATUS_LED, state);
|
||||
digitalWrite(PIN_STATUS_LED, state == LED_STATE_ON);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -292,10 +297,12 @@ void UITask::shutdown(bool restart){
|
||||
|
||||
#endif // PIN_BUZZER
|
||||
|
||||
if (restart)
|
||||
if (restart) {
|
||||
_board->reboot();
|
||||
else
|
||||
} else {
|
||||
radio_driver.powerOff();
|
||||
_board->powerOff();
|
||||
}
|
||||
}
|
||||
|
||||
void UITask::loop() {
|
||||
@@ -394,6 +401,8 @@ void UITask::handleButtonTriplePress() {
|
||||
buzzer.quiet(true);
|
||||
sprintf(_alert, "Buzzer: OFF");
|
||||
}
|
||||
_node_prefs->buzzer_quiet = buzzer.isQuiet();
|
||||
the_mesh.savePrefs();
|
||||
_need_refresh = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -41,16 +41,21 @@
|
||||
#define TXT_ACK_DELAY 200
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_VER_LEVEL 1
|
||||
#define FIRMWARE_VER_LEVEL 2
|
||||
|
||||
#define REQ_TYPE_GET_STATUS 0x01 // same as _GET_STATS
|
||||
#define REQ_TYPE_KEEP_ALIVE 0x02
|
||||
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03
|
||||
#define REQ_TYPE_GET_ACCESS_LIST 0x05
|
||||
#define REQ_TYPE_GET_NEIGHBOURS 0x06
|
||||
#define REQ_TYPE_GET_OWNER_INFO 0x07 // FIRMWARE_VER_LEVEL >= 2
|
||||
|
||||
#define RESP_SERVER_LOGIN_OK 0 // response to ANON_REQ
|
||||
|
||||
#define ANON_REQ_TYPE_REGIONS 0x01
|
||||
#define ANON_REQ_TYPE_OWNER 0x02
|
||||
#define ANON_REQ_TYPE_BASIC 0x03 // just remote clock
|
||||
|
||||
#define CLI_REPLY_DELAY_MILLIS 600
|
||||
|
||||
#define LAZY_CONTACTS_WRITE_DELAY 5000
|
||||
@@ -82,7 +87,7 @@ void MyMesh::putNeighbour(const mesh::Identity &id, uint32_t timestamp, float sn
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) {
|
||||
ClientInfo* client = NULL;
|
||||
if (data[0] == 0) { // blank password, just check if sender is in ACL
|
||||
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||
@@ -123,6 +128,10 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
}
|
||||
}
|
||||
|
||||
if (is_flood) {
|
||||
client->out_path_len = -1; // need to rediscover out_path
|
||||
}
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
||||
@@ -135,6 +144,64 @@ uint8_t MyMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secr
|
||||
return 13; // reply length
|
||||
}
|
||||
|
||||
uint8_t MyMesh::handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
// request data has: {reply-path-len}{reply-path}
|
||||
reply_path_len = *data++ & 0x3F;
|
||||
memcpy(reply_path, data, reply_path_len);
|
||||
// data += reply_path_len;
|
||||
|
||||
memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness)
|
||||
|
||||
return 8 + region_map.exportNamesTo((char *) &reply_data[8], sizeof(reply_data) - 12, REGION_DENY_FLOOD); // reply length
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t MyMesh::handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
// request data has: {reply-path-len}{reply-path}
|
||||
reply_path_len = *data++ & 0x3F;
|
||||
memcpy(reply_path, data, reply_path_len);
|
||||
// data += reply_path_len;
|
||||
|
||||
memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness)
|
||||
sprintf((char *) &reply_data[8], "%s\n%s", _prefs.node_name, _prefs.owner_info);
|
||||
|
||||
return 8 + strlen((char *) &reply_data[8]); // reply length
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t MyMesh::handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
if (anon_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
// request data has: {reply-path-len}{reply-path}
|
||||
reply_path_len = *data++ & 0x3F;
|
||||
memcpy(reply_path, data, reply_path_len);
|
||||
// data += reply_path_len;
|
||||
|
||||
memcpy(reply_data, &sender_timestamp, 4); // prefix with sender_timestamp, like a tag
|
||||
uint32_t now = getRTCClock()->getCurrentTime();
|
||||
memcpy(&reply_data[4], &now, 4); // include our clock (for easy clock sync, and packet hash uniqueness)
|
||||
reply_data[8] = 0; // features
|
||||
#ifdef WITH_RS232_BRIDGE
|
||||
reply_data[8] |= 0x01; // is bridge, type UART
|
||||
#elif WITH_ESPNOW_BRIDGE
|
||||
reply_data[8] |= 0x03; // is bridge, type ESP-NOW
|
||||
#endif
|
||||
if (_prefs.disable_fwd) { // is this repeater currently disabled
|
||||
reply_data[8] |= 0x80; // is disabled
|
||||
}
|
||||
// TODO: add some kind of moving-window utilisation metric, so can query 'how busy' is this repeater
|
||||
return 9; // reply length
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t *payload, size_t payload_len) {
|
||||
// uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
// memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
@@ -169,12 +236,19 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
|
||||
telemetry.reset();
|
||||
telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f);
|
||||
|
||||
// query other sensors -- target specific
|
||||
if ((sender->permissions & PERM_ACL_ROLE_MASK) == PERM_ACL_GUEST) {
|
||||
perm_mask = 0x00; // just base telemetry allowed
|
||||
}
|
||||
sensors.querySensors(perm_mask, telemetry);
|
||||
|
||||
// This default temperature will be overridden by external sensors (if any)
|
||||
float temperature = board.getMCUTemperature();
|
||||
if(!isnan(temperature)) { // Supported boards with built-in temperature sensor. ESP32-C3 may return NAN
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature
|
||||
}
|
||||
|
||||
uint8_t tlen = telemetry.getSize();
|
||||
memcpy(&reply_data[4], telemetry.getBuffer(), tlen);
|
||||
return 4 + tlen; // reply_len
|
||||
@@ -285,6 +359,9 @@ int MyMesh::handleRequest(ClientInfo *sender, uint32_t sender_timestamp, uint8_t
|
||||
|
||||
return reply_offset;
|
||||
}
|
||||
} else if (payload[0] == REQ_TYPE_GET_OWNER_INFO) {
|
||||
sprintf((char *) &reply_data[4], "%s\n%s\n%s", FIRMWARE_VERSION, _prefs.node_name, _prefs.owner_info);
|
||||
return 4 + strlen((char *) &reply_data[4]);
|
||||
}
|
||||
return 0; // unknown command
|
||||
}
|
||||
@@ -437,12 +514,18 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len;
|
||||
|
||||
reply_path_len = -1;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||
// TODO
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood());
|
||||
} else if (data[4] == ANON_REQ_TYPE_REGIONS && packet->isRouteDirect()) {
|
||||
reply_len = handleAnonRegionsReq(sender, timestamp, &data[5]);
|
||||
} else if (data[4] == ANON_REQ_TYPE_OWNER && packet->isRouteDirect()) {
|
||||
reply_len = handleAnonOwnerReq(sender, timestamp, &data[5]);
|
||||
} else if (data[4] == ANON_REQ_TYPE_BASIC && packet->isRouteDirect()) {
|
||||
reply_len = handleAnonClockReq(sender, timestamp, &data[5]);
|
||||
} else {
|
||||
reply_len = 0; // unknown request type
|
||||
reply_len = 0; // unknown/invalid request type
|
||||
}
|
||||
|
||||
if (reply_len == 0) return; // invalid request
|
||||
@@ -452,9 +535,12 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
mesh::Packet* path = createPathReturn(sender, secret, packet->path, packet->path_len,
|
||||
PAYLOAD_TYPE_RESPONSE, reply_data, reply_len);
|
||||
if (path) sendFlood(path, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
} else if (reply_path_len < 0) {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendFlood(reply, SERVER_RESPONSE_DELAY);
|
||||
} else {
|
||||
mesh::Packet* reply = createDatagram(PAYLOAD_TYPE_RESPONSE, sender, secret, reply_data, reply_len);
|
||||
if (reply) sendDirect(reply, reply_path, reply_path_len, SERVER_RESPONSE_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -541,7 +627,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && client->isAdmin()) { // a CLI command
|
||||
uint32_t sender_timestamp;
|
||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
uint8_t flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported text type received: flags=%02x", (uint32_t)flags);
|
||||
@@ -626,7 +712,9 @@ bool MyMesh::onPeerPathRecv(mesh::Packet *packet, int sender_idx, const uint8_t
|
||||
|
||||
void MyMesh::onControlDataRecv(mesh::Packet* packet) {
|
||||
uint8_t type = packet->payload[0] & 0xF0; // just test upper 4 bits
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6 && discover_limiter.allow(rtc_clock.getCurrentTime())) {
|
||||
if (type == CTL_TYPE_NODE_DISCOVER_REQ && packet->payload_len >= 6
|
||||
&& !_prefs.disable_fwd && discover_limiter.allow(rtc_clock.getCurrentTime())
|
||||
) {
|
||||
int i = 1;
|
||||
uint8_t filter = packet->payload[i++];
|
||||
uint32_t tag;
|
||||
@@ -657,7 +745,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
mesh::RTCClock &rtc, mesh::MeshTables &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),
|
||||
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
|
||||
#if defined(WITH_RS232_BRIDGE)
|
||||
, bridge(&_prefs, WITH_RS232_BRIDGE, _mgr, &rtc)
|
||||
#endif
|
||||
@@ -710,6 +799,8 @@ MyMesh::MyMesh(mesh::MainBoard &board, mesh::Radio &radio, mesh::MillisecondCloc
|
||||
_prefs.gps_enabled = 0;
|
||||
_prefs.gps_interval = 0;
|
||||
_prefs.advert_loc_policy = ADVERT_LOC_PREFS;
|
||||
|
||||
_prefs.adc_multiplier = 0.0f; // 0.0f means use default board multiplier
|
||||
}
|
||||
|
||||
void MyMesh::begin(FILESYSTEM *fs) {
|
||||
@@ -733,6 +824,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
board.setAdcMultiplier(_prefs.adc_multiplier);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
@@ -1101,3 +1194,8 @@ void MyMesh::loop() {
|
||||
uptime_millis += now - last_millis;
|
||||
last_millis = now;
|
||||
}
|
||||
|
||||
// To check if there is pending work
|
||||
bool MyMesh::hasPendingWork() const {
|
||||
return _mgr->getOutboundCount(0xFFFFFFFF) > 0;
|
||||
}
|
||||
|
||||
@@ -68,11 +68,11 @@ struct NeighbourInfo {
|
||||
};
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#define FIRMWARE_BUILD_DATE "30 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#define FIRMWARE_VERSION "v1.11.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "repeater"
|
||||
@@ -88,12 +88,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
NodePrefs _prefs;
|
||||
CommonCLI _cli;
|
||||
uint8_t reply_data[MAX_PACKET_PAYLOAD];
|
||||
uint8_t reply_path[MAX_PATH_SIZE];
|
||||
int8_t reply_path_len;
|
||||
ClientACL acl;
|
||||
TransportKeyStore key_store;
|
||||
RegionMap region_map, temp_map;
|
||||
RegionEntry* load_stack[8];
|
||||
RegionEntry* recv_pkt_region;
|
||||
RateLimiter discover_limiter;
|
||||
RateLimiter discover_limiter, anon_limiter;
|
||||
bool region_load_active;
|
||||
unsigned long dirty_contacts_expiry;
|
||||
#if MAX_NEIGHBOURS
|
||||
@@ -113,7 +115,10 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks {
|
||||
#endif
|
||||
|
||||
void putNeighbour(const mesh::Identity& id, uint32_t timestamp, float snr);
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
uint8_t handleAnonRegionsReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleAnonOwnerReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleAnonClockReq(const mesh::Identity& sender, uint32_t sender_timestamp, const uint8_t* data);
|
||||
int handleRequest(ClientInfo* sender, uint32_t sender_timestamp, uint8_t* payload, size_t payload_len);
|
||||
mesh::Packet* createSelfAdvert();
|
||||
|
||||
@@ -225,4 +230,7 @@ public:
|
||||
bridge.begin();
|
||||
}
|
||||
#endif
|
||||
|
||||
// To check if there is pending work
|
||||
bool hasPendingWork() const;
|
||||
};
|
||||
|
||||
@@ -19,12 +19,19 @@ void halt() {
|
||||
|
||||
static char command[160];
|
||||
|
||||
// For power saving
|
||||
unsigned long lastActive = 0; // mark last active time
|
||||
unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
delay(1000);
|
||||
|
||||
board.begin();
|
||||
|
||||
// For power saving
|
||||
lastActive = millis(); // mark last active time since boot
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
if (display.begin()) {
|
||||
display.startFrame();
|
||||
@@ -117,4 +124,15 @@ void loop() {
|
||||
ui_task.loop();
|
||||
#endif
|
||||
rtc_clock.tick();
|
||||
|
||||
if (the_mesh.getNodePrefs()->powersaving_enabled && // To check if power saving is enabled
|
||||
the_mesh.millisHasNowPassed(lastActive + nextSleepinSecs * 1000)) { // To check if it is time to sleep
|
||||
if (!the_mesh.hasPendingWork()) { // No pending work. Safe to sleep
|
||||
board.sleep(1800); // To sleep. Wake up after 30 minutes or when receiving a LoRa packet
|
||||
lastActive = millis();
|
||||
nextSleepinSecs = 5; // Default: To work for 5s and sleep again
|
||||
} else {
|
||||
nextSleepinSecs += 5; // When there is pending work, to work another 5s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,6 +332,10 @@ void MyMesh::onAnonDataRecv(mesh::Packet *packet, const uint8_t *secret, const m
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
}
|
||||
|
||||
if (packet->isRouteFlood()) {
|
||||
client->out_path_len = -1; // need to rediscover out_path
|
||||
}
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
// TODO: maybe reply with count of messages waiting to be synced for THIS client?
|
||||
@@ -394,7 +398,7 @@ void MyMesh::onPeerDataRecv(mesh::Packet *packet, uint8_t type, int sender_idx,
|
||||
if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) { // a CLI command or new Post
|
||||
uint32_t sender_timestamp;
|
||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
uint8_t flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
|
||||
if (!(flags == TXT_TYPE_PLAIN || flags == TXT_TYPE_CLI_DATA)) {
|
||||
MESH_DEBUG_PRINTLN("onPeerDataRecv: unsupported command flags received: flags=%02x", (uint32_t)flags);
|
||||
@@ -641,6 +645,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
board.setAdcMultiplier(_prefs.adc_multiplier);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
|
||||
@@ -26,11 +26,11 @@
|
||||
/* ------------------------------ Config -------------------------------- */
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#define FIRMWARE_BUILD_DATE "30 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#define FIRMWARE_VERSION "v1.11.0"
|
||||
#endif
|
||||
|
||||
#ifndef LORA_FREQ
|
||||
|
||||
@@ -326,7 +326,7 @@ int SensorMesh::getAGCResetInterval() const {
|
||||
return ((int)_prefs.agc_reset_interval) * 4000; // milliseconds
|
||||
}
|
||||
|
||||
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data) {
|
||||
uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood) {
|
||||
ClientInfo* client;
|
||||
if (data[0] == 0) { // blank password, just check if sender is in ACL
|
||||
client = acl.getClient(sender.pub_key, PUB_KEY_SIZE);
|
||||
@@ -359,6 +359,10 @@ uint8_t SensorMesh::handleLoginReq(const mesh::Identity& sender, const uint8_t*
|
||||
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
|
||||
}
|
||||
|
||||
if (is_flood) {
|
||||
client->out_path_len = -1; // need to rediscover out_path
|
||||
}
|
||||
|
||||
uint32_t now = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(reply_data, &now, 4); // response packets always prefixed with timestamp
|
||||
reply_data[4] = RESP_SERVER_LOGIN_OK;
|
||||
@@ -451,7 +455,7 @@ void SensorMesh::onAnonDataRecv(mesh::Packet* packet, const uint8_t* secret, con
|
||||
data[len] = 0; // ensure null terminator
|
||||
uint8_t reply_len;
|
||||
if (data[4] == 0 || data[4] >= ' ') { // is password, ie. a login request
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4]);
|
||||
reply_len = handleLoginReq(sender, secret, timestamp, &data[4], packet->isRouteFlood());
|
||||
//} else if (data[4] == ANON_REQ_TYPE_*) { // future type codes
|
||||
// TODO
|
||||
} else {
|
||||
@@ -550,7 +554,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
} else if (type == PAYLOAD_TYPE_TXT_MSG && len > 5 && from->isAdmin()) { // a CLI command
|
||||
uint32_t sender_timestamp;
|
||||
memcpy(&sender_timestamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
uint8_t flags = (data[4] >> 2); // message attempt number, and other flags
|
||||
|
||||
if (sender_timestamp > from->last_timestamp) { // prevent replay attacks
|
||||
if (flags == TXT_TYPE_PLAIN) {
|
||||
@@ -608,7 +612,7 @@ void SensorMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender_i
|
||||
}
|
||||
}
|
||||
|
||||
bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len) {
|
||||
bool SensorMesh::handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len) {
|
||||
MESH_DEBUG_PRINT("handleIncomingMsg: unhandled msg from ");
|
||||
#ifdef MESH_DEBUG
|
||||
mesh::Utils::printHex(Serial, from.id.pub_key, PUB_KEY_SIZE);
|
||||
@@ -740,6 +744,8 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
|
||||
board.setAdcMultiplier(_prefs.adc_multiplier);
|
||||
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
applyGpsPrefs();
|
||||
#endif
|
||||
|
||||
@@ -33,11 +33,11 @@
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "13 Nov 2025"
|
||||
#define FIRMWARE_BUILD_DATE "30 Nov 2025"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.10.0"
|
||||
#define FIRMWARE_VERSION "v1.11.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
@@ -127,7 +127,7 @@ protected:
|
||||
bool onPeerPathRecv(mesh::Packet* packet, int sender_idx, const uint8_t* secret, uint8_t* path, uint8_t path_len, uint8_t extra_type, uint8_t* extra, uint8_t extra_len) override;
|
||||
void onControlDataRecv(mesh::Packet* packet) override;
|
||||
void onAckRecv(mesh::Packet* packet, uint32_t ack_crc) override;
|
||||
virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint flags, size_t len);
|
||||
virtual bool handleIncomingMsg(ClientInfo& from, uint32_t timestamp, uint8_t* data, uint8_t flags, size_t len);
|
||||
void sendAckTo(const ClientInfo& dest, uint32_t ack_hash);
|
||||
private:
|
||||
FILESYSTEM* _fs;
|
||||
@@ -148,7 +148,7 @@ private:
|
||||
uint8_t pending_sf;
|
||||
uint8_t pending_cr;
|
||||
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data);
|
||||
uint8_t handleLoginReq(const mesh::Identity& sender, const uint8_t* secret, uint32_t sender_timestamp, const uint8_t* data, bool is_flood);
|
||||
uint8_t handleRequest(uint8_t perms, uint32_t sender_timestamp, uint8_t req_type, uint8_t* payload, size_t payload_len);
|
||||
mesh::Packet* createSelfAdvert();
|
||||
|
||||
|
||||
+3
-1
@@ -79,7 +79,9 @@ extends = arduino_base
|
||||
platform = nordicnrf52
|
||||
platform_packages =
|
||||
framework-arduinoadafruitnrf52 @ 1.10700.0
|
||||
extra_scripts = create-uf2.py
|
||||
extra_scripts =
|
||||
create-uf2.py
|
||||
arch/nrf52/extra_scripts/patch_bluefruit.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D NRF52_PLATFORM
|
||||
-D LFS_NO_ASSERT=1
|
||||
|
||||
@@ -23,6 +23,9 @@ public:
|
||||
bool isHashMatch(const uint8_t* hash) const {
|
||||
return memcmp(hash, pub_key, PATH_HASH_SIZE) == 0;
|
||||
}
|
||||
bool isHashMatch(const uint8_t* hash, uint8_t len) const {
|
||||
return memcmp(hash, pub_key, len) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief Performs Ed25519 signature verification.
|
||||
|
||||
+15
-4
@@ -52,14 +52,15 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
uint32_t auth_code;
|
||||
memcpy(&auth_code, &pkt->payload[i], 4); i += 4;
|
||||
uint8_t flags = pkt->payload[i++];
|
||||
uint8_t path_sz = flags & 0x03; // NEW v1.11+: lower 2 bits is path hash size
|
||||
|
||||
uint8_t len = pkt->payload_len - i;
|
||||
if (pkt->path_len >= len) { // TRACE has reached end of given path
|
||||
uint8_t offset = pkt->path_len << path_sz;
|
||||
if (offset >= len) { // TRACE has reached end of given path
|
||||
onTraceRecv(pkt, trace_tag, auth_code, flags, pkt->path, &pkt->payload[i], len);
|
||||
} else if (self_id.isHashMatch(&pkt->payload[i + pkt->path_len]) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
|
||||
} else if (self_id.isHashMatch(&pkt->payload[i + offset], 1 << path_sz) && allowPacketForward(pkt) && !_tables->hasSeen(pkt)) {
|
||||
// append SNR (Not hash!)
|
||||
pkt->path[pkt->path_len] = (int8_t) (pkt->getSNR()*4);
|
||||
pkt->path_len += PATH_HASH_SIZE;
|
||||
pkt->path[pkt->path_len++] = (int8_t) (pkt->getSNR()*4);
|
||||
|
||||
uint32_t d = getDirectRetransmitDelay(pkt);
|
||||
return ACTION_RETRANSMIT_DELAYED(5, d); // schedule with priority 5 (for now), maybe make configurable?
|
||||
@@ -77,6 +78,16 @@ DispatcherAction Mesh::onRecvPacket(Packet* pkt) {
|
||||
}
|
||||
|
||||
if (pkt->isRouteDirect() && pkt->path_len >= PATH_HASH_SIZE) {
|
||||
// check for 'early received' ACK
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_ACK) {
|
||||
int i = 0;
|
||||
uint32_t ack_crc;
|
||||
memcpy(&ack_crc, &pkt->payload[i], 4); i += 4;
|
||||
if (i <= pkt->payload_len) {
|
||||
onAckRecv(pkt, ack_crc);
|
||||
}
|
||||
}
|
||||
|
||||
if (self_id.isHashMatch(pkt->path) && allowPacketForward(pkt)) {
|
||||
if (pkt->getPayloadType() == PAYLOAD_TYPE_MULTIPART) {
|
||||
return forwardMultipartDirect(pkt);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <math.h>
|
||||
|
||||
#define MAX_HASH_SIZE 8
|
||||
#define PUB_KEY_SIZE 32
|
||||
@@ -42,11 +43,15 @@ namespace mesh {
|
||||
class MainBoard {
|
||||
public:
|
||||
virtual uint16_t getBattMilliVolts() = 0;
|
||||
virtual float getMCUTemperature() { return NAN; }
|
||||
virtual bool setAdcMultiplier(float multiplier) { return false; };
|
||||
virtual float getAdcMultiplier() const { return 0.0f; }
|
||||
virtual const char* getManufacturerName() const = 0;
|
||||
virtual void onBeforeTransmit() { }
|
||||
virtual void onAfterTransmit() { }
|
||||
virtual void reboot() = 0;
|
||||
virtual void powerOff() { /* no op */ }
|
||||
virtual void sleep(uint32_t secs) { /* no op */ }
|
||||
virtual uint32_t getGpio() { return 0; }
|
||||
virtual void setGpio(uint32_t values) {}
|
||||
virtual uint8_t getStartupReason() const = 0;
|
||||
|
||||
+106
-49
@@ -55,6 +55,54 @@ void BaseChatMesh::sendAckTo(const ContactInfo& dest, uint32_t ack_hash) {
|
||||
}
|
||||
}
|
||||
|
||||
void BaseChatMesh::bootstrapRTCfromContacts() {
|
||||
uint32_t latest = 0;
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
if (contacts[i].lastmod > latest) {
|
||||
latest = contacts[i].lastmod;
|
||||
}
|
||||
}
|
||||
if (latest != 0) {
|
||||
getRTCClock()->setCurrentTime(latest + 1);
|
||||
}
|
||||
}
|
||||
|
||||
ContactInfo* BaseChatMesh::allocateContactSlot() {
|
||||
if (num_contacts < MAX_CONTACTS) {
|
||||
return &contacts[num_contacts++];
|
||||
} else if (shouldOverwriteWhenFull()) {
|
||||
// Find oldest non-favourite contact by oldest lastmod timestamp
|
||||
int oldest_idx = -1;
|
||||
uint32_t oldest_lastmod = 0xFFFFFFFF;
|
||||
for (int i = 0; i < num_contacts; i++) {
|
||||
bool is_favourite = (contacts[i].flags & 0x01) != 0;
|
||||
if (!is_favourite && contacts[i].lastmod < oldest_lastmod) {
|
||||
oldest_lastmod = contacts[i].lastmod;
|
||||
oldest_idx = i;
|
||||
}
|
||||
}
|
||||
if (oldest_idx >= 0) {
|
||||
onContactOverwrite(contacts[oldest_idx].id.pub_key);
|
||||
return &contacts[oldest_idx];
|
||||
}
|
||||
}
|
||||
return NULL; // no space, no overwrite or all contacts are all favourites
|
||||
}
|
||||
|
||||
void BaseChatMesh::populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp) {
|
||||
memset(&ci, 0, sizeof(ci));
|
||||
ci.id = id;
|
||||
ci.out_path_len = -1; // initially out_path is unknown
|
||||
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
|
||||
ci.type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
ci.gps_lat = parser.getIntLat();
|
||||
ci.gps_lon = parser.getIntLon();
|
||||
}
|
||||
ci.last_advert_timestamp = timestamp;
|
||||
ci.lastmod = getRTCClock()->getCurrentTime();
|
||||
}
|
||||
|
||||
void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id, uint32_t timestamp, const uint8_t* app_data, size_t app_data_len) {
|
||||
AdvertDataParser parser(app_data, app_data_len);
|
||||
if (!(parser.isValid() && parser.hasName())) {
|
||||
@@ -87,49 +135,37 @@ void BaseChatMesh::onAdvertRecv(mesh::Packet* packet, const mesh::Identity& id,
|
||||
|
||||
bool is_new = false;
|
||||
if (from == NULL) {
|
||||
if (!isAutoAddEnabled()) {
|
||||
if (!shouldAutoAddContactType(parser.getType())) {
|
||||
ContactInfo ci;
|
||||
memset(&ci, 0, sizeof(ci));
|
||||
ci.id = id;
|
||||
ci.out_path_len = -1; // initially out_path is unknown
|
||||
StrHelper::strncpy(ci.name, parser.getName(), sizeof(ci.name));
|
||||
ci.type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
ci.gps_lat = parser.getIntLat();
|
||||
ci.gps_lon = parser.getIntLon();
|
||||
}
|
||||
ci.last_advert_timestamp = timestamp;
|
||||
ci.lastmod = getRTCClock()->getCurrentTime();
|
||||
populateContactFromAdvert(ci, id, parser, timestamp);
|
||||
onDiscoveredContact(ci, true, packet->path_len, packet->path); // let UI know
|
||||
return;
|
||||
}
|
||||
|
||||
is_new = true;
|
||||
if (num_contacts < MAX_CONTACTS) {
|
||||
from = &contacts[num_contacts++];
|
||||
from->id = id;
|
||||
from->out_path_len = -1; // initially out_path is unknown
|
||||
from->gps_lat = 0; // initially unknown GPS loc
|
||||
from->gps_lon = 0;
|
||||
from->sync_since = 0;
|
||||
|
||||
// only need to calculate the shared_secret once, for better performance
|
||||
self_id.calcSharedSecret(from->shared_secret, id);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("onAdvertRecv: contacts table is full!");
|
||||
from = allocateContactSlot();
|
||||
if (from == NULL) {
|
||||
ContactInfo ci;
|
||||
populateContactFromAdvert(ci, id, parser, timestamp);
|
||||
onDiscoveredContact(ci, true, packet->path_len, packet->path);
|
||||
onContactsFull();
|
||||
MESH_DEBUG_PRINTLN("onAdvertRecv: unable to allocate contact slot for new contact");
|
||||
return;
|
||||
}
|
||||
|
||||
populateContactFromAdvert(*from, id, parser, timestamp);
|
||||
from->sync_since = 0;
|
||||
from->shared_secret_valid = false;
|
||||
}
|
||||
|
||||
// update
|
||||
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
|
||||
from->type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
from->gps_lat = parser.getIntLat();
|
||||
from->gps_lon = parser.getIntLon();
|
||||
}
|
||||
from->last_advert_timestamp = timestamp;
|
||||
from->lastmod = getRTCClock()->getCurrentTime();
|
||||
StrHelper::strncpy(from->name, parser.getName(), sizeof(from->name));
|
||||
from->type = parser.getType();
|
||||
if (parser.hasLatLon()) {
|
||||
from->gps_lat = parser.getIntLat();
|
||||
from->gps_lon = parser.getIntLon();
|
||||
}
|
||||
from->last_advert_timestamp = timestamp;
|
||||
from->lastmod = getRTCClock()->getCurrentTime();
|
||||
|
||||
onDiscoveredContact(*from, is_new, packet->path_len, packet->path); // let UI know
|
||||
}
|
||||
@@ -147,8 +183,7 @@ int BaseChatMesh::searchPeersByHash(const uint8_t* hash) {
|
||||
void BaseChatMesh::getPeerSharedSecret(uint8_t* dest_secret, int peer_idx) {
|
||||
int i = matching_peer_indexes[peer_idx];
|
||||
if (i >= 0 && i < num_contacts) {
|
||||
// lookup pre-calculated shared_secret
|
||||
memcpy(dest_secret, contacts[i].shared_secret, PUB_KEY_SIZE);
|
||||
memcpy(dest_secret, contacts[i].getSharedSecret(self_id), PUB_KEY_SIZE);
|
||||
} else {
|
||||
MESH_DEBUG_PRINTLN("getPeerSharedSecret: Invalid peer idx: %d", i);
|
||||
}
|
||||
@@ -166,7 +201,7 @@ void BaseChatMesh::onPeerDataRecv(mesh::Packet* packet, uint8_t type, int sender
|
||||
if (type == PAYLOAD_TYPE_TXT_MSG && len > 5) {
|
||||
uint32_t timestamp;
|
||||
memcpy(×tamp, data, 4); // timestamp (by sender's RTC clock - which could be wrong)
|
||||
uint flags = data[4] >> 2; // message attempt number, and other flags
|
||||
uint8_t flags = data[4] >> 2; // message attempt number, and other flags
|
||||
|
||||
// len can be > original length, but 'text' will be padded with zeroes
|
||||
data[len] = 0; // need to make a C string again, with null terminator
|
||||
@@ -293,7 +328,7 @@ void BaseChatMesh::onAckRecv(mesh::Packet* packet, uint32_t ack_crc) {
|
||||
void BaseChatMesh::handleReturnPathRetry(const ContactInfo& contact, const uint8_t* path, uint8_t path_len) {
|
||||
// NOTE: simplest impl is just to re-send a reciprocal return path to sender (DIRECTLY)
|
||||
// override this method in various firmwares, if there's a better strategy
|
||||
mesh::Packet* rpath = createPathReturn(contact.id, contact.shared_secret, path, path_len, 0, NULL, 0);
|
||||
mesh::Packet* rpath = createPathReturn(contact.id, contact.getSharedSecret(self_id), path, path_len, 0, NULL, 0);
|
||||
if (rpath) sendDirect(rpath, contact.out_path, contact.out_path_len, 3000); // 3 second delay
|
||||
}
|
||||
|
||||
@@ -342,7 +377,7 @@ mesh::Packet* BaseChatMesh::composeMsgPacket(const ContactInfo& recipient, uint3
|
||||
temp[len++] = attempt; // hide attempt number at tail end of payload
|
||||
}
|
||||
|
||||
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, len);
|
||||
return createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, len);
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendMessage(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& expected_ack, uint32_t& est_timeout) {
|
||||
@@ -373,7 +408,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo& recipient, uint32_t timest
|
||||
temp[4] = (attempt & 3) | (TXT_TYPE_CLI_DATA << 2);
|
||||
memcpy(&temp[5], text, text_len + 1);
|
||||
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.shared_secret, temp, 5 + text_len);
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_TXT_MSG, recipient.id, recipient.getSharedSecret(self_id), temp, 5 + text_len);
|
||||
if (pkt == NULL) return MSG_SEND_FAILED;
|
||||
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
@@ -462,7 +497,32 @@ int BaseChatMesh::sendLogin(const ContactInfo& recipient, const char* password,
|
||||
tlen = 4 + len;
|
||||
}
|
||||
|
||||
pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.shared_secret, temp, tlen);
|
||||
pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, tlen);
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
if (recipient.out_path_len < 0) {
|
||||
sendFloodScoped(recipient, pkt);
|
||||
est_timeout = calcFloodTimeoutMillisFor(t);
|
||||
return MSG_SEND_SENT_FLOOD;
|
||||
} else {
|
||||
sendDirect(pkt, recipient.out_path, recipient.out_path_len);
|
||||
est_timeout = calcDirectTimeoutMillisFor(t, recipient.out_path_len);
|
||||
return MSG_SEND_SENT_DIRECT;
|
||||
}
|
||||
}
|
||||
return MSG_SEND_FAILED;
|
||||
}
|
||||
|
||||
int BaseChatMesh::sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout) {
|
||||
mesh::Packet* pkt;
|
||||
{
|
||||
uint8_t temp[MAX_PACKET_PAYLOAD];
|
||||
tag = getRTCClock()->getCurrentTimeUnique();
|
||||
memcpy(temp, &tag, 4); // tag to match later (also extra blob to help make packet_hash unique)
|
||||
memcpy(&temp[4], data, len);
|
||||
|
||||
pkt = createAnonDatagram(PAYLOAD_TYPE_ANON_REQ, self_id, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + len);
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
@@ -489,7 +549,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, const uint8_t* req_
|
||||
memcpy(temp, &tag, 4); // mostly an extra blob to help make packet_hash unique
|
||||
memcpy(&temp[4], req_data, data_len);
|
||||
|
||||
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, 4 + data_len);
|
||||
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, 4 + data_len);
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
@@ -516,7 +576,7 @@ int BaseChatMesh::sendRequest(const ContactInfo& recipient, uint8_t req_type, u
|
||||
memset(&temp[5], 0, 4); // reserved (possibly for 'since' param)
|
||||
getRNG()->random(&temp[9], 4); // random blob to help make packet-hash unique
|
||||
|
||||
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.shared_secret, temp, sizeof(temp));
|
||||
pkt = createDatagram(PAYLOAD_TYPE_REQ, recipient.id, recipient.getSharedSecret(self_id), temp, sizeof(temp));
|
||||
}
|
||||
if (pkt) {
|
||||
uint32_t t = _radio->getEstAirtimeFor(pkt->getRawLength());
|
||||
@@ -639,7 +699,7 @@ void BaseChatMesh::checkConnections() {
|
||||
// calc expected ACK reply
|
||||
mesh::Utils::sha256((uint8_t *)&connections[i].expected_ack, 4, data, 9, self_id.pub_key, PUB_KEY_SIZE);
|
||||
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->shared_secret, data, 9);
|
||||
auto pkt = createDatagram(PAYLOAD_TYPE_REQ, contact->id, contact->getSharedSecret(self_id), data, 9);
|
||||
if (pkt) {
|
||||
sendDirect(pkt, contact->out_path, contact->out_path_len);
|
||||
}
|
||||
@@ -699,13 +759,10 @@ ContactInfo* BaseChatMesh::lookupContactByPubKey(const uint8_t* pub_key, int pre
|
||||
}
|
||||
|
||||
bool BaseChatMesh::addContact(const ContactInfo& contact) {
|
||||
if (num_contacts < MAX_CONTACTS) {
|
||||
auto dest = &contacts[num_contacts++];
|
||||
ContactInfo* dest = allocateContactSlot();
|
||||
if (dest) {
|
||||
*dest = contact;
|
||||
|
||||
// calc the ECDH shared secret (just once for performance)
|
||||
self_id.calcSharedSecret(dest->shared_secret, contact.id);
|
||||
|
||||
dest->shared_secret_valid = false; // mark shared_secret as needing calculation
|
||||
return true; // success
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -88,10 +88,17 @@ protected:
|
||||
memset(connections, 0, sizeof(connections));
|
||||
}
|
||||
|
||||
void bootstrapRTCfromContacts();
|
||||
void resetContacts() { num_contacts = 0; }
|
||||
void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp);
|
||||
ContactInfo* allocateContactSlot(); // helper to find slot for new contact
|
||||
|
||||
// 'UI' concepts, for sub-classes to implement
|
||||
virtual bool isAutoAddEnabled() const { return true; }
|
||||
virtual bool shouldAutoAddContactType(uint8_t type) const { return true; }
|
||||
virtual void onContactsFull() {};
|
||||
virtual bool shouldOverwriteWhenFull() const { return false; }
|
||||
virtual void onContactOverwrite(const uint8_t* pub_key) {};
|
||||
virtual void onDiscoveredContact(ContactInfo& contact, bool is_new, uint8_t path_len, const uint8_t* path) = 0;
|
||||
virtual ContactInfo* processAck(const uint8_t *data) = 0;
|
||||
virtual void onContactPathUpdated(const ContactInfo& contact) = 0;
|
||||
@@ -141,6 +148,7 @@ public:
|
||||
int sendCommandData(const ContactInfo& recipient, uint32_t timestamp, uint8_t attempt, const char* text, uint32_t& est_timeout);
|
||||
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel& channel, const char* sender_name, const char* text, int text_len);
|
||||
int sendLogin(const ContactInfo& recipient, const char* password, uint32_t& est_timeout);
|
||||
int sendAnonReq(const ContactInfo& recipient, const uint8_t* data, uint8_t len, uint32_t& tag, uint32_t& est_timeout);
|
||||
int sendRequest(const ContactInfo& recipient, uint8_t req_type, uint32_t& tag, uint32_t& est_timeout);
|
||||
int sendRequest(const ContactInfo& recipient, const uint8_t* req_data, uint8_t data_len, uint32_t& tag, uint32_t& est_timeout);
|
||||
bool shareContactZeroHop(const ContactInfo& contact);
|
||||
|
||||
+86
-12
@@ -14,6 +14,14 @@ static uint32_t _atoi(const char* sp) {
|
||||
return n;
|
||||
}
|
||||
|
||||
static bool isValidName(const char *n) {
|
||||
while (*n) {
|
||||
if (*n == '[' || *n == ']' || *n == '/' || *n == '\\' || *n == ':' || *n == ',' || *n == '?' || *n == '*') return false;
|
||||
n++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CommonCLI::loadPrefs(FILESYSTEM* fs) {
|
||||
if (fs->exists("/com_prefs")) {
|
||||
loadPrefsInt(fs, "/com_prefs"); // new filename
|
||||
@@ -65,12 +73,15 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
file.read((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.read((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.read((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.read(pad, 4); // 152
|
||||
file.read((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
|
||||
file.read(pad, 3); // 153
|
||||
file.read((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.read((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.read((uint8_t *)&_prefs->advert_loc_policy, sizeof (_prefs->advert_loc_policy)); // 161
|
||||
file.read((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
// 166
|
||||
file.read((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.read((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// 290
|
||||
|
||||
// sanitise bad pref values
|
||||
_prefs->rx_delay_base = constrain(_prefs->rx_delay_base, 0, 20.0f);
|
||||
@@ -83,6 +94,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->cr = constrain(_prefs->cr, 5, 8);
|
||||
_prefs->tx_power_dbm = constrain(_prefs->tx_power_dbm, 1, 30);
|
||||
_prefs->multi_acks = constrain(_prefs->multi_acks, 0, 1);
|
||||
_prefs->adc_multiplier = constrain(_prefs->adc_multiplier, 0.0f, 10.0f);
|
||||
|
||||
// sanitise bad bridge pref values
|
||||
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
||||
@@ -91,6 +103,8 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||
_prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200);
|
||||
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
|
||||
|
||||
_prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1);
|
||||
|
||||
_prefs->gps_enabled = constrain(_prefs->gps_enabled, 0, 1);
|
||||
_prefs->advert_loc_policy = constrain(_prefs->advert_loc_policy, 0, 2);
|
||||
|
||||
@@ -143,12 +157,15 @@ void CommonCLI::savePrefs(FILESYSTEM* fs) {
|
||||
file.write((uint8_t *)&_prefs->bridge_baud, sizeof(_prefs->bridge_baud)); // 131
|
||||
file.write((uint8_t *)&_prefs->bridge_channel, sizeof(_prefs->bridge_channel)); // 135
|
||||
file.write((uint8_t *)&_prefs->bridge_secret, sizeof(_prefs->bridge_secret)); // 136
|
||||
file.write(pad, 4); // 152
|
||||
file.write((uint8_t *)&_prefs->powersaving_enabled, sizeof(_prefs->powersaving_enabled)); // 152
|
||||
file.write(pad, 3); // 153
|
||||
file.write((uint8_t *)&_prefs->gps_enabled, sizeof(_prefs->gps_enabled)); // 156
|
||||
file.write((uint8_t *)&_prefs->gps_interval, sizeof(_prefs->gps_interval)); // 157
|
||||
file.write((uint8_t *)&_prefs->advert_loc_policy, sizeof(_prefs->advert_loc_policy)); // 161
|
||||
file.write((uint8_t *)&_prefs->discovery_mod_timestamp, sizeof(_prefs->discovery_mod_timestamp)); // 162
|
||||
// 166
|
||||
file.write((uint8_t *)&_prefs->adc_multiplier, sizeof(_prefs->adc_multiplier)); // 166
|
||||
file.write((uint8_t *)_prefs->owner_info, sizeof(_prefs->owner_info)); // 170
|
||||
// 290
|
||||
|
||||
file.close();
|
||||
}
|
||||
@@ -228,8 +245,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(tmp, &command[10]);
|
||||
const char *parts[5];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 5);
|
||||
float freq = num > 0 ? atof(parts[0]) : 0.0f;
|
||||
float bw = num > 1 ? atof(parts[1]) : 0.0f;
|
||||
float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f;
|
||||
float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f;
|
||||
uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
|
||||
uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
|
||||
int temp_timeout_mins = num > 4 ? atoi(parts[4]) : 0;
|
||||
@@ -284,7 +301,7 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(config, "radio", 5) == 0) {
|
||||
char freq[16], bw[16];
|
||||
strcpy(freq, StrHelper::ftoa(_prefs->freq));
|
||||
strcpy(bw, StrHelper::ftoa(_prefs->bw));
|
||||
strcpy(bw, StrHelper::ftoa3(_prefs->bw));
|
||||
sprintf(reply, "> %s,%s,%d,%d", freq, bw, (uint32_t)_prefs->sf, (uint32_t)_prefs->cr);
|
||||
} else if (memcmp(config, "rxdelay", 7) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->rx_delay_base));
|
||||
@@ -294,6 +311,15 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
sprintf(reply, "> %d", (uint32_t)_prefs->flood_max);
|
||||
} else if (memcmp(config, "direct.txdelay", 14) == 0) {
|
||||
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
|
||||
} else if (memcmp(config, "owner.info", 10) == 0) {
|
||||
*reply++ = '>';
|
||||
*reply++ = ' ';
|
||||
const char* sp = _prefs->owner_info;
|
||||
while (*sp) {
|
||||
*reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
|
||||
sp++;
|
||||
}
|
||||
*reply = 0; // set null terminator
|
||||
} else if (memcmp(config, "tx", 2) == 0 && (config[2] == 0 || config[2] == ' ')) {
|
||||
sprintf(reply, "> %d", (uint32_t) _prefs->tx_power_dbm);
|
||||
} else if (memcmp(config, "freq", 4) == 0) {
|
||||
@@ -331,6 +357,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else if (memcmp(config, "bridge.secret", 13) == 0) {
|
||||
sprintf(reply, "> %s", _prefs->bridge_secret);
|
||||
#endif
|
||||
} else if (memcmp(config, "adc.multiplier", 14) == 0) {
|
||||
float adc_mult = _board->getAdcMultiplier();
|
||||
if (adc_mult == 0.0f) {
|
||||
strcpy(reply, "Error: unsupported by this board");
|
||||
} else {
|
||||
sprintf(reply, "> %.3f", adc_mult);
|
||||
}
|
||||
} else {
|
||||
sprintf(reply, "??: %s", config);
|
||||
}
|
||||
@@ -396,9 +429,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(reply, "Error, invalid key");
|
||||
}
|
||||
} else if (memcmp(config, "name ", 5) == 0) {
|
||||
StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
if (isValidName(&config[5])) {
|
||||
StrHelper::strncpy(_prefs->node_name, &config[5], sizeof(_prefs->node_name));
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else {
|
||||
strcpy(reply, "Error, bad chars");
|
||||
}
|
||||
} else if (memcmp(config, "repeat ", 7) == 0) {
|
||||
_prefs->disable_fwd = memcmp(&config[7], "off", 3) == 0;
|
||||
savePrefs();
|
||||
@@ -407,8 +444,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(tmp, &config[6]);
|
||||
const char *parts[4];
|
||||
int num = mesh::Utils::parseTextParts(tmp, parts, 4);
|
||||
float freq = num > 0 ? atof(parts[0]) : 0.0f;
|
||||
float bw = num > 1 ? atof(parts[1]) : 0.0f;
|
||||
float freq = num > 0 ? strtof(parts[0], nullptr) : 0.0f;
|
||||
float bw = num > 1 ? strtof(parts[1], nullptr) : 0.0f;
|
||||
uint8_t sf = num > 2 ? atoi(parts[2]) : 0;
|
||||
uint8_t cr = num > 3 ? atoi(parts[3]) : 0;
|
||||
if (freq >= 300.0f && freq <= 2500.0f && sf >= 5 && sf <= 12 && cr >= 5 && cr <= 8 && bw >= 7.0f && bw <= 500.0f) {
|
||||
@@ -465,6 +502,16 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
} else {
|
||||
strcpy(reply, "Error, cannot be negative");
|
||||
}
|
||||
} else if (memcmp(config, "owner.info ", 11) == 0) {
|
||||
config += 11;
|
||||
char *dp = _prefs->owner_info;
|
||||
while (*config && dp - _prefs->owner_info < sizeof(_prefs->owner_info)-1) {
|
||||
*dp++ = (*config == '|') ? '\n' : *config; // translate '|' to newline chars
|
||||
config++;
|
||||
}
|
||||
*dp = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
} else if (memcmp(config, "tx ", 3) == 0) {
|
||||
_prefs->tx_power_dbm = atoi(&config[3]);
|
||||
savePrefs();
|
||||
@@ -523,6 +570,19 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
savePrefs();
|
||||
strcpy(reply, "OK");
|
||||
#endif
|
||||
} else if (memcmp(config, "adc.multiplier ", 15) == 0) {
|
||||
_prefs->adc_multiplier = atof(&config[15]);
|
||||
if (_board->setAdcMultiplier(_prefs->adc_multiplier)) {
|
||||
savePrefs();
|
||||
if (_prefs->adc_multiplier == 0.0f) {
|
||||
strcpy(reply, "OK - using default board multiplier");
|
||||
} else {
|
||||
sprintf(reply, "OK - multiplier set to %.3f", _prefs->adc_multiplier);
|
||||
}
|
||||
} else {
|
||||
_prefs->adc_multiplier = 0.0f;
|
||||
strcpy(reply, "Error: unsupported by this board");
|
||||
};
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
}
|
||||
@@ -653,6 +713,20 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||
strcpy(reply, "Can't find GPS");
|
||||
}
|
||||
#endif
|
||||
} else if (memcmp(command, "powersaving on", 14) == 0) {
|
||||
_prefs->powersaving_enabled = 1;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok"); // TODO: to return Not supported if required
|
||||
} else if (memcmp(command, "powersaving off", 15) == 0) {
|
||||
_prefs->powersaving_enabled = 0;
|
||||
savePrefs();
|
||||
strcpy(reply, "ok");
|
||||
} else if (memcmp(command, "powersaving", 11) == 0) {
|
||||
if (_prefs->powersaving_enabled) {
|
||||
strcpy(reply, "on");
|
||||
} else {
|
||||
strcpy(reply, "off");
|
||||
}
|
||||
} else if (memcmp(command, "log start", 9) == 0) {
|
||||
_callbacks->setLoggingOn(true);
|
||||
strcpy(reply, " logging on");
|
||||
|
||||
@@ -42,11 +42,15 @@ struct NodePrefs { // persisted to file
|
||||
uint32_t bridge_baud; // 9600, 19200, 38400, 57600, 115200 (default 115200)
|
||||
uint8_t bridge_channel; // 1-14 (ESP-NOW only)
|
||||
char bridge_secret[16]; // for XOR encryption of bridge packets (ESP-NOW only)
|
||||
// Power setting
|
||||
uint8_t powersaving_enabled; // boolean
|
||||
// Gps settings
|
||||
uint8_t gps_enabled;
|
||||
uint32_t gps_interval; // in seconds
|
||||
uint8_t advert_loc_policy;
|
||||
uint32_t discovery_mod_timestamp;
|
||||
float adc_multiplier;
|
||||
char owner_info[120];
|
||||
};
|
||||
|
||||
class CommonCLICallbacks {
|
||||
|
||||
@@ -9,10 +9,21 @@ struct ContactInfo {
|
||||
uint8_t type; // on of ADV_TYPE_*
|
||||
uint8_t flags;
|
||||
int8_t out_path_len;
|
||||
mutable bool shared_secret_valid; // flag to indicate if shared_secret has been calculated
|
||||
uint8_t out_path[MAX_PATH_SIZE];
|
||||
uint32_t last_advert_timestamp; // by THEIR clock
|
||||
uint8_t shared_secret[PUB_KEY_SIZE];
|
||||
uint32_t lastmod; // by OUR clock
|
||||
int32_t gps_lat, gps_lon; // 6 dec places
|
||||
uint32_t sync_since;
|
||||
|
||||
const uint8_t* getSharedSecret(const mesh::LocalIdentity& self_id) const {
|
||||
if (!shared_secret_valid) {
|
||||
self_id.calcSharedSecret(shared_secret, id.pub_key);
|
||||
shared_secret_valid = true;
|
||||
}
|
||||
return shared_secret;
|
||||
}
|
||||
|
||||
private:
|
||||
mutable uint8_t shared_secret[PUB_KEY_SIZE];
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <rom/rtc.h>
|
||||
#include <sys/time.h>
|
||||
#include <Wire.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "driver/rtc_io.h"
|
||||
|
||||
class ESP32Board : public mesh::MainBoard {
|
||||
protected:
|
||||
@@ -42,6 +44,43 @@ public:
|
||||
#endif
|
||||
}
|
||||
|
||||
// Temperature from ESP32 MCU
|
||||
float getMCUTemperature() override {
|
||||
uint32_t raw = 0;
|
||||
|
||||
// To get and average the temperature so it is more accurate, especially in low temperature
|
||||
for (int i = 0; i < 4; i++) {
|
||||
raw += temperatureRead();
|
||||
}
|
||||
|
||||
return raw / 4;
|
||||
}
|
||||
|
||||
void enterLightSleep(uint32_t secs) {
|
||||
#if defined(CONFIG_IDF_TARGET_ESP32S3) && defined(P_LORA_DIO_1) // Supported ESP32 variants
|
||||
if (rtc_gpio_is_valid_gpio((gpio_num_t)P_LORA_DIO_1)) { // Only enter sleep mode if P_LORA_DIO_1 is RTC pin
|
||||
esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON);
|
||||
esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // To wake up when receiving a LoRa packet
|
||||
|
||||
if (secs > 0) {
|
||||
esp_sleep_enable_timer_wakeup(secs * 1000000); // To wake up every hour to do periodically jobs
|
||||
}
|
||||
|
||||
esp_light_sleep_start(); // CPU enters light sleep
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void sleep(uint32_t secs) override {
|
||||
// To check for WiFi status to see if there is active OTA
|
||||
wifi_mode_t mode;
|
||||
esp_err_t err = esp_wifi_get_mode(&mode);
|
||||
|
||||
if (err != ESP_OK) { // WiFi is off ~ No active OTA, safe to go to sleep
|
||||
enterLightSleep(secs); // To wake up after "secs" seconds or when receiving a LoRa packet
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#include "NRF52Board.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void NRF52Board::begin() {
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
}
|
||||
|
||||
void NRF52BoardDCDC::begin() {
|
||||
NRF52Board::begin();
|
||||
|
||||
// Enable DC/DC converter for improved power efficiency
|
||||
uint8_t sd_enabled = 0;
|
||||
sd_softdevice_is_enabled(&sd_enabled);
|
||||
if (sd_enabled) {
|
||||
sd_power_dcdc_mode_set(NRF_POWER_DCDC_ENABLE);
|
||||
} else {
|
||||
NRF_POWER->DCDCEN = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Temperature from NRF52 MCU
|
||||
float NRF52Board::getMCUTemperature() {
|
||||
NRF_TEMP->TASKS_START = 1; // Start temperature measurement
|
||||
|
||||
long startTime = millis();
|
||||
while (NRF_TEMP->EVENTS_DATARDY == 0) { // Wait for completion. Should complete in 50us
|
||||
if(millis() - startTime > 5) { // To wait 5ms just in case
|
||||
NRF_TEMP->TASKS_STOP = 1;
|
||||
return NAN;
|
||||
}
|
||||
}
|
||||
|
||||
NRF_TEMP->EVENTS_DATARDY = 0; // Clear event flag
|
||||
|
||||
int32_t temp = NRF_TEMP->TEMP; // In 0.25 *C units
|
||||
NRF_TEMP->TASKS_STOP = 1;
|
||||
|
||||
return temp * 0.25f; // Convert to *C
|
||||
}
|
||||
|
||||
bool NRF52BoardOTA::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName(ota_name);
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
uint8_t mac_addr[6];
|
||||
memset(mac_addr, 0, sizeof(mac_addr));
|
||||
Bluefruit.getAddr(mac_addr);
|
||||
sprintf(reply, "OK - mac: %02X:%02X:%02X:%02X:%02X:%02X", mac_addr[5], mac_addr[4], mac_addr[3],
|
||||
mac_addr[2], mac_addr[1], mac_addr[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
|
||||
class NRF52Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
public:
|
||||
virtual void begin();
|
||||
virtual uint8_t getStartupReason() const override { return startup_reason; }
|
||||
virtual float getMCUTemperature() override;
|
||||
virtual void reboot() override { NVIC_SystemReset(); }
|
||||
};
|
||||
|
||||
/*
|
||||
* The NRF52 has an internal DC/DC regulator that allows increased efficiency
|
||||
* compared to the LDO regulator. For being able to use it, the module/board
|
||||
* needs to have the required inductors and and capacitors populated. If the
|
||||
* hardware requirements are met, this subclass can be used to enable the DC/DC
|
||||
* regulator.
|
||||
*/
|
||||
class NRF52BoardDCDC : virtual public NRF52Board {
|
||||
public:
|
||||
virtual void begin() override;
|
||||
};
|
||||
|
||||
class NRF52BoardOTA : virtual public NRF52Board {
|
||||
private:
|
||||
char *ota_name;
|
||||
|
||||
public:
|
||||
NRF52BoardOTA(char *name) : ota_name(name) {}
|
||||
virtual bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
#endif
|
||||
@@ -9,8 +9,9 @@ RegionMap::RegionMap(TransportKeyStore& store) : _store(&store) {
|
||||
strcpy(wildcard.name, "*");
|
||||
}
|
||||
|
||||
bool RegionMap::is_name_char(char c) {
|
||||
return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-' || c == '.' || c == '_' || c == '#';
|
||||
bool RegionMap::is_name_char(uint8_t c) {
|
||||
// accept all alpha-num or accented characters, but exclude most punctuation chars
|
||||
return c == '-' || c == '#' || (c >= '0' && c <= '9') || c >= 'A';
|
||||
}
|
||||
|
||||
static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
@@ -24,12 +25,12 @@ static File openWrite(FILESYSTEM* _fs, const char* filename) {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool RegionMap::load(FILESYSTEM* _fs) {
|
||||
if (_fs->exists("/regions2")) {
|
||||
bool RegionMap::load(FILESYSTEM* _fs, const char* path) {
|
||||
if (_fs->exists(path ? path : "/regions2")) {
|
||||
#if defined(RP2040_PLATFORM)
|
||||
File file = _fs->open("/regions2", "r");
|
||||
File file = _fs->open(path ? path : "/regions2", "r");
|
||||
#else
|
||||
File file = _fs->open("/regions2");
|
||||
File file = _fs->open(path ? path : "/regions2");
|
||||
#endif
|
||||
|
||||
if (file) {
|
||||
@@ -67,8 +68,8 @@ bool RegionMap::load(FILESYSTEM* _fs) {
|
||||
return false; // failed
|
||||
}
|
||||
|
||||
bool RegionMap::save(FILESYSTEM* _fs) {
|
||||
File file = openWrite(_fs, "/regions2");
|
||||
bool RegionMap::save(FILESYSTEM* _fs, const char* path) {
|
||||
File file = openWrite(_fs, path ? path : "/regions2");
|
||||
if (file) {
|
||||
uint8_t pad[128];
|
||||
memset(pad, 0, sizeof(pad));
|
||||
@@ -235,3 +236,27 @@ void RegionMap::printChildRegions(int indent, const RegionEntry* parent, Stream&
|
||||
void RegionMap::exportTo(Stream& out) const {
|
||||
printChildRegions(0, &wildcard, out); // recursive
|
||||
}
|
||||
|
||||
int RegionMap::exportNamesTo(char *dest, int max_len, uint8_t mask) {
|
||||
char *dp = dest;
|
||||
if ((wildcard.flags & mask) == 0) {
|
||||
*dp++ = '*';
|
||||
*dp++ = ',';
|
||||
}
|
||||
|
||||
for (int i = 0; i < num_regions; i++) {
|
||||
auto region = ®ions[i];
|
||||
if ((region->flags & mask) == 0) { // region allowed? (per 'mask' param)
|
||||
int len = strlen(region->name);
|
||||
if ((dp - dest) + len + 2 < max_len) { // only append if name will fit
|
||||
memcpy(dp, region->name, len);
|
||||
dp += len;
|
||||
*dp++ = ',';
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dp > dest) { dp--; } // don't include trailing comma
|
||||
|
||||
*dp = 0; // set null terminator
|
||||
return dp - dest; // return length
|
||||
}
|
||||
|
||||
@@ -30,10 +30,10 @@ class RegionMap {
|
||||
public:
|
||||
RegionMap(TransportKeyStore& store);
|
||||
|
||||
static bool is_name_char(char c);
|
||||
static bool is_name_char(uint8_t c);
|
||||
|
||||
bool load(FILESYSTEM* _fs);
|
||||
bool save(FILESYSTEM* _fs);
|
||||
bool load(FILESYSTEM* _fs, const char* path=NULL);
|
||||
bool save(FILESYSTEM* _fs, const char* path=NULL);
|
||||
|
||||
RegionEntry* putRegion(const char* name, uint16_t parent_id, uint16_t id = 0);
|
||||
RegionEntry* findMatch(mesh::Packet* packet, uint8_t mask);
|
||||
@@ -47,6 +47,9 @@ public:
|
||||
bool clear();
|
||||
void resetFrom(const RegionMap& src) { num_regions = 0; next_id = src.next_id; }
|
||||
int getCount() const { return num_regions; }
|
||||
const RegionEntry* getByIdx(int i) const { return ®ions[i]; }
|
||||
const RegionEntry* getRoot() const { return &wildcard; }
|
||||
int exportNamesTo(char *dest, int max_len, uint8_t mask);
|
||||
|
||||
void exportTo(Stream& out) const;
|
||||
};
|
||||
|
||||
@@ -140,6 +140,19 @@ const char* StrHelper::ftoa(float f) {
|
||||
return tmp;
|
||||
}
|
||||
|
||||
const char* StrHelper::ftoa3(float f) {
|
||||
static char s[16];
|
||||
int v = (int)(f * 1000.0f + (f >= 0 ? 0.5f : -0.5f)); // rounded ×1000
|
||||
int w = v / 1000; // whole
|
||||
int d = abs(v % 1000); // decimals
|
||||
snprintf(s, sizeof(s), "%d.%03d", w, d);
|
||||
for (int i = strlen(s) - 1; i > 0 && s[i] == '0'; i--)
|
||||
s[i] = 0;
|
||||
int L = strlen(s);
|
||||
if (s[L - 1] == '.') s[L - 1] = 0;
|
||||
return s;
|
||||
}
|
||||
|
||||
uint32_t StrHelper::fromHex(const char* src) {
|
||||
uint32_t n = 0;
|
||||
while (*src) {
|
||||
|
||||
@@ -12,6 +12,7 @@ public:
|
||||
static void strncpy(char* dest, const char* src, size_t buf_sz);
|
||||
static void strzcpy(char* dest, const char* src, size_t buf_sz); // pads with trailing nulls
|
||||
static const char* ftoa(float f);
|
||||
static const char* ftoa3(float f); //Converts float to string with 3 decimal places
|
||||
static bool isBlank(const char* str);
|
||||
static uint32_t fromHex(const char* src);
|
||||
};
|
||||
|
||||
@@ -16,7 +16,8 @@ void RS232Bridge::begin() {
|
||||
#if defined(ESP32)
|
||||
((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX);
|
||||
#elif defined(NRF52_PLATFORM)
|
||||
((HardwareSerial *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX);
|
||||
// Tested with RAK_4631 and T114
|
||||
((Uart *)_serial)->setPins(WITH_RS232_BRIDGE_RX, WITH_RS232_BRIDGE_TX);
|
||||
#elif defined(RP2040_PLATFORM)
|
||||
((SerialUART *)_serial)->setRX(WITH_RS232_BRIDGE_RX);
|
||||
((SerialUART *)_serial)->setTX(WITH_RS232_BRIDGE_TX);
|
||||
@@ -121,8 +122,7 @@ void RS232Bridge::sendPacket(mesh::Packet *packet) {
|
||||
|
||||
// Check if packet fits within our maximum payload size
|
||||
if (len > (MAX_TRANS_UNIT + 1)) {
|
||||
BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len,
|
||||
MAX_TRANS_UNIT + 1);
|
||||
BRIDGE_DEBUG_PRINTLN("TX packet too large (payload=%d, max=%d)\n", len, MAX_TRANS_UNIT + 1);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@
|
||||
* Platform Support:
|
||||
* Different platforms require different pin configuration methods:
|
||||
* - ESP32: Uses HardwareSerial::setPins(rx, tx)
|
||||
* - NRF52: Uses HardwareSerial::setPins(rx, tx)
|
||||
* - NRF52: Uses Uart::setPins(rx, tx)
|
||||
* - RP2040: Uses SerialUART::setRX(rx) and SerialUART::setTX(tx)
|
||||
* - STM32: Uses HardwareSerial::setRx(rx) and HardwareSerial::setTx(tx)
|
||||
*/
|
||||
|
||||
@@ -43,6 +43,15 @@ bool SerialWifiInterface::isWriteBusy() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SerialWifiInterface::hasReceivedFrameHeader() {
|
||||
return received_frame_header.type != 0 && received_frame_header.length != 0;
|
||||
}
|
||||
|
||||
void SerialWifiInterface::resetReceivedFrameHeader() {
|
||||
received_frame_header.type = 0;
|
||||
received_frame_header.length = 0;
|
||||
}
|
||||
|
||||
size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
// check if new client connected
|
||||
auto newClient = server.available();
|
||||
@@ -54,6 +63,9 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
|
||||
// switch active connection to new client
|
||||
client = newClient;
|
||||
|
||||
// forget received frame header
|
||||
resetReceivedFrameHeader();
|
||||
|
||||
}
|
||||
|
||||
@@ -86,13 +98,69 @@ size_t SerialWifiInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
send_queue[i] = send_queue[i + 1];
|
||||
}
|
||||
} else {
|
||||
int len = client.available();
|
||||
if (len > 0) {
|
||||
uint8_t buf[MAX_FRAME_SIZE + 4];
|
||||
client.readBytes(buf, len);
|
||||
memcpy(dest, buf+3, len-3); // remove header (don't even check ... problems are on the other dir)
|
||||
return len-3;
|
||||
|
||||
// check if we are waiting for a frame header
|
||||
if(!hasReceivedFrameHeader()){
|
||||
|
||||
// make sure we have received enough bytes for a frame header
|
||||
// 3 bytes frame header = (1 byte frame type) + (2 bytes frame length as unsigned 16-bit little endian)
|
||||
int frame_header_length = 3;
|
||||
if(client.available() >= frame_header_length){
|
||||
|
||||
// read frame header
|
||||
client.readBytes(&received_frame_header.type, 1);
|
||||
client.readBytes((uint8_t*)&received_frame_header.length, 2);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// check if we have received a frame header
|
||||
if(hasReceivedFrameHeader()){
|
||||
|
||||
// make sure we have received enough bytes for the required frame length
|
||||
int available = client.available();
|
||||
int frame_type = received_frame_header.type;
|
||||
int frame_length = received_frame_header.length;
|
||||
if(frame_length > available){
|
||||
WIFI_DEBUG_PRINTLN("Waiting for %d more bytes", frame_length - available);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// skip frames that are larger than MAX_FRAME_SIZE
|
||||
if(frame_length > MAX_FRAME_SIZE){
|
||||
WIFI_DEBUG_PRINTLN("Skipping frame: length=%d is larger than MAX_FRAME_SIZE=%d", frame_length, MAX_FRAME_SIZE);
|
||||
while(frame_length > 0){
|
||||
uint8_t skip[1];
|
||||
int skipped = client.read(skip, 1);
|
||||
frame_length -= skipped;
|
||||
}
|
||||
resetReceivedFrameHeader();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// skip frames that are not expected type
|
||||
// '<' is 0x3c which indicates a frame sent from app to radio
|
||||
if(frame_type != '<'){
|
||||
WIFI_DEBUG_PRINTLN("Skipping frame: type=0x%x is unexpected", frame_type);
|
||||
while(frame_length > 0){
|
||||
uint8_t skip[1];
|
||||
int skipped = client.read(skip, 1);
|
||||
frame_length -= skipped;
|
||||
}
|
||||
resetReceivedFrameHeader();
|
||||
return 0;
|
||||
}
|
||||
|
||||
// read frame data to provided buffer
|
||||
client.readBytes(dest, frame_length);
|
||||
|
||||
// ready for next frame
|
||||
resetReceivedFrameHeader();
|
||||
return frame_length;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,18 @@ class SerialWifiInterface : public BaseSerialInterface {
|
||||
WiFiServer server;
|
||||
WiFiClient client;
|
||||
|
||||
struct FrameHeader {
|
||||
uint8_t type;
|
||||
uint16_t length;
|
||||
};
|
||||
|
||||
struct Frame {
|
||||
uint8_t len;
|
||||
uint8_t buf[MAX_FRAME_SIZE];
|
||||
};
|
||||
|
||||
FrameHeader received_frame_header;
|
||||
|
||||
#define FRAME_QUEUE_SIZE 4
|
||||
int recv_queue_len;
|
||||
Frame recv_queue[FRAME_QUEUE_SIZE];
|
||||
@@ -33,6 +40,8 @@ public:
|
||||
_isEnabled = false;
|
||||
_last_write = 0;
|
||||
send_queue_len = recv_queue_len = 0;
|
||||
received_frame_header.type = 0;
|
||||
received_frame_header.length = 0;
|
||||
}
|
||||
|
||||
void begin(int port);
|
||||
@@ -47,6 +56,9 @@ public:
|
||||
|
||||
size_t writeFrame(const uint8_t src[], size_t len) override;
|
||||
size_t checkRecvFrame(uint8_t dest[]) override;
|
||||
|
||||
bool hasReceivedFrameHeader();
|
||||
void resetReceivedFrameHeader();
|
||||
};
|
||||
|
||||
#if WIFI_DEBUG_LOGGING && ARDUINO
|
||||
|
||||
@@ -2,16 +2,7 @@
|
||||
|
||||
#if defined(TBEAM_SUPREME_SX1262) || defined(TBEAM_SX1262) || defined(TBEAM_SX1276)
|
||||
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
#include "XPowersLib.h"
|
||||
#include "helpers/ESP32Board.h"
|
||||
#include <driver/rtc_io.h>
|
||||
//#include <RadioLib.h>
|
||||
//#include <helpers/RadioLibWrappers.h>
|
||||
//#include <helpers/CustomSX1262Wrapper.h>
|
||||
//#include <helpers/CustomSX1276Wrapper.h>
|
||||
|
||||
// Define pin mappings BEFORE including ESP32Board.h so sleep() can use P_LORA_DIO_1
|
||||
#ifdef TBEAM_SUPREME_SX1262
|
||||
// LoRa radio module pins for TBeam S3 Supreme SX1262
|
||||
#define P_LORA_DIO_0 -1 //NC
|
||||
@@ -90,6 +81,13 @@
|
||||
// SX1276
|
||||
// };
|
||||
|
||||
// Include headers AFTER pin definitions so ESP32Board::sleep() can use P_LORA_DIO_1
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
#include "XPowersLib.h"
|
||||
#include "helpers/ESP32Board.h"
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
class TBeamBoard : public ESP32Board {
|
||||
XPowersLibInterface *PMU = NULL;
|
||||
//PhysicalLayer * pl;
|
||||
|
||||
@@ -1,193 +1,387 @@
|
||||
#include "SerialBLEInterface.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "ble_gap.h"
|
||||
#include "ble_hci.h"
|
||||
|
||||
static SerialBLEInterface* instance;
|
||||
// Magic numbers came from actual testing
|
||||
#define BLE_HEALTH_CHECK_INTERVAL 10000 // Advertising watchdog check every 10 seconds
|
||||
#define BLE_RETRY_THROTTLE_MS 250 // Throttle retries to 250ms when queue buildup detected
|
||||
|
||||
// Connection parameters (units: interval=1.25ms, timeout=10ms)
|
||||
#define BLE_MIN_CONN_INTERVAL 12 // 15ms
|
||||
#define BLE_MAX_CONN_INTERVAL 24 // 30ms
|
||||
#define BLE_SLAVE_LATENCY 4
|
||||
#define BLE_CONN_SUP_TIMEOUT 200 // 2000ms
|
||||
|
||||
// Advertising parameters
|
||||
#define BLE_ADV_INTERVAL_MIN 32 // 20ms (units: 0.625ms)
|
||||
#define BLE_ADV_INTERVAL_MAX 244 // 152.5ms (units: 0.625ms)
|
||||
#define BLE_ADV_FAST_TIMEOUT 30 // seconds
|
||||
|
||||
// RX drain buffer size for overflow protection
|
||||
#define BLE_RX_DRAIN_BUF_SIZE 32
|
||||
|
||||
static SerialBLEInterface* instance = nullptr;
|
||||
|
||||
void SerialBLEInterface::onConnect(uint16_t connection_handle) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: connected");
|
||||
// we now set _isDeviceConnected=true in onSecured callback instead
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: connected handle=0x%04X", connection_handle);
|
||||
if (instance) {
|
||||
instance->_conn_handle = connection_handle;
|
||||
instance->_isDeviceConnected = false;
|
||||
instance->clearBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onDisconnect(uint16_t connection_handle, uint8_t reason) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected reason=%d", reason);
|
||||
if(instance){
|
||||
instance->_isDeviceConnected = false;
|
||||
instance->startAdv();
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: disconnected handle=0x%04X reason=%u", connection_handle, reason);
|
||||
if (instance) {
|
||||
if (instance->_conn_handle == connection_handle) {
|
||||
instance->_conn_handle = BLE_CONN_HANDLE_INVALID;
|
||||
instance->_isDeviceConnected = false;
|
||||
instance->clearBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onSecured(uint16_t connection_handle) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured");
|
||||
if(instance){
|
||||
instance->_isDeviceConnected = true;
|
||||
// no need to stop advertising on connect, as the ble stack does this automatically
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: onSecured handle=0x%04X", connection_handle);
|
||||
if (instance) {
|
||||
if (instance->isValidConnection(connection_handle, true)) {
|
||||
instance->_isDeviceConnected = true;
|
||||
|
||||
// Connection interval units: 1.25ms, supervision timeout units: 10ms
|
||||
// Apple: "The product will not read or use the parameters in the Peripheral Preferred Connection Parameters characteristic."
|
||||
// So we explicitly set it here to make Android & Apple match
|
||||
ble_gap_conn_params_t conn_params;
|
||||
conn_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
|
||||
conn_params.max_conn_interval = BLE_MAX_CONN_INTERVAL;
|
||||
conn_params.slave_latency = BLE_SLAVE_LATENCY;
|
||||
conn_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT;
|
||||
|
||||
uint32_t err_code = sd_ble_gap_conn_param_update(connection_handle, &conn_params);
|
||||
if (err_code == NRF_SUCCESS) {
|
||||
BLE_DEBUG_PRINTLN("Connection parameter update requested: %u-%ums interval, latency=%u, %ums timeout",
|
||||
conn_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units)
|
||||
conn_params.max_conn_interval * 5 / 4,
|
||||
conn_params.slave_latency,
|
||||
conn_params.conn_sup_timeout * 10); // convert to ms (10ms units)
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("Failed to request connection parameter update: %lu", err_code);
|
||||
}
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("onSecured: ignoring stale/duplicate callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request) {
|
||||
(void)connection_handle;
|
||||
(void)passkey;
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing passkey request match=%d", match_request);
|
||||
return true;
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onPairingComplete(uint16_t connection_handle, uint8_t auth_status) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing complete handle=0x%04X status=%u", connection_handle, auth_status);
|
||||
if (instance) {
|
||||
if (instance->isValidConnection(connection_handle)) {
|
||||
if (auth_status == BLE_GAP_SEC_STATUS_SUCCESS) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing successful");
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: pairing failed, disconnecting");
|
||||
instance->disconnect();
|
||||
}
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("onPairingComplete: ignoring stale callback");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::onBLEEvent(ble_evt_t* evt) {
|
||||
if (!instance) return;
|
||||
|
||||
if (evt->header.evt_id == BLE_GAP_EVT_CONN_PARAM_UPDATE_REQUEST) {
|
||||
uint16_t conn_handle = evt->evt.gap_evt.conn_handle;
|
||||
if (instance->isValidConnection(conn_handle)) {
|
||||
BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: handle=0x%04X, min_interval=%u, max_interval=%u, latency=%u, timeout=%u",
|
||||
conn_handle,
|
||||
evt->evt.gap_evt.params.conn_param_update_request.conn_params.min_conn_interval,
|
||||
evt->evt.gap_evt.params.conn_param_update_request.conn_params.max_conn_interval,
|
||||
evt->evt.gap_evt.params.conn_param_update_request.conn_params.slave_latency,
|
||||
evt->evt.gap_evt.params.conn_param_update_request.conn_params.conn_sup_timeout);
|
||||
|
||||
uint32_t err_code = sd_ble_gap_conn_param_update(conn_handle, NULL);
|
||||
if (err_code == NRF_SUCCESS) {
|
||||
BLE_DEBUG_PRINTLN("Accepted CONN_PARAM_UPDATE_REQUEST (using PPCP)");
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("ERROR: Failed to accept CONN_PARAM_UPDATE_REQUEST: 0x%08X", err_code);
|
||||
}
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("CONN_PARAM_UPDATE_REQUEST: ignoring stale callback for handle=0x%04X", conn_handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::begin(const char* device_name, uint32_t pin_code) {
|
||||
|
||||
instance = this;
|
||||
|
||||
char charpin[20];
|
||||
sprintf(charpin, "%d", pin_code);
|
||||
|
||||
snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code);
|
||||
|
||||
// If we want to control BLE LED ourselves, uncomment this:
|
||||
// Bluefruit.autoConnLed(false);
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(250, BLE_GAP_EVENT_LENGTH_MIN, 16, 16); // increase MTU
|
||||
Bluefruit.setTxPower(BLE_TX_POWER);
|
||||
Bluefruit.begin();
|
||||
|
||||
// Connection interval units: 1.25ms, supervision timeout units: 10ms
|
||||
ble_gap_conn_params_t ppcp_params;
|
||||
ppcp_params.min_conn_interval = BLE_MIN_CONN_INTERVAL;
|
||||
ppcp_params.max_conn_interval = BLE_MAX_CONN_INTERVAL;
|
||||
ppcp_params.slave_latency = BLE_SLAVE_LATENCY;
|
||||
ppcp_params.conn_sup_timeout = BLE_CONN_SUP_TIMEOUT;
|
||||
|
||||
uint32_t err_code = sd_ble_gap_ppcp_set(&ppcp_params);
|
||||
if (err_code == NRF_SUCCESS) {
|
||||
BLE_DEBUG_PRINTLN("PPCP set: %u-%ums interval, latency=%u, %ums timeout",
|
||||
ppcp_params.min_conn_interval * 5 / 4, // convert to ms (1.25ms units)
|
||||
ppcp_params.max_conn_interval * 5 / 4,
|
||||
ppcp_params.slave_latency,
|
||||
ppcp_params.conn_sup_timeout * 10); // convert to ms (10ms units)
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("Failed to set PPCP: %lu", err_code);
|
||||
}
|
||||
|
||||
Bluefruit.setTxPower(BLE_TX_POWER);
|
||||
Bluefruit.setName(device_name);
|
||||
|
||||
Bluefruit.Security.setMITM(true);
|
||||
Bluefruit.Security.setPIN(charpin);
|
||||
Bluefruit.Security.setIOCaps(true, false, false);
|
||||
Bluefruit.Security.setPairPasskeyCallback(onPairingPasskey);
|
||||
Bluefruit.Security.setPairCompleteCallback(onPairingComplete);
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(onConnect);
|
||||
Bluefruit.Periph.setDisconnectCallback(onDisconnect);
|
||||
Bluefruit.Security.setSecuredCallback(onSecured);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
//bledfu.begin();
|
||||
Bluefruit.setEventCallback(onBLEEvent);
|
||||
|
||||
// Configure and start the BLE Uart service
|
||||
bleuart.setPermission(SECMODE_ENC_WITH_MITM, SECMODE_ENC_WITH_MITM);
|
||||
bleuart.begin();
|
||||
|
||||
}
|
||||
bleuart.setRxCallback(onBleUartRX);
|
||||
|
||||
void SerialBLEInterface::startAdv() {
|
||||
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: starting advertising");
|
||||
|
||||
// clean restart if already advertising
|
||||
if(Bluefruit.Advertising.isRunning()){
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: already advertising, stopping to allow clean restart");
|
||||
Bluefruit.Advertising.stop();
|
||||
}
|
||||
|
||||
Bluefruit.Advertising.clearData(); // clear advertising data
|
||||
Bluefruit.ScanResponse.clearData(); // clear scan response data
|
||||
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
|
||||
// Include the BLE UART (AKA 'NUS') 128-bit UUID
|
||||
Bluefruit.Advertising.addService(bleuart);
|
||||
|
||||
// Secondary Scan Response packet (optional)
|
||||
// Since there is no room for 'Name' in Advertising packet
|
||||
Bluefruit.ScanResponse.addName();
|
||||
|
||||
/* Start Advertising
|
||||
* - Enable auto advertising if disconnected
|
||||
* - Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
* - Timeout for fast mode is 30 seconds
|
||||
* - Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
*
|
||||
* For recommended advertising interval
|
||||
* https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(false); // don't restart automatically as we handle it in onDisconnect
|
||||
Bluefruit.Advertising.setInterval(32, 244);
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
Bluefruit.Advertising.setInterval(BLE_ADV_INTERVAL_MIN, BLE_ADV_INTERVAL_MAX);
|
||||
Bluefruit.Advertising.setFastTimeout(BLE_ADV_FAST_TIMEOUT);
|
||||
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
|
||||
}
|
||||
|
||||
void SerialBLEInterface::stopAdv() {
|
||||
void SerialBLEInterface::clearBuffers() {
|
||||
send_queue_len = 0;
|
||||
recv_queue_len = 0;
|
||||
_last_retry_attempt = 0;
|
||||
bleuart.flush();
|
||||
}
|
||||
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: stopping advertising");
|
||||
|
||||
// we only want to stop advertising if it's running, otherwise an invalid state error is logged by ble stack
|
||||
if(!Bluefruit.Advertising.isRunning()){
|
||||
return;
|
||||
void SerialBLEInterface::shiftSendQueueLeft() {
|
||||
if (send_queue_len > 0) {
|
||||
send_queue_len--;
|
||||
for (uint8_t i = 0; i < send_queue_len; i++) {
|
||||
send_queue[i] = send_queue[i + 1];
|
||||
}
|
||||
}
|
||||
|
||||
// stop advertising
|
||||
Bluefruit.Advertising.stop();
|
||||
|
||||
}
|
||||
|
||||
// ---------- public methods
|
||||
void SerialBLEInterface::shiftRecvQueueLeft() {
|
||||
if (recv_queue_len > 0) {
|
||||
recv_queue_len--;
|
||||
for (uint8_t i = 0; i < recv_queue_len; i++) {
|
||||
recv_queue[i] = recv_queue[i + 1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::enable() {
|
||||
bool SerialBLEInterface::isValidConnection(uint16_t handle, bool requireWaitingForSecurity) const {
|
||||
if (_conn_handle != handle) {
|
||||
return false;
|
||||
}
|
||||
BLEConnection* conn = Bluefruit.Connection(handle);
|
||||
if (conn == nullptr || !conn->connected()) {
|
||||
return false;
|
||||
}
|
||||
if (requireWaitingForSecurity && _isDeviceConnected) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isAdvertising() const {
|
||||
ble_gap_addr_t adv_addr;
|
||||
uint32_t err_code = sd_ble_gap_adv_addr_get(0, &adv_addr);
|
||||
return (err_code == NRF_SUCCESS);
|
||||
}
|
||||
|
||||
void SerialBLEInterface::enable() {
|
||||
if (_isEnabled) return;
|
||||
|
||||
_isEnabled = true;
|
||||
clearBuffers();
|
||||
_last_health_check = millis();
|
||||
|
||||
// Start advertising
|
||||
startAdv();
|
||||
Bluefruit.Advertising.start(0);
|
||||
}
|
||||
|
||||
void SerialBLEInterface::disconnect() {
|
||||
if (_conn_handle != BLE_CONN_HANDLE_INVALID) {
|
||||
sd_ble_gap_disconnect(_conn_handle, BLE_HCI_REMOTE_USER_TERMINATED_CONNECTION);
|
||||
}
|
||||
}
|
||||
|
||||
void SerialBLEInterface::disable() {
|
||||
_isEnabled = false;
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface::disable");
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: disable");
|
||||
|
||||
#ifdef RAK_BOARD
|
||||
Bluefruit.disconnect(Bluefruit.connHandle());
|
||||
#else
|
||||
uint16_t conn_id;
|
||||
if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) {
|
||||
Bluefruit.disconnect(conn_id);
|
||||
}
|
||||
#endif
|
||||
|
||||
Bluefruit.Advertising.restartOnDisconnect(false);
|
||||
disconnect();
|
||||
Bluefruit.Advertising.stop();
|
||||
Bluefruit.Advertising.clearData();
|
||||
|
||||
stopAdv();
|
||||
_last_health_check = 0;
|
||||
}
|
||||
|
||||
size_t SerialBLEInterface::writeFrame(const uint8_t src[], size_t len) {
|
||||
if (len > MAX_FRAME_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%d", len);
|
||||
BLE_DEBUG_PRINTLN("writeFrame(), frame too big, len=%u", (unsigned)len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (_isDeviceConnected && len > 0) {
|
||||
bool connected = isConnected();
|
||||
if (connected && len > 0) {
|
||||
if (send_queue_len >= FRAME_QUEUE_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("writeFrame(), send_queue is full!");
|
||||
return 0;
|
||||
}
|
||||
|
||||
send_queue[send_queue_len].len = len; // add to send queue
|
||||
send_queue[send_queue_len].len = len;
|
||||
memcpy(send_queue[send_queue_len].buf, src, len);
|
||||
send_queue_len++;
|
||||
|
||||
|
||||
return len;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define BLE_WRITE_MIN_INTERVAL 60
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return millis() < _last_write + BLE_WRITE_MIN_INTERVAL; // still too soon to start another write?
|
||||
}
|
||||
|
||||
size_t SerialBLEInterface::checkRecvFrame(uint8_t dest[]) {
|
||||
if (send_queue_len > 0 // first, check send queue
|
||||
&& millis() >= _last_write + BLE_WRITE_MIN_INTERVAL // space the writes apart
|
||||
) {
|
||||
_last_write = millis();
|
||||
bleuart.write(send_queue[0].buf, send_queue[0].len);
|
||||
BLE_DEBUG_PRINTLN("writeBytes: sz=%d, hdr=%d", (uint32_t)send_queue[0].len, (uint32_t) send_queue[0].buf[0]);
|
||||
if (send_queue_len > 0) {
|
||||
if (!isConnected()) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes: connection invalid, clearing send queue");
|
||||
send_queue_len = 0;
|
||||
} else {
|
||||
unsigned long now = millis();
|
||||
bool throttle_active = (_last_retry_attempt > 0 && (now - _last_retry_attempt) < BLE_RETRY_THROTTLE_MS);
|
||||
|
||||
send_queue_len--;
|
||||
for (int i = 0; i < send_queue_len; i++) { // delete top item from queue
|
||||
send_queue[i] = send_queue[i + 1];
|
||||
}
|
||||
} else {
|
||||
int len = bleuart.available();
|
||||
if (len > 0) {
|
||||
bleuart.readBytes(dest, len);
|
||||
BLE_DEBUG_PRINTLN("readBytes: sz=%d, hdr=%d", len, (uint32_t) dest[0]);
|
||||
return len;
|
||||
if (!throttle_active) {
|
||||
Frame frame_to_send = send_queue[0];
|
||||
|
||||
size_t written = bleuart.write(frame_to_send.buf, frame_to_send.len);
|
||||
if (written == frame_to_send.len) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes: sz=%u, hdr=%u", (unsigned)frame_to_send.len, (unsigned)frame_to_send.buf[0]);
|
||||
_last_retry_attempt = 0;
|
||||
shiftSendQueueLeft();
|
||||
} else if (written > 0) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes: partial write, sent=%u of %u, dropping corrupted frame", (unsigned)written, (unsigned)frame_to_send.len);
|
||||
_last_retry_attempt = 0;
|
||||
shiftSendQueueLeft();
|
||||
} else {
|
||||
if (!isConnected()) {
|
||||
BLE_DEBUG_PRINTLN("writeBytes failed: connection lost, dropping frame");
|
||||
_last_retry_attempt = 0;
|
||||
shiftSendQueueLeft();
|
||||
} else {
|
||||
BLE_DEBUG_PRINTLN("writeBytes failed (buffer full), keeping frame for retry");
|
||||
_last_retry_attempt = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (recv_queue_len > 0) {
|
||||
size_t len = recv_queue[0].len;
|
||||
memcpy(dest, recv_queue[0].buf, len);
|
||||
|
||||
BLE_DEBUG_PRINTLN("readBytes: sz=%u, hdr=%u", (unsigned)len, (unsigned)dest[0]);
|
||||
|
||||
shiftRecvQueueLeft();
|
||||
return len;
|
||||
}
|
||||
|
||||
// Advertising watchdog: periodically check if advertising is running, restart if not
|
||||
// Only run when truly disconnected (no connection handle), not during connection establishment
|
||||
unsigned long now = millis();
|
||||
if (_isEnabled && !isConnected() && _conn_handle == BLE_CONN_HANDLE_INVALID) {
|
||||
if (now - _last_health_check >= BLE_HEALTH_CHECK_INTERVAL) {
|
||||
_last_health_check = now;
|
||||
|
||||
if (!isAdvertising()) {
|
||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: advertising watchdog - advertising stopped, restarting");
|
||||
Bluefruit.Advertising.start(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isConnected() const {
|
||||
return _isDeviceConnected;
|
||||
void SerialBLEInterface::onBleUartRX(uint16_t conn_handle) {
|
||||
if (!instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (instance->_conn_handle != conn_handle || !instance->isConnected()) {
|
||||
while (instance->bleuart.available() > 0) {
|
||||
instance->bleuart.read();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
while (instance->bleuart.available() > 0) {
|
||||
if (instance->recv_queue_len >= FRAME_QUEUE_SIZE) {
|
||||
while (instance->bleuart.available() > 0) {
|
||||
instance->bleuart.read();
|
||||
}
|
||||
BLE_DEBUG_PRINTLN("onBleUartRX: recv queue full, dropping data");
|
||||
break;
|
||||
}
|
||||
|
||||
int avail = instance->bleuart.available();
|
||||
|
||||
if (avail > MAX_FRAME_SIZE) {
|
||||
BLE_DEBUG_PRINTLN("onBleUartRX: WARN: BLE RX overflow, avail=%d, draining all", avail);
|
||||
uint8_t drain_buf[BLE_RX_DRAIN_BUF_SIZE];
|
||||
while (instance->bleuart.available() > 0) {
|
||||
int chunk = instance->bleuart.available() > BLE_RX_DRAIN_BUF_SIZE ? BLE_RX_DRAIN_BUF_SIZE : instance->bleuart.available();
|
||||
instance->bleuart.readBytes(drain_buf, chunk);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int read_len = avail;
|
||||
instance->recv_queue[instance->recv_queue_len].len = read_len;
|
||||
instance->bleuart.readBytes(instance->recv_queue[instance->recv_queue_len].buf, read_len);
|
||||
instance->recv_queue_len++;
|
||||
}
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isConnected() const {
|
||||
return _isDeviceConnected && Bluefruit.connected() > 0;
|
||||
}
|
||||
|
||||
bool SerialBLEInterface::isWriteBusy() const {
|
||||
return send_queue_len >= (FRAME_QUEUE_SIZE * 2 / 3);
|
||||
}
|
||||
|
||||
@@ -11,41 +11,53 @@ class SerialBLEInterface : public BaseSerialInterface {
|
||||
BLEUart bleuart;
|
||||
bool _isEnabled;
|
||||
bool _isDeviceConnected;
|
||||
unsigned long _last_write;
|
||||
uint16_t _conn_handle;
|
||||
unsigned long _last_health_check;
|
||||
unsigned long _last_retry_attempt;
|
||||
|
||||
struct Frame {
|
||||
uint8_t len;
|
||||
uint8_t buf[MAX_FRAME_SIZE];
|
||||
};
|
||||
|
||||
#define FRAME_QUEUE_SIZE 4
|
||||
int send_queue_len;
|
||||
#define FRAME_QUEUE_SIZE 12
|
||||
|
||||
uint8_t send_queue_len;
|
||||
Frame send_queue[FRAME_QUEUE_SIZE];
|
||||
|
||||
uint8_t recv_queue_len;
|
||||
Frame recv_queue[FRAME_QUEUE_SIZE];
|
||||
|
||||
void clearBuffers() { send_queue_len = 0; }
|
||||
void clearBuffers();
|
||||
void shiftSendQueueLeft();
|
||||
void shiftRecvQueueLeft();
|
||||
bool isValidConnection(uint16_t handle, bool requireWaitingForSecurity = false) const;
|
||||
bool isAdvertising() const;
|
||||
static void onConnect(uint16_t connection_handle);
|
||||
static void onDisconnect(uint16_t connection_handle, uint8_t reason);
|
||||
static void onSecured(uint16_t connection_handle);
|
||||
static bool onPairingPasskey(uint16_t connection_handle, uint8_t const passkey[6], bool match_request);
|
||||
static void onPairingComplete(uint16_t connection_handle, uint8_t auth_status);
|
||||
static void onBLEEvent(ble_evt_t* evt);
|
||||
static void onBleUartRX(uint16_t conn_handle);
|
||||
|
||||
public:
|
||||
SerialBLEInterface() {
|
||||
_isEnabled = false;
|
||||
_isDeviceConnected = false;
|
||||
_last_write = 0;
|
||||
_conn_handle = BLE_CONN_HANDLE_INVALID;
|
||||
_last_health_check = 0;
|
||||
_last_retry_attempt = 0;
|
||||
send_queue_len = 0;
|
||||
recv_queue_len = 0;
|
||||
}
|
||||
|
||||
void startAdv();
|
||||
void stopAdv();
|
||||
void begin(const char* device_name, uint32_t pin_code);
|
||||
|
||||
// BaseSerialInterface methods
|
||||
void disconnect();
|
||||
void enable() override;
|
||||
void disable() override;
|
||||
bool isEnabled() const override { return _isEnabled; }
|
||||
|
||||
bool isConnected() const override;
|
||||
|
||||
bool isWriteBusy() const override;
|
||||
size_t writeFrame(const uint8_t src[], size_t len) override;
|
||||
size_t checkRecvFrame(uint8_t dest[]) override;
|
||||
|
||||
@@ -10,7 +10,7 @@ class CustomLR1110 : public LR1110 {
|
||||
size_t getPacketLength(bool update) override {
|
||||
size_t len = LR1110::getPacketLength(update);
|
||||
if (len == 0 && getIrqStatus() & RADIOLIB_LR11X0_IRQ_HEADER_ERR) {
|
||||
// we've just recieved a corrupted packet
|
||||
// we've just received a corrupted packet
|
||||
// this may have triggered a bug causing subsequent packets to be shifted
|
||||
// call standby() to return radio to known-good state
|
||||
// recvRaw will call startReceive() to restart rx
|
||||
|
||||
@@ -19,4 +19,7 @@ public:
|
||||
int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
virtual void powerOff() override {
|
||||
((CustomSX1262 *)_radio)->sleep(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,6 +137,7 @@ bool RadioLibWrapper::startSendRaw(const uint8_t* bytes, int len) {
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("RadioLibWrapper: error: startTransmit(%d)", err);
|
||||
idle(); // trigger another startRecv()
|
||||
_board->onAfterTransmit();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }
|
||||
|
||||
void begin() override;
|
||||
virtual void powerOff() { _radio->sleep(); }
|
||||
int recvRaw(uint8_t* bytes, int sz) override;
|
||||
uint32_t getEstAirtimeFor(int len_bytes) override;
|
||||
bool startSendRaw(const uint8_t* bytes, int len) override;
|
||||
|
||||
@@ -178,10 +178,27 @@ bool EnvironmentSensorManager::begin() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID());
|
||||
// Reduce self-heating: single-shot conversions, light oversampling, long standby.
|
||||
BME280.setSampling(Adafruit_BME280::MODE_FORCED,
|
||||
Adafruit_BME280::SAMPLING_X1, // temperature
|
||||
Adafruit_BME280::SAMPLING_X1, // pressure
|
||||
Adafruit_BME280::SAMPLING_X1, // humidity
|
||||
Adafruit_BME280::FILTER_OFF,
|
||||
Adafruit_BME280::STANDBY_MS_1000);
|
||||
BME280_initialized = true;
|
||||
} else {
|
||||
BME280_initialized = false;
|
||||
@@ -301,16 +318,6 @@ bool EnvironmentSensorManager::begin() {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
// First argument is MODE (aka oversampling)
|
||||
// choose ULTRALOWPOWER
|
||||
@@ -344,12 +351,27 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680_initialized) {
|
||||
if (BME680.performReading()) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity);
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903)));
|
||||
telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance);
|
||||
next_available_channel++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
if (BME280_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature());
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
|
||||
if (BME280.takeForcedMeasurement()) { // trigger a fresh reading in forced mode
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature());
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -386,7 +408,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
if (LPS22HB_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BARO.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BARO.readPressure() * 10); // convert kPa to hPa
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -452,19 +474,6 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680_initialized) {
|
||||
if (BME680.performReading()) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity);
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903)));
|
||||
telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance);
|
||||
next_available_channel++;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
if (BMP085_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature());
|
||||
@@ -521,6 +530,15 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (strcmp(name, "gps_interval") == 0) {
|
||||
uint32_t interval_seconds = atoi(value);
|
||||
if (interval_seconds > 0) {
|
||||
gps_update_interval_sec = interval_seconds;
|
||||
} else {
|
||||
gps_update_interval_sec = 1; // Default to 1 second if 0
|
||||
}
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false; // not supported
|
||||
}
|
||||
@@ -548,7 +566,11 @@ void EnvironmentSensorManager::initBasicGPS() {
|
||||
delay(1000);
|
||||
|
||||
// We'll consider GPS detected if we see any data on Serial1
|
||||
#ifdef ENV_SKIP_GPS_DETECT
|
||||
gps_detected = true;
|
||||
#else
|
||||
gps_detected = (Serial1.available() > 0);
|
||||
#endif
|
||||
|
||||
if (gps_detected) {
|
||||
MESH_DEBUG_PRINTLN("GPS detected");
|
||||
@@ -563,7 +585,7 @@ void EnvironmentSensorManager::initBasicGPS() {
|
||||
gps_active = false; //Set GPS visibility off until setting is changed
|
||||
}
|
||||
|
||||
// gps code for rak might be moved to MicroNMEALoactionProvider
|
||||
// gps code for rak might be moved to MicroNMEALoactionProvider
|
||||
// or make a new location provider ...
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
void EnvironmentSensorManager::rakGPSInit(){
|
||||
@@ -593,6 +615,7 @@ void EnvironmentSensorManager::rakGPSInit(){
|
||||
MESH_DEBUG_PRINTLN("No GPS found");
|
||||
gps_active = false;
|
||||
gps_detected = false;
|
||||
Serial1.end();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -631,8 +654,7 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
|
||||
_location = &RAK12500_provider;
|
||||
return true;
|
||||
}
|
||||
else if(Serial1){
|
||||
} else if (Serial1.available()) {
|
||||
MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on");
|
||||
if(PIN_GPS_EN){
|
||||
gpsResetPin = PIN_GPS_EN;
|
||||
@@ -642,6 +664,8 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
gps_detected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
pinMode(ioPin, INPUT);
|
||||
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
|
||||
return false;
|
||||
}
|
||||
@@ -683,8 +707,8 @@ void EnvironmentSensorManager::loop() {
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
_location->loop();
|
||||
|
||||
if (millis() > next_gps_update) {
|
||||
|
||||
if(gps_active){
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) {
|
||||
@@ -704,7 +728,7 @@ void EnvironmentSensorManager::loop() {
|
||||
}
|
||||
#endif
|
||||
}
|
||||
next_gps_update = millis() + 1000;
|
||||
next_gps_update = millis() + (gps_update_interval_sec * 1000);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ protected:
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
uint32_t gps_update_interval_sec = 1; // Default 1 second
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
LocationProvider* _location;
|
||||
|
||||
@@ -113,7 +113,7 @@ public:
|
||||
return _pos <= _len;
|
||||
}
|
||||
bool readCurrent(float& amps) {
|
||||
amps = getFloat(&_buf[_pos], 2, 1000, false); _pos += 2;
|
||||
amps = getFloat(&_buf[_pos], 2, 1000, true); _pos += 2;
|
||||
return _pos <= _len;
|
||||
}
|
||||
bool readPower(float& watts) {
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
|
||||
#include "GxEPDDisplay.h"
|
||||
|
||||
#ifdef EXP_PIN_BACKLIGHT
|
||||
#include <PCA9557.h>
|
||||
extern PCA9557 expander;
|
||||
#endif
|
||||
|
||||
#ifndef DISPLAY_ROTATION
|
||||
#define DISPLAY_ROTATION 3
|
||||
#endif
|
||||
|
||||
#ifdef ESP32
|
||||
SPIClass SPI1 = SPIClass(FSPI);
|
||||
#endif
|
||||
|
||||
bool GxEPDDisplay::begin() {
|
||||
display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0));
|
||||
#ifdef ESP32
|
||||
SPI1.begin(PIN_DISPLAY_SCLK, PIN_DISPLAY_MISO, PIN_DISPLAY_MOSI, PIN_DISPLAY_CS);
|
||||
#else
|
||||
SPI1.begin();
|
||||
#endif
|
||||
display.init(115200, true, 2, false);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
setTextSize(1); // Default to size 1
|
||||
@@ -27,6 +40,8 @@ void GxEPDDisplay::turnOn() {
|
||||
if (!_init) begin();
|
||||
#if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN)
|
||||
digitalWrite(DISP_BACKLIGHT, HIGH);
|
||||
#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN)
|
||||
expander.digitalWrite(EXP_PIN_BACKLIGHT, HIGH);
|
||||
#endif
|
||||
_isOn = true;
|
||||
}
|
||||
@@ -34,6 +49,8 @@ void GxEPDDisplay::turnOn() {
|
||||
void GxEPDDisplay::turnOff() {
|
||||
#if defined(DISP_BACKLIGHT) && !defined(BACKLIGHT_BTN)
|
||||
digitalWrite(DISP_BACKLIGHT, LOW);
|
||||
#elif defined(EXP_PIN_BACKLIGHT) && !defined(BACKLIGHT_BTN)
|
||||
expander.digitalWrite(EXP_PIN_BACKLIGHT, LOW);
|
||||
#endif
|
||||
_isOn = false;
|
||||
}
|
||||
|
||||
@@ -23,9 +23,13 @@ bool ST7789LCDDisplay::begin() {
|
||||
if (!_isOn) {
|
||||
if (_peripher_power) _peripher_power->claim();
|
||||
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
if (PIN_TFT_LEDA_CTL != -1) {
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
}
|
||||
if (PIN_TFT_RST != -1) {
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
}
|
||||
|
||||
// Im not sure if this is just a t-deck problem or not, if your display is slow try this.
|
||||
#ifdef LILYGO_TDECK
|
||||
@@ -54,9 +58,15 @@ void ST7789LCDDisplay::turnOn() {
|
||||
|
||||
void ST7789LCDDisplay::turnOff() {
|
||||
if (_isOn) {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
if (PIN_TFT_LEDA_CTL != -1) {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
}
|
||||
if (PIN_TFT_RST != -1) {
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
}
|
||||
if (PIN_TFT_LEDA_CTL != -1) {
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, LOW);
|
||||
}
|
||||
_isOn = false;
|
||||
|
||||
if (_peripher_power) _peripher_power->release();
|
||||
|
||||
@@ -1,28 +1,10 @@
|
||||
#include <Arduino.h>
|
||||
#include "MeshSolarBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle)
|
||||
{
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
||||
{
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
#include "MeshSolarBoard.h"
|
||||
|
||||
void MeshSolarBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
|
||||
meshSolarStart();
|
||||
|
||||
@@ -32,46 +14,3 @@ void MeshSolarBoard::begin() {
|
||||
|
||||
Wire.begin();
|
||||
}
|
||||
|
||||
bool MeshSolarBoard::startOTAUpdate(const char* id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("MESH_SOLAR_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
#ifdef HELTEC_MESH_SOLAR
|
||||
#include "meshSolarApp.h"
|
||||
@@ -19,14 +20,10 @@
|
||||
#define SX126X_DIO2_AS_RF_SWITCH true
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
|
||||
|
||||
class MeshSolarBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class MeshSolarBoard : public NRF52BoardOTA {
|
||||
public:
|
||||
MeshSolarBoard() : NRF52BoardOTA("MESH_SOLAR_OTA") {}
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
return meshSolarGetBattVoltage();
|
||||
@@ -35,10 +32,4 @@ public:
|
||||
const char* getManufacturerName() const override {
|
||||
return "Heltec Mesh Solar";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
|
||||
@@ -2,25 +2,10 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <bluefruit.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void T114Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
NRF_POWER->DCDCEN = 1;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
@@ -38,47 +23,4 @@ void T114Board::begin() {
|
||||
pinMode(SX126X_POWER_EN, OUTPUT);
|
||||
digitalWrite(SX126X_POWER_EN, HIGH);
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool T114Board::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("T114_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,19 +2,17 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
// built-ins
|
||||
#define PIN_VBAT_READ 4
|
||||
#define PIN_BAT_CTL 6
|
||||
#define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range
|
||||
|
||||
class T114Board : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class T114Board : public NRF52BoardOTA {
|
||||
public:
|
||||
T114Board() : NRF52BoardOTA("T114_OTA") {}
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
@@ -43,13 +41,14 @@ public:
|
||||
return "Heltec T114";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
#ifdef LED_PIN
|
||||
digitalWrite(LED_PIN, HIGH);
|
||||
#endif
|
||||
#if ENV_INCLUDE_GPS == 1
|
||||
pinMode(GPS_EN, OUTPUT);
|
||||
digitalWrite(GPS_EN, LOW);
|
||||
#endif
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
|
||||
@@ -59,6 +59,25 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:Heltec_t114_without_display_repeater_bridge_rs232]
|
||||
extends = Heltec_t114
|
||||
build_flags =
|
||||
${Heltec_t114.build_flags}
|
||||
-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=9
|
||||
-D WITH_RS232_BRIDGE_TX=10
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_t114.build_src_filter}
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
[env:Heltec_t114_without_display_room_server]
|
||||
extends = Heltec_t114
|
||||
build_src_filter = ${Heltec_t114.build_src_filter}
|
||||
@@ -151,6 +170,25 @@ build_flags =
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:Heltec_t114_repeater_bridge_rs232]
|
||||
extends = Heltec_t114
|
||||
build_flags =
|
||||
${Heltec_t114.build_flags}
|
||||
-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=9
|
||||
-D WITH_RS232_BRIDGE_TX=10
|
||||
; -D BRIDGE_DEBUG=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_t114_with_display.build_src_filter}
|
||||
+<helpers/bridges/RS232Bridge.cpp>
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
[env:Heltec_t114_room_server]
|
||||
extends = Heltec_t114_with_display
|
||||
build_src_filter = ${Heltec_t114_with_display.build_src_filter}
|
||||
|
||||
@@ -6,6 +6,14 @@ build_flags =
|
||||
-I variants/heltec_tracker
|
||||
-D HELTEC_LORA_V3
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
||||
-D ESP32_CPU_FREQ=80
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=13
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <../heltec_v3/HeltecV3Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
@@ -185,6 +185,7 @@ build_flags =
|
||||
-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 = ${Heltec_tracker_v2.build_src_filter}
|
||||
|
||||
@@ -183,6 +183,7 @@ build_flags =
|
||||
-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 = ${Heltec_lora32_v2.build_src_filter}
|
||||
|
||||
@@ -189,6 +189,7 @@ build_flags =
|
||||
-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 = ${Heltec_lora32_v3.build_src_filter}
|
||||
@@ -322,7 +323,7 @@ lib_deps =
|
||||
extends = Heltec_lora32_v3
|
||||
build_flags =
|
||||
${Heltec_lora32_v3.build_flags}
|
||||
-D MAX_CONTACTS=140
|
||||
-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
|
||||
@@ -341,6 +342,7 @@ build_flags =
|
||||
-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 = ${Heltec_lora32_v3.build_src_filter}
|
||||
|
||||
@@ -176,6 +176,7 @@ build_flags =
|
||||
-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 = ${Heltec_lora32_v4.build_src_filter}
|
||||
|
||||
@@ -5,12 +5,20 @@ build_flags =
|
||||
${esp32_base.build_flags}
|
||||
-I variants/heltec_wireless_paper
|
||||
-D HELTEC_WIRELESS_PAPER
|
||||
-D ARDUINO_USB_CDC_ON_BOOT=1 ; need for Serial
|
||||
-D P_LORA_DIO_1=14
|
||||
-D P_LORA_NSS=8
|
||||
-D P_LORA_RESET=RADIOLIB_NC
|
||||
-D P_LORA_BUSY=13
|
||||
-D P_LORA_SCLK=9
|
||||
-D P_LORA_MISO=11
|
||||
-D P_LORA_MOSI=10
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D P_LORA_TX_LED=18
|
||||
; -D PIN_BOARD_SDA=17
|
||||
; -D PIN_BOARD_SCL=18
|
||||
-D PIN_BOARD_SDA=17
|
||||
-D PIN_BOARD_SCL=18
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_VEXT_EN=45
|
||||
-D PIN_VBAT_READ=20
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/HeltecV3Board.h>
|
||||
#include <../heltec_v3/HeltecV3Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#ifdef IKOKA_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "IkokaNrf52Board.h"
|
||||
|
||||
void IkokaNrf52Board::begin() {
|
||||
NRF52Board::begin();
|
||||
|
||||
// ensure we have pull ups on the screen i2c, this isn't always available
|
||||
// in hardware and it should only be 20k ohms. Disable the pullups if we
|
||||
// are using the rotated lcd breakout board
|
||||
#if defined(DISPLAY_CLASS) && DISPLAY_ROTATION == 0
|
||||
pinMode(PIN_WIRE_SDA, INPUT_PULLUP);
|
||||
pinMode(PIN_WIRE_SCL, INPUT_PULLUP);
|
||||
#endif
|
||||
|
||||
pinMode(PIN_VBAT, INPUT);
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
digitalWrite(VBAT_ENABLE, HIGH);
|
||||
|
||||
// required button pullup is handled as part of button initilization
|
||||
// in target.cpp
|
||||
|
||||
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
|
||||
#ifdef P_LORA_TX_LED
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, HIGH);
|
||||
#endif
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
#ifdef IKOKA_NRF52
|
||||
|
||||
class IkokaNrf52Board : public NRF52BoardOTA {
|
||||
public:
|
||||
IkokaNrf52Board() : NRF52BoardOTA("XIAO_NRF52_OTA") {}
|
||||
void begin();
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on
|
||||
}
|
||||
void onAfterTransmit() override {
|
||||
digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
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 {
|
||||
return "Ikoka Handheld E22 30dBm (Xiao_nrf52)";
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,103 @@
|
||||
[ikoka_handheld_nrf]
|
||||
extends = nrf52_base
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
-I variants/ikoka_handheld_nrf
|
||||
-UENV_INCLUDE_GPS
|
||||
-D IKOKA_NRF52
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_TX_LED=11
|
||||
-D P_LORA_DIO_1=D1
|
||||
-D P_LORA_RESET=D2
|
||||
-D P_LORA_BUSY=D3
|
||||
-D P_LORA_NSS=D4
|
||||
-D SX126X_RXEN=D5
|
||||
-D SX126X_TXEN=RADIOLIB_NC
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<../variants/ikoka_handheld_nrf>
|
||||
+<helpers/sensors>
|
||||
lib_deps = ${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
# larger screen has a different driver, this is for the 0.96 inch
|
||||
[ikoka_handheld_nrf_ssd1306_companion]
|
||||
lib_deps = ${ikoka_handheld_nrf.lib_deps}
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
build_flags = ${ikoka_handheld_nrf.build_flags}
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D DISPLAY_ROTATION=0
|
||||
-D PIN_WIRE_SCL=D6
|
||||
-D PIN_WIRE_SDA=D7
|
||||
-D PIN_USER_BTN=D0
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-D QSPIFLASH=1
|
||||
-I examples/companion_radio/ui-new
|
||||
build_src_filter = ${ikoka_handheld_nrf.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/companion_radio/ui-new/UITask.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
|
||||
[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_ble]
|
||||
extends = ikoka_nrf52
|
||||
build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags}
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
|
||||
[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_ble]
|
||||
extends = ikoka_nrf52
|
||||
build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags}
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D LORA_TX_POWER=20
|
||||
-D DISPLAY_ROTATION=2
|
||||
build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
|
||||
[env:ikoka_handheld_nrf_e22_30dbm_096_companion_radio_usb]
|
||||
extends = ikoka_nrf52
|
||||
build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags}
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter}
|
||||
|
||||
[env:ikoka_handheld_nrf_e22_30dbm_096_rotated_companion_radio_usb]
|
||||
extends = ikoka_nrf52
|
||||
build_flags = ${ikoka_handheld_nrf_ssd1306_companion.build_flags}
|
||||
-D LORA_TX_POWER=20
|
||||
-D DISPLAY_ROTATION=2
|
||||
build_src_filter = ${ikoka_handheld_nrf_ssd1306_companion.build_src_filter}
|
||||
|
||||
[env:ikoka_handheld_nrf_e22_30dbm_repeater]
|
||||
extends = ikoka_nrf52
|
||||
build_flags =
|
||||
${ikoka_handheld_nrf.build_flags}
|
||||
-D ADVERT_NAME='"ikoka_handheld Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${ikoka_handheld_nrf.build_src_filter}
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
|
||||
[env:ikoka_handheld_nrf_e22_30dbm_room_server]
|
||||
extends = ikoka_nrf52
|
||||
build_flags =
|
||||
${ikoka_handheld_nrf.build_flags}
|
||||
-D ADVERT_NAME='"ikoka_handheld Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${ikoka_handheld_nrf.build_src_filter}
|
||||
+<../examples/simple_room_server/*.cpp>
|
||||
@@ -0,0 +1,46 @@
|
||||
#include <Arduino.h>
|
||||
#include "target.h"
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
IkokaNrf52Board board;
|
||||
|
||||
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);
|
||||
|
||||
EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true, true);
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <IkokaNrf52Board.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/SSD1306Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
|
||||
extern IkokaNrf52Board 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();
|
||||
@@ -0,0 +1,84 @@
|
||||
#include "variant.h"
|
||||
|
||||
#include "nrf.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
// D0 .. D10
|
||||
2, // D0 is P0.02 (A0)
|
||||
3, // D1 is P0.03 (A1)
|
||||
28, // D2 is P0.28 (A2)
|
||||
29, // D3 is P0.29 (A3)
|
||||
4, // D4 is P0.04 (A4,SDA)
|
||||
5, // D5 is P0.05 (A5,SCL)
|
||||
43, // D6 is P1.11 (TX)
|
||||
44, // D7 is P1.12 (RX)
|
||||
45, // D8 is P1.13 (SCK)
|
||||
46, // D9 is P1.14 (MISO)
|
||||
47, // D10 is P1.15 (MOSI)
|
||||
|
||||
// LEDs
|
||||
26, // D11 is P0.26 (LED RED)
|
||||
6, // D12 is P0.06 (LED BLUE)
|
||||
30, // D13 is P0.30 (LED GREEN)
|
||||
14, // D14 is P0.14 (READ_BAT)
|
||||
|
||||
// LSM6DS3TR
|
||||
40, // D15 is P1.08 (6D_PWR)
|
||||
27, // D16 is P0.27 (6D_I2C_SCL)
|
||||
7, // D17 is P0.07 (6D_I2C_SDA)
|
||||
11, // D18 is P0.11 (6D_INT1)
|
||||
|
||||
// MIC
|
||||
42, // D19 is P1.10 (MIC_PWR)
|
||||
32, // D20 is P1.00 (PDM_CLK)
|
||||
16, // D21 is P0.16 (PDM_DATA)
|
||||
|
||||
// BQ25100
|
||||
13, // D22 is P0.13 (HICHG)
|
||||
17, // D23 is P0.17 (~CHG)
|
||||
|
||||
//
|
||||
21, // D24 is P0.21 (QSPI_SCK)
|
||||
25, // D25 is P0.25 (QSPI_CSN)
|
||||
20, // D26 is P0.20 (QSPI_SIO_0 DI)
|
||||
24, // D27 is P0.24 (QSPI_SIO_1 DO)
|
||||
22, // D28 is P0.22 (QSPI_SIO_2 WP)
|
||||
23, // D29 is P0.23 (QSPI_SIO_3 HOLD)
|
||||
|
||||
// NFC
|
||||
9, // D30 is P0.09 (NFC1)
|
||||
10, // D31 is P0.10 (NFC2)
|
||||
|
||||
// VBAT
|
||||
31, // D32 is P0.31 (VBAT)
|
||||
};
|
||||
|
||||
void initVariant() {
|
||||
// Disable reading of the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
// digitalWrite(VBAT_ENABLE, HIGH);
|
||||
// This was taken from Seeed github butis not coherent with the doc,
|
||||
// VBAT_ENABLE should be kept to LOW to protect P0.14, (1500/500)*(4.2-3.3)+3.3 = 3.9V > 3.6V
|
||||
// This induces a 3mA current in the resistors :( but it's better than burning the nrf
|
||||
digitalWrite(VBAT_ENABLE, LOW);
|
||||
|
||||
// disable xiao charging current, the handheld uses a tp4056 to charge
|
||||
// instead of the onboard xiao charging circuit. This charges at a max of
|
||||
// 780ma instead of 100ma. In theory you could enable both, but in practice
|
||||
// fire is scary.
|
||||
pinMode(PIN_CHARGING_CURRENT, OUTPUT);
|
||||
digitalWrite(PIN_CHARGING_CURRENT, HIGH);
|
||||
|
||||
pinMode(PIN_QSPI_CS, OUTPUT);
|
||||
digitalWrite(PIN_QSPI_CS, HIGH);
|
||||
|
||||
pinMode(LED_RED, OUTPUT);
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
pinMode(LED_GREEN, OUTPUT);
|
||||
digitalWrite(LED_GREEN, HIGH);
|
||||
pinMode(LED_BLUE, OUTPUT);
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
#ifndef _SEEED_XIAO_NRF52840_H_
|
||||
#define _SEEED_XIAO_NRF52840_H_
|
||||
|
||||
/** 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
|
||||
|
||||
#define PINS_COUNT (33)
|
||||
#define NUM_DIGITAL_PINS (33)
|
||||
#define NUM_ANALOG_INPUTS (8)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
// LEDs
|
||||
#define PIN_LED (LED_RED)
|
||||
#define LED_PWR (PINS_COUNT)
|
||||
#define PIN_NEOPIXEL (PINS_COUNT)
|
||||
#define NEOPIXEL_NUM (0)
|
||||
|
||||
#define LED_BUILTIN (PIN_LED)
|
||||
|
||||
#define LED_RED (11)
|
||||
#define LED_GREEN (13)
|
||||
#define LED_BLUE (12)
|
||||
|
||||
#define LED_STATE_ON (1) // State when LED is litted
|
||||
|
||||
// Buttons
|
||||
#define PIN_BUTTON1 (PINS_COUNT)
|
||||
|
||||
// Digital PINs
|
||||
static const uint8_t D0 = 0 ;
|
||||
static const uint8_t D1 = 1 ;
|
||||
static const uint8_t D2 = 2 ;
|
||||
static const uint8_t D3 = 3 ;
|
||||
static const uint8_t D4 = 4 ;
|
||||
static const uint8_t D5 = 5 ;
|
||||
static const uint8_t D6 = 6 ;
|
||||
static const uint8_t D7 = 7 ;
|
||||
static const uint8_t D8 = 8 ;
|
||||
static const uint8_t D9 = 9 ;
|
||||
static const uint8_t D10 = 10;
|
||||
|
||||
#define VBAT_ENABLE (14) // Output LOW to enable reading of the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
#define PIN_CHARGING_CURRENT (22) // Battery Charging current
|
||||
// https://wiki.seedstudio.com/XIAO_BLE#battery-charging-current
|
||||
// Analog pins
|
||||
#define PIN_A0 (0)
|
||||
#define PIN_A1 (1)
|
||||
#define PIN_A2 (2)
|
||||
#define PIN_A3 (3)
|
||||
#define PIN_A4 (4)
|
||||
#define PIN_A5 (5)
|
||||
#define PIN_VBAT (32) // Read the BAT voltage.
|
||||
// https://wiki.seeedstudio.com/XIAO_BLE#q3-what-are-the-considerations-when-using-xiao-nrf52840-sense-for-battery-charging
|
||||
|
||||
#define BAT_NOT_CHARGING (23) // LOW when charging
|
||||
|
||||
#define AREF_VOLTAGE (3.0)
|
||||
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
||||
|
||||
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;
|
||||
|
||||
#define ADC_RESOLUTION (12)
|
||||
|
||||
// Other pins
|
||||
#define PIN_NFC1 (30)
|
||||
#define PIN_NFC2 (31)
|
||||
|
||||
// Serial interfaces
|
||||
#define PIN_SERIAL1_RX (7)
|
||||
#define PIN_SERIAL1_TX (6)
|
||||
|
||||
// SPI Interfaces
|
||||
#define SPI_INTERFACES_COUNT (2)
|
||||
|
||||
#define PIN_SPI_MISO (9)
|
||||
#define PIN_SPI_MOSI (10)
|
||||
#define PIN_SPI_SCK (8)
|
||||
|
||||
#define PIN_SPI1_MISO (25)
|
||||
#define PIN_SPI1_MOSI (26)
|
||||
#define PIN_SPI1_SCK (29)
|
||||
|
||||
// Lora SPI is on SPI0
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
|
||||
// Wire Interfaces
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_WIRE_SDA (6) // 4 and 5 are used for the sx1262 !
|
||||
#define PIN_WIRE_SCL (7) // use WIRE1_SDA
|
||||
|
||||
static const uint8_t SDA = (6);
|
||||
static const uint8_t SCL = (7);
|
||||
|
||||
//#define PIN_WIRE1_SDA (17)
|
||||
//#define PIN_WIRE1_SCL (16)
|
||||
#define PIN_LSM6DS3TR_C_POWER (15)
|
||||
#define PIN_LSM6DS3TR_C_INT1 (18)
|
||||
|
||||
// PDM Interfaces
|
||||
#define PIN_PDM_PWR (19)
|
||||
#define PIN_PDM_CLK (20)
|
||||
#define PIN_PDM_DIN (21)
|
||||
|
||||
// QSPI Pins
|
||||
#define PIN_QSPI_SCK (24)
|
||||
#define PIN_QSPI_CS (25)
|
||||
#define PIN_QSPI_IO0 (26)
|
||||
#define PIN_QSPI_IO1 (27)
|
||||
#define PIN_QSPI_IO2 (28)
|
||||
#define PIN_QSPI_IO3 (29)
|
||||
|
||||
// On-board QSPI Flash
|
||||
#define EXTERNAL_FLASH_DEVICES (P25Q16H)
|
||||
#define EXTERNAL_FLASH_USE_QSPI
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
/*----------------------------------------------------------------------------
|
||||
* Arduino objects - C++ only
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
#endif
|
||||
@@ -1,28 +1,12 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IkokaNanoNRFBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
#include "IkokaNanoNRFBoard.h"
|
||||
|
||||
void IkokaNanoNRFBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
|
||||
pinMode(PIN_VBAT, INPUT);
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
@@ -48,47 +32,4 @@ void IkokaNanoNRFBoard::begin() {
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool IkokaNanoNRFBoard::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("XIAO_NRF52_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
class IkokaNanoNRFBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class IkokaNanoNRFBoard : public NRF52BoardOTA {
|
||||
public:
|
||||
IkokaNanoNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {}
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
@@ -49,12 +47,6 @@ public:
|
||||
const char *getManufacturerName() const override {
|
||||
return MANUFACTURER_STRING;
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,151 +1,124 @@
|
||||
[nrf52840_xiao]
|
||||
[ikoka_nano_nrf]
|
||||
extends = nrf52_base
|
||||
platform_packages =
|
||||
toolchain-gccarmnoneeabi@~1.100301.0
|
||||
framework-arduinoadafruitnrf52
|
||||
board = seeed-xiao-afruitnrf52-nrf52840
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-D NRF52_PLATFORM -D XIAO_NRF52
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
lvgl
|
||||
lib5b4
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[ikoka_nano_nrf_baseboard]
|
||||
extends = nrf52840_xiao
|
||||
;board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52840_xiao.build_flags}
|
||||
-D P_LORA_TX_LED=11
|
||||
-I variants/ikoka_nano_nrf
|
||||
-I src/helpers/nrf52
|
||||
-D P_LORA_TX_LED=11
|
||||
-D DISPLAY_CLASS=NullDisplayDriver
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D P_LORA_DIO_1=D1
|
||||
; -D P_LORA_BUSY=D3
|
||||
-D P_LORA_BUSY=D2 ; specific to ikoka nano variant.
|
||||
; -D P_LORA_RESET=D2
|
||||
-D P_LORA_RESET=D3 ; specific to ikoka nano variant.
|
||||
; -D P_LORA_NSS=D4
|
||||
-D P_LORA_NSS=D0 ; specific to ikoka nano variant.
|
||||
; -D SX126X_RXEN=D5
|
||||
-D P_LORA_BUSY=D2
|
||||
-D P_LORA_RESET=D3
|
||||
-D P_LORA_NSS=D0
|
||||
-D SX126X_RXEN=D7
|
||||
-D SX126X_TXEN=RADIOLIB_NC
|
||||
-D SX126X_DIO2_AS_RF_SWITCH=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8
|
||||
-D SX126X_CURRENT_LIMIT=140
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D PIN_WIRE_SCL=5 ; specific to ikoka nano variant.
|
||||
-D PIN_WIRE_SDA=4 ; specific to ikoka nano variant.
|
||||
-D ENV_INCLUDE_AHTX0=1
|
||||
-D ENV_INCLUDE_BME280=1
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
-D PIN_WIRE_SCL=5
|
||||
-D PIN_WIRE_SDA=4
|
||||
-UENV_INCLUDE_GPS
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
|
||||
;;; abstracted hardware variants
|
||||
lib_deps = ${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
|
||||
[ikoka_nano_nrf_e22_22dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
; No PA in this model, full 22dBm
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-22dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=22
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
[ikoka_nano_nrf_e22_30dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
; limit txpower to 20dBm on E22-900M30S. Anything higher will
|
||||
; cause distortion in the PA output. 20dBm in -> 30dBm out
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-30dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
[ikoka_nano_nrf_e22_33dbm]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
; limit txpower to 9dBm on E22-900M33S to avoid hardware damage
|
||||
; to the rf amplifier frontend. 9dBm in -> 33dBm out
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Nano-E22-33dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=9
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/NullDisplayDriver.cpp>
|
||||
+<../variants/ikoka_nano_nrf>
|
||||
|
||||
;;; abstracted firmware roles
|
||||
|
||||
[ikoka_nano_nrf_companion_radio_ble]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-I examples/companion_radio/ui-new
|
||||
-D QSPIFLASH=1
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_nano_nrf_baseboard.lib_deps}
|
||||
${ikoka_nano_nrf.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_nano_nrf_companion_radio_usb]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-I examples/companion_radio/ui-new
|
||||
-D QSPIFLASH=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_nano_nrf_baseboard.lib_deps}
|
||||
${ikoka_nano_nrf.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_nano_nrf_repeater]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Nano Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -153,26 +126,23 @@ build_flags =
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
|
||||
[ikoka_nano_nrf_room_server]
|
||||
extends = ikoka_nano_nrf_baseboard
|
||||
extends = ikoka_nano_nrf
|
||||
build_flags =
|
||||
${ikoka_nano_nrf_baseboard.build_flags}
|
||||
${ikoka_nano_nrf.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Nano Room"'
|
||||
-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 = ${ikoka_nano_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_nano_nrf.build_src_filter}
|
||||
+<../examples/simple_room_server/*.cpp>
|
||||
|
||||
;;; hardware + firmware variants
|
||||
|
||||
;;; 22dBm EBYTE E22-900M22 variants
|
||||
|
||||
[env:ikoka_nano_nrf_22dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_22dbm
|
||||
@@ -219,7 +189,6 @@ build_src_filter =
|
||||
|
||||
|
||||
;;; 30dBm EBYTE E22-900M30 variants
|
||||
|
||||
[env:ikoka_nano_nrf_30dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_30dbm
|
||||
@@ -266,7 +235,6 @@ build_src_filter =
|
||||
|
||||
|
||||
;;; 33dBm EBYTE E22-900M33 variants
|
||||
|
||||
[env:ikoka_nano_nrf_33dbm_companion_radio_usb]
|
||||
extends =
|
||||
ikoka_nano_nrf_e22_33dbm
|
||||
|
||||
@@ -1,28 +1,12 @@
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IkokaStickNRFBoard.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
#include "IkokaStickNRFBoard.h"
|
||||
|
||||
void IkokaStickNRFBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
|
||||
pinMode(PIN_VBAT, INPUT);
|
||||
pinMode(VBAT_ENABLE, OUTPUT);
|
||||
@@ -48,47 +32,4 @@ void IkokaStickNRFBoard::begin() {
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
bool IkokaStickNRFBoard::startOTAUpdate(const char *id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("XIAO_NRF52_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -2,16 +2,14 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
#ifdef XIAO_NRF52
|
||||
|
||||
class IkokaStickNRFBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class IkokaStickNRFBoard : public NRF52BoardOTA {
|
||||
public:
|
||||
IkokaStickNRFBoard() : NRF52BoardOTA("XIAO_NRF52_OTA") {}
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#if defined(P_LORA_TX_LED)
|
||||
void onBeforeTransmit() override {
|
||||
@@ -49,12 +47,6 @@ public:
|
||||
const char *getManufacturerName() const override {
|
||||
return MANUFACTURER_STRING;
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1,34 +1,15 @@
|
||||
[nrf52840_xiao]
|
||||
[ikoka_stick_nrf]
|
||||
extends = nrf52_base
|
||||
platform_packages =
|
||||
toolchain-gccarmnoneeabi@~1.100301.0
|
||||
framework-arduinoadafruitnrf52
|
||||
board = seeed-xiao-afruitnrf52-nrf52840
|
||||
board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-D NRF52_PLATFORM -D XIAO_NRF52
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include
|
||||
-I lib/nrf52/s140_nrf52_7.3.0_API/include/nrf52
|
||||
lib_ignore =
|
||||
BluetoothOTA
|
||||
lvgl
|
||||
lib5b4
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
rweather/Crypto @ ^0.4.0
|
||||
adafruit/Adafruit INA3221 Library @ ^1.0.1
|
||||
adafruit/Adafruit INA219 @ ^1.2.3
|
||||
adafruit/Adafruit AHTX0 @ ^2.0.5
|
||||
adafruit/Adafruit BME280 Library @ ^2.3.0
|
||||
adafruit/Adafruit SSD1306 @ ^2.5.13
|
||||
|
||||
[ikoka_stick_nrf_baseboard]
|
||||
extends = nrf52840_xiao
|
||||
;board_build.ldscript = boards/nrf52840_s140_v7.ld
|
||||
build_flags = ${nrf52840_xiao.build_flags}
|
||||
-D P_LORA_TX_LED=11
|
||||
-I variants/ikoka_stick_nrf
|
||||
-I src/helpers/nrf52
|
||||
-D P_LORA_TX_LED=11
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D DISPLAY_ROTATION=2
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
@@ -46,24 +27,18 @@ build_flags = ${nrf52840_xiao.build_flags}
|
||||
-D PIN_USER_BTN=0
|
||||
-D PIN_WIRE_SCL=7
|
||||
-D PIN_WIRE_SDA=6
|
||||
-D ENV_INCLUDE_AHTX0=1
|
||||
-D ENV_INCLUDE_BME280=1
|
||||
-D ENV_INCLUDE_INA3221=1
|
||||
-D ENV_INCLUDE_INA219=1
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
|
||||
;;; abstracted hardware variants
|
||||
-UENV_INCLUDE_GPS
|
||||
lib_deps = ${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
|
||||
[ikoka_stick_nrf_e22_22dbm]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
; No PA in this model, full 22dBm
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Stick-E22-22dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=22
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
@@ -71,14 +46,14 @@ build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<../variants/ikoka_stick_nrf>
|
||||
|
||||
[ikoka_stick_nrf_e22_30dbm]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
; limit txpower to 20dBm on E22-900M30S. Anything higher will
|
||||
; cause distortion in the PA output. 20dBm in -> 30dBm out
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Stick-E22-30dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=20
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
@@ -86,14 +61,14 @@ build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
+<../variants/ikoka_stick_nrf>
|
||||
|
||||
[ikoka_stick_nrf_e22_33dbm]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
; limit txpower to 9dBm on E22-900M33S to avoid hardware damage
|
||||
; to the rf amplifier frontend. 9dBm in -> 33dBm out
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D MANUFACTURER_STRING='"Ikoka Stick-E22-33dBm (Xiao_nrf52)"'
|
||||
-D LORA_TX_POWER=9
|
||||
build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
@@ -103,50 +78,52 @@ build_src_filter = ${nrf52840_xiao.build_src_filter}
|
||||
;;; abstracted firmware roles
|
||||
|
||||
[ikoka_stick_nrf_companion_radio_ble]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
-I examples/companion_radio/ui-new
|
||||
-D QSPIFLASH=1
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_stick_nrf_baseboard.lib_deps}
|
||||
${ikoka_stick_nrf.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_stick_nrf_companion_radio_usb]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
board_build.ldscript = boards/nrf52840_s140_v7_extrafs.ld
|
||||
board_upload.maximum_size = 708608
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-I examples/companion_radio/ui-new
|
||||
-D QSPIFLASH=1
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${ikoka_stick_nrf_baseboard.lib_deps}
|
||||
${ikoka_stick_nrf.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[ikoka_stick_nrf_repeater]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Stick Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
@@ -154,21 +131,21 @@ build_flags =
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${ikoka_stick_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<../examples/simple_repeater/*.cpp>
|
||||
|
||||
[ikoka_stick_nrf_room_server]
|
||||
extends = ikoka_stick_nrf_baseboard
|
||||
extends = ikoka_stick_nrf
|
||||
build_flags =
|
||||
${ikoka_stick_nrf_baseboard.build_flags}
|
||||
${ikoka_stick_nrf.build_flags}
|
||||
-D ADVERT_NAME='"Ikoka Stick Room"'
|
||||
-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 = ${ikoka_stick_nrf_baseboard.build_src_filter}
|
||||
build_src_filter = ${ikoka_stick_nrf.build_src_filter}
|
||||
+<../examples/simple_room_server/*.cpp>
|
||||
|
||||
;;; hardware + firmware variants
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
#include <Arduino.h>
|
||||
#include "KeepteenLT1Board.h"
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
#include "KeepteenLT1Board.h"
|
||||
|
||||
void KeepteenLT1Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
@@ -18,58 +14,4 @@ void KeepteenLT1Board::begin() {
|
||||
#endif
|
||||
|
||||
Wire.begin();
|
||||
}
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
bool KeepteenLT1Board::startOTAUpdate(const char* id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("KeepteenLT1_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -2,17 +2,16 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
class KeepteenLT1Board : public mesh::MainBoard {
|
||||
class KeepteenLT1Board : public NRF52BoardOTA {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
uint8_t btn_prev_state;
|
||||
|
||||
public:
|
||||
KeepteenLT1Board() : NRF52BoardOTA("KeepteenLT1_OTA") {}
|
||||
void begin();
|
||||
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
#define BATTERY_SAMPLES 8
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
@@ -39,13 +38,7 @@ public:
|
||||
}
|
||||
#endif
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
|
||||
@@ -138,8 +138,6 @@ build_flags =
|
||||
-D DISPLAY_CLASS=SSD1306Display
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D MESH_PACKET_LOGGING=1
|
||||
-D MESH_DEBUG=1
|
||||
build_src_filter = ${LilyGo_T3S3_sx1276.build_src_filter}
|
||||
+<helpers/ui/SSD1306Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
|
||||
@@ -19,6 +19,21 @@ build_flags =
|
||||
-D SX126X_RX_BOOSTED_GAIN=1
|
||||
-D SX126X_DIO3_TCXO_VOLTAGE=1.8f
|
||||
-D P_LORA_DIO_1=45 ; LORA IRQ pin
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
-D ENV_INCLUDE_AHTX0=0
|
||||
-D ENV_INCLUDE_BME280=0
|
||||
-D ENV_INCLUDE_BMP280=0
|
||||
-D ENV_INCLUDE_SHTC3=0
|
||||
-D ENV_INCLUDE_SHT4X=0
|
||||
-D ENV_INCLUDE_LPS22HB=0
|
||||
-D ENV_INCLUDE_INA3221=0
|
||||
-D ENV_INCLUDE_INA219=0
|
||||
-D ENV_INCLUDE_INA226=0
|
||||
-D ENV_INCLUDE_INA260=0
|
||||
-D ENV_INCLUDE_MLX90614=0
|
||||
-D ENV_INCLUDE_VL53L0X=0
|
||||
-D ENV_INCLUDE_BME680=0
|
||||
-D ENV_INCLUDE_BMP085=0
|
||||
-D P_LORA_NSS=9 ; LORA SS pin
|
||||
-D P_LORA_RESET=17 ; LORA RST pin
|
||||
-D P_LORA_BUSY=13 ; LORA Busy pin
|
||||
@@ -35,8 +50,12 @@ build_flags =
|
||||
-D PIN_TFT_DC=11
|
||||
-D PIN_TFT_SCL=40
|
||||
-D PIN_TFT_SDA=41
|
||||
-D PIN_GPS_RX=43
|
||||
-D PIN_GPS_TX=44
|
||||
-D GPS_BAUD_RATE=38400
|
||||
build_src_filter = ${esp32_base.build_src_filter}
|
||||
+<../variants/lilygo_tdeck>
|
||||
+<helpers/sensors/*.cpp>
|
||||
lib_deps =
|
||||
${esp32_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
|
||||
@@ -14,7 +14,8 @@ WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
ESP32RTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
SensorManager sensors;
|
||||
MicroNMEALocationProvider gps(Serial1, &rtc_clock);
|
||||
EnvironmentSensorManager sensors(gps);
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display;
|
||||
@@ -24,6 +25,7 @@ SensorManager sensors;
|
||||
bool radio_init() {
|
||||
fallback_clock.begin();
|
||||
rtc_clock.begin(Wire);
|
||||
Wire.begin(18, 8);
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
return radio.std_init(&spi);
|
||||
|
||||
@@ -11,11 +11,13 @@
|
||||
#include <helpers/ui/ST7789LCDDisplay.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#endif
|
||||
#include "helpers/sensors/EnvironmentSensorManager.h"
|
||||
#include "helpers/sensors/MicroNMEALocationProvider.h"
|
||||
|
||||
extern TDeckBoard board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
|
||||
@@ -1,28 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "TechoBoard.h"
|
||||
|
||||
#ifdef LILYGO_TECHO
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void TechoBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
|
||||
Wire.begin();
|
||||
|
||||
@@ -44,47 +28,4 @@ uint16_t TechoBoard::getBattMilliVolts() {
|
||||
// divider into account (providing the actual LIPO voltage)
|
||||
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
|
||||
}
|
||||
|
||||
bool TechoBoard::startOTAUpdate(const char* id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("TECHO_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
// built-ins
|
||||
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
|
||||
@@ -12,19 +13,11 @@
|
||||
#define PIN_VBAT_READ (4)
|
||||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||
|
||||
class TechoBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class TechoBoard : public NRF52BoardOTA {
|
||||
public:
|
||||
|
||||
TechoBoard() : NRF52BoardOTA("TECHO_OTA") {}
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
|
||||
uint8_t getStartupReason() const override {
|
||||
return startup_reason;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "LilyGo T-Echo";
|
||||
@@ -32,13 +25,13 @@ public:
|
||||
|
||||
void powerOff() override {
|
||||
#ifdef LED_RED
|
||||
digitalWrite(LED_RED, LOW);
|
||||
digitalWrite(LED_RED, HIGH);
|
||||
#endif
|
||||
#ifdef LED_GREEN
|
||||
digitalWrite(LED_GREEN, LOW);
|
||||
digitalWrite(LED_GREEN, HIGH);
|
||||
#endif
|
||||
#ifdef LED_BLUE
|
||||
digitalWrite(LED_BLUE, LOW);
|
||||
digitalWrite(LED_BLUE, HIGH);
|
||||
#endif
|
||||
#ifdef DISP_BACKLIGHT
|
||||
digitalWrite(DISP_BACKLIGHT, LOW);
|
||||
@@ -48,8 +41,4 @@ public:
|
||||
#endif
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,28 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#include "TechoBoard.h"
|
||||
|
||||
#ifdef LILYGO_TECHO
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
void TechoBoard::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
|
||||
Wire.begin();
|
||||
|
||||
@@ -44,47 +28,4 @@ uint16_t TechoBoard::getBattMilliVolts() {
|
||||
// divider into account (providing the actual LIPO voltage)
|
||||
return (uint16_t)((float)adcvalue * REAL_VBAT_MV_PER_LSB);
|
||||
}
|
||||
|
||||
bool TechoBoard::startOTAUpdate(const char* id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("TECHO_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
// built-ins
|
||||
#define VBAT_MV_PER_LSB (0.73242188F) // 3.0V ADC range and 12-bit ADC resolution = 3000mV/4096
|
||||
@@ -12,19 +13,11 @@
|
||||
#define PIN_VBAT_READ (4)
|
||||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||
|
||||
class TechoBoard : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class TechoBoard : public NRF52BoardOTA {
|
||||
public:
|
||||
|
||||
TechoBoard() : NRF52BoardOTA("TECHO_OTA") {}
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
|
||||
uint8_t getStartupReason() const override {
|
||||
return startup_reason;
|
||||
}
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "LilyGo T-Echo";
|
||||
@@ -48,8 +41,4 @@ public:
|
||||
#endif
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -136,6 +136,7 @@ build_flags =
|
||||
-D WIFI_SSID='"ssid"'
|
||||
-D WIFI_PWD='"password"'
|
||||
-D WIFI_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${LilyGo_TLora_V2_1_1_6.build_src_filter}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
|
||||
@@ -1,72 +1,12 @@
|
||||
#include <Arduino.h>
|
||||
#include "MeshPocket.h"
|
||||
#include <bluefruit.h>
|
||||
#include <Wire.h>
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle)
|
||||
{
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
||||
{
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
#include "MeshPocket.h"
|
||||
|
||||
void HeltecMeshPocket::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
Serial.begin(115200);
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
pinMode(PIN_USER_BTN, INPUT);
|
||||
}
|
||||
|
||||
bool HeltecMeshPocket::startOTAUpdate(const char* id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("MESH_POCKET_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -2,21 +2,17 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
// built-ins
|
||||
#define PIN_VBAT_READ 29
|
||||
#define PIN_BAT_CTL 34
|
||||
#define MV_LSB (3000.0F / 4096.0F) // 12-bit ADC with 3.0V input range
|
||||
|
||||
class HeltecMeshPocket : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class HeltecMeshPocket : public NRF52BoardOTA {
|
||||
public:
|
||||
HeltecMeshPocket() : NRF52BoardOTA("MESH_POCKET_OTA") {}
|
||||
void begin();
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
|
||||
|
||||
uint16_t getBattMilliVolts() override {
|
||||
int adcvalue = 0;
|
||||
@@ -37,13 +33,7 @@ public:
|
||||
return "Heltec MeshPocket";
|
||||
}
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
void powerOff() override {
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
#include <Arduino.h>
|
||||
#include "MinewsemiME25LS01Board.h"
|
||||
#include <Wire.h>
|
||||
|
||||
#include <bluefruit.h>
|
||||
#include "MinewsemiME25LS01Board.h"
|
||||
|
||||
void MinewsemiME25LS01Board::begin() {
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
btn_prev_state = HIGH;
|
||||
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
|
||||
sd_power_mode_set(NRF_POWER_MODE_LOWPWR);
|
||||
|
||||
#ifdef BUTTON_PIN
|
||||
pinMode(BUTTON_PIN, INPUT);
|
||||
pinMode(LED_PIN, OUTPUT);
|
||||
@@ -30,62 +26,4 @@ void MinewsemiME25LS01Board::begin() {
|
||||
#endif
|
||||
|
||||
delay(10); // give sx1262 some time to power up
|
||||
}
|
||||
|
||||
static BLEDfu bledfu;
|
||||
|
||||
static void connect_callback(uint16_t conn_handle) {
|
||||
(void)conn_handle;
|
||||
MESH_DEBUG_PRINTLN("BLE client connected");
|
||||
}
|
||||
|
||||
static void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
|
||||
(void)conn_handle;
|
||||
(void)reason;
|
||||
|
||||
MESH_DEBUG_PRINTLN("BLE client disconnected");
|
||||
}
|
||||
|
||||
|
||||
bool MinewsemiME25LS01Board::startOTAUpdate(const char* id, char reply[]) {
|
||||
// Config the peripheral connection with maximum bandwidth
|
||||
// more SRAM required by SoftDevice
|
||||
// Note: All config***() function must be called before begin()
|
||||
Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);
|
||||
Bluefruit.configPrphConn(92, BLE_GAP_EVENT_LENGTH_MIN, 16, 16);
|
||||
|
||||
Bluefruit.begin(1, 0);
|
||||
// Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
|
||||
Bluefruit.setTxPower(4);
|
||||
// Set the BLE device name
|
||||
Bluefruit.setName("Minewsemi_OTA");
|
||||
|
||||
Bluefruit.Periph.setConnectCallback(connect_callback);
|
||||
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
|
||||
|
||||
// To be consistent OTA DFU should be added first if it exists
|
||||
bledfu.begin();
|
||||
|
||||
// Set up and start advertising
|
||||
// Advertising packet
|
||||
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
|
||||
Bluefruit.Advertising.addTxPower();
|
||||
Bluefruit.Advertising.addName();
|
||||
|
||||
/* Start Advertising
|
||||
- Enable auto advertising if disconnected
|
||||
- Interval: fast mode = 20 ms, slow mode = 152.5 ms
|
||||
- Timeout for fast mode is 30 seconds
|
||||
- Start(timeout) with timeout = 0 will advertise forever (until connected)
|
||||
|
||||
For recommended advertising interval
|
||||
https://developer.apple.com/library/content/qa/qa1931/_index.html
|
||||
*/
|
||||
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||
Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
|
||||
Bluefruit.Advertising.setFastTimeout(30); // number of seconds in fast mode
|
||||
Bluefruit.Advertising.start(0); // 0 = Don't stop advertising after n seconds
|
||||
|
||||
strcpy(reply, "OK - started");
|
||||
return true;
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <MeshCore.h>
|
||||
#include <Arduino.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
// LoRa and SPI pins
|
||||
|
||||
@@ -19,13 +20,12 @@
|
||||
#define PIN_VBAT_READ BATTERY_PIN
|
||||
#define ADC_MULTIPLIER (1.815f) // dependent on voltage divider resistors. TODO: more accurate battery tracking
|
||||
|
||||
|
||||
class MinewsemiME25LS01Board : public mesh::MainBoard {
|
||||
class MinewsemiME25LS01Board : public NRF52BoardOTA {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
uint8_t btn_prev_state;
|
||||
|
||||
public:
|
||||
MinewsemiME25LS01Board() : NRF52BoardOTA("Minewsemi_OTA") {}
|
||||
void begin();
|
||||
|
||||
#define BATTERY_SAMPLES 8
|
||||
@@ -41,8 +41,6 @@ public:
|
||||
return (ADC_MULTIPLIER * raw);
|
||||
}
|
||||
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
const char* getManufacturerName() const override {
|
||||
return "Minewsemi";
|
||||
}
|
||||
@@ -78,11 +76,4 @@ public:
|
||||
digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void reboot() override {
|
||||
NVIC_SystemReset();
|
||||
}
|
||||
|
||||
bool startOTAUpdate(const char* id, char reply[]) override;
|
||||
};
|
||||
@@ -24,8 +24,7 @@ static void disconnect_callback(uint16_t conn_handle, uint8_t reason)
|
||||
|
||||
void NanoG2Ultra::begin()
|
||||
{
|
||||
// for future use, sub-classes SHOULD call this from their begin()
|
||||
startup_reason = BD_STARTUP_NORMAL;
|
||||
NRF52Board::begin();
|
||||
|
||||
// set user button
|
||||
pinMode(PIN_BUTTON1, INPUT);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
|
||||
// LoRa radio module pins
|
||||
#define P_LORA_DIO_1 (32 + 10)
|
||||
@@ -34,21 +35,14 @@
|
||||
#define PIN_VBAT_READ (0 + 2)
|
||||
#define REAL_VBAT_MV_PER_LSB (VBAT_DIVIDER_COMP * VBAT_MV_PER_LSB)
|
||||
|
||||
class NanoG2Ultra : public mesh::MainBoard {
|
||||
protected:
|
||||
uint8_t startup_reason;
|
||||
|
||||
class NanoG2Ultra : public NRF52Board {
|
||||
public:
|
||||
void begin();
|
||||
uint16_t getBattMilliVolts() override;
|
||||
bool startOTAUpdate(const char *id, char reply[]) override;
|
||||
|
||||
uint8_t getStartupReason() const override { return startup_reason; }
|
||||
|
||||
const char *getManufacturerName() const override { return "Nano G2 Ultra"; }
|
||||
|
||||
void reboot() override { NVIC_SystemReset(); }
|
||||
|
||||
void powerOff() override {
|
||||
// put GPS chip to sleep
|
||||
digitalWrite(PIN_GPS_STANDBY, LOW);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user