mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-07-16 18:52:00 +00:00
Merge remote-tracking branch 'upstream/dev' into rak-ethernet
# Conflicts: # examples/companion_radio/main.cpp
This commit is contained in:
@@ -42,6 +42,10 @@ jobs:
|
||||
- wio-e5-mini_repeater
|
||||
# ESP32-C6
|
||||
- LilyGo_Tlora_C6_repeater_
|
||||
# LR1110 (nRF52)
|
||||
- wio_wm1110_repeater
|
||||
# SX1276 (ESP32)
|
||||
- Tbeam_SX1276_repeater
|
||||
|
||||
steps:
|
||||
- name: Clone Repo
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Run Unit Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- master
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Clone Repo
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Build Environment
|
||||
uses: ./.github/actions/setup-build-environment
|
||||
|
||||
- name: Run Unit Tests
|
||||
run: pio test -e native -vv
|
||||
|
||||
- name: Upload Test Results
|
||||
# Upload test results even if the test step failed.
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: test-results
|
||||
path: .pio/build/native/
|
||||
@@ -98,6 +98,14 @@ Here are some general principals you should try to adhere to:
|
||||
|
||||
Help us prioritize! Please react with thumbs-up to issues/PRs you care about most. We look at reaction counts when planning work.
|
||||
|
||||
### Running unit tests
|
||||
|
||||
To run unit tests, run the following command:
|
||||
|
||||
```bash
|
||||
pio test --environment native --verbose
|
||||
```
|
||||
|
||||
## Road-Map / To-Do
|
||||
|
||||
There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order:
|
||||
@@ -117,4 +125,4 @@ There are a number of fairly major features in the pipeline, with no particular
|
||||
|
||||
- Report bugs and request features on the [GitHub Issues](https://github.com/ripplebiz/MeshCore/issues) page.
|
||||
- Find additional guides and components on [my site](https://buymeacoffee.com/ripplebiz).
|
||||
- Join [MeshCore Discord](https://discord.gg/BMwCtwHj5V) to chat with the developers and get help from the community.
|
||||
- Join [MeshCore Discord](https://meshcore.gg) to chat with the developers and get help from the community.
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
"""
|
||||
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,61 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
["0x239A","0x8029"],
|
||||
["0x239A","0x0029"],
|
||||
["0x239A","0x002A"],
|
||||
["0x239A","0x802A"]
|
||||
],
|
||||
"usb_product": "HT-n5262T1",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "heltec_t1",
|
||||
"bsp": {
|
||||
"name": "adafruit"
|
||||
},
|
||||
"softdevice": {
|
||||
"sd_flags": "-DS140",
|
||||
"sd_name": "s140",
|
||||
"sd_version": "6.1.1",
|
||||
"sd_fwid": "0x00B6"
|
||||
},
|
||||
"bootloader": {
|
||||
"settings_addr": "0xFF000"
|
||||
}
|
||||
},
|
||||
"connectivity": [
|
||||
"bluetooth"
|
||||
],
|
||||
"debug": {
|
||||
"jlink_device": "nRF52840_xxAA",
|
||||
"svd_path": "nrf52840.svd",
|
||||
"openocd_target": "nrf52.cfg"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "Heltec Mesh Node T1 Board",
|
||||
"upload": {
|
||||
"maximum_ram_size": 235520,
|
||||
"maximum_size": 815104,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"require_upload_port": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://heltec.org/",
|
||||
"vendor": "Heltec"
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino":{
|
||||
"ldscript": "nrf52840_s140_v6.ld"
|
||||
},
|
||||
"core": "nRF5",
|
||||
"cpu": "cortex-m4",
|
||||
"extra_flags": "-DARDUINO_NRF52840_T_IMPULSE_PLUS -DNRF52840_XXAA",
|
||||
"f_cpu": "64000000L",
|
||||
"hwids": [
|
||||
[
|
||||
"0x239A",
|
||||
"0x8029"
|
||||
]
|
||||
],
|
||||
"usb_product": "T-Impulse-Plus-nRF52840",
|
||||
"mcu": "nrf52840",
|
||||
"variant": "t_impulse_plus_nrf52840",
|
||||
"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"
|
||||
},
|
||||
"frameworks": [
|
||||
"arduino"
|
||||
],
|
||||
"name": "Lilygo T-Impulse-Plus-nRF52840",
|
||||
"upload": {
|
||||
"maximum_ram_size": 248832,
|
||||
"maximum_size": 815104,
|
||||
"require_upload_port": true,
|
||||
"speed": 115200,
|
||||
"protocol": "nrfutil",
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"stlink",
|
||||
"cmsis-dap",
|
||||
"blackmagic"
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://www.lilygo.cc/",
|
||||
"vendor": "Lilygo"
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"build": {
|
||||
"arduino": {
|
||||
"ldscript": "esp32s3_out.ld",
|
||||
"memory_type": "qio_opi"
|
||||
},
|
||||
"core": "esp32",
|
||||
"extra_flags": [
|
||||
"-DBOARD_HAS_PSRAM",
|
||||
"-DARDUINO_USB_CDC_ON_BOOT=1",
|
||||
"-DARDUINO_USB_MODE=0",
|
||||
"-DARDUINO_RUNNING_CORE=1",
|
||||
"-DARDUINO_EVENT_RUNNING_CORE=0"
|
||||
],
|
||||
"f_cpu": "240000000L",
|
||||
"f_flash": "80000000L",
|
||||
"flash_mode": "qio",
|
||||
"hwids": [["0x303A", "0x1001"]],
|
||||
"mcu": "esp32s3",
|
||||
"variant": "esp32s3"
|
||||
},
|
||||
"connectivity": [
|
||||
"wifi"
|
||||
],
|
||||
"debug": {
|
||||
"default_tool": "esp-builtin",
|
||||
"onboard_tools": ["esp-builtin"],
|
||||
"openocd_target": "esp32s3.cfg"
|
||||
},
|
||||
"frameworks": ["arduino", "espidf"],
|
||||
"name": "Station G3 ESP32",
|
||||
"upload": {
|
||||
"flash_size": "16MB",
|
||||
"maximum_ram_size": 327680,
|
||||
"maximum_size": 16777216,
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true,
|
||||
"require_upload_port": true,
|
||||
"speed": 921600
|
||||
},
|
||||
"url": "https://wiki.bqvoy.com/en/devkits/station-g3",
|
||||
"vendor": "BQ Consulting"
|
||||
}
|
||||
+5
-2
@@ -53,11 +53,14 @@
|
||||
"protocols": [
|
||||
"jlink",
|
||||
"nrfjprog",
|
||||
"nrfutil",
|
||||
"stlink",
|
||||
"cmsis-dap",
|
||||
"blackmagic"
|
||||
]
|
||||
],
|
||||
"use_1200bps_touch": true,
|
||||
"wait_for_upload_port": true
|
||||
},
|
||||
"url": "https://os.mbed.com/platforms/Nordic-nRF52840-DK/",
|
||||
"vendor": "Nordic"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,7 +457,7 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
**Note:** the 'path.hash.mode' sets the low-level ID/hash encoding size used when the repeater adverts. This setting has no impact on what packet ID/hash size this repeater forwards, all sizes should be forwarded on firmware >= 1.14. This feature was added in firmware 1.14
|
||||
|
||||
**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propogation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are suppored. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes.
|
||||
**Temporary Note:** adverts with ID/hash sizes of 2 or 3 bytes may have limited flood propagation in your network while this feature is new as v1.13.0 firmware and older will drop packets with multibyte path ID/hashes as only 1-byte hashes are supported. Consider your install base of firmware >=1.14 has reached a criticality for effective network flooding before implementing higher ID/hash sizes.
|
||||
|
||||
---
|
||||
|
||||
@@ -491,6 +491,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
**Default:** `0.5`
|
||||
|
||||
**Note:** When multiple nearby repeaters all hear the same flood packet, each waits a random amount of time before retransmitting to avoid simultaneous collisions. This factor scales the size of that random window. Higher values reduce collision risk at the cost of added latency. `0` disables the window entirely.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the retransmit delay factor for direct traffic
|
||||
@@ -503,6 +505,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
**Default:** `0.2`
|
||||
|
||||
**Note:** Same collision-avoidance random window as `txdelay`, but applied to direct (non-flood, routed) traffic. The default is lower because direct packets are addressed to a specific next hop, so far fewer nodes compete to retransmit them.
|
||||
|
||||
---
|
||||
|
||||
#### [Experimental] View or change the processing delay for received traffic
|
||||
@@ -515,6 +519,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||
|
||||
**Default:** `0.0`
|
||||
|
||||
**Note:** When enabled, repeaters that received a flood packet with a weak signal are held in a delay queue before processing, while those that received it with a strong signal process it immediately. This gives strong-signal paths forwarding priority. By the time weak-signal nodes process their copy, the packet may have already propagated and will be suppressed as a duplicate, reducing redundant retransmissions.
|
||||
|
||||
---
|
||||
|
||||
#### View or change the duty cycle limit
|
||||
@@ -928,7 +934,7 @@ region save
|
||||
|
||||
---
|
||||
|
||||
#### View or change thevalue of a sensor
|
||||
#### View or change the value of a sensor
|
||||
**Usage:**
|
||||
- `sensor get <key>`
|
||||
- `sensor set <key> <value>`
|
||||
|
||||
+115
-29
@@ -283,32 +283,110 @@ Bytes 7+: Message Text (UTF-8, variable length)
|
||||
|
||||
### 6. Send Channel Data Datagram
|
||||
|
||||
**Purpose**: Send binary datagram data to a channel.
|
||||
**Purpose**: Send a binary datagram to a channel. Unlike channel text messages, datagrams carry no built-in sender identity and no timestamp — applications needing either must encode them inside the binary payload.
|
||||
|
||||
**Command Format**:
|
||||
```
|
||||
Byte 0: 0x3E
|
||||
Bytes 1-2: Data Type (`data_type`, 16-bit little-endian)
|
||||
Byte 3: Channel Index (0-7)
|
||||
Bytes 4+: Binary payload bytes (variable length)
|
||||
Byte 0: 0x3E
|
||||
Byte 1: Channel Index (0-7)
|
||||
Byte 2: Path Length (0xFF = flood, otherwise actual path length)
|
||||
Bytes 3 .. 2+path_len: Path (omitted when path_len == 0xFF)
|
||||
Next 2 bytes (little-endian): Data Type (`data_type`, uint16)
|
||||
Remaining bytes: Binary payload (variable length)
|
||||
```
|
||||
|
||||
**Example** (flood, `DATA_TYPE_DEV`, payload `A1 B2 C3`, channel 1):
|
||||
```
|
||||
3E 01 FF FF FF A1 B2 C3
|
||||
```
|
||||
|
||||
**Data Type / Transport Mapping**:
|
||||
- `0x0000` is invalid for this command.
|
||||
- `0x0000` (`DATA_TYPE_RESERVED`) is invalid and rejected with `PACKET_ERROR`.
|
||||
- `0xFFFF` (`DATA_TYPE_DEV`) is the developer namespace for experimenting and developing apps.
|
||||
- Other non-zero values can be used as assigned application/community namespaces.
|
||||
|
||||
**Note**: Applications that need a timestamp should encode it inside the binary payload.
|
||||
- Values `0x0001`–`0xFFFE` are available for registered application/community namespaces. See the [Registered data_type values](#registered-data_type-values) table below.
|
||||
|
||||
**Limits**:
|
||||
- Maximum payload length is `163` bytes.
|
||||
- Larger payloads are rejected with `PACKET_ERROR`.
|
||||
- Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163` bytes.
|
||||
- Larger payloads are rejected with `PACKET_ERROR` (`ERR_CODE_ILLEGAL_ARG`).
|
||||
|
||||
**Response**: `PACKET_OK` (0x00) on success
|
||||
**Response**: `PACKET_OK` (0x00) on success, or `PACKET_ERROR` (0x01) with one of:
|
||||
- `ERR_CODE_NOT_FOUND` (2) — unknown `channel_idx`
|
||||
- `ERR_CODE_ILLEGAL_ARG` (6) — invalid `path_len`, reserved `data_type` (`0x0000`), or payload larger than `MAX_CHANNEL_DATA_LENGTH`
|
||||
- `ERR_CODE_TABLE_FULL` (3) — outbound send queue is full; retry later
|
||||
|
||||
**Inbound datagrams** are delivered to the host via `RESP_CODE_CHANNEL_DATA_RECV` (0x1B); see [Receive Channel Data Datagram](#receive-channel-data-datagram).
|
||||
|
||||
#### Registered `data_type` values
|
||||
|
||||
`data_type` is an **application identifier**, not a payload-format identifier. Each registered value identifies an application that owns its own internal payload schemas. The firmware does not inspect payload contents — `data_type` is transported opaquely.
|
||||
|
||||
| Value | Constant | Purpose |
|
||||
|-----------------|----------------------|--------------------------------------------------------------------------|
|
||||
| 0x0000 | `DATA_TYPE_RESERVED` | Reserved; invalid on send |
|
||||
| 0x0001 – 0x00FF | — | Reserved for internal use |
|
||||
| 0x0100 – 0xFEFF | — | Registered application namespaces (see [number_allocations.md](number_allocations.md)) |
|
||||
| 0xFF00 – 0xFFFE | — | Testing/development; no registration required |
|
||||
| 0xFFFF | `DATA_TYPE_DEV` | Developer/experimental namespace |
|
||||
|
||||
To register a new application, submit a PR adding a row to the table in [docs/number_allocations.md](number_allocations.md). Internal sub-formats within an allocated application ID are owned by that application and are not tracked in MeshCore firmware or this document.
|
||||
|
||||
---
|
||||
|
||||
### 6. Get Message
|
||||
### Receive Channel Data Datagram
|
||||
|
||||
Inbound group datagrams (radio-level `PAYLOAD_TYPE_GRP_DATA`, 0x06) are forwarded to the host as `RESP_CODE_CHANNEL_DATA_RECV` notifications.
|
||||
|
||||
**Frame Format** (`RESP_CODE_CHANNEL_DATA_RECV`, 0x1B):
|
||||
```
|
||||
Byte 0: 0x1B (packet type)
|
||||
Byte 1: SNR (signed int8, scaled ×4 — divide by 4.0 to recover dB)
|
||||
Bytes 2-3: Reserved (clients MUST ignore)
|
||||
Byte 4: Channel Index (0-7)
|
||||
Byte 5: Path Length (actual path length when flooded, otherwise 0xFF for direct)
|
||||
Bytes 6-7: Data Type (uint16 little-endian)
|
||||
Byte 8: Data Length
|
||||
Bytes 9 .. 8+data_len: Payload
|
||||
```
|
||||
|
||||
**Path bytes are not forwarded**: Only `path_len` is reported in the receive frame — the path itself is not copied to the host. There are no path bytes between byte 5 and the data_type field at bytes 6–7, regardless of `path_len`.
|
||||
|
||||
**Path Length semantics differ between send and receive**:
|
||||
|
||||
| Direction | `path_len = 0xFF` | `path_len ≠ 0xFF` |
|
||||
|-----------|---------------------------------|-------------------------------------------------------------------------------------|
|
||||
| Send | Flood the network | Direct route; the encoded path follows (low 6 bits = hash count, top 2 bits + 1 = hash size; on-wire byte count = `hash_count × hash_size`) |
|
||||
| Receive | Packet arrived via direct route | Packet was flooded; this is the encoded `pkt->path_len` field as observed (no path bytes follow) |
|
||||
|
||||
In other words, the meaning of `0xFF` is inverted between the two directions, and on receive the field carries metadata only — never a routable path. `path_len` is an encoded byte (see `Packet::isValidPathLen` / `Packet::writePath` in `src/Packet.cpp`), not a raw byte count.
|
||||
|
||||
**Note**: The device may also emit `PACKET_MESSAGES_WAITING` (0x83) to notify the host that datagrams are queued; poll with `CMD_SYNC_NEXT_MESSAGE` (0x0A) to retrieve them.
|
||||
|
||||
**Parsing Pseudocode**:
|
||||
```python
|
||||
def parse_channel_data_recv(data):
|
||||
if len(data) < 9:
|
||||
return None
|
||||
snr_byte = data[1]
|
||||
snr = (snr_byte if snr_byte < 128 else snr_byte - 256) / 4.0
|
||||
channel_idx = data[4]
|
||||
path_len = data[5]
|
||||
data_type = int.from_bytes(data[6:8], 'little')
|
||||
data_len = data[8]
|
||||
if 9 + data_len > len(data):
|
||||
return None
|
||||
payload = data[9:9 + data_len]
|
||||
return {
|
||||
'snr': snr,
|
||||
'channel_idx': channel_idx,
|
||||
'path_len': path_len,
|
||||
'data_type': data_type,
|
||||
'payload': bytes(payload),
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. Get Message
|
||||
|
||||
**Purpose**: Request the next queued message from the device.
|
||||
|
||||
@@ -325,13 +403,14 @@ Byte 0: 0x0A
|
||||
**Response**:
|
||||
- `PACKET_CHANNEL_MSG_RECV` (0x08) or `PACKET_CHANNEL_MSG_RECV_V3` (0x11) for channel messages
|
||||
- `PACKET_CONTACT_MSG_RECV` (0x07) or `PACKET_CONTACT_MSG_RECV_V3` (0x10) for contact messages
|
||||
- `PACKET_CHANNEL_DATA_RECV` (0x1B) for channel data datagrams
|
||||
- `PACKET_NO_MORE_MSGS` (0x0A) if no messages available
|
||||
|
||||
**Note**: Poll this command periodically to retrieve queued messages. The device may also send `PACKET_MESSAGES_WAITING` (0x83) as a notification when messages are available.
|
||||
|
||||
---
|
||||
|
||||
### 7. Get Battery and Storage
|
||||
### 8. Get Battery and Storage
|
||||
|
||||
**Purpose**: Query device battery voltage and storage usage.
|
||||
|
||||
@@ -527,6 +606,15 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)).
|
||||
|
||||
## Response Parsing
|
||||
|
||||
### Terminology
|
||||
|
||||
This document uses a spec-level naming convention (`PACKET_*`) for bytes the firmware sends back to the host. In the firmware source these same values are split across two `#define` families by purpose:
|
||||
|
||||
- `RESP_CODE_*` — direct replies to a command (e.g. `RESP_CODE_CHANNEL_DATA_RECV` = `PACKET_CHANNEL_DATA_RECV` = 0x1B).
|
||||
- `PUSH_CODE_*` — asynchronous notifications not tied to a specific command (e.g. `PUSH_CODE_MSG_WAITING` = `PACKET_MESSAGES_WAITING` = 0x83).
|
||||
|
||||
Byte values are authoritative; names are aliases. When reading firmware source, `RESP_CODE_X` / `PUSH_CODE_X` correspond to this doc's `PACKET_X` of the same numeric value.
|
||||
|
||||
### Packet Types
|
||||
|
||||
| Value | Name | Description |
|
||||
@@ -547,6 +635,7 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)).
|
||||
| 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
|
||||
| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
|
||||
| 0x12 | PACKET_CHANNEL_INFO | Channel information |
|
||||
| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data datagram |
|
||||
| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
|
||||
| 0x82 | PACKET_ACK | Acknowledgment |
|
||||
| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
|
||||
@@ -718,22 +807,18 @@ Bytes 1-6: ACK Code (6 bytes, hex)
|
||||
|
||||
### Error Codes
|
||||
|
||||
**PACKET_ERROR** (0x01) may include an error code in byte 1:
|
||||
`PACKET_ERROR` (0x01) carries a single-byte error code in byte 1. Values match the `ERR_CODE_*` constants defined in `examples/companion_radio/MyMesh.cpp`:
|
||||
|
||||
| Error Code | Description |
|
||||
|------------|-------------|
|
||||
| 0x00 | Generic error (no specific code) |
|
||||
| 0x01 | Invalid command |
|
||||
| 0x02 | Invalid parameter |
|
||||
| 0x03 | Channel not found |
|
||||
| 0x04 | Channel already exists |
|
||||
| 0x05 | Channel index out of range |
|
||||
| 0x06 | Secret mismatch |
|
||||
| 0x07 | Message too long |
|
||||
| 0x08 | Device busy |
|
||||
| 0x09 | Not enough storage |
|
||||
| Code | Constant (firmware) | Description |
|
||||
|------|----------------------------|------------------------------------------------------------------------------|
|
||||
| 1 | `ERR_CODE_UNSUPPORTED_CMD` | Unknown or unsupported command byte / sub-command |
|
||||
| 2 | `ERR_CODE_NOT_FOUND` | Target not found (channel, contact, message, etc.) |
|
||||
| 3 | `ERR_CODE_TABLE_FULL` | Internal queue or table is full — retry later |
|
||||
| 4 | `ERR_CODE_BAD_STATE` | Operation not valid in current device state (e.g. iterator already running) |
|
||||
| 5 | `ERR_CODE_FILE_IO_ERROR` | Filesystem or storage I/O failure |
|
||||
| 6 | `ERR_CODE_ILLEGAL_ARG` | Invalid argument (bad length, out-of-range value, reserved field, etc.) |
|
||||
|
||||
**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response.
|
||||
**Note**: Error codes may vary by firmware version. Always check byte 1 of `PACKET_ERROR` response, and treat unknown codes as generic errors.
|
||||
|
||||
### Frame Handling
|
||||
|
||||
@@ -765,7 +850,8 @@ BLE implementations enqueue and deliver one protocol frame per BLE write/notific
|
||||
- `GET_CHANNEL` → `PACKET_CHANNEL_INFO`
|
||||
- `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR`
|
||||
- `SEND_CHANNEL_MESSAGE` → `PACKET_MSG_SENT`
|
||||
- `GET_MESSAGE` → `PACKET_CHANNEL_MSG_RECV`, `PACKET_CONTACT_MSG_RECV`, or `PACKET_NO_MORE_MSGS`
|
||||
- `GET_MESSAGE` → `PACKET_CHANNEL_MSG_RECV`, `PACKET_CONTACT_MSG_RECV`, `PACKET_CHANNEL_DATA_RECV`, or `PACKET_NO_MORE_MSGS`
|
||||
- `SEND_CHANNEL_DATA` → `PACKET_OK` or `PACKET_ERROR`
|
||||
- `GET_BATTERY` → `PACKET_BATTERY`
|
||||
|
||||
4. **Timeout Handling**:
|
||||
|
||||
+19
-19
@@ -23,8 +23,8 @@ A list of frequently-asked questions and answers for MeshCore
|
||||
- [3.3. Q: What is the password to administer a repeater or a room server?](#33-q-what-is-the-password-to-administer-a-repeater-or-a-room-server)
|
||||
- [3.4. Q: What is the password to join a room server?](#34-q-what-is-the-password-to-join-a-room-server)
|
||||
- [3.5. Q: Can I retrieve a repeater's private key or set a repeater's private key?](#35-q-can-i-retrieve-a-repeaters-private-key-or-set-a-repeaters-private-key)
|
||||
- [3.6. Q: The first byte of my repeater's public key collides with an exisitng repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?](#36-q-the-first-byte-of-my-repeaters-public-key-collides-with-an-exisitng-repeater-on-the-mesh--how-do-i-get-a-new-private-key-with-a-matching-public-key-that-has-its-first-byte-of-my-choosing)
|
||||
- [3.7. Q: My repeater maybe suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?](#37-q-my-repeater-maybe-suffering-from-deafness-due-to-high-power-interference-near-my-meshs-frequency-it-is-not-hearing-other-in-range-meshcore-radios--what-can-i-do)
|
||||
- [3.6. Q: The first byte of my repeater's public key collides with an existing repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?](#36-q-the-first-byte-of-my-repeaters-public-key-collides-with-an-existing-repeater-on-the-mesh--how-do-i-get-a-new-private-key-with-a-matching-public-key-that-has-its-first-byte-of-my-choosing)
|
||||
- [3.7. Q: My repeater may be suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?](#37-q-my-repeater-may-be-suffering-from-deafness-due-to-high-power-interference-near-my-meshs-frequency-it-is-not-hearing-other-in-range-meshcore-radios--what-can-i-do)
|
||||
- [3.8. Q: How do I make my repeater an observer on the mesh?](#38-q-how-do-i-make-my-repeater-an-observer-on-the-mesh)
|
||||
- [3.9. Q: What is multi-byte support? What do 1-byte, 2-byte, 3-byte adverts and messages mean?](#39-q-what-is-multi-byte-support--what-do-1-byte-2-byte-3-byte-adverts-and-messages-mean)
|
||||
- [3.9.1. Q: **What path hash sizes will my repeater forward?**](#391-q-what-path-hash-sizes-will-my-repeater-forward)
|
||||
@@ -39,7 +39,7 @@ A list of frequently-asked questions and answers for MeshCore
|
||||
- [4.3. Q: Why is my T-Deck Plus not getting any satellite lock?](#43-q-why-is-my-t-deck-plus-not-getting-any-satellite-lock)
|
||||
- [4.4. Q: Why is my OG (non-Plus) T-Deck not getting any satellite lock?](#44-q-why-is-my-og-non-plus-t-deck-not-getting-any-satellite-lock)
|
||||
- [4.5. Q: What size of SD card does the T-Deck support?](#45-q-what-size-of-sd-card-does-the-t-deck-support)
|
||||
- [4.6. Q: what is the public key for the default public channel?](#46-q-what-is-the-public-key-for-the-default-public-channel)
|
||||
- [4.6. Q: What is the public key for the default public channel?](#46-q-what-is-the-public-key-for-the-default-public-channel)
|
||||
- [4.7. Q: How do I get maps on T-Deck?](#47-q-how-do-i-get-maps-on-t-deck)
|
||||
- [4.8. Q: Where do the map tiles go?](#48-q-where-do-the-map-tiles-go)
|
||||
- [4.9. Q: How to unlock deeper map zoom and server management features on T-Deck?](#49-q-how-to-unlock-deeper-map-zoom-and-server-management-features-on-t-deck)
|
||||
@@ -52,9 +52,9 @@ A list of frequently-asked questions and answers for MeshCore
|
||||
- [5.1. Q: What are BW, SF, and CR?](#51-q-what-are-bw-sf-and-cr)
|
||||
- [5.2. Q: Do MeshCore clients repeat?](#52-q-do-meshcore-clients-repeat)
|
||||
- [5.3. Q: What happens when a node learns a route via a mobile repeater, and that repeater is gone?](#53-q-what-happens-when-a-node-learns-a-route-via-a-mobile-repeater-and-that-repeater-is-gone)
|
||||
- [5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discovery-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic)
|
||||
- [5.4. Q: How does a node discover a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?](#54-q-how-does-a-node-discover-a-path-to-its-destination-and-then-use-it-to-send-messages-in-the-future-instead-of-flooding-every-message-it-sends-like-meshtastic)
|
||||
- [5.5. Q: Do public channels always flood? Do private channels always flood?](#55-q-do-public-channels-always-flood-do-private-channels-always-flood)
|
||||
- [5.6. Q: what is the public key for the default public channel?](#56-q-what-is-the-public-key-for-the-default-public-channel)
|
||||
- [5.6. Q: What is the public key for the default public channel?](#56-q-what-is-the-public-key-for-the-default-public-channel)
|
||||
- [5.7. Q: Is MeshCore open source?](#57-q-is-meshcore-open-source)
|
||||
- [5.8. Q: How can I support MeshCore?](#58-q-how-can-i-support-meshcore)
|
||||
- [5.9. Q: How do I build MeshCore firmware from source?](#59-q-how-do-i-build-meshcore-firmware-from-source)
|
||||
@@ -62,7 +62,7 @@ A list of frequently-asked questions and answers for MeshCore
|
||||
- [5.11. Q: Does MeshCore support ATAK](#511-q-does-meshcore-support-atak)
|
||||
- [5.12. Q: How do I add a node to the MeshCore Map](#512-q-how-do-i-add-a-node-to-the-meshcore-map)
|
||||
- [5.13. Q: Can I use a Raspberry Pi to update a MeshCore radio?](#513-q-can-i-use-a-raspberry-pi-to-update-a-meshcore-radio)
|
||||
- [5.14. Q: Are there are projects built around MeshCore?](#514-q-are-there-are-projects-built-around-meshcore)
|
||||
- [5.14. Q: Are there projects built around MeshCore?](#514-q-are-there-projects-built-around-meshcore)
|
||||
- [5.15. Q: Are there client applications for Windows or Mac?](#515-q-are-there-client-applications-for-windows-or-mac)
|
||||
- [5.16. Q: Are there any resources that compare MeshCore to other LoRa systems?](#516-q-are-there-any-resources-that-compare-meshcore-to-other-lora-systems)
|
||||
- [6. Troubleshooting](#6-troubleshooting)
|
||||
@@ -189,13 +189,13 @@ The T-Deck firmware is free to download and most features are available without
|
||||
### 2.3. Q: What frequencies are supported by MeshCore?
|
||||
**A:** It supports the 868MHz range in the UK/EU and the 915MHz range in New Zealand, Australia, and the USA. Countries and regions in these two frequency ranges are also supported.
|
||||
|
||||
Use the smartphone client or the repeater setup feature on there web flasher to set your radios' RF settings by choosing the preset for your regions.
|
||||
Use the smartphone client or the repeater setup feature on the web flasher to set your radios' RF settings by choosing the preset for your regions.
|
||||
|
||||
Recently, as of October 2025, many regions have moved to the "narrow" setting, aka using BW62.5 and a lower SF number (instead of the original SF11). For example, USA/Canada (Recommended) preset is 910.525MHz, SF7, BW62.5, CR5.
|
||||
|
||||
After extensive testing, many regions have switched or about to switch over to BW62.5 and SF7, 8, or 9. Narrower bandwidth setting and lower SF setting allow MeshCore's radio signals to fit between interference in the ISM band, provide for a lower noise floor, better SNR, and faster transmissions.
|
||||
|
||||
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://discord.gg/cYtQNYCCRK) to let Liam Cottle know.
|
||||
If you have consensus from your community in your region to update your region's preset recommendation, please post your update request on the [#meshcore-app](https://discord.com/channels/1343693475589263471/1391681655911088241) channel on the [MeshCore Discord server ](https://meshcore.gg) to let Liam Cottle know.
|
||||
|
||||
|
||||
|
||||
@@ -273,7 +273,7 @@ You can get the latitude and longitude from Google Maps by right-clicking the lo
|
||||
|
||||
Reboot the repeater after `set prv.key <hex>` command for the new private key to take effect.
|
||||
|
||||
### 3.6. Q: The first byte of my repeater's public key collides with an exisitng repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?
|
||||
### 3.6. Q: The first byte of my repeater's public key collides with an existing repeater on the mesh. How do I get a new private key with a matching public key that has its first byte of my choosing?
|
||||
|
||||
**A:** You can generate a new private key and specific the first byte of its public key here: https://gessaman.com/mc-keygen/
|
||||
|
||||
@@ -282,7 +282,7 @@ Having multiple repeaters with the same first byte ID does not negatively affect
|
||||
Best practice is when you set up a new repeater, choose a public key that is not in use. If it is not possible to find a unique first byte for your repeater's public key, choose one that is unique within about 10 miles (16 km) to minimize collision with nearby repeaters.
|
||||
|
||||
|
||||
### 3.7. Q: My repeater maybe suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?
|
||||
### 3.7. Q: My repeater may be suffering from deafness due to high power interference near my mesh's frequency, it is not hearing other in-range MeshCore radios. What can I do?
|
||||
|
||||
**A:** This may be due to the SX1262 radio's auto gain control feature. You can use this command to periodically reset its AGC.
|
||||
|
||||
@@ -381,7 +381,7 @@ GPS on T-Deck is always enabled. You can skip the "GPS clock sync" and the T-De
|
||||
### 4.5. Q: What size of SD card does the T-Deck support?
|
||||
**A:** Users have had no issues using 16GB or 32GB SD cards. Format the SD card to **FAT32**.
|
||||
|
||||
### 4.6. Q: what is the public key for the default public channel?
|
||||
### 4.6. Q: What is the public key for the default public channel?
|
||||
**A:**
|
||||
T-Deck uses the same key the smartphone apps use but in base64
|
||||
`izOH6cXN6mrJ5e26oRXNcg==`
|
||||
@@ -500,7 +500,7 @@ In MeshCore, only repeaters and room server with `set repeat on` repeat.
|
||||
|
||||
In the case if users are moving around frequently, and the paths are breaking, they just see the phone client retries and revert to flood to attempt to re-establish a path.
|
||||
|
||||
### 5.4. Q: How does a node discovery a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?
|
||||
### 5.4. Q: How does a node discover a path to its destination and then use it to send messages in the future, instead of flooding every message it sends like Meshtastic?
|
||||
|
||||
Routes are stored in sender's contact list. When you send a message the first time, the message first gets to your destination by flood routing. When your destination node gets the message, it will send back a delivery report to the sender with all repeaters that the original message went through. This delivery report is flood-routed back to you the sender and is a basis for future direct path. When you send the next message, the path will get embedded into the packet and be evaluated by repeaters. If the hop and address of the repeater matches, it will retransmit the message, otherwise it will not retransmit, hence minimizing utilization.
|
||||
|
||||
@@ -513,7 +513,7 @@ Routes are stored in sender's contact list. When you send a message the first t
|
||||
[Source](https://discord.com/channels/1343693475589263471/1343693475589263474/1350023009527664672)
|
||||
|
||||
|
||||
### 5.6. Q: what is the public key for the default public channel?
|
||||
### 5.6. Q: What is the public key for the default public channel?
|
||||
**A:** The smartphone app key is in hex:
|
||||
` 8b3387e9c5cdea6ac9e5edbaa115cd72`
|
||||
|
||||
@@ -527,7 +527,7 @@ The third character is the capital letter 'O', not zero `0`
|
||||
- Firmware repo: https://github.com/meshcore-dev/MeshCore
|
||||
|
||||
### 5.8. Q: How can I support MeshCore?
|
||||
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://discord.gg/BMwCtwHj5V). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
**A:** Provide your honest feedback on GitHub and on [MeshCore Discord server](https://meshcore.gg). Spread the word of MeshCore to your friends and communities; help them get started with MeshCore. Support Scott's MeshCore development at <https://buymeacoffee.com/ripplebiz>.
|
||||
|
||||
Support Liam Cottle's smartphone client development by unlocking the server administration wait gate with in-app purchase
|
||||
|
||||
@@ -659,11 +659,11 @@ From here, reference repeater and room server command line commands on MeshCore
|
||||
- https://github.com/meshcore-dev/MeshCore/wiki/Repeater-&-Room-Server-CLI-Reference
|
||||
|
||||
|
||||
### 5.14. Q: Are there are projects built around MeshCore?
|
||||
### 5.14. Q: Are there projects built around MeshCore?
|
||||
|
||||
**A:** Yes, there are many. MeshCore's protocol is open source using the MIT license. The MIT license and the open source protocol makes it very easy for the MeshCore community to build new firmware for radios, applications on mobile devices, map tools, and analysis tools, and integration with other projects like Home Asistant.
|
||||
**A:** Yes, there are many. MeshCore's protocol is open source using the MIT license. The MIT license and the open source protocol makes it very easy for the MeshCore community to build new firmware for radios, applications on mobile devices, map tools, and analysis tools, and integration with other projects like Home Assistant.
|
||||
|
||||
As new MeshCore community projects become available on a weekly basis, we have stopped tracking them here in this FAQ. [samuk](https://github.com/samuk) maintains a very exhausive list of MeshCore community project at https://github.com/samuk/awesome-meshcore/blob/main/README.md. samuk accepts PRs and merges them regularly.
|
||||
As new MeshCore community projects become available on a weekly basis, we have stopped tracking them here in this FAQ. [samuk](https://github.com/samuk) maintains a very exhaustive list of MeshCore community project at https://github.com/samuk/awesome-meshcore/blob/main/README.md. samuk accepts PRs and merges them regularly.
|
||||
|
||||
|
||||
### 5.15. Q: Are there client applications for Windows or Mac?
|
||||
@@ -717,7 +717,7 @@ You can get the epoch time on <https://www.epochconverter.com/> and use it to se
|
||||
- For RAK, click the reset button **TWICE**
|
||||
- For T1000-e, quickly disconnect and reconnect the magnetic side of the cable from the device **TWICE**
|
||||
- For Heltec T114, click the reset button **TWICE** (the bottom button)
|
||||
- For Xiao nRF52, click the reset button once. If that doesn't work, quickly double click the reset button twice. If that doesn't work, disconnection the board from your PC and reconnect again ([seeed studio wiki](https://wiki.seeedstudio.com/XIAO_BLE/#access-the-swd-pins-for-debugging-and-reflashing-bootloader))
|
||||
- For Xiao nRF52, click the reset button once. If that doesn't work, quickly double click the reset button twice. If that doesn't work, disconnect the board from your PC and reconnect again ([seeed studio wiki](https://wiki.seeedstudio.com/XIAO_BLE/#access-the-swd-pins-for-debugging-and-reflashing-bootloader))
|
||||
5. A new folder will appear on your computer's desktop
|
||||
6. Download the `flash_erase*.uf2` file for your device on https://flasher.meshcore.io
|
||||
- RAK WisBlock and Heltec T114: `Flash_erase-nRF32_softdevice_v6.uf2`
|
||||
@@ -827,7 +827,7 @@ Edit WIFI_SSID and WIFI_PWD in `./variants/heltec_v3/platformio.ini` and then fl
|
||||
|
||||
### 7.7. Q: I have a Station G2, or a Heltec V4, or an Ikoka Stick, or a radio with a EByte E22-900M30S or a E22-900M33S module, what should their transmit power be set to?
|
||||
**A:**
|
||||
For companion radios, you can set these radios' transmit power in the smartphone app. For repeater and room server radios, you can set their transmit power using the command line command `set tx`. You can get their current value using command line comand `get tx`
|
||||
For companion radios, you can set these radios' transmit power in the smartphone app. For repeater and room server radios, you can set their transmit power using the command line command `set tx`. You can get their current value using command line command `get tx`
|
||||
|
||||
|
||||
⚠️ **WARNING: Set these values at your own risk. Incorrect power settings can permanently damage your radio hardware.**
|
||||
|
||||
@@ -156,6 +156,7 @@ Response codes use the high-bit convention: `response = command | 0x80`. Generic
|
||||
| MacFailed | `0x04` | MAC verification failed |
|
||||
| UnknownCmd | `0x05` | Unknown sub-command |
|
||||
| EncryptFailed | `0x06` | Encryption failed |
|
||||
| TxBusy | `0x07` | Transmit busy |
|
||||
|
||||
### Unsolicited Events
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ Once you have a working app/project, you need to be able to demonstrate it exist
|
||||
| Data-Type range | App name | Contact |
|
||||
|-----------------|-----------------------------|------------------------------------------------------|
|
||||
| 0000 - 00FF | -reserved for internal use- | |
|
||||
| 0100 | MeshCore Open | zsylvester@monitormx.com — https://github.com/zjs81/meshcore-open |
|
||||
| FF00 - FFFF | -reserved for testing/dev- | |
|
||||
|
||||
(add rows, inside the range 0100 - FEFF for custom apps)
|
||||
|
||||
@@ -61,6 +61,7 @@
|
||||
#define CMD_SEND_CHANNEL_DATA 62
|
||||
#define CMD_SET_DEFAULT_FLOOD_SCOPE 63
|
||||
#define CMD_GET_DEFAULT_FLOOD_SCOPE 64
|
||||
#define CMD_SEND_RAW_PACKET 65
|
||||
|
||||
// Stats sub-types for CMD_GET_STATS
|
||||
#define STATS_TYPE_CORE 0
|
||||
@@ -495,19 +496,27 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3
|
||||
|
||||
void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: dynamic send_scope, depending on recipient and current 'home' Region
|
||||
TransportKey default_scope;
|
||||
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
|
||||
if (send_unscoped) {
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); // app has explicitly requested un-scoped
|
||||
} else {
|
||||
TransportKey default_scope;
|
||||
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
|
||||
|
||||
auto scope = send_scope.isNull() ? &default_scope : &send_scope;
|
||||
sendFloodScoped(*scope, pkt, delay_millis);
|
||||
auto scope = send_scope.isNull() ? &default_scope : &send_scope;
|
||||
sendFloodScoped(*scope, pkt, delay_millis);
|
||||
}
|
||||
}
|
||||
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
|
||||
// TODO: have per-channel send_scope
|
||||
TransportKey default_scope;
|
||||
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
|
||||
if (send_unscoped) {
|
||||
sendFlood(pkt, delay_millis, _prefs.path_hash_mode + 1); // app has explicitly requested un-scoped
|
||||
} else {
|
||||
TransportKey default_scope;
|
||||
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key));
|
||||
|
||||
auto scope = send_scope.isNull() ? &default_scope : &send_scope;
|
||||
sendFloodScoped(*scope, pkt, delay_millis);
|
||||
auto scope = send_scope.isNull() ? &default_scope : &send_scope;
|
||||
sendFloodScoped(*scope, pkt, delay_millis);
|
||||
}
|
||||
}
|
||||
|
||||
void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
|
||||
@@ -856,6 +865,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
|
||||
dirty_contacts_expiry = 0;
|
||||
memset(advert_paths, 0, sizeof(advert_paths));
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key));
|
||||
send_unscoped = false;
|
||||
|
||||
// defaults
|
||||
memset(&_prefs, 0, sizeof(_prefs));
|
||||
@@ -951,8 +961,8 @@ void MyMesh::begin(bool has_display) {
|
||||
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
|
||||
_store->loadChannels(this);
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
|
||||
@@ -973,9 +983,13 @@ struct FreqRange {
|
||||
};
|
||||
|
||||
static FreqRange repeat_freq_ranges[] = {
|
||||
#ifdef ALLOWED_REPEAT_FREQ_RANGE
|
||||
ALLOWED_REPEAT_FREQ_RANGE
|
||||
#else
|
||||
{ 433000, 433000 },
|
||||
{ 869000, 869000 },
|
||||
{ 918000, 918000 }
|
||||
#endif
|
||||
};
|
||||
|
||||
bool MyMesh::isValidClientRepeatFreq(uint32_t f) const {
|
||||
@@ -1374,7 +1388,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
_prefs.client_repeat = repeat;
|
||||
savePrefs();
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
MESH_DEBUG_PRINTLN("OK: CMD_SET_RADIO_PARAMS: f=%d, bw=%d, sf=%d, cr=%d", freq, bw, (uint32_t)sf,
|
||||
(uint32_t)cr);
|
||||
|
||||
@@ -1391,7 +1405,7 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
} else {
|
||||
_prefs.tx_power_dbm = power;
|
||||
savePrefs();
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
writeOKFrame();
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_TUNING_PARAMS) {
|
||||
@@ -1885,10 +1899,14 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
}
|
||||
} else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE_KEY && len >= 2 && cmd_frame[1] == 0) {
|
||||
if (len >= 2 + 16) {
|
||||
memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set curr scope TransportKey
|
||||
memcpy(send_scope.key, &cmd_frame[2], sizeof(send_scope.key)); // set scope override TransportKey
|
||||
} else {
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key)); // set scope to null
|
||||
memset(send_scope.key, 0, sizeof(send_scope.key)); // reset scope override
|
||||
}
|
||||
send_unscoped = false;
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE_KEY && len >= 2 && cmd_frame[1] == 1) { // ver 12+
|
||||
send_unscoped = true;
|
||||
writeOKFrame();
|
||||
} else if (cmd_frame[0] == CMD_SET_DEFAULT_FLOOD_SCOPE && len >= 1) {
|
||||
if (len >= 1+31+16) {
|
||||
@@ -1946,6 +1964,19 @@ void MyMesh::handleCmdFrame(size_t len) {
|
||||
memcpy(&out_frame[i], &r->upper_freq, 4); i += 4;
|
||||
}
|
||||
_serial->writeFrame(out_frame, i);
|
||||
} else if (cmd_frame[0] == CMD_SEND_RAW_PACKET && len >= 4) {
|
||||
auto pkt = obtainNewPacket();
|
||||
if (pkt) {
|
||||
uint8_t priority = cmd_frame[1];
|
||||
if (tryParsePacket(pkt, &cmd_frame[2], len - 2)) {
|
||||
sendPacket(pkt, priority, 0);
|
||||
writeOKFrame();
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_ILLEGAL_ARG);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_TABLE_FULL);
|
||||
}
|
||||
} else {
|
||||
writeErrFrame(ERR_CODE_UNSUPPORTED_CMD);
|
||||
MESH_DEBUG_PRINTLN("ERROR: unknown command: %02X", cmd_frame[0]);
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#include "AbstractUITask.h"
|
||||
|
||||
/*------------ Frame Protocol --------------*/
|
||||
#define FIRMWARE_VER_CODE 11
|
||||
#define FIRMWARE_VER_CODE 12
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "19 Apr 2026"
|
||||
@@ -215,6 +215,7 @@ private:
|
||||
uint32_t _active_ble_pin;
|
||||
bool _iter_started;
|
||||
bool _cli_rescue;
|
||||
bool send_unscoped; // force un-scoped flood (instead of using send_scope)
|
||||
char cli_command[80];
|
||||
uint8_t app_target_ver;
|
||||
uint8_t *sign_data;
|
||||
|
||||
@@ -108,6 +108,12 @@ void halt() {
|
||||
while (1) ;
|
||||
}
|
||||
|
||||
/* WIFI RECONNECT TRACKERS */
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
bool wifi_needs_reconnect = false;
|
||||
unsigned long last_wifi_reconnect_attempt = 0;
|
||||
#endif
|
||||
|
||||
void setup() {
|
||||
Serial.begin(115200);
|
||||
board.begin();
|
||||
@@ -127,7 +133,7 @@ void setup() {
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
InternalFS.begin();
|
||||
@@ -210,6 +216,18 @@ void setup() {
|
||||
|
||||
#ifdef WIFI_SSID
|
||||
board.setInhibitSleep(true); // prevent sleep when WiFi is active
|
||||
WiFi.setAutoReconnect(true);
|
||||
|
||||
WiFi.onEvent([](WiFiEvent_t event, WiFiEventInfo_t info){
|
||||
if (event == ARDUINO_EVENT_WIFI_STA_DISCONNECTED) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi disconnected. Flagging for reconnect...");
|
||||
wifi_needs_reconnect = true;
|
||||
} else if (event == ARDUINO_EVENT_WIFI_STA_GOT_IP) {
|
||||
WIFI_DEBUG_PRINTLN("WiFi connected successfully!");
|
||||
wifi_needs_reconnect = false;
|
||||
}
|
||||
});
|
||||
|
||||
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
||||
serial_interface.begin(TCP_PORT);
|
||||
#elif defined(BLE_PIN_CODE)
|
||||
@@ -248,4 +266,14 @@ void loop() {
|
||||
#ifdef ETHERNET_ENABLED
|
||||
serial_interface.loop();
|
||||
#endif
|
||||
|
||||
#if defined(ESP32) && defined(WIFI_SSID)
|
||||
// Safely attempt to reconnect every 10 seconds if flagged
|
||||
if (wifi_needs_reconnect && (millis() - last_wifi_reconnect_attempt > 10000)) {
|
||||
WIFI_DEBUG_PRINTLN("Attempting manual WiFi reconnect...");
|
||||
WiFi.disconnect();
|
||||
WiFi.reconnect();
|
||||
last_wifi_reconnect_attempt = millis();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -57,13 +57,21 @@ public:
|
||||
int logoWidth = 128;
|
||||
display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
// meshcore website
|
||||
const char* website = "https://meshcore.io";
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setTextSize(1);
|
||||
uint16_t websiteWidth = display.getTextWidth(website);
|
||||
display.setCursor((display.width() - websiteWidth) / 2, 22);
|
||||
display.print(website);
|
||||
|
||||
// version info
|
||||
display.setColor(DisplayDriver::LIGHT);
|
||||
display.setTextSize(2);
|
||||
display.drawTextCentered(display.width()/2, 22, _version_info);
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width()/2, 35, _version_info);
|
||||
|
||||
display.setTextSize(1);
|
||||
display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE);
|
||||
display.drawTextCentered(display.width()/2, 48, FIRMWARE_BUILD_DATE);
|
||||
|
||||
return 1000;
|
||||
}
|
||||
@@ -146,7 +154,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();
|
||||
@@ -170,7 +178,7 @@ class HomeScreen : public UIScreen {
|
||||
|
||||
public:
|
||||
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
|
||||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
||||
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0),
|
||||
_shutdown_init(false), sensors_lpp(200) { }
|
||||
|
||||
void poll() override {
|
||||
@@ -213,7 +221,7 @@ public:
|
||||
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);
|
||||
display.drawTextCentered(display.width() / 2, 54, tmp);
|
||||
#endif
|
||||
if (_task->hasConnection()) {
|
||||
display.setColor(DisplayDriver::GREEN);
|
||||
@@ -241,10 +249,10 @@ public:
|
||||
} else {
|
||||
sprintf(tmp, "%dh", secs / (60*60));
|
||||
}
|
||||
|
||||
|
||||
int timestamp_width = display.getTextWidth(tmp);
|
||||
int max_name_width = display.width() - timestamp_width - 1;
|
||||
|
||||
|
||||
char filtered_recent_name[sizeof(a->name)];
|
||||
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name));
|
||||
display.drawTextEllipsized(0, y, max_name_width, filtered_recent_name);
|
||||
@@ -310,7 +318,7 @@ public:
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
display.drawTextLeftAlign(0, y, "pos");
|
||||
sprintf(buf, "%.4f %.4f",
|
||||
sprintf(buf, "%.4f %.4f",
|
||||
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
|
||||
display.drawTextRightAlign(display.width()-1, y, buf);
|
||||
y = y + 12;
|
||||
@@ -567,6 +575,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#ifdef PIN_BUZZER
|
||||
buzzer.begin();
|
||||
buzzer.quiet(_node_prefs->buzzer_quiet);
|
||||
buzzer.startup();
|
||||
#endif
|
||||
|
||||
#ifdef PIN_VIBRATION
|
||||
@@ -741,7 +750,7 @@ void UITask::loop() {
|
||||
#endif
|
||||
#if defined(PIN_USER_BTN_ANA)
|
||||
if (abs(millis() - _analogue_pin_read_millis) > 10) {
|
||||
ev = analog_btn.check();
|
||||
int ev = analog_btn.check();
|
||||
if (ev == BUTTON_EVENT_CLICK) {
|
||||
c = checkDisplayOn(KEY_NEXT);
|
||||
} else if (ev == BUTTON_EVENT_LONG_PRESS) {
|
||||
@@ -878,7 +887,7 @@ bool UITask::getGPSState() {
|
||||
return !strcmp(_sensors->getSettingValue(i), "1");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
|
||||
#ifdef PIN_BUZZER
|
||||
buzzer.begin();
|
||||
buzzer.quiet(_node_prefs->buzzer_quiet);
|
||||
buzzer.startup();
|
||||
#endif
|
||||
|
||||
// Initialize digital button if available
|
||||
|
||||
@@ -129,6 +129,8 @@ void KissModem::processFrame() {
|
||||
memcpy(_pending_tx, data, data_len);
|
||||
_pending_tx_len = data_len;
|
||||
_has_pending_tx = true;
|
||||
} else if (_has_pending_tx) {
|
||||
writeHardwareError(HW_ERR_TX_BUSY);
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -257,6 +259,7 @@ void KissModem::processTx() {
|
||||
_tx_timer = millis();
|
||||
_tx_state = TX_DELAY;
|
||||
} else {
|
||||
_tx_timer = millis();
|
||||
_tx_state = TX_WAIT_CLEAR;
|
||||
}
|
||||
}
|
||||
@@ -273,19 +276,30 @@ void KissModem::processTx() {
|
||||
_tx_timer = millis();
|
||||
_tx_state = TX_SLOT_WAIT;
|
||||
}
|
||||
} else if (millis() - _tx_timer >= _radio.getEstAirtimeFor(KISS_MAX_PACKET_SIZE) * KISS_TX_TIMEOUT_FACTOR) {
|
||||
_tx_timer = millis();
|
||||
_tx_state = TX_DELAY;
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_SLOT_WAIT:
|
||||
if (millis() - _tx_timer >= (uint32_t)_slottime * 10) {
|
||||
_tx_timer = millis();
|
||||
_tx_state = TX_WAIT_CLEAR;
|
||||
}
|
||||
break;
|
||||
|
||||
case TX_DELAY:
|
||||
if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) {
|
||||
_radio.startSendRaw(_pending_tx, _pending_tx_len);
|
||||
_tx_state = TX_SENDING;
|
||||
if (_radio.startSendRaw(_pending_tx, _pending_tx_len)) {
|
||||
_tx_timer = millis();
|
||||
_tx_state = TX_SENDING;
|
||||
} else {
|
||||
uint8_t result = 0x00;
|
||||
writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
|
||||
_has_pending_tx = false;
|
||||
_tx_state = TX_IDLE;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -296,6 +310,12 @@ void KissModem::processTx() {
|
||||
writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
|
||||
_has_pending_tx = false;
|
||||
_tx_state = TX_IDLE;
|
||||
} else if (millis() - _tx_timer >= _radio.getEstAirtimeFor(_pending_tx_len) * KISS_TX_TIMEOUT_FACTOR) {
|
||||
_radio.onSendFinished();
|
||||
uint8_t result = 0x00;
|
||||
writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
|
||||
_has_pending_tx = false;
|
||||
_tx_state = TX_IDLE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define KISS_DEFAULT_TXDELAY 50
|
||||
#define KISS_DEFAULT_PERSISTENCE 63
|
||||
#define KISS_DEFAULT_SLOTTIME 10
|
||||
#define KISS_TX_TIMEOUT_FACTOR 3/2 // 1.5x estimated airtime
|
||||
|
||||
#define HW_CMD_GET_IDENTITY 0x01
|
||||
#define HW_CMD_GET_RANDOM 0x02
|
||||
@@ -71,6 +72,7 @@
|
||||
#define HW_ERR_MAC_FAILED 0x04
|
||||
#define HW_ERR_UNKNOWN_CMD 0x05
|
||||
#define HW_ERR_ENCRYPT_FAILED 0x06
|
||||
#define HW_ERR_TX_BUSY 0x07
|
||||
|
||||
#define KISS_FIRMWARE_VERSION 1
|
||||
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
#include <LittleFS.h>
|
||||
#elif defined(ESP32)
|
||||
#include <SPIFFS.h>
|
||||
#else
|
||||
#include <InternalFileSystem.h>
|
||||
#endif
|
||||
|
||||
#if defined(KISS_UART_RX) && defined(KISS_UART_TX)
|
||||
#include <HardwareSerial.h>
|
||||
#endif
|
||||
@@ -29,7 +32,7 @@ void halt() {
|
||||
}
|
||||
|
||||
void loadOrCreateIdentity() {
|
||||
#if defined(NRF52_PLATFORM)
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
InternalFS.begin();
|
||||
IdentityStore store(InternalFS, "");
|
||||
#elif defined(ESP32)
|
||||
@@ -53,11 +56,11 @@ void loadOrCreateIdentity() {
|
||||
}
|
||||
|
||||
void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio_set_params(freq, bw, sf, cr);
|
||||
radio_driver.setParams(freq, bw, sf, cr);
|
||||
}
|
||||
|
||||
void onSetTxPower(uint8_t power) {
|
||||
radio_set_tx_power(power);
|
||||
radio_driver.setTxPower(power);
|
||||
}
|
||||
|
||||
float onGetCurrentRssi() {
|
||||
@@ -79,7 +82,7 @@ void setup() {
|
||||
|
||||
radio_driver.begin();
|
||||
|
||||
rng.begin(radio_get_rng_seed());
|
||||
rng.begin(radio_driver.getRngSeed());
|
||||
loadOrCreateIdentity();
|
||||
|
||||
sensors.begin();
|
||||
|
||||
@@ -953,8 +953,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
}
|
||||
#endif
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
|
||||
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
|
||||
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
|
||||
@@ -1050,7 +1050,7 @@ void MyMesh::dumpLogFile() {
|
||||
}
|
||||
|
||||
void MyMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_set_tx_power(power_dbm);
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
#if defined(USE_SX1262) || defined(USE_SX1268)
|
||||
@@ -1279,13 +1279,13 @@ void MyMesh::loop() {
|
||||
|
||||
if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
|
||||
set_radio_at = 0; // clear timer
|
||||
radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
radio_driver.setParams(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
MESH_DEBUG_PRINTLN("Temp radio params");
|
||||
}
|
||||
|
||||
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
|
||||
revert_radio_at = 0; // clear timer
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||
}
|
||||
|
||||
|
||||
@@ -52,17 +52,25 @@ void UITask::renderCurrScreen() {
|
||||
int logoWidth = 128;
|
||||
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
// meshcore website
|
||||
const char* website = "https://meshcore.io";
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t websiteWidth = _display->getTextWidth(website);
|
||||
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
|
||||
_display->print(website);
|
||||
|
||||
// version info
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t versionWidth = _display->getTextWidth(_version_info);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 22);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 35);
|
||||
_display->print(_version_info);
|
||||
|
||||
// node type
|
||||
const char* node_type = "< Repeater >";
|
||||
uint16_t typeWidth = _display->getTextWidth(node_type);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 35);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 48);
|
||||
_display->print(node_type);
|
||||
} else { // home screen
|
||||
// node name
|
||||
|
||||
@@ -65,7 +65,7 @@ void setup() {
|
||||
halt();
|
||||
}
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
|
||||
FILESYSTEM* fs;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
|
||||
@@ -691,8 +691,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
|
||||
}
|
||||
}
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -796,7 +796,7 @@ void MyMesh::dumpLogFile() {
|
||||
}
|
||||
|
||||
void MyMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_set_tx_power(power_dbm);
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
void MyMesh::saveIdentity(const mesh::LocalIdentity &new_id) {
|
||||
@@ -999,13 +999,13 @@ void MyMesh::loop() {
|
||||
|
||||
if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
|
||||
set_radio_at = 0; // clear timer
|
||||
radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
radio_driver.setParams(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
MESH_DEBUG_PRINTLN("Temp radio params");
|
||||
}
|
||||
|
||||
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
|
||||
revert_radio_at = 0; // clear timer
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||
}
|
||||
|
||||
|
||||
@@ -52,17 +52,25 @@ void UITask::renderCurrScreen() {
|
||||
int logoWidth = 128;
|
||||
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
// meshcore website
|
||||
const char* website = "https://meshcore.io";
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t websiteWidth = _display->getTextWidth(website);
|
||||
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
|
||||
_display->print(website);
|
||||
|
||||
// version info
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t versionWidth = _display->getTextWidth(_version_info);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 22);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 35);
|
||||
_display->print(_version_info);
|
||||
|
||||
// node type
|
||||
const char* node_type = "< Room Server >";
|
||||
uint16_t typeWidth = _display->getTextWidth(node_type);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 35);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 48);
|
||||
_display->print(node_type);
|
||||
} else { // home screen
|
||||
// node name
|
||||
|
||||
@@ -43,7 +43,7 @@ void setup() {
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
|
||||
FILESYSTEM* fs;
|
||||
#if defined(NRF52_PLATFORM)
|
||||
|
||||
@@ -562,7 +562,7 @@ void setup() {
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
|
||||
#if defined(NRF52_PLATFORM)
|
||||
InternalFS.begin();
|
||||
@@ -577,8 +577,8 @@ void setup() {
|
||||
#error "need to define filesystem"
|
||||
#endif
|
||||
|
||||
radio_set_params(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR);
|
||||
radio_set_tx_power(the_mesh.getTxPowerPref());
|
||||
radio_driver.setParams(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR);
|
||||
radio_driver.setTxPower(the_mesh.getTxPowerPref());
|
||||
|
||||
the_mesh.showWelcome();
|
||||
|
||||
|
||||
@@ -764,8 +764,8 @@ void SensorMesh::begin(FILESYSTEM* fs) {
|
||||
}
|
||||
}
|
||||
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_set_tx_power(_prefs.tx_power_dbm);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setTxPower(_prefs.tx_power_dbm);
|
||||
|
||||
updateAdvertTimer();
|
||||
updateFloodAdvertTimer();
|
||||
@@ -842,7 +842,7 @@ void SensorMesh::updateFloodAdvertTimer() {
|
||||
}
|
||||
|
||||
void SensorMesh::setTxPower(int8_t power_dbm) {
|
||||
radio_set_tx_power(power_dbm);
|
||||
radio_driver.setTxPower(power_dbm);
|
||||
}
|
||||
|
||||
void SensorMesh::formatStatsReply(char *reply) {
|
||||
@@ -908,13 +908,13 @@ void SensorMesh::loop() {
|
||||
|
||||
if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
|
||||
set_radio_at = 0; // clear timer
|
||||
radio_set_params(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
radio_driver.setParams(pending_freq, pending_bw, pending_sf, pending_cr);
|
||||
MESH_DEBUG_PRINTLN("Temp radio params");
|
||||
}
|
||||
|
||||
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
|
||||
revert_radio_at = 0; // clear timer
|
||||
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
|
||||
MESH_DEBUG_PRINTLN("Radio params restored");
|
||||
}
|
||||
|
||||
|
||||
@@ -34,11 +34,11 @@
|
||||
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
|
||||
|
||||
#ifndef FIRMWARE_BUILD_DATE
|
||||
#define FIRMWARE_BUILD_DATE "20 Mar 2026"
|
||||
#define FIRMWARE_BUILD_DATE "19 Apr 2026"
|
||||
#endif
|
||||
|
||||
#ifndef FIRMWARE_VERSION
|
||||
#define FIRMWARE_VERSION "v1.14.1"
|
||||
#define FIRMWARE_VERSION "v1.15.0"
|
||||
#endif
|
||||
|
||||
#define FIRMWARE_ROLE "sensor"
|
||||
|
||||
@@ -52,17 +52,25 @@ void UITask::renderCurrScreen() {
|
||||
int logoWidth = 128;
|
||||
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
|
||||
|
||||
// meshcore website
|
||||
const char* website = "https://meshcore.io";
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t websiteWidth = _display->getTextWidth(website);
|
||||
_display->setCursor((_display->width() - websiteWidth) / 2, 22);
|
||||
_display->print(website);
|
||||
|
||||
// version info
|
||||
_display->setColor(DisplayDriver::LIGHT);
|
||||
_display->setTextSize(1);
|
||||
uint16_t versionWidth = _display->getTextWidth(_version_info);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 22);
|
||||
_display->setCursor((_display->width() - versionWidth) / 2, 35);
|
||||
_display->print(_version_info);
|
||||
|
||||
// node type
|
||||
const char* node_type = "< Sensor >";
|
||||
uint16_t typeWidth = _display->getTextWidth(node_type);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 35);
|
||||
_display->setCursor((_display->width() - typeWidth) / 2, 48);
|
||||
_display->print(node_type);
|
||||
} else { // home screen
|
||||
// node name
|
||||
|
||||
@@ -68,7 +68,7 @@ void setup() {
|
||||
|
||||
if (!radio_init()) { halt(); }
|
||||
|
||||
fast_rng.begin(radio_get_rng_seed());
|
||||
fast_rng.begin(radio_driver.getRngSeed());
|
||||
|
||||
FILESYSTEM* fs;
|
||||
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
||||
|
||||
+22
-7
@@ -10,8 +10,8 @@
|
||||
|
||||
[platformio]
|
||||
extra_configs =
|
||||
variants/*/platformio.ini
|
||||
platformio.local.ini
|
||||
variants/*/platformio.ini
|
||||
platformio.local.ini
|
||||
|
||||
[arduino_base]
|
||||
framework = arduino
|
||||
@@ -81,17 +81,18 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
|
||||
extends = arduino_base
|
||||
platform = nordicnrf52
|
||||
platform_packages =
|
||||
framework-arduinoadafruitnrf52 @ 1.10700.0
|
||||
extra_scripts =
|
||||
create-uf2.py
|
||||
arch/nrf52/extra_scripts/patch_bluefruit.py
|
||||
; use internal fork that includes patch to ble stack to prevent firmware lockup during rapid connect/disconnect
|
||||
; https://github.com/meshcore-dev/MeshCore/pull/1177
|
||||
; https://github.com/meshcore-dev/MeshCore/pull/1295
|
||||
framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301
|
||||
extra_scripts = create-uf2.py
|
||||
build_flags = ${arduino_base.build_flags}
|
||||
-D NRF52_PLATFORM
|
||||
-D LFS_NO_ASSERT=1
|
||||
-D EXTRAFS=1
|
||||
lib_deps =
|
||||
${arduino_base.lib_deps}
|
||||
https://github.com/oltaco/CustomLFS @ 0.2.1
|
||||
https://github.com/oltaco/CustomLFS#0.2.2
|
||||
; ----------------- RP2040 ---------------------
|
||||
|
||||
[rp2040_base]
|
||||
@@ -151,3 +152,17 @@ lib_deps =
|
||||
stevemarple/MicroNMEA @ ^2.0.6
|
||||
adafruit/Adafruit BME680 Library @ ^2.0.4
|
||||
adafruit/Adafruit BMP085 Library @ ^1.2.4
|
||||
|
||||
; ----------------- TESTING ---------------------
|
||||
|
||||
[env:native]
|
||||
platform = native
|
||||
build_flags = -std=c++17
|
||||
-I src
|
||||
-I test/mocks
|
||||
test_build_src = yes
|
||||
build_src_filter =
|
||||
-<*>
|
||||
+<../src/Utils.cpp>
|
||||
lib_deps =
|
||||
google/googletest @ 1.17.0
|
||||
|
||||
+2
-1
@@ -193,8 +193,9 @@ public:
|
||||
bool millisHasNowPassed(unsigned long timestamp) const;
|
||||
unsigned long futureMillis(int millis_from_now) const;
|
||||
|
||||
private:
|
||||
bool tryParsePacket(Packet* pkt, const uint8_t* raw, int len);
|
||||
|
||||
private:
|
||||
void checkRecv();
|
||||
void checkSend();
|
||||
};
|
||||
|
||||
@@ -27,9 +27,11 @@ bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
|
||||
}
|
||||
|
||||
void AutoDiscoverRTCClock::begin(TwoWire& wire) {
|
||||
#if !defined(DISABLE_DS3231_PROBE)
|
||||
if (i2c_probe(wire, DS3231_ADDRESS)) {
|
||||
ds3231_success = rtc_3231.begin(&wire);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (i2c_probe(wire, RV3028_ADDRESS)) {
|
||||
rtc_rv3028.initI2C(wire);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "CommonCLI.h"
|
||||
#include "TxtDataHelpers.h"
|
||||
#include "AdvertDataHelpers.h"
|
||||
#include "TxtDataHelpers.h"
|
||||
#include <RTClib.h>
|
||||
|
||||
#ifndef BRIDGE_MAX_BAUD
|
||||
@@ -285,7 +286,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
|
||||
// change admin password
|
||||
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
|
||||
savePrefs();
|
||||
sprintf(reply, "password now: %s", _prefs->password); // echo back just to let admin know for sure!!
|
||||
sprintf(reply, "password now: ");
|
||||
StrHelper::strncpy(&reply[14], _prefs->password, 160-15); // echo back just to let admin know for sure!!
|
||||
} else if (memcmp(command, "clear stats", 11) == 0) {
|
||||
_callbacks->clearStats();
|
||||
strcpy(reply, "(OK - stats reset)");
|
||||
@@ -726,7 +728,8 @@ void CommonCLI::handleSetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
strcpy(reply, "Error: unsupported by this board");
|
||||
};
|
||||
} else {
|
||||
sprintf(reply, "unknown config: %s", config);
|
||||
strcpy(reply, "unknown config: ");
|
||||
StrHelper::strncpy(&reply[16], config, 160-17);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -784,10 +787,11 @@ void CommonCLI::handleGetCmd(uint32_t sender_timestamp, char* command, char* rep
|
||||
} 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) {
|
||||
auto start = reply;
|
||||
*reply++ = '>';
|
||||
*reply++ = ' ';
|
||||
const char* sp = _prefs->owner_info;
|
||||
while (*sp) {
|
||||
while (*sp && reply - start < 159) {
|
||||
*reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
|
||||
sp++;
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ void ESPNOWRadio::init() {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ESPNOWRadio::getRngSeed() {
|
||||
return millis() + intID(); // TODO: where to get some entropy?
|
||||
}
|
||||
|
||||
void ESPNOWRadio::setTxPower(uint8_t dbm) {
|
||||
esp_wifi_set_max_tx_power(dbm * 4);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,13 @@ protected:
|
||||
public:
|
||||
ESPNOWRadio() { n_recv = n_sent = n_recv_errors = 0; }
|
||||
|
||||
uint32_t getRngSeed();
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
// no-op
|
||||
}
|
||||
void powerOff() { /* no-op */ }
|
||||
|
||||
void init();
|
||||
int recvRaw(uint8_t* bytes, int sz) override;
|
||||
uint32_t getEstAirtimeFor(int len_bytes) override;
|
||||
|
||||
@@ -7,6 +7,15 @@
|
||||
class CustomLLCC68Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomLLCC68Wrapper(CustomLLCC68& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
((CustomLLCC68 *)_radio)->setFrequency(freq);
|
||||
((CustomLLCC68 *)_radio)->setSpreadingFactor(sf);
|
||||
((CustomLLCC68 *)_radio)->setBandwidth(bw);
|
||||
((CustomLLCC68 *)_radio)->setCodingRate(cr);
|
||||
updatePreamble(sf);
|
||||
}
|
||||
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomLLCC68 *)_radio)->isReceiving();
|
||||
}
|
||||
@@ -20,6 +29,7 @@ public:
|
||||
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; }
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
|
||||
@@ -36,4 +36,6 @@ class CustomLR1110 : public LR1110 {
|
||||
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
|
||||
return detected;
|
||||
}
|
||||
|
||||
uint8_t getSpreadingFactor() const { return spreadingFactor; }
|
||||
};
|
||||
@@ -7,6 +7,15 @@
|
||||
class CustomLR1110Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
((CustomLR1110 *)_radio)->setFrequency(freq);
|
||||
((CustomLR1110 *)_radio)->setSpreadingFactor(sf);
|
||||
((CustomLR1110 *)_radio)->setBandwidth(bw);
|
||||
((CustomLR1110 *)_radio)->setCodingRate(cr);
|
||||
updatePreamble(sf);
|
||||
}
|
||||
|
||||
void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomLR1110 *)_radio)->isReceiving();
|
||||
@@ -19,12 +28,14 @@ public:
|
||||
|
||||
void onSendFinished() override {
|
||||
RadioLibWrapper::onSendFinished();
|
||||
_radio->setPreambleLength(16); // overcomes weird issues with small and big pkts
|
||||
_radio->setPreambleLength(preambleLengthForSF(getSpreadingFactor())); // overcomes weird issues with small and big pkts
|
||||
}
|
||||
|
||||
float getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
|
||||
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
|
||||
|
||||
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
|
||||
|
||||
void setRxBoostedGainMode(bool en) override {
|
||||
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,15 @@
|
||||
class CustomSTM32WLxWrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
((CustomSTM32WLx *)_radio)->setFrequency(freq);
|
||||
((CustomSTM32WLx *)_radio)->setSpreadingFactor(sf);
|
||||
((CustomSTM32WLx *)_radio)->setBandwidth(bw);
|
||||
((CustomSTM32WLx *)_radio)->setCodingRate(cr);
|
||||
updatePreamble(sf);
|
||||
}
|
||||
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSTM32WLx *)_radio)->isReceiving();
|
||||
}
|
||||
@@ -21,6 +30,7 @@ public:
|
||||
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; }
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
};
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
class CustomSX1262Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
((CustomSX1262 *)_radio)->setFrequency(freq);
|
||||
((CustomSX1262 *)_radio)->setSpreadingFactor(sf);
|
||||
((CustomSX1262 *)_radio)->setBandwidth(bw);
|
||||
((CustomSX1262 *)_radio)->setCodingRate(cr);
|
||||
updatePreamble(sf);
|
||||
}
|
||||
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSX1262 *)_radio)->isReceiving();
|
||||
}
|
||||
@@ -24,6 +33,7 @@ public:
|
||||
int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; }
|
||||
virtual void powerOff() override {
|
||||
((CustomSX1262 *)_radio)->sleep(false);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,15 @@
|
||||
class CustomSX1268Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
((CustomSX1268 *)_radio)->setFrequency(freq);
|
||||
((CustomSX1268 *)_radio)->setSpreadingFactor(sf);
|
||||
((CustomSX1268 *)_radio)->setBandwidth(bw);
|
||||
((CustomSX1268 *)_radio)->setCodingRate(cr);
|
||||
updatePreamble(sf);
|
||||
}
|
||||
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSX1268 *)_radio)->isReceiving();
|
||||
}
|
||||
@@ -24,6 +33,7 @@ public:
|
||||
int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; }
|
||||
|
||||
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
|
||||
|
||||
|
||||
@@ -10,6 +10,15 @@
|
||||
class CustomSX1276Wrapper : public RadioLibWrapper {
|
||||
public:
|
||||
CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { }
|
||||
|
||||
void setParams(float freq, float bw, uint8_t sf, uint8_t cr) override {
|
||||
((CustomSX1276 *)_radio)->setFrequency(freq);
|
||||
((CustomSX1276 *)_radio)->setSpreadingFactor(sf);
|
||||
((CustomSX1276 *)_radio)->setBandwidth(bw);
|
||||
((CustomSX1276 *)_radio)->setCodingRate(cr);
|
||||
updatePreamble(sf);
|
||||
}
|
||||
|
||||
bool isReceivingPacket() override {
|
||||
return ((CustomSX1276 *)_radio)->isReceiving();
|
||||
}
|
||||
@@ -23,4 +32,5 @@ public:
|
||||
int sf = ((CustomSX1276 *)_radio)->spreadingFactor;
|
||||
return packetScoreInt(snr, sf, packet_len);
|
||||
}
|
||||
uint8_t getSpreadingFactor() const override { return ((CustomSX1276 *)_radio)->spreadingFactor; }
|
||||
};
|
||||
|
||||
@@ -26,6 +26,8 @@ void setFlag(void) {
|
||||
|
||||
void RadioLibWrapper::begin() {
|
||||
_radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt
|
||||
_preamble_sf = getSpreadingFactor();
|
||||
_radio->setPreambleLength(preambleLengthForSF(_preamble_sf)); // longer preamble for lower SF improves reliability
|
||||
state = STATE_IDLE;
|
||||
|
||||
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep)
|
||||
@@ -40,6 +42,14 @@ void RadioLibWrapper::begin() {
|
||||
_floor_sample_sum = 0;
|
||||
}
|
||||
|
||||
uint32_t RadioLibWrapper::getRngSeed() {
|
||||
return _radio->random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void RadioLibWrapper::setTxPower(int8_t dbm) {
|
||||
_radio->setOutputPower(dbm);
|
||||
}
|
||||
|
||||
void RadioLibWrapper::idle() {
|
||||
_radio->standby();
|
||||
state = STATE_IDLE; // need another startReceive()
|
||||
|
||||
@@ -11,6 +11,7 @@ protected:
|
||||
int16_t _noise_floor, _threshold;
|
||||
uint16_t _num_floor_samples;
|
||||
int32_t _floor_sample_sum;
|
||||
uint8_t _preamble_sf;
|
||||
|
||||
void idle();
|
||||
void startRecv();
|
||||
@@ -19,7 +20,7 @@ protected:
|
||||
virtual void doResetAGC();
|
||||
|
||||
public:
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board) { n_recv = n_sent = 0; }
|
||||
RadioLibWrapper(PhysicalLayer& radio, mesh::MainBoard& board) : _radio(&radio), _board(&board), _preamble_sf(0) { n_recv = n_sent = 0; }
|
||||
|
||||
void begin() override;
|
||||
virtual void powerOff() { _radio->sleep(); }
|
||||
@@ -37,7 +38,14 @@ public:
|
||||
return isChannelActive();
|
||||
}
|
||||
|
||||
virtual void setParams(float freq, float bw, uint8_t sf, uint8_t cr) = 0;
|
||||
uint32_t getRngSeed();
|
||||
void setTxPower(int8_t dbm);
|
||||
|
||||
virtual float getCurrentRSSI() =0;
|
||||
virtual uint8_t getSpreadingFactor() const { return LORA_SF; }
|
||||
static uint16_t preambleLengthForSF(uint8_t sf) { return sf <= 8 ? 32 : 16; }
|
||||
void updatePreamble(uint8_t sf) { _preamble_sf = sf; _radio->setPreambleLength(preambleLengthForSF(sf)); }
|
||||
|
||||
int getNoiseFloor() const override { return _noise_floor; }
|
||||
void triggerNoiseFloorCalibrate(int threshold) override;
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
#include "EnvironmentSensorManager.h"
|
||||
|
||||
#include <Wire.h>
|
||||
|
||||
#if ENV_PIN_SDA && ENV_PIN_SCL
|
||||
#define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors
|
||||
#else
|
||||
#define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// Sensor library includes and static driver instances
|
||||
// ============================================================
|
||||
|
||||
#ifdef ENV_INCLUDE_BME680
|
||||
#ifndef TELEM_BME680_ADDRESS
|
||||
#define TELEM_BME680_ADDRESS 0x76
|
||||
@@ -31,7 +37,7 @@ static Adafruit_AHTX0 AHTX0;
|
||||
#ifndef TELEM_BME280_ADDRESS
|
||||
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
|
||||
#endif
|
||||
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
||||
#define TELEM_BME280_SEALEVELPRESSURE_HPA (1013.25) // Atmospheric pressure at sea level
|
||||
#include <Adafruit_BME280.h>
|
||||
static Adafruit_BME280 BME280;
|
||||
#endif
|
||||
@@ -40,7 +46,7 @@ static Adafruit_BME280 BME280;
|
||||
#ifndef TELEM_BMP280_ADDRESS
|
||||
#define TELEM_BMP280_ADDRESS 0x76 // BMP280 environmental sensor I2C address
|
||||
#endif
|
||||
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Athmospheric pressure at sea level
|
||||
#define TELEM_BMP280_SEALEVELPRESSURE_HPA (1013.25) // Atmospheric pressure at sea level
|
||||
#include <Adafruit_BMP280.h>
|
||||
static Adafruit_BMP280 BMP280(TELEM_WIRE);
|
||||
#endif
|
||||
@@ -51,7 +57,7 @@ static Adafruit_SHTC3 SHTC3;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
#define TELEM_SHT4X_ADDRESS 0x44 //0x44 - 0x46
|
||||
#define TELEM_SHT4X_ADDRESS 0x44
|
||||
#include <SensirionI2cSht4x.h>
|
||||
static SensirionI2cSht4x SHT4X;
|
||||
#endif
|
||||
@@ -63,7 +69,7 @@ LPS22HBClass LPS22HB(*TELEM_WIRE);
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
#ifndef TELEM_INA3221_ADDRESS
|
||||
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
|
||||
#define TELEM_INA3221_ADDRESS 0x42 // INA3221 3 channel current sensor I2C address
|
||||
#endif
|
||||
#ifndef TELEM_INA3221_SHUNT_VALUE
|
||||
#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts
|
||||
@@ -88,9 +94,9 @@ static Adafruit_INA260 INA260;
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA226
|
||||
#define TELEM_INA226_ADDRESS 0x44
|
||||
#define TELEM_INA226_ADDRESS 0x44
|
||||
#define TELEM_INA226_SHUNT_VALUE 0.100
|
||||
#define TELEM_INA226_MAX_AMP 0.8
|
||||
#define TELEM_INA226_MAX_AMP 0.8
|
||||
#include <INA226.h>
|
||||
static INA226 INA226(TELEM_INA226_ADDRESS, TELEM_WIRE);
|
||||
#endif
|
||||
@@ -161,10 +167,352 @@ public:
|
||||
static RAK12500LocationProvider RAK12500_provider;
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// I2C bus scanner
|
||||
// Probes every valid address and records which ones ACK.
|
||||
// This runs before any sensor library is touched, so a missing
|
||||
// or misbehaving device cannot stall or crash the boot sequence.
|
||||
// ============================================================
|
||||
|
||||
static void scanI2CBus(TwoWire* wire, bool found[128]) {
|
||||
for (uint8_t addr = 0x08; addr < 0x78; addr++) {
|
||||
wire->beginTransmission(addr);
|
||||
found[addr] = (wire->endTransmission() == 0);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Per-sensor init and query functions
|
||||
//
|
||||
// init(wire, address) — called only when the address was seen
|
||||
// on the bus. Returns 0 on failure, or the number of
|
||||
// telemetry channels the sensor will consume (1 for all
|
||||
// single-output sensors; INA3221 returns one per enabled
|
||||
// hardware channel; MLX90614 and RAK12035+calibration
|
||||
// return 2).
|
||||
//
|
||||
// query(channel, sub_channel, lpp) — called once per active
|
||||
// sensor entry during querySensors(). sub_channel is always
|
||||
// 0 for single-output sensors.
|
||||
// ============================================================
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
static uint8_t init_ahtx0(TwoWire* wire, uint8_t addr) {
|
||||
return AHTX0.begin(wire, 0, addr) ? 1 : 0;
|
||||
}
|
||||
static void query_ahtx0(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
sensors_event_t humidity, temp;
|
||||
AHTX0.getEvent(&humidity, &temp);
|
||||
lpp.addTemperature(ch, temp.temperature);
|
||||
lpp.addRelativeHumidity(ch, humidity.relative_humidity);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BME680
|
||||
static uint8_t init_bme680(TwoWire*, uint8_t addr) {
|
||||
// Wire was set in the static constructor; begin() takes address only.
|
||||
return BME680.begin(addr) ? 1 : 0;
|
||||
}
|
||||
static void query_bme680(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
if (BME680.performReading()) {
|
||||
lpp.addTemperature(ch, BME680.temperature);
|
||||
lpp.addRelativeHumidity(ch, BME680.humidity);
|
||||
lpp.addBarometricPressure(ch, BME680.pressure / 100);
|
||||
lpp.addAltitude(ch, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903)));
|
||||
lpp.addAnalogInput(ch, BME680.gas_resistance);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BME280
|
||||
static uint8_t init_bme280(TwoWire* wire, uint8_t addr) {
|
||||
if (!BME280.begin(addr, wire)) return 0;
|
||||
BME280.setSampling(Adafruit_BME280::MODE_FORCED,
|
||||
Adafruit_BME280::SAMPLING_X1,
|
||||
Adafruit_BME280::SAMPLING_X1,
|
||||
Adafruit_BME280::SAMPLING_X1,
|
||||
Adafruit_BME280::FILTER_OFF,
|
||||
Adafruit_BME280::STANDBY_MS_1000);
|
||||
return 1;
|
||||
}
|
||||
static void query_bme280(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
if (BME280.takeForcedMeasurement()) {
|
||||
lpp.addTemperature(ch, BME280.readTemperature());
|
||||
lpp.addRelativeHumidity(ch, BME280.readHumidity());
|
||||
lpp.addBarometricPressure(ch, BME280.readPressure() / 100);
|
||||
lpp.addAltitude(ch, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP280
|
||||
static uint8_t init_bmp280(TwoWire*, uint8_t addr) {
|
||||
// BMP280 static instance was constructed with TELEM_WIRE; begin() uses it.
|
||||
return BMP280.begin(addr) ? 1 : 0;
|
||||
}
|
||||
static void query_bmp280(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
lpp.addTemperature(ch, BMP280.readTemperature());
|
||||
lpp.addBarometricPressure(ch, BMP280.readPressure() / 100);
|
||||
lpp.addAltitude(ch, BMP280.readAltitude(TELEM_BMP280_SEALEVELPRESSURE_HPA));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
static uint8_t init_shtc3(TwoWire* wire, uint8_t) {
|
||||
// Adafruit_SHTC3::begin() does not accept an address (fixed at 0x70).
|
||||
return SHTC3.begin(wire) ? 1 : 0;
|
||||
}
|
||||
static void query_shtc3(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
sensors_event_t humidity, temp;
|
||||
SHTC3.getEvent(&humidity, &temp);
|
||||
lpp.addTemperature(ch, temp.temperature);
|
||||
lpp.addRelativeHumidity(ch, humidity.relative_humidity);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
static uint8_t init_sht4x(TwoWire* wire, uint8_t addr) {
|
||||
// SensirionI2cSht4x::begin() does not probe the hardware; use serialNumber()
|
||||
// as the actual presence check since it performs a real I2C transaction.
|
||||
SHT4X.begin(*wire, addr);
|
||||
uint32_t serial = 0;
|
||||
return (SHT4X.serialNumber(serial) == 0) ? 1 : 0;
|
||||
}
|
||||
static void query_sht4x(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
float temperature, humidity;
|
||||
if (SHT4X.measureLowestPrecision(temperature, humidity) == 0) {
|
||||
lpp.addTemperature(ch, temperature);
|
||||
lpp.addRelativeHumidity(ch, humidity);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
static uint8_t init_lps22hb(TwoWire*, uint8_t) {
|
||||
// LPS22HBClass is constructed with the wire reference; begin() uses it.
|
||||
return LPS22HB.begin() ? 1 : 0;
|
||||
}
|
||||
static void query_lps22hb(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
lpp.addTemperature(ch, LPS22HB.readTemperature());
|
||||
lpp.addBarometricPressure(ch, LPS22HB.readPressure() * 10); // convert kPa to hPa
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
static uint8_t init_ina3221(TwoWire* wire, uint8_t addr) {
|
||||
if (!INA3221.begin(addr, wire)) return 0;
|
||||
for (int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
|
||||
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
|
||||
}
|
||||
// Each enabled hardware channel becomes its own telemetry channel.
|
||||
uint8_t enabled = 0;
|
||||
for (int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
|
||||
if (INA3221.isChannelEnabled(i)) enabled++;
|
||||
}
|
||||
return enabled > 0 ? enabled : 1;
|
||||
}
|
||||
static void query_ina3221(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) {
|
||||
// sub_ch is the index of the nth enabled hardware channel.
|
||||
uint8_t seen = 0;
|
||||
for (int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
|
||||
if (INA3221.isChannelEnabled(i)) {
|
||||
if (seen == sub_ch) {
|
||||
float v = INA3221.getBusVoltage(i);
|
||||
float c = INA3221.getCurrentAmps(i);
|
||||
lpp.addVoltage(ch, v);
|
||||
lpp.addCurrent(ch, c);
|
||||
lpp.addPower(ch, v * c);
|
||||
return;
|
||||
}
|
||||
seen++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
static uint8_t init_ina219(TwoWire* wire, uint8_t) {
|
||||
// INA219 static instance was constructed with the address; begin() uses it.
|
||||
return INA219.begin(wire) ? 1 : 0;
|
||||
}
|
||||
static void query_ina219(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
lpp.addVoltage(ch, INA219.getBusVoltage_V());
|
||||
lpp.addCurrent(ch, INA219.getCurrent_mA() / 1000.0f);
|
||||
lpp.addPower(ch, INA219.getPower_mW() / 1000.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA260
|
||||
static uint8_t init_ina260(TwoWire* wire, uint8_t addr) {
|
||||
return INA260.begin(addr, wire) ? 1 : 0;
|
||||
}
|
||||
static void query_ina260(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
lpp.addVoltage(ch, INA260.readBusVoltage() / 1000.0f);
|
||||
lpp.addCurrent(ch, INA260.readCurrent() / 1000.0f);
|
||||
lpp.addPower(ch, INA260.readPower() / 1000.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA226
|
||||
static uint8_t init_ina226(TwoWire*, uint8_t) {
|
||||
// INA226 static instance was constructed with address and wire.
|
||||
if (!INA226.begin()) return 0;
|
||||
INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE);
|
||||
return 1;
|
||||
}
|
||||
static void query_ina226(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
lpp.addVoltage(ch, INA226.getBusVoltage());
|
||||
lpp.addCurrent(ch, INA226.getCurrent_mA() / 1000.0f);
|
||||
lpp.addPower(ch, INA226.getPower_mW() / 1000.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_MLX90614
|
||||
static uint8_t init_mlx90614(TwoWire* wire, uint8_t addr) {
|
||||
return MLX90614.begin(addr, wire) ? 2 : 0; // 2 channels: object temp, ambient temp
|
||||
}
|
||||
static void query_mlx90614(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) {
|
||||
if (sub_ch == 0)
|
||||
lpp.addTemperature(ch, MLX90614.readObjectTempC());
|
||||
else
|
||||
lpp.addTemperature(ch, MLX90614.readAmbientTempC());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_VL53L0X
|
||||
static uint8_t init_vl53l0x(TwoWire* wire, uint8_t addr) {
|
||||
return VL53L0X.begin(addr, false, wire) ? 1 : 0;
|
||||
}
|
||||
static void query_vl53l0x(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
VL53L0X_RangingMeasurementData_t measure;
|
||||
VL53L0X.rangingTest(&measure, false);
|
||||
lpp.addDistance(ch, measure.RangeStatus != 4 ? measure.RangeMilliMeter / 1000.0f : 0.0f);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENV_INCLUDE_BMP085
|
||||
static uint8_t init_bmp085(TwoWire* wire, uint8_t) {
|
||||
return BMP085.begin(0, wire) ? 1 : 0; // mode 0 = ULTRALOWPOWER
|
||||
}
|
||||
static void query_bmp085(uint8_t ch, uint8_t, CayenneLPP& lpp) {
|
||||
lpp.addTemperature(ch, BMP085.readTemperature());
|
||||
lpp.addBarometricPressure(ch, BMP085.readPressure() / 100);
|
||||
lpp.addAltitude(ch, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
static uint8_t init_rak12035(TwoWire* wire, uint8_t addr) {
|
||||
// RAK12035 requires setup() before begin().
|
||||
RAK12035.setup(*wire);
|
||||
if (!RAK12035.begin(addr)) return 0;
|
||||
#ifdef ENABLE_RAK12035_CALIBRATION
|
||||
return 2; // moisture channel + calibration channel
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
static void query_rak12035(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) {
|
||||
if (sub_ch == 0) {
|
||||
lpp.addTemperature(ch, RAK12035.get_sensor_temperature());
|
||||
lpp.addPercentage(ch, RAK12035.get_sensor_moisture());
|
||||
} else {
|
||||
#ifdef ENABLE_RAK12035_CALIBRATION
|
||||
float cap = RAK12035.get_sensor_capacitance();
|
||||
float wet = RAK12035.get_humidity_full();
|
||||
float dry = RAK12035.get_humidity_zero();
|
||||
lpp.addFrequency(ch, cap);
|
||||
lpp.addTemperature(ch, wet);
|
||||
lpp.addPower(ch, dry);
|
||||
if (cap > dry) RAK12035.set_humidity_zero(cap);
|
||||
if (cap < wet) RAK12035.set_humidity_full(cap);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// ============================================================
|
||||
// Sensor descriptor table
|
||||
//
|
||||
// Each entry maps an I2C address to a sensor's init and query
|
||||
// functions. Only entries whose ENV_INCLUDE_* guard is defined
|
||||
// are compiled in. The sentinel at the end keeps the array
|
||||
// non-empty regardless of which sensors are enabled.
|
||||
//
|
||||
// Ordering here determines channel assignment at runtime:
|
||||
// the first detected+initialized sensor gets channel 2, the
|
||||
// next gets channel 3, and so on.
|
||||
// ============================================================
|
||||
|
||||
struct SensorDef {
|
||||
uint8_t address;
|
||||
const char* name;
|
||||
uint8_t (*init)(TwoWire* wire, uint8_t address);
|
||||
void (*query)(uint8_t channel, uint8_t sub_channel, CayenneLPP& telemetry);
|
||||
};
|
||||
|
||||
static const SensorDef SENSOR_TABLE[] = {
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
{ TELEM_AHTX_ADDRESS, "AHT10/AHT20", init_ahtx0, query_ahtx0 },
|
||||
#endif
|
||||
#ifdef ENV_INCLUDE_BME680
|
||||
{ TELEM_BME680_ADDRESS, "BME680", init_bme680, query_bme680 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_BME280
|
||||
{ TELEM_BME280_ADDRESS, "BME280", init_bme280, query_bme280 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_BMP280
|
||||
{ TELEM_BMP280_ADDRESS, "BMP280", init_bmp280, query_bmp280 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
{ 0x70, "SHTC3", init_shtc3, query_shtc3 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
{ TELEM_SHT4X_ADDRESS, "SHT4X", init_sht4x, query_sht4x },
|
||||
#endif
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
{ 0x5C, "LPS22HB", init_lps22hb, query_lps22hb },
|
||||
#endif
|
||||
#if ENV_INCLUDE_INA3221
|
||||
{ TELEM_INA3221_ADDRESS, "INA3221", init_ina3221, query_ina3221 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_INA219
|
||||
{ TELEM_INA219_ADDRESS, "INA219", init_ina219, query_ina219 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_INA260
|
||||
{ TELEM_INA260_ADDRESS, "INA260", init_ina260, query_ina260 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_INA226
|
||||
{ TELEM_INA226_ADDRESS, "INA226", init_ina226, query_ina226 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_MLX90614
|
||||
{ TELEM_MLX90614_ADDRESS,"MLX90614", init_mlx90614, query_mlx90614 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_VL53L0X
|
||||
{ TELEM_VL53L0X_ADDRESS, "VL53L0X", init_vl53l0x, query_vl53l0x },
|
||||
#endif
|
||||
#ifdef ENV_INCLUDE_BMP085
|
||||
{ 0x77, "BMP085", init_bmp085, query_bmp085 },
|
||||
#endif
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
{ TELEM_RAK12035_ADDRESS,"RAK12035", init_rak12035, query_rak12035 },
|
||||
#endif
|
||||
{ 0, nullptr, nullptr, nullptr } // sentinel — keeps the array non-empty
|
||||
};
|
||||
|
||||
static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TABLE[0])) - 1;
|
||||
|
||||
// ============================================================
|
||||
// begin() — scan the I2C bus, then initialize only what was
|
||||
// found. A sensor whose address does not ACK during the scan
|
||||
// is never touched by a library call, preventing hangs or
|
||||
// crashes caused by absent or misbehaving hardware.
|
||||
// ============================================================
|
||||
|
||||
bool EnvironmentSensorManager::begin() {
|
||||
#if ENV_INCLUDE_GPS
|
||||
#ifdef RAK_WISBLOCK_GPS
|
||||
rakGPSInit(); //probe base board/sockets for GPS
|
||||
rakGPSInit();
|
||||
#else
|
||||
initBasicGPS();
|
||||
#endif
|
||||
@@ -181,361 +529,52 @@ bool EnvironmentSensorManager::begin() {
|
||||
MESH_DEBUG_PRINTLN("Second I2C initialized on pins SDA: %d SCL: %d", ENV_PIN_SDA, ENV_PIN_SCL);
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
if (AHTX0.begin(TELEM_WIRE, 0, TELEM_AHTX_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found AHT10/AHT20 at address: %02X", TELEM_AHTX_ADDRESS);
|
||||
AHTX0_initialized = true;
|
||||
} else {
|
||||
AHTX0_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("AHT10/AHT20 was not found at I2C address %02X", TELEM_AHTX_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
// Scan the I2C bus before touching any sensor library.
|
||||
bool detected[128] = {};
|
||||
scanI2CBus(TELEM_WIRE, detected);
|
||||
|
||||
#if ENV_INCLUDE_BME680
|
||||
if (BME680.begin(TELEM_BME680_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS);
|
||||
BME680_initialized = true;
|
||||
} else {
|
||||
BME680_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS);
|
||||
}
|
||||
#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;
|
||||
MESH_DEBUG_PRINTLN("BME280 was not found at I2C address %02X", TELEM_BME280_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP280
|
||||
if (BMP280.begin(TELEM_BMP280_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID());
|
||||
BMP280_initialized = true;
|
||||
} else {
|
||||
BMP280_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BMP280 was not found at I2C address %02X", TELEM_BMP280_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
if (SHTC3.begin(TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor: SHTC3");
|
||||
SHTC3_initialized = true;
|
||||
} else {
|
||||
SHTC3_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("SHTC3 was not found at I2C address %02X", 0x70);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS);
|
||||
uint32_t serialNumber = 0;
|
||||
int16_t sht4x_error;
|
||||
sht4x_error = SHT4X.serialNumber(serialNumber);
|
||||
if (sht4x_error == 0) {
|
||||
MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS);
|
||||
SHT4X_initialized = true;
|
||||
} else {
|
||||
SHT4X_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
if (LPS22HB.begin()) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB");
|
||||
LPS22HB_initialized = true;
|
||||
} else {
|
||||
LPS22HB_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("LPS22HB was not found at I2C address %02X", 0x5C);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS);
|
||||
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID());
|
||||
|
||||
for(int i = 0; i < 3; i++) {
|
||||
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE);
|
||||
// Walk the sensor table and initialize only detected devices.
|
||||
_active_sensor_count = 0;
|
||||
for (size_t i = 0; i < SENSOR_TABLE_SIZE && _active_sensor_count < MAX_ACTIVE_SENSORS; i++) {
|
||||
const SensorDef& def = SENSOR_TABLE[i];
|
||||
if (!detected[def.address]) {
|
||||
MESH_DEBUG_PRINTLN("%s not detected at I2C address %02X", def.name, def.address);
|
||||
continue;
|
||||
}
|
||||
uint8_t n = def.init(TELEM_WIRE, def.address);
|
||||
if (n == 0) {
|
||||
MESH_DEBUG_PRINTLN("%s found at %02X but failed to initialize", def.name, def.address);
|
||||
continue;
|
||||
}
|
||||
MESH_DEBUG_PRINTLN("Found %s at address: %02X", def.name, def.address);
|
||||
for (uint8_t sub = 0; sub < n && _active_sensor_count < MAX_ACTIVE_SENSORS; sub++) {
|
||||
_active_sensors[_active_sensor_count++] = { def.query, sub };
|
||||
}
|
||||
INA3221_initialized = true;
|
||||
} else {
|
||||
INA3221_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219.begin(TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS);
|
||||
INA219_initialized = true;
|
||||
} else {
|
||||
INA219_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA260
|
||||
if (INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found INA260 at address: %02X", TELEM_INA260_ADDRESS);
|
||||
INA260_initialized = true;
|
||||
} else {
|
||||
INA260_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA226
|
||||
if (INA226.begin()) {
|
||||
MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS);
|
||||
INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE);
|
||||
INA226_initialized = true;
|
||||
} else {
|
||||
INA226_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("INA226 was not found at I2C address %02X", TELEM_INA226_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_MLX90614
|
||||
if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS);
|
||||
MLX90614_initialized = true;
|
||||
} else {
|
||||
MLX90614_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("MLX90614 was not found at I2C address %02X", TELEM_MLX90614_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_VL53L0X
|
||||
if (VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found VL53L0X at address: %02X", TELEM_VL53L0X_ADDRESS);
|
||||
VL53L0X_initialized = true;
|
||||
} else {
|
||||
VL53L0X_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("VL53L0X was not found at I2C address %02X", TELEM_VL53L0X_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
// First argument is MODE (aka oversampling)
|
||||
// choose ULTRALOWPOWER
|
||||
if (BMP085.begin(0, TELEM_WIRE)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor BMP085");
|
||||
BMP085_initialized = true;
|
||||
} else {
|
||||
BMP085_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("BMP085 was not found at I2C address %02X", 0x77);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
RAK12035.setup(*TELEM_WIRE);
|
||||
if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) {
|
||||
MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS);
|
||||
RAK12035_initialized = true;
|
||||
} else {
|
||||
RAK12035_initialized = false;
|
||||
MESH_DEBUG_PRINTLN("RAK12035 was not found at I2C address %02X", TELEM_RAK12035_ADDRESS);
|
||||
}
|
||||
#endif
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// querySensors() — GPS stays on channel 1; each active sensor
|
||||
// gets the next available channel in the order it was
|
||||
// initialized.
|
||||
// ============================================================
|
||||
|
||||
bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
|
||||
next_available_channel = TELEM_CHANNEL_SELF + 1;
|
||||
|
||||
if (requester_permissions & TELEM_PERM_LOCATION && gps_active) {
|
||||
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); // allow lat/lon via telemetry even if no GPS is detected
|
||||
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude);
|
||||
}
|
||||
|
||||
if (requester_permissions & TELEM_PERM_ENVIRONMENT) {
|
||||
|
||||
#if ENV_INCLUDE_AHTX0
|
||||
if (AHTX0_initialized) {
|
||||
sensors_event_t humidity, temp;
|
||||
AHTX0.getEvent(&humidity, &temp);
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
|
||||
}
|
||||
#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) {
|
||||
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
|
||||
|
||||
#if ENV_INCLUDE_BMP280
|
||||
if (BMP280_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP280.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()/100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP280.readAltitude(TELEM_BMP280_SEALEVELPRESSURE_HPA));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHTC3
|
||||
if (SHTC3_initialized) {
|
||||
sensors_event_t humidity, temp;
|
||||
SHTC3.getEvent(&humidity, &temp);
|
||||
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_SHT4X
|
||||
if (SHT4X_initialized) {
|
||||
float sht4x_humidity, sht4x_temperature;
|
||||
int16_t sht4x_error;
|
||||
sht4x_error = SHT4X.measureLowestPrecision(sht4x_temperature, sht4x_humidity);
|
||||
if (sht4x_error == 0) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, sht4x_temperature);
|
||||
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, sht4x_humidity);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_LPS22HB
|
||||
if (LPS22HB_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, LPS22HB.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, LPS22HB.readPressure() * 10); // convert kPa to hPa
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA3221
|
||||
if (INA3221_initialized) {
|
||||
for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) {
|
||||
// add only enabled INA3221 channels to telemetry
|
||||
if (INA3221.isChannelEnabled(i)) {
|
||||
float voltage = INA3221.getBusVoltage(i);
|
||||
float current = INA3221.getCurrentAmps(i);
|
||||
telemetry.addVoltage(next_available_channel, voltage);
|
||||
telemetry.addCurrent(next_available_channel, current);
|
||||
telemetry.addPower(next_available_channel, voltage * current);
|
||||
next_available_channel++;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA219
|
||||
if (INA219_initialized) {
|
||||
telemetry.addVoltage(next_available_channel, INA219.getBusVoltage_V());
|
||||
telemetry.addCurrent(next_available_channel, INA219.getCurrent_mA() / 1000);
|
||||
telemetry.addPower(next_available_channel, INA219.getPower_mW() / 1000);
|
||||
for (int i = 0; i < _active_sensor_count; i++) {
|
||||
_active_sensors[i].query(next_available_channel, _active_sensors[i].sub_channel, telemetry);
|
||||
next_available_channel++;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA260
|
||||
if (INA260_initialized) {
|
||||
telemetry.addVoltage(next_available_channel, INA260.readBusVoltage() / 1000);
|
||||
telemetry.addCurrent(next_available_channel, INA260.readCurrent() / 1000);
|
||||
telemetry.addPower(next_available_channel, INA260.readPower() / 1000);
|
||||
next_available_channel++;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_INA226
|
||||
if (INA226_initialized) {
|
||||
telemetry.addVoltage(next_available_channel, INA226.getBusVoltage());
|
||||
telemetry.addCurrent(next_available_channel, INA226.getCurrent_mA() / 1000.0);
|
||||
telemetry.addPower(next_available_channel, INA226.getPower_mW() / 1000.0);
|
||||
next_available_channel++;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_MLX90614
|
||||
if (MLX90614_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, MLX90614.readObjectTempC());
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF + 1, MLX90614.readAmbientTempC());
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_VL53L0X
|
||||
if (VL53L0X_initialized) {
|
||||
VL53L0X_RangingMeasurementData_t measure;
|
||||
VL53L0X.rangingTest(&measure, false); // pass in 'true' to get debug data
|
||||
if (measure.RangeStatus != 4) { // phase failures
|
||||
telemetry.addDistance(TELEM_CHANNEL_SELF, measure.RangeMilliMeter / 1000.0f); // convert mm to m
|
||||
} else {
|
||||
telemetry.addDistance(TELEM_CHANNEL_SELF, 0.0f); // no valid measurement
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_BMP085
|
||||
if (BMP085_initialized) {
|
||||
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP085.readTemperature());
|
||||
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP085.readPressure() / 100);
|
||||
telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if ENV_INCLUDE_RAK12035
|
||||
if (RAK12035_initialized) {
|
||||
|
||||
// RAK12035 Telemetry is Channel 2
|
||||
telemetry.addTemperature(2, RAK12035.get_sensor_temperature());
|
||||
telemetry.addPercentage(2, RAK12035.get_sensor_moisture());
|
||||
|
||||
// RAK12035 CALIBRATION Telemetry is Channel 3, if enabled
|
||||
|
||||
#ifdef ENABLE_RAK12035_CALIBRATION
|
||||
// Calibration Data Screen is Channel 3
|
||||
float cap = RAK12035.get_sensor_capacitance();
|
||||
float _wet = RAK12035.get_humidity_full();
|
||||
float _dry = RAK12035.get_humidity_zero();
|
||||
|
||||
telemetry.addFrequency(3, cap);
|
||||
telemetry.addTemperature(3, _wet);
|
||||
telemetry.addPower(3, _dry);
|
||||
|
||||
if(cap > _dry){
|
||||
RAK12035.set_humidity_zero(cap);
|
||||
}
|
||||
|
||||
if(cap < _wet){
|
||||
RAK12035.set_humidity_full(cap);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -555,8 +594,6 @@ const char* EnvironmentSensorManager::getSettingName(int i) const {
|
||||
return "gps";
|
||||
}
|
||||
#endif
|
||||
// convenient way to add params (needed for some tests)
|
||||
// if (i == settings++) return "param.2";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -567,8 +604,6 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const {
|
||||
return gps_active ? "1" : "0";
|
||||
}
|
||||
#endif
|
||||
// convenient way to add params ...
|
||||
// if (i == settings++) return "2";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -584,11 +619,7 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val
|
||||
}
|
||||
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
|
||||
}
|
||||
gps_update_interval_sec = interval_seconds > 0 ? interval_seconds : 1;
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
@@ -652,16 +683,10 @@ void EnvironmentSensorManager::rakGPSInit(){
|
||||
|
||||
//search for the correct IO standby pin depending on socket used
|
||||
if(gpsIsAwake(WB_IO2)){
|
||||
// MESH_DEBUG_PRINTLN("RAK base board is RAK19007/10");
|
||||
// MESH_DEBUG_PRINTLN("GPS is installed on Socket A");
|
||||
}
|
||||
else if(gpsIsAwake(WB_IO4)){
|
||||
// MESH_DEBUG_PRINTLN("RAK base board is RAK19003/9");
|
||||
// MESH_DEBUG_PRINTLN("GPS is installed on Socket C");
|
||||
}
|
||||
else if(gpsIsAwake(WB_IO5)){
|
||||
// MESH_DEBUG_PRINTLN("RAK base board is RAK19001/11");
|
||||
// MESH_DEBUG_PRINTLN("GPS is installed on Socket F");
|
||||
}
|
||||
else{
|
||||
MESH_DEBUG_PRINTLN("No GPS found");
|
||||
@@ -715,15 +740,17 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
|
||||
return true;
|
||||
} else if (Serial1.available()) {
|
||||
MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on");
|
||||
#ifdef PIN_GPS_EN
|
||||
if(PIN_GPS_EN){
|
||||
gpsResetPin = PIN_GPS_EN;
|
||||
}
|
||||
#endif
|
||||
serialGPSFlag = true;
|
||||
gps_active = true;
|
||||
gps_detected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
pinMode(ioPin, INPUT);
|
||||
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
|
||||
return false;
|
||||
|
||||
@@ -6,27 +6,22 @@
|
||||
|
||||
class EnvironmentSensorManager : public SensorManager {
|
||||
protected:
|
||||
int next_available_channel = TELEM_CHANNEL_SELF + 1;
|
||||
static const int MAX_ACTIVE_SENSORS = 16;
|
||||
|
||||
bool AHTX0_initialized = false;
|
||||
bool BME280_initialized = false;
|
||||
bool BMP280_initialized = false;
|
||||
bool INA3221_initialized = false;
|
||||
bool INA219_initialized = false;
|
||||
bool INA260_initialized = false;
|
||||
bool INA226_initialized = false;
|
||||
bool SHTC3_initialized = false;
|
||||
bool LPS22HB_initialized = false;
|
||||
bool MLX90614_initialized = false;
|
||||
bool VL53L0X_initialized = false;
|
||||
bool SHT4X_initialized = false;
|
||||
bool BME680_initialized = false;
|
||||
bool BMP085_initialized = false;
|
||||
bool RAK12035_initialized = false;
|
||||
// Query function pointer + sub-channel index (for multi-channel sensors like INA3221).
|
||||
// Sub-channel is 0 for all single-output sensors.
|
||||
struct ActiveSensor {
|
||||
void (*query)(uint8_t channel, uint8_t sub_channel, CayenneLPP& telemetry);
|
||||
uint8_t sub_channel;
|
||||
};
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
uint32_t gps_update_interval_sec = 1; // Default 1 second
|
||||
ActiveSensor _active_sensors[MAX_ACTIVE_SENSORS];
|
||||
int _active_sensor_count = 0;
|
||||
uint8_t next_available_channel = TELEM_CHANNEL_SELF + 1;
|
||||
|
||||
bool gps_detected = false;
|
||||
bool gps_active = false;
|
||||
uint32_t gps_update_interval_sec = 1;
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
LocationProvider* _location;
|
||||
@@ -39,7 +34,6 @@ protected:
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
public:
|
||||
#if ENV_INCLUDE_GPS
|
||||
EnvironmentSensorManager(LocationProvider &location): _location(&location){};
|
||||
|
||||
@@ -431,7 +431,7 @@ bool RAK12035_SoilMoisture::sensor_on()
|
||||
delay(10); // Wait for the sensor code to complete initialization
|
||||
*/
|
||||
uint8_t v = 0;
|
||||
time_t timeout = millis();
|
||||
uint32_t timeout = millis();
|
||||
while ((!query_sensor())) //Wait for sensor to respond to I2C commands,
|
||||
{ //indicating it is ready
|
||||
if ((millis() - timeout) > 50){ //0.5 second timeout for sensor to respond
|
||||
|
||||
@@ -28,7 +28,10 @@ bool ST7735Display::begin() {
|
||||
#endif
|
||||
digitalWrite(PIN_TFT_RST, HIGH);
|
||||
|
||||
#if defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
|
||||
#if defined(HELTEC_T1)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
#elif defined(HELTEC_TRACKER_V2) || defined(HELTEC_T096)
|
||||
display.initR(INITR_MINI160x80);
|
||||
display.setRotation(DISPLAY_ROTATION);
|
||||
uint8_t madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV |ST7735_MADCTL_BGR;//Adjust color to BGR
|
||||
|
||||
@@ -27,12 +27,6 @@ bool ST7789LCDDisplay::begin() {
|
||||
pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
|
||||
digitalWrite(PIN_TFT_LEDA_CTL, HIGH);
|
||||
}
|
||||
if (PIN_TFT_RST != -1) {
|
||||
pinMode(PIN_TFT_RST, OUTPUT);
|
||||
digitalWrite(PIN_TFT_RST, LOW);
|
||||
delay(10);
|
||||
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.
|
||||
#if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT)
|
||||
@@ -166,4 +160,4 @@ uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
|
||||
|
||||
void ST7789LCDDisplay::endFrame() {
|
||||
// display.display();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "Arduino.h"
|
||||
#ifdef PIN_BUZZER
|
||||
#include "buzzer.h"
|
||||
|
||||
@@ -12,7 +13,6 @@ void genericBuzzer::begin() {
|
||||
quiet(false);
|
||||
pinMode(PIN_BUZZER, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER, LOW); // need to pull low by default to avoid extreme power draw
|
||||
startup();
|
||||
}
|
||||
|
||||
void genericBuzzer::play(const char *melody) {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Mock AES128 class for testing
|
||||
// Provides minimal interface to allow Utils.cpp to compile
|
||||
class AES128 {
|
||||
public:
|
||||
void setKey(const uint8_t* key, size_t keySize) {}
|
||||
void encryptBlock(uint8_t* output, const uint8_t* input) {}
|
||||
void decryptBlock(uint8_t* output, const uint8_t* input) {}
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
// Mock SHA256 class for testing
|
||||
// Provides minimal interface to allow Utils.cpp to compile
|
||||
class SHA256 {
|
||||
public:
|
||||
void update(const uint8_t* data, size_t len) {}
|
||||
void finalize(uint8_t* hash, size_t hashLen) {}
|
||||
void resetHMAC(const uint8_t* key, size_t keyLen) {}
|
||||
void finalizeHMAC(const uint8_t* key, size_t keyLen, uint8_t* hash, size_t hashLen) {}
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
// Mock Stream class for native testing
|
||||
// Provides minimal interface needed by Utils.h
|
||||
|
||||
class Stream {
|
||||
public:
|
||||
virtual void print(char c) {}
|
||||
virtual void print(const char* str) {}
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include "Utils.h"
|
||||
|
||||
using namespace mesh;
|
||||
|
||||
#define HEX_BUFFER_SIZE(input) (sizeof(input) * 2 + 1)
|
||||
|
||||
TEST(UtilsToHex, ConvertSingleByte) {
|
||||
uint8_t input[] = {0xAB};
|
||||
char output[HEX_BUFFER_SIZE(input)];
|
||||
|
||||
Utils::toHex(output, input, sizeof(input));
|
||||
|
||||
EXPECT_STREQ("AB", output);
|
||||
}
|
||||
|
||||
TEST(UtilsToHex, ConvertMultipleBytes) {
|
||||
uint8_t input[] = {0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF};
|
||||
char output[HEX_BUFFER_SIZE(input)];
|
||||
|
||||
Utils::toHex(output, input, sizeof(input));
|
||||
|
||||
EXPECT_STREQ("0123456789ABCDEF", output);
|
||||
}
|
||||
|
||||
TEST(UtilsToHex, ConvertZeroByte) {
|
||||
uint8_t input[] = {0x00};
|
||||
char output[HEX_BUFFER_SIZE(input)];
|
||||
|
||||
Utils::toHex(output, input, sizeof(input));
|
||||
|
||||
EXPECT_STREQ("00", output);
|
||||
}
|
||||
|
||||
TEST(UtilsToHex, ConvertMaxByte) {
|
||||
uint8_t input[] = {0xFF};
|
||||
char output[HEX_BUFFER_SIZE(input)];
|
||||
|
||||
Utils::toHex(output, input, sizeof(input));
|
||||
|
||||
EXPECT_STREQ("FF", output);
|
||||
}
|
||||
|
||||
TEST(UtilsToHex, NullTerminatesOnEmptyInput) {
|
||||
uint8_t input[] = {0xAB};
|
||||
char output[] = "X"; // Pre-fill with X.
|
||||
|
||||
Utils::toHex(output, input, 0);
|
||||
|
||||
// Should just null-terminate at position 0
|
||||
EXPECT_EQ('\0', output[0]);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -135,3 +135,8 @@ build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||
lib_deps =
|
||||
${Ebyte_EoRa-S3.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Ebyte_EoRa-S3_kiss_modem]
|
||||
extends = Ebyte_EoRa-S3
|
||||
build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -64,21 +64,6 @@ bool radio_init() {
|
||||
return true; // success
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -23,7 +23,4 @@ extern SensorManager sensors;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -112,3 +112,7 @@ lib_deps =
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
|
||||
[env:GAT562_30S_Mesh_Kit_kiss_modem]
|
||||
extends = GAT562_30S_Mesh_Kit
|
||||
build_src_filter = ${GAT562_30S_Mesh_Kit.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -38,21 +38,6 @@ bool radio_init() {
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -24,7 +24,4 @@ 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(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -50,3 +50,8 @@ build_flags =
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${GAT562_Mesh_EVB_Pro.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
|
||||
[env:GAT562_Mesh_EVB_Pro_kiss_modem]
|
||||
extends = GAT562_Mesh_EVB_Pro
|
||||
build_src_filter = ${GAT562_Mesh_EVB_Pro.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -37,21 +37,6 @@ bool radio_init() {
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -23,7 +23,4 @@ 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(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -106,3 +106,7 @@ lib_deps =
|
||||
${GAT562_Mesh_Tracker_Pro.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:GAT562_Mesh_Tracker_Pro_kiss_modem]
|
||||
extends = GAT562_Mesh_Tracker_Pro
|
||||
build_src_filter = ${GAT562_Mesh_Tracker_Pro.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -38,21 +38,6 @@ bool radio_init() {
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -24,7 +24,4 @@ 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(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -87,3 +87,7 @@ lib_deps =
|
||||
${GAT562_Mesh_Watch13.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:GAT562_Mesh_Watch13_kiss_modem]
|
||||
extends = GAT562_Mesh_Watch13
|
||||
build_src_filter = ${GAT562_Mesh_Watch13.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -29,21 +29,6 @@ bool radio_init() {
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -25,7 +25,4 @@ 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(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -95,6 +95,11 @@ lib_deps =
|
||||
${Generic_E22.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Generic_E22_kiss_modem]
|
||||
extends = Generic_E22
|
||||
build_src_filter = ${Generic_E22.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
[env:Generic_E22_sx1268_repeater]
|
||||
extends = Generic_E22
|
||||
build_src_filter = ${Generic_E22.build_src_filter}
|
||||
|
||||
@@ -27,21 +27,6 @@ bool radio_init() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -15,8 +15,5 @@ extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
|
||||
@@ -17,18 +17,6 @@ bool radio_init() {
|
||||
return true; // success
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return millis() + radio_driver.intID(); // TODO: where to get some entropy?
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio_driver.setTxPower(dbm);
|
||||
}
|
||||
|
||||
// NOTE: as we are using the WiFi radio, the ESP_IDF will have enabled hardware RNG:
|
||||
// https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
|
||||
class ESP_RNG : public mesh::RNG {
|
||||
|
||||
@@ -10,7 +10,4 @@ extern ESP32RTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -150,3 +150,8 @@ build_src_filter = ${Heltec_ct62.build_src_filter}
|
||||
lib_deps =
|
||||
${Heltec_ct62.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
|
||||
[env:Heltec_ct62_kiss_modem]
|
||||
extends = Heltec_ct62
|
||||
build_src_filter = ${Heltec_ct62.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -16,21 +16,6 @@ bool radio_init() {
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -14,7 +14,4 @@ extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern SensorManager sensors;
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -166,3 +166,8 @@ lib_deps =
|
||||
${Heltec_E213_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:Heltec_E213_kiss_modem]
|
||||
extends = Heltec_E213_base
|
||||
build_src_filter = ${Heltec_E213_base.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -33,21 +33,6 @@ bool radio_init() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -23,7 +23,4 @@ extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -66,7 +66,6 @@ build_flags =
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D DISPLAY_CLASS=E290Display
|
||||
-D AUTO_OFF_MILLIS=0
|
||||
-D BLE_PIN_CODE=123456 ; dynamic, random PIN
|
||||
-D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
@@ -162,3 +161,8 @@ lib_deps =
|
||||
${Heltec_E290_base.lib_deps}
|
||||
${esp32_ota.lib_deps}
|
||||
bakercp/CRC32 @ ^2.0.0
|
||||
|
||||
[env:Heltec_E290_kiss_modem]
|
||||
extends = Heltec_E290_base
|
||||
build_src_filter = ${Heltec_E290_base.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -33,21 +33,6 @@ bool radio_init() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -23,7 +23,4 @@ extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -92,4 +92,9 @@ build_src_filter = ${Heltec_mesh_solar.build_src_filter}
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_mesh_solar.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_mesh_solar_kiss_modem]
|
||||
extends = Heltec_mesh_solar
|
||||
build_src_filter = ${Heltec_mesh_solar.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -23,21 +23,6 @@ bool radio_init() {
|
||||
return radio.std_init(&SPI);
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -40,7 +40,4 @@ extern SolarSensorManager sensors;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -9,7 +9,7 @@ void LoRaFEMControl::init(void)
|
||||
pinMode(P_LORA_KCT8103L_PA_CSD, OUTPUT);
|
||||
digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH);
|
||||
pinMode(P_LORA_KCT8103L_PA_CTX, OUTPUT);
|
||||
digitalWrite(P_LORA_KCT8103L_PA_CTX, HIGH);
|
||||
digitalWrite(P_LORA_KCT8103L_PA_CTX, lna_enabled ? LOW : HIGH);
|
||||
setLnaCanControl(true);
|
||||
}
|
||||
|
||||
|
||||
@@ -92,7 +92,7 @@ build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
[env:Heltec_t096_room_server]
|
||||
extends = Heltec_t096
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
+<../examples/simple_room_server>
|
||||
build_flags =
|
||||
${Heltec_t096.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_t096 Room"'
|
||||
@@ -145,4 +145,9 @@ build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_t096.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
|
||||
[env:Heltec_t096_kiss_modem]
|
||||
extends = Heltec_t096
|
||||
build_src_filter = ${Heltec_t096.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
|
||||
@@ -43,21 +43,6 @@ bool radio_init() {
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t radio_get_rng_seed() {
|
||||
return radio.random(0x7FFFFFFF);
|
||||
}
|
||||
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) {
|
||||
radio.setFrequency(freq);
|
||||
radio.setSpreadingFactor(sf);
|
||||
radio.setBandwidth(bw);
|
||||
radio.setCodingRate(cr);
|
||||
}
|
||||
|
||||
void radio_set_tx_power(int8_t dbm) {
|
||||
radio.setOutputPower(dbm);
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng); // create new random identity
|
||||
|
||||
@@ -27,7 +27,4 @@ extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
uint32_t radio_get_rng_seed();
|
||||
void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr);
|
||||
void radio_set_tx_power(int8_t dbm);
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#include "T1Board.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
const PowerMgtConfig power_config = {
|
||||
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
|
||||
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
|
||||
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
|
||||
};
|
||||
|
||||
void T1Board::initiateShutdown(uint8_t reason) {
|
||||
variant_shutdown();
|
||||
|
||||
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
|
||||
reason == SHUTDOWN_REASON_BOOT_PROTECT);
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, enable_lpcomp ? ADC_CTRL_ENABLED : !ADC_CTRL_ENABLED);
|
||||
|
||||
if (enable_lpcomp) {
|
||||
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
|
||||
}
|
||||
|
||||
enterSystemOff(reason);
|
||||
}
|
||||
#endif
|
||||
|
||||
void T1Board::begin() {
|
||||
NRF52Board::begin();
|
||||
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
checkBootVoltage(&power_config);
|
||||
#endif
|
||||
|
||||
#if defined(PIN_BOARD_SDA) && defined(PIN_BOARD_SCL)
|
||||
Wire.setPins(PIN_BOARD_SDA, PIN_BOARD_SCL);
|
||||
#endif
|
||||
Wire.begin();
|
||||
|
||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
|
||||
pinMode(PIN_GPS_EN, OUTPUT);
|
||||
digitalWrite(PIN_GPS_EN, !PIN_GPS_EN_ACTIVE);
|
||||
|
||||
pinMode(PIN_SENSOR_EN, OUTPUT);
|
||||
digitalWrite(PIN_SENSOR_EN, PIN_SENSOR_EN_ACTIVE);
|
||||
|
||||
pinMode(PIN_BUZZER, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER, LOW);
|
||||
pinMode(PIN_BUZZER_VOLTAGE_MULTIPLIER_1, OUTPUT);
|
||||
pinMode(PIN_BUZZER_VOLTAGE_MULTIPLIER_2, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER_VOLTAGE_MULTIPLIER_1, HIGH);
|
||||
digitalWrite(PIN_BUZZER_VOLTAGE_MULTIPLIER_2, HIGH);
|
||||
|
||||
periph_power.begin();
|
||||
delay(1);
|
||||
}
|
||||
|
||||
void T1Board::onBeforeTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, LED_STATE_ON);
|
||||
}
|
||||
|
||||
void T1Board::onAfterTransmit() {
|
||||
digitalWrite(P_LORA_TX_LED, !LED_STATE_ON);
|
||||
}
|
||||
|
||||
uint16_t T1Board::getBattMilliVolts() {
|
||||
analogReadResolution(12);
|
||||
analogReference(VBAT_AR_INTERNAL);
|
||||
pinMode(PIN_VBAT_READ, INPUT);
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, ADC_CTRL_ENABLED);
|
||||
|
||||
delay(10);
|
||||
int adcvalue = analogRead(PIN_VBAT_READ);
|
||||
digitalWrite(PIN_BAT_CTL, !ADC_CTRL_ENABLED);
|
||||
|
||||
return (uint16_t)((float)adcvalue * MV_LSB * ADC_MULTIPLIER);
|
||||
}
|
||||
|
||||
void T1Board::variant_shutdown() {
|
||||
nrf_gpio_cfg_default(PIN_TFT_CS);
|
||||
nrf_gpio_cfg_default(PIN_TFT_DC);
|
||||
nrf_gpio_cfg_default(PIN_TFT_SDA);
|
||||
nrf_gpio_cfg_default(PIN_TFT_SCL);
|
||||
nrf_gpio_cfg_default(PIN_TFT_RST);
|
||||
nrf_gpio_cfg_default(PIN_TFT_LEDA_CTL);
|
||||
nrf_gpio_cfg_default(PIN_TFT_VDD_CTL);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_WIRE_SDA);
|
||||
nrf_gpio_cfg_default(PIN_WIRE_SCL);
|
||||
|
||||
nrf_gpio_cfg_default(LORA_CS);
|
||||
nrf_gpio_cfg_default(SX126X_DIO1);
|
||||
nrf_gpio_cfg_default(SX126X_BUSY);
|
||||
nrf_gpio_cfg_default(SX126X_RESET);
|
||||
nrf_gpio_cfg_default(PIN_SPI_MISO);
|
||||
nrf_gpio_cfg_default(PIN_SPI_MOSI);
|
||||
nrf_gpio_cfg_default(PIN_SPI_SCK);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_SPI1_MOSI);
|
||||
nrf_gpio_cfg_default(PIN_SPI1_SCK);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_GPS_RESET);
|
||||
nrf_gpio_cfg_default(PIN_GPS_EN);
|
||||
nrf_gpio_cfg_default(PIN_GPS_PPS);
|
||||
nrf_gpio_cfg_default(PIN_GPS_RX);
|
||||
nrf_gpio_cfg_default(PIN_GPS_TX);
|
||||
|
||||
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_1);
|
||||
nrf_gpio_cfg_default(PIN_BUZZER_VOLTAGE_MULTIPLIER_2);
|
||||
|
||||
pinMode(PIN_BUZZER, OUTPUT);
|
||||
digitalWrite(PIN_BUZZER, LOW);
|
||||
|
||||
pinMode(PIN_SENSOR_EN, OUTPUT);
|
||||
digitalWrite(PIN_SENSOR_EN, !PIN_SENSOR_EN_ACTIVE);
|
||||
|
||||
pinMode(PIN_LED1, OUTPUT);
|
||||
digitalWrite(PIN_LED1, !LED_STATE_ON);
|
||||
|
||||
pinMode(PIN_BAT_CTL, OUTPUT);
|
||||
digitalWrite(PIN_BAT_CTL, !ADC_CTRL_ENABLED);
|
||||
}
|
||||
|
||||
void T1Board::powerOff() {
|
||||
variant_shutdown();
|
||||
sd_power_system_off();
|
||||
}
|
||||
|
||||
const char* T1Board::getManufacturerName() const {
|
||||
return "Heltec T1";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <MeshCore.h>
|
||||
#include <helpers/NRF52Board.h>
|
||||
#include <helpers/RefCountedDigitalPin.h>
|
||||
|
||||
class T1Board : public NRF52BoardDCDC {
|
||||
protected:
|
||||
#ifdef NRF52_POWER_MANAGEMENT
|
||||
void initiateShutdown(uint8_t reason) override;
|
||||
#endif
|
||||
void variant_shutdown();
|
||||
|
||||
public:
|
||||
RefCountedDigitalPin periph_power;
|
||||
|
||||
T1Board() : periph_power(PIN_TFT_VDD_CTL, PIN_TFT_VDD_CTL_ACTIVE), NRF52Board("T1_OTA") {}
|
||||
|
||||
void begin();
|
||||
void onBeforeTransmit() override;
|
||||
void onAfterTransmit() override;
|
||||
uint16_t getBattMilliVolts() override;
|
||||
const char* getManufacturerName() const override;
|
||||
void powerOff() override;
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
[Heltec_t1]
|
||||
extends = nrf52_base
|
||||
board = heltec_t1
|
||||
board_build.ldscript = boards/nrf52840_s140_v6.ld
|
||||
build_flags = ${nrf52_base.build_flags}
|
||||
${sensor_base.build_flags}
|
||||
-I lib/nrf52/s140_nrf52_6.1.1_API/include
|
||||
-I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52
|
||||
-I variants/heltec_t1
|
||||
-I src/helpers/ui
|
||||
-D HELTEC_T1
|
||||
-D NRF52_POWER_MANAGEMENT
|
||||
-D RADIO_CLASS=CustomSX1262
|
||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||
-D LORA_TX_POWER=22
|
||||
-D DISPLAY_CLASS=ST7735Display
|
||||
build_src_filter = ${nrf52_base.build_src_filter}
|
||||
+<helpers/*.cpp>
|
||||
+<helpers/sensors>
|
||||
+<../variants/heltec_t1>
|
||||
+<helpers/ui/ST7735Display.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
lib_deps =
|
||||
${nrf52_base.lib_deps}
|
||||
${sensor_base.lib_deps}
|
||||
adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0
|
||||
debug_tool = jlink
|
||||
upload_protocol = nrfutil
|
||||
|
||||
[env:Heltec_t1_repeater]
|
||||
extends = Heltec_t1
|
||||
build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
+<../examples/simple_repeater>
|
||||
|
||||
build_flags =
|
||||
${Heltec_t1.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_t1 Repeater"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D MAX_NEIGHBOURS=50
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:Heltec_t1_room_server]
|
||||
extends = Heltec_t1
|
||||
build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
+<../examples/simple_room_server>
|
||||
build_flags =
|
||||
${Heltec_t1.build_flags}
|
||||
-D ADVERT_NAME='"Heltec_t1 Room"'
|
||||
-D ADVERT_LAT=0.0
|
||||
-D ADVERT_LON=0.0
|
||||
-D ADMIN_PASSWORD='"password"'
|
||||
-D ROOM_PASSWORD='"hello"'
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
|
||||
[env:Heltec_t1_companion_radio_ble]
|
||||
extends = Heltec_t1
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
board_upload.maximum_size = 712704
|
||||
build_flags =
|
||||
${Heltec_t1.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
-D BLE_PIN_CODE=123456
|
||||
-D ENV_INCLUDE_GPS=1
|
||||
; -D BLE_DEBUG_LOGGING=1
|
||||
-D OFFLINE_QUEUE_SIZE=256
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_t1.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
|
||||
[env:Heltec_t1_companion_radio_usb]
|
||||
extends = Heltec_t1
|
||||
board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld
|
||||
board_upload.maximum_size = 712704
|
||||
build_flags =
|
||||
${Heltec_t1.build_flags}
|
||||
-I examples/companion_radio/ui-new
|
||||
-D MAX_CONTACTS=350
|
||||
-D MAX_GROUP_CHANNELS=40
|
||||
; -D MESH_PACKET_LOGGING=1
|
||||
; -D MESH_DEBUG=1
|
||||
build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
+<helpers/nrf52/*.cpp>
|
||||
+<helpers/ui/buzzer.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
+<../examples/companion_radio/ui-new/*.cpp>
|
||||
lib_deps =
|
||||
${Heltec_t1.lib_deps}
|
||||
densaugeo/base64 @ ~1.4.0
|
||||
end2endzone/NonBlockingRTTTL@^1.3.0
|
||||
|
||||
[env:Heltec_t1_kiss_modem]
|
||||
extends = Heltec_t1
|
||||
build_src_filter = ${Heltec_t1.build_src_filter}
|
||||
+<../examples/kiss_modem/>
|
||||
@@ -0,0 +1,48 @@
|
||||
#include "target.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <helpers/ArduinoHelpers.h>
|
||||
|
||||
#ifdef ENV_INCLUDE_GPS
|
||||
#include <helpers/sensors/MicroNMEALocationProvider.h>
|
||||
#endif
|
||||
|
||||
T1Board board;
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
||||
#else
|
||||
RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY);
|
||||
#endif
|
||||
|
||||
WRAPPER_CLASS radio_driver(radio, board);
|
||||
|
||||
VolatileRTCClock fallback_clock;
|
||||
AutoDiscoverRTCClock rtc_clock(fallback_clock);
|
||||
|
||||
#if ENV_INCLUDE_GPS
|
||||
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power);
|
||||
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
|
||||
#else
|
||||
EnvironmentSensorManager sensors;
|
||||
#endif
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
DISPLAY_CLASS display(&board.periph_power);
|
||||
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
|
||||
#endif
|
||||
|
||||
bool radio_init() {
|
||||
rtc_clock.begin(Wire);
|
||||
|
||||
#if defined(P_LORA_SCLK)
|
||||
return radio.std_init(&SPI);
|
||||
#else
|
||||
return radio.std_init();
|
||||
#endif
|
||||
}
|
||||
|
||||
mesh::LocalIdentity radio_new_identity() {
|
||||
RadioNoiseListener rng(radio);
|
||||
return mesh::LocalIdentity(&rng);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <T1Board.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/sensors/EnvironmentSensorManager.h>
|
||||
#include <helpers/sensors/LocationProvider.h>
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#include <helpers/ui/ST7735Display.h>
|
||||
#else
|
||||
#include "helpers/ui/NullDisplayDriver.h"
|
||||
#endif
|
||||
|
||||
extern T1Board board;
|
||||
extern WRAPPER_CLASS radio_driver;
|
||||
extern AutoDiscoverRTCClock rtc_clock;
|
||||
extern EnvironmentSensorManager sensors;
|
||||
|
||||
#ifdef DISPLAY_CLASS
|
||||
extern DISPLAY_CLASS display;
|
||||
extern MomentaryButton user_btn;
|
||||
#endif
|
||||
|
||||
bool radio_init();
|
||||
mesh::LocalIdentity radio_new_identity();
|
||||
@@ -0,0 +1,15 @@
|
||||
#include "variant.h"
|
||||
#include "wiring_constants.h"
|
||||
#include "wiring_digital.h"
|
||||
|
||||
const uint32_t g_ADigitalPinMap[] = {
|
||||
0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
||||
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
|
||||
40, 41, 42, 43, 44, 45, 46, 47
|
||||
};
|
||||
|
||||
void initVariant()
|
||||
{
|
||||
pinMode(PIN_USER_BTN, INPUT);
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Heltec Mesh Node T1 nRF52840 variant.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "WVariant.h"
|
||||
|
||||
#define USE_LFXO
|
||||
#define VARIANT_MCK (64000000ul)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Number of pins
|
||||
|
||||
#define PINS_COUNT (48)
|
||||
#define NUM_DIGITAL_PINS (48)
|
||||
#define NUM_ANALOG_INPUTS (1)
|
||||
#define NUM_ANALOG_OUTPUTS (0)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Display
|
||||
|
||||
#define ST7735_CS (0 + 12)
|
||||
#define ST7735_RS (0 + 22)
|
||||
#define ST7735_SDA (0 + 24)
|
||||
#define ST7735_SCK (32 + 0)
|
||||
#define ST7735_RESET (0 + 20)
|
||||
#define ST7735_MISO (-1)
|
||||
#define ST7735_BUSY (-1)
|
||||
#define ST7735_BL (0 + 15)
|
||||
#define VTFT_CTRL (0 + 13)
|
||||
|
||||
#define PIN_TFT_CS ST7735_CS
|
||||
#define PIN_TFT_DC ST7735_RS
|
||||
#define PIN_TFT_SDA ST7735_SDA
|
||||
#define PIN_TFT_SCL ST7735_SCK
|
||||
#define PIN_TFT_RST ST7735_RESET
|
||||
#define PIN_TFT_LEDA_CTL ST7735_BL
|
||||
#define PIN_TFT_LEDA_CTL_ACTIVE LOW
|
||||
#define PIN_TFT_VDD_CTL VTFT_CTRL
|
||||
#define PIN_TFT_VDD_CTL_ACTIVE LOW
|
||||
#define DISPLAY_ROTATION 3
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Builtin LEDs
|
||||
|
||||
#define PIN_LED1 (0 + 16)
|
||||
#define LED_BUILTIN PIN_LED1
|
||||
#define PIN_LED LED_BUILTIN
|
||||
#define LED_RED (-1)
|
||||
#define LED_BLUE (-1)
|
||||
#define LED_GREEN (-1)
|
||||
#define LED_PIN LED_BUILTIN
|
||||
#define LED_STATE_ON LOW
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Builtin buttons
|
||||
|
||||
#define PIN_BUTTON1 (32 + 10)
|
||||
#define PIN_BUTTON2 (0 + 14)
|
||||
#define BUTTON_PIN PIN_BUTTON1
|
||||
#define BUTTON_PIN2 PIN_BUTTON2
|
||||
#define PIN_USER_BTN BUTTON_PIN
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// UART
|
||||
|
||||
// No longer populated on PCB.
|
||||
#define PIN_SERIAL2_RX (-1)
|
||||
#define PIN_SERIAL2_TX (-1)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// I2C
|
||||
|
||||
#define WIRE_INTERFACES_COUNT (1)
|
||||
|
||||
#define PIN_WIRE_SDA (32 + 3)
|
||||
#define PIN_WIRE_SCL (0 + 10)
|
||||
|
||||
#define PIN_SENSOR_EN (32 + 6)
|
||||
#define PIN_SENSOR_EN_ACTIVE LOW
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// LoRa
|
||||
|
||||
#define USE_SX1262
|
||||
#define SX126X_CS (32 + 11)
|
||||
#define LORA_CS SX126X_CS
|
||||
#define SX126X_DIO1 (0 + 31)
|
||||
#define SX126X_BUSY (0 + 29)
|
||||
#define SX126X_RESET (0 + 2)
|
||||
#define SX126X_DIO2_AS_RF_SWITCH true
|
||||
#define SX126X_DIO3_TCXO_VOLTAGE 1.8
|
||||
#define SX126X_CURRENT_LIMIT 140
|
||||
#define SX126X_RX_BOOSTED_GAIN 1
|
||||
|
||||
#define P_LORA_DIO_1 SX126X_DIO1
|
||||
#define P_LORA_NSS LORA_CS
|
||||
#define P_LORA_RESET SX126X_RESET
|
||||
#define P_LORA_BUSY SX126X_BUSY
|
||||
#define P_LORA_TX_LED PIN_LED1
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// SPI
|
||||
|
||||
#define SPI_INTERFACES_COUNT (2)
|
||||
|
||||
#define PIN_SPI_MISO (0 + 3)
|
||||
#define PIN_SPI_MOSI (32 + 14)
|
||||
#define PIN_SPI_SCK (32 + 13)
|
||||
#define PIN_SPI_NSS LORA_CS
|
||||
|
||||
#define PIN_SPI1_MISO ST7735_MISO
|
||||
#define PIN_SPI1_MOSI ST7735_SDA
|
||||
#define PIN_SPI1_SCK ST7735_SCK
|
||||
|
||||
#define P_LORA_SCLK PIN_SPI_SCK
|
||||
#define P_LORA_MISO PIN_SPI_MISO
|
||||
#define P_LORA_MOSI PIN_SPI_MOSI
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// GPS
|
||||
|
||||
#define GPS_UC6580
|
||||
#define GPS_BAUD_RATE 115200
|
||||
#define PIN_GPS_RESET (0 + 26)
|
||||
#define GPS_RESET PIN_GPS_RESET
|
||||
#define GPS_RESET_MODE LOW
|
||||
#define PIN_GPS_RESET_ACTIVE LOW
|
||||
#define PIN_GPS_EN (0 + 4)
|
||||
#define GPS_EN PIN_GPS_EN
|
||||
#define GPS_EN_ACTIVE LOW
|
||||
#define PIN_GPS_EN_ACTIVE LOW
|
||||
#define PIN_GPS_PPS (32 + 9)
|
||||
#define GPS_TX_PIN (0 + 8)
|
||||
#define GPS_RX_PIN (0 + 7)
|
||||
#define PIN_GPS_TX GPS_TX_PIN
|
||||
#define PIN_GPS_RX GPS_RX_PIN
|
||||
|
||||
#define PIN_SERIAL1_RX GPS_RX_PIN
|
||||
#define PIN_SERIAL1_TX GPS_TX_PIN
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Buzzer
|
||||
|
||||
#define PIN_BUZZER (0 + 9)
|
||||
#define PIN_BUZZER_VOLTAGE_MULTIPLIER_1 (32 + 2)
|
||||
#define PIN_BUZZER_VOLTAGE_MULTIPLIER_2 (32 + 5)
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Battery
|
||||
|
||||
#define ADC_CTRL (0 + 11)
|
||||
#define PIN_BAT_CTL ADC_CTRL
|
||||
#define ADC_CTRL_ENABLED HIGH
|
||||
#define BATTERY_PIN (0 + 5)
|
||||
#define PIN_VBAT_READ BATTERY_PIN
|
||||
#define ADC_RESOLUTION (14)
|
||||
|
||||
#define AREF_VOLTAGE (3.0)
|
||||
#define VBAT_AR_INTERNAL AR_INTERNAL_3_0
|
||||
#define ADC_MULTIPLIER (4.916F)
|
||||
#define MV_LSB (3000.0F / 4096.0F)
|
||||
|
||||
#define PWRMGT_VOLTAGE_BOOTLOCK 3100
|
||||
#define PWRMGT_LPCOMP_AIN 3
|
||||
#define PWRMGT_LPCOMP_REFSEL 1
|
||||
|
||||
#define HAS_RTC 0
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user