Browse Source

Merge remote-tracking branch 'upstream/dev' into meshcore_seeed_sensecap_indicator

pull/2568/head
Christos Themelis 1 month ago
parent
commit
581dc526cb
  1. 4
      .github/workflows/pr-build-check.yml
  2. 31
      .github/workflows/run-unit-tests.yml
  3. 8
      README.md
  4. 198
      arch/nrf52/extra_scripts/patch_bluefruit.py
  5. 7
      boards/t-echo.json
  6. 10
      docs/cli_commands.md
  7. 144
      docs/companion_protocol.md
  8. 34
      docs/faq.md
  9. 1
      docs/number_allocations.md
  10. 45
      examples/companion_radio/MyMesh.cpp
  11. 3
      examples/companion_radio/MyMesh.h
  12. 2
      examples/companion_radio/main.cpp
  13. 31
      examples/companion_radio/ui-new/UITask.cpp
  14. 1
      examples/companion_radio/ui-orig/UITask.cpp
  15. 24
      examples/kiss_modem/KissModem.cpp
  16. 2
      examples/kiss_modem/KissModem.h
  17. 11
      examples/kiss_modem/main.cpp
  18. 10
      examples/simple_repeater/MyMesh.cpp
  19. 12
      examples/simple_repeater/UITask.cpp
  20. 2
      examples/simple_repeater/main.cpp
  21. 10
      examples/simple_room_server/MyMesh.cpp
  22. 12
      examples/simple_room_server/UITask.cpp
  23. 2
      examples/simple_room_server/main.cpp
  24. 6
      examples/simple_secure_chat/main.cpp
  25. 10
      examples/simple_sensor/SensorMesh.cpp
  26. 4
      examples/simple_sensor/SensorMesh.h
  27. 12
      examples/simple_sensor/UITask.cpp
  28. 2
      examples/simple_sensor/main.cpp
  29. 29
      platformio.ini
  30. 2
      src/helpers/AutoDiscoverRTCClock.cpp
  31. 10
      src/helpers/CommonCLI.cpp
  32. 4
      src/helpers/esp32/ESPNOWRadio.cpp
  33. 7
      src/helpers/esp32/ESPNOWRadio.h
  34. 10
      src/helpers/radiolib/CustomLLCC68Wrapper.h
  35. 2
      src/helpers/radiolib/CustomLR1110.h
  36. 13
      src/helpers/radiolib/CustomLR1110Wrapper.h
  37. 10
      src/helpers/radiolib/CustomSTM32WLxWrapper.h
  38. 10
      src/helpers/radiolib/CustomSX1262Wrapper.h
  39. 10
      src/helpers/radiolib/CustomSX1268Wrapper.h
  40. 10
      src/helpers/radiolib/CustomSX1276Wrapper.h
  41. 10
      src/helpers/radiolib/RadioLibWrappers.cpp
  42. 10
      src/helpers/radiolib/RadioLibWrappers.h
  43. 733
      src/helpers/sensors/EnvironmentSensorManager.cpp
  44. 34
      src/helpers/sensors/EnvironmentSensorManager.h
  45. 2
      src/helpers/sensors/RAK12035_SoilMoisture.cpp
  46. 8
      src/helpers/ui/ST7789LCDDisplay.cpp
  47. 1
      src/helpers/ui/buzzer.cpp
  48. 13
      test/mocks/AES.h
  49. 14
      test/mocks/SHA256.h
  50. 10
      test/mocks/Stream.h
  51. 57
      test/test_utils/test_tohex.cpp
  52. 5
      variants/ebyte_eora_s3/platformio.ini
  53. 15
      variants/ebyte_eora_s3/target.cpp
  54. 3
      variants/ebyte_eora_s3/target.h
  55. 4
      variants/gat562_30s_mesh_kit/platformio.ini
  56. 15
      variants/gat562_30s_mesh_kit/target.cpp
  57. 3
      variants/gat562_30s_mesh_kit/target.h
  58. 5
      variants/gat562_mesh_evb_pro/platformio.ini
  59. 15
      variants/gat562_mesh_evb_pro/target.cpp
  60. 3
      variants/gat562_mesh_evb_pro/target.h
  61. 4
      variants/gat562_mesh_tracker_pro/platformio.ini
  62. 15
      variants/gat562_mesh_tracker_pro/target.cpp
  63. 3
      variants/gat562_mesh_tracker_pro/target.h
  64. 4
      variants/gat562_mesh_watch13/platformio.ini
  65. 15
      variants/gat562_mesh_watch13/target.cpp
  66. 3
      variants/gat562_mesh_watch13/target.h
  67. 5
      variants/generic-e22/platformio.ini
  68. 15
      variants/generic-e22/target.cpp
  69. 3
      variants/generic-e22/target.h
  70. 12
      variants/generic_espnow/target.cpp
  71. 3
      variants/generic_espnow/target.h
  72. 5
      variants/heltec_ct62/platformio.ini
  73. 15
      variants/heltec_ct62/target.cpp
  74. 3
      variants/heltec_ct62/target.h
  75. 5
      variants/heltec_e213/platformio.ini
  76. 15
      variants/heltec_e213/target.cpp
  77. 3
      variants/heltec_e213/target.h
  78. 5
      variants/heltec_e290/platformio.ini
  79. 15
      variants/heltec_e290/target.cpp
  80. 3
      variants/heltec_e290/target.h
  81. 7
      variants/heltec_mesh_solar/platformio.ini
  82. 15
      variants/heltec_mesh_solar/target.cpp
  83. 3
      variants/heltec_mesh_solar/target.h
  84. 2
      variants/heltec_t096/LoRaFEMControl.cpp
  85. 9
      variants/heltec_t096/platformio.ini
  86. 15
      variants/heltec_t096/target.cpp
  87. 3
      variants/heltec_t096/target.h
  88. 12
      variants/heltec_t114/platformio.ini
  89. 15
      variants/heltec_t114/target.cpp
  90. 3
      variants/heltec_t114/target.h
  91. 5
      variants/heltec_t190/platformio.ini
  92. 15
      variants/heltec_t190/target.cpp
  93. 3
      variants/heltec_t190/target.h
  94. 5
      variants/heltec_tracker/platformio.ini
  95. 15
      variants/heltec_tracker/target.cpp
  96. 3
      variants/heltec_tracker/target.h
  97. 2
      variants/heltec_tracker_v2/LoRaFEMControl.cpp
  98. 7
      variants/heltec_tracker_v2/platformio.ini
  99. 15
      variants/heltec_tracker_v2/target.cpp
  100. 3
      variants/heltec_tracker_v2/target.h

4
.github/workflows/pr-build-check.yml

@ -39,6 +39,10 @@ jobs:
- wio-e5-mini_repeater - wio-e5-mini_repeater
# ESP32-C6 # ESP32-C6
- LilyGo_Tlora_C6_repeater_ - LilyGo_Tlora_C6_repeater_
# LR1110 (nRF52)
- wio_wm1110_repeater
# SX1276 (ESP32)
- Tbeam_SX1276_repeater
steps: steps:
- name: Clone Repo - name: Clone Repo

31
.github/workflows/run-unit-tests.yml

@ -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/

8
README.md

@ -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. 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 ## 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: There are a number of fairly major features in the pipeline, with no particular time-frames attached yet. In very rough chronological order:

198
arch/nrf52/extra_scripts/patch_bluefruit.py

@ -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)

7
boards/t-echo.json

@ -53,11 +53,14 @@
"protocols": [ "protocols": [
"jlink", "jlink",
"nrfjprog", "nrfjprog",
"nrfutil",
"stlink", "stlink",
"cmsis-dap", "cmsis-dap",
"blackmagic" "blackmagic"
] ],
"use_1200bps_touch": true,
"wait_for_upload_port": true
}, },
"url": "https://os.mbed.com/platforms/Nordic-nRF52840-DK/", "url": "https://os.mbed.com/platforms/Nordic-nRF52840-DK/",
"vendor": "Nordic" "vendor": "Nordic"
} }

10
docs/cli_commands.md

@ -456,7 +456,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 **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.
--- ---
@ -490,6 +490,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Default:** `0.5` **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 #### View or change the retransmit delay factor for direct traffic
@ -502,6 +504,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Default:** `0.2` **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 #### [Experimental] View or change the processing delay for received traffic
@ -514,6 +518,8 @@ This document provides an overview of CLI commands that can be sent to MeshCore
**Default:** `0.0` **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 #### View or change the duty cycle limit
@ -927,7 +933,7 @@ region save
--- ---
#### View or change thevalue of a sensor #### View or change the value of a sensor
**Usage:** **Usage:**
- `sensor get <key>` - `sensor get <key>`
- `sensor set <key> <value>` - `sensor set <key> <value>`

144
docs/companion_protocol.md

@ -283,32 +283,110 @@ Bytes 7+: Message Text (UTF-8, variable length)
### 6. Send Channel Data Datagram ### 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**: **Command Format**:
``` ```
Byte 0: 0x3E Byte 0: 0x3E
Bytes 1-2: Data Type (`data_type`, 16-bit little-endian) Byte 1: Channel Index (0-7)
Byte 3: Channel Index (0-7) Byte 2: Path Length (0xFF = flood, otherwise actual path length)
Bytes 4+: Binary payload bytes (variable 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**: **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. - `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. - Values `0x0001`–`0xFFFE` are available for registered application/community namespaces. See the [Registered data_type values](#registered-data_type-values) table below.
**Note**: Applications that need a timestamp should encode it inside the binary payload.
**Limits**: **Limits**:
- Maximum payload length is `163` bytes. - Maximum payload length is `MAX_CHANNEL_DATA_LENGTH = MAX_FRAME_SIZE - 9 = 163` bytes.
- Larger payloads are rejected with `PACKET_ERROR`. - Larger payloads are rejected with `PACKET_ERROR` (`ERR_CODE_ILLEGAL_ARG`).
**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 |
**Response**: `PACKET_OK` (0x00) on success 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. **Purpose**: Request the next queued message from the device.
@ -325,13 +403,14 @@ Byte 0: 0x0A
**Response**: **Response**:
- `PACKET_CHANNEL_MSG_RECV` (0x08) or `PACKET_CHANNEL_MSG_RECV_V3` (0x11) for channel messages - `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_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 - `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. **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. **Purpose**: Query device battery voltage and storage usage.
@ -527,6 +606,15 @@ Use the `SEND_CHANNEL_MESSAGE` command (see [Commands](#commands)).
## Response Parsing ## 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 ### Packet Types
| Value | Name | Description | | 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) | | 0x10 | PACKET_CONTACT_MSG_RECV_V3 | Contact message (V3 with SNR) |
| 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) | | 0x11 | PACKET_CHANNEL_MSG_RECV_V3 | Channel message (V3 with SNR) |
| 0x12 | PACKET_CHANNEL_INFO | Channel information | | 0x12 | PACKET_CHANNEL_INFO | Channel information |
| 0x1B | PACKET_CHANNEL_DATA_RECV | Channel data datagram |
| 0x80 | PACKET_ADVERTISEMENT | Advertisement packet | | 0x80 | PACKET_ADVERTISEMENT | Advertisement packet |
| 0x82 | PACKET_ACK | Acknowledgment | | 0x82 | PACKET_ACK | Acknowledgment |
| 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification | | 0x83 | PACKET_MESSAGES_WAITING | Messages waiting notification |
@ -718,22 +807,18 @@ Bytes 1-6: ACK Code (6 bytes, hex)
### Error Codes ### 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 | | Code | Constant (firmware) | Description |
|------------|-------------| |------|----------------------------|------------------------------------------------------------------------------|
| 0x00 | Generic error (no specific code) | | 1 | `ERR_CODE_UNSUPPORTED_CMD` | Unknown or unsupported command byte / sub-command |
| 0x01 | Invalid command | | 2 | `ERR_CODE_NOT_FOUND` | Target not found (channel, contact, message, etc.) |
| 0x02 | Invalid parameter | | 3 | `ERR_CODE_TABLE_FULL` | Internal queue or table is full — retry later |
| 0x03 | Channel not found | | 4 | `ERR_CODE_BAD_STATE` | Operation not valid in current device state (e.g. iterator already running) |
| 0x04 | Channel already exists | | 5 | `ERR_CODE_FILE_IO_ERROR` | Filesystem or storage I/O failure |
| 0x05 | Channel index out of range | | 6 | `ERR_CODE_ILLEGAL_ARG` | Invalid argument (bad length, out-of-range value, reserved field, etc.) |
| 0x06 | Secret mismatch |
| 0x07 | Message too long |
| 0x08 | Device busy |
| 0x09 | Not enough storage |
**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 ### Frame Handling
@ -765,7 +850,8 @@ BLE implementations enqueue and deliver one protocol frame per BLE write/notific
- `GET_CHANNEL``PACKET_CHANNEL_INFO` - `GET_CHANNEL``PACKET_CHANNEL_INFO`
- `SET_CHANNEL``PACKET_OK` or `PACKET_ERROR` - `SET_CHANNEL``PACKET_OK` or `PACKET_ERROR`
- `SEND_CHANNEL_MESSAGE``PACKET_MSG_SENT` - `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` - `GET_BATTERY``PACKET_BATTERY`
4. **Timeout Handling**: 4. **Timeout Handling**:

34
docs/faq.md

@ -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.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.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.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.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 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.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.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. 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) - [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.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.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.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.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.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) - [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.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.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.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.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.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.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) - [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.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.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.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.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) - [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) - [6. Troubleshooting](#6-troubleshooting)
@ -187,7 +187,7 @@ The T-Deck firmware is free to download and most features are available without
### 2.3. Q: What frequencies are supported by MeshCore? ### 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. **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. 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.
@ -271,7 +271,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. 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/ **A:** You can generate a new private key and specific the first byte of its public key here: https://gessaman.com/mc-keygen/
@ -280,7 +280,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. 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. **A:** This may be due to the SX1262 radio's auto gain control feature. You can use this command to periodically reset its AGC.
@ -379,7 +379,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? ### 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**. **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:** **A:**
T-Deck uses the same key the smartphone apps use but in base64 T-Deck uses the same key the smartphone apps use but in base64
`izOH6cXN6mrJ5e26oRXNcg==` `izOH6cXN6mrJ5e26oRXNcg==`
@ -495,7 +495,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. 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. 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.
@ -508,7 +508,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) [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: **A:** The smartphone app key is in hex:
` 8b3387e9c5cdea6ac9e5edbaa115cd72` ` 8b3387e9c5cdea6ac9e5edbaa115cd72`
@ -650,11 +650,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 - 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? ### 5.15. Q: Are there client applications for Windows or Mac?
@ -708,7 +708,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 RAK, click the reset button **TWICE**
- For T1000-e, quickly disconnect and reconnect the magnetic side of the cable from the device **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 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 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 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` - RAK WisBlock and Heltec T114: `Flash_erase-nRF32_softdevice_v6.uf2`
@ -818,7 +818,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? ### 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:** **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.** ⚠️ **WARNING: Set these values at your own risk. Incorrect power settings can permanently damage your radio hardware.**

1
docs/number_allocations.md

@ -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 | | Data-Type range | App name | Contact |
|-----------------|-----------------------------|------------------------------------------------------| |-----------------|-----------------------------|------------------------------------------------------|
| 0000 - 00FF | -reserved for internal use- | | | 0000 - 00FF | -reserved for internal use- | |
| 0100 | MeshCore Open | [email protected] — https://github.com/zjs81/meshcore-open |
| FF00 - FFFF | -reserved for testing/dev- | | | FF00 - FFFF | -reserved for testing/dev- | |
(add rows, inside the range 0100 - FEFF for custom apps) (add rows, inside the range 0100 - FEFF for custom apps)

45
examples/companion_radio/MyMesh.cpp

@ -495,19 +495,27 @@ void MyMesh::sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint3
void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) { void MyMesh::sendFloodScoped(const ContactInfo& recipient, mesh::Packet* pkt, uint32_t delay_millis) {
// TODO: dynamic send_scope, depending on recipient and current 'home' Region // TODO: dynamic send_scope, depending on recipient and current 'home' Region
TransportKey default_scope; if (send_unscoped) {
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key)); 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; auto scope = send_scope.isNull() ? &default_scope : &send_scope;
sendFloodScoped(*scope, pkt, delay_millis); sendFloodScoped(*scope, pkt, delay_millis);
}
} }
void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) { void MyMesh::sendFloodScoped(const mesh::GroupChannel& channel, mesh::Packet* pkt, uint32_t delay_millis) {
// TODO: have per-channel send_scope // TODO: have per-channel send_scope
TransportKey default_scope; if (send_unscoped) {
memcpy(&default_scope.key, _prefs.default_scope_key, sizeof(default_scope.key)); 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; auto scope = send_scope.isNull() ? &default_scope : &send_scope;
sendFloodScoped(*scope, pkt, delay_millis); sendFloodScoped(*scope, pkt, delay_millis);
}
} }
void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp, void MyMesh::onMessageRecv(const ContactInfo &from, mesh::Packet *pkt, uint32_t sender_timestamp,
@ -856,6 +864,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
dirty_contacts_expiry = 0; dirty_contacts_expiry = 0;
memset(advert_paths, 0, sizeof(advert_paths)); memset(advert_paths, 0, sizeof(advert_paths));
memset(send_scope.key, 0, sizeof(send_scope.key)); memset(send_scope.key, 0, sizeof(send_scope.key));
send_unscoped = false;
// defaults // defaults
memset(&_prefs, 0, sizeof(_prefs)); memset(&_prefs, 0, sizeof(_prefs));
@ -951,8 +960,8 @@ void MyMesh::begin(bool has_display) {
addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel addChannel("Public", PUBLIC_GROUP_PSK); // pre-configure Andy's public channel
_store->loadChannels(this); _store->loadChannels(this);
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm); radio_driver.setTxPower(_prefs.tx_power_dbm);
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled"); radio_driver.getRxBoostedGainMode() ? "Enabled" : "Disabled");
@ -977,9 +986,13 @@ struct FreqRange {
}; };
static FreqRange repeat_freq_ranges[] = { static FreqRange repeat_freq_ranges[] = {
#ifdef ALLOWED_REPEAT_FREQ_RANGE
ALLOWED_REPEAT_FREQ_RANGE
#else
{ 433000, 433000 }, { 433000, 433000 },
{ 869000, 869000 }, { 869000, 869000 },
{ 918000, 918000 } { 918000, 918000 }
#endif
}; };
bool MyMesh::isValidClientRepeatFreq(uint32_t f) const { bool MyMesh::isValidClientRepeatFreq(uint32_t f) const {
@ -1378,7 +1391,7 @@ void MyMesh::handleCmdFrame(size_t len) {
_prefs.client_repeat = repeat; _prefs.client_repeat = repeat;
savePrefs(); 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, MESH_DEBUG_PRINTLN("OK: CMD_SET_RADIO_PARAMS: f=%d, bw=%d, sf=%d, cr=%d", freq, bw, (uint32_t)sf,
(uint32_t)cr); (uint32_t)cr);
@ -1395,7 +1408,7 @@ void MyMesh::handleCmdFrame(size_t len) {
} else { } else {
_prefs.tx_power_dbm = power; _prefs.tx_power_dbm = power;
savePrefs(); savePrefs();
radio_set_tx_power(_prefs.tx_power_dbm); radio_driver.setTxPower(_prefs.tx_power_dbm);
writeOKFrame(); writeOKFrame();
} }
} else if (cmd_frame[0] == CMD_SET_TUNING_PARAMS) { } else if (cmd_frame[0] == CMD_SET_TUNING_PARAMS) {
@ -1889,10 +1902,14 @@ void MyMesh::handleCmdFrame(size_t len) {
} }
} else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE_KEY && len >= 2 && cmd_frame[1] == 0) { } else if (cmd_frame[0] == CMD_SET_FLOOD_SCOPE_KEY && len >= 2 && cmd_frame[1] == 0) {
if (len >= 2 + 16) { 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 { } 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(); writeOKFrame();
} else if (cmd_frame[0] == CMD_SET_DEFAULT_FLOOD_SCOPE && len >= 1) { } else if (cmd_frame[0] == CMD_SET_DEFAULT_FLOOD_SCOPE && len >= 1) {
if (len >= 1+31+16) { if (len >= 1+31+16) {

3
examples/companion_radio/MyMesh.h

@ -5,7 +5,7 @@
#include "AbstractUITask.h" #include "AbstractUITask.h"
/*------------ Frame Protocol --------------*/ /*------------ Frame Protocol --------------*/
#define FIRMWARE_VER_CODE 11 #define FIRMWARE_VER_CODE 12
#ifndef FIRMWARE_BUILD_DATE #ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "19 Apr 2026" #define FIRMWARE_BUILD_DATE "19 Apr 2026"
@ -218,6 +218,7 @@ private:
uint32_t _active_ble_pin; uint32_t _active_ble_pin;
bool _iter_started; bool _iter_started;
bool _cli_rescue; bool _cli_rescue;
bool send_unscoped; // force un-scoped flood (instead of using send_scope)
char cli_command[80]; char cli_command[80];
uint8_t app_target_ver; uint8_t app_target_ver;
uint8_t *sign_data; uint8_t *sign_data;

2
examples/companion_radio/main.cpp

@ -125,7 +125,7 @@ void setup() {
if (!radio_init()) { halt(); } if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed()); fast_rng.begin(radio_driver.getRngSeed());
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
InternalFS.begin(); InternalFS.begin();

31
examples/companion_radio/ui-new/UITask.cpp

@ -57,13 +57,21 @@ public:
int logoWidth = 128; int logoWidth = 128;
display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); 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 // version info
display.setColor(DisplayDriver::LIGHT); display.setColor(DisplayDriver::LIGHT);
display.setTextSize(2); display.setTextSize(1);
display.drawTextCentered(display.width()/2, 22, _version_info); display.drawTextCentered(display.width()/2, 35, _version_info);
display.setTextSize(1); display.setTextSize(1);
display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE); display.drawTextCentered(display.width()/2, 48, FIRMWARE_BUILD_DATE);
return 1000; return 1000;
} }
@ -146,7 +154,7 @@ class HomeScreen : public UIScreen {
bool sensors_scroll = false; bool sensors_scroll = false;
int sensors_scroll_offset = 0; int sensors_scroll_offset = 0;
int next_sensors_refresh = 0; int next_sensors_refresh = 0;
void refresh_sensors() { void refresh_sensors() {
if (millis() > next_sensors_refresh) { if (millis() > next_sensors_refresh) {
sensors_lpp.reset(); sensors_lpp.reset();
@ -170,7 +178,7 @@ class HomeScreen : public UIScreen {
public: public:
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs) 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) { } _shutdown_init(false), sensors_lpp(200) { }
void poll() override { void poll() override {
@ -213,7 +221,7 @@ public:
IPAddress ip = WiFi.localIP(); IPAddress ip = WiFi.localIP();
snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); snprintf(tmp, sizeof(tmp), "IP: %d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);
display.setTextSize(1); display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 54, tmp); display.drawTextCentered(display.width() / 2, 54, tmp);
#endif #endif
if (_task->hasConnection()) { if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN); display.setColor(DisplayDriver::GREEN);
@ -241,10 +249,10 @@ public:
} else { } else {
sprintf(tmp, "%dh", secs / (60*60)); sprintf(tmp, "%dh", secs / (60*60));
} }
int timestamp_width = display.getTextWidth(tmp); int timestamp_width = display.getTextWidth(tmp);
int max_name_width = display.width() - timestamp_width - 1; int max_name_width = display.width() - timestamp_width - 1;
char filtered_recent_name[sizeof(a->name)]; char filtered_recent_name[sizeof(a->name)];
display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name)); display.translateUTF8ToBlocks(filtered_recent_name, a->name, sizeof(filtered_recent_name));
display.drawTextEllipsized(0, y, max_name_width, 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); display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 12; y = y + 12;
display.drawTextLeftAlign(0, y, "pos"); display.drawTextLeftAlign(0, y, "pos");
sprintf(buf, "%.4f %.4f", sprintf(buf, "%.4f %.4f",
nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.); nmea->getLatitude()/1000000., nmea->getLongitude()/1000000.);
display.drawTextRightAlign(display.width()-1, y, buf); display.drawTextRightAlign(display.width()-1, y, buf);
y = y + 12; y = y + 12;
@ -567,6 +575,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#ifdef PIN_BUZZER #ifdef PIN_BUZZER
buzzer.begin(); buzzer.begin();
buzzer.quiet(_node_prefs->buzzer_quiet); buzzer.quiet(_node_prefs->buzzer_quiet);
buzzer.startup();
#endif #endif
#ifdef PIN_VIBRATION #ifdef PIN_VIBRATION
@ -741,7 +750,7 @@ void UITask::loop() {
#endif #endif
#if defined(PIN_USER_BTN_ANA) #if defined(PIN_USER_BTN_ANA)
if (abs(millis() - _analogue_pin_read_millis) > 10) { if (abs(millis() - _analogue_pin_read_millis) > 10) {
ev = analog_btn.check(); int ev = analog_btn.check();
if (ev == BUTTON_EVENT_CLICK) { if (ev == BUTTON_EVENT_CLICK) {
c = checkDisplayOn(KEY_NEXT); c = checkDisplayOn(KEY_NEXT);
} else if (ev == BUTTON_EVENT_LONG_PRESS) { } else if (ev == BUTTON_EVENT_LONG_PRESS) {
@ -878,7 +887,7 @@ bool UITask::getGPSState() {
return !strcmp(_sensors->getSettingValue(i), "1"); return !strcmp(_sensors->getSettingValue(i), "1");
} }
} }
} }
return false; return false;
} }

1
examples/companion_radio/ui-orig/UITask.cpp

@ -57,6 +57,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#ifdef PIN_BUZZER #ifdef PIN_BUZZER
buzzer.begin(); buzzer.begin();
buzzer.quiet(_node_prefs->buzzer_quiet); buzzer.quiet(_node_prefs->buzzer_quiet);
buzzer.startup();
#endif #endif
// Initialize digital button if available // Initialize digital button if available

24
examples/kiss_modem/KissModem.cpp

@ -129,6 +129,8 @@ void KissModem::processFrame() {
memcpy(_pending_tx, data, data_len); memcpy(_pending_tx, data, data_len);
_pending_tx_len = data_len; _pending_tx_len = data_len;
_has_pending_tx = true; _has_pending_tx = true;
} else if (_has_pending_tx) {
writeHardwareError(HW_ERR_TX_BUSY);
} }
break; break;
@ -257,6 +259,7 @@ void KissModem::processTx() {
_tx_timer = millis(); _tx_timer = millis();
_tx_state = TX_DELAY; _tx_state = TX_DELAY;
} else { } else {
_tx_timer = millis();
_tx_state = TX_WAIT_CLEAR; _tx_state = TX_WAIT_CLEAR;
} }
} }
@ -273,19 +276,30 @@ void KissModem::processTx() {
_tx_timer = millis(); _tx_timer = millis();
_tx_state = TX_SLOT_WAIT; _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; break;
case TX_SLOT_WAIT: case TX_SLOT_WAIT:
if (millis() - _tx_timer >= (uint32_t)_slottime * 10) { if (millis() - _tx_timer >= (uint32_t)_slottime * 10) {
_tx_timer = millis();
_tx_state = TX_WAIT_CLEAR; _tx_state = TX_WAIT_CLEAR;
} }
break; break;
case TX_DELAY: case TX_DELAY:
if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) { if (millis() - _tx_timer >= (uint32_t)_txdelay * 10) {
_radio.startSendRaw(_pending_tx, _pending_tx_len); if (_radio.startSendRaw(_pending_tx, _pending_tx_len)) {
_tx_state = TX_SENDING; _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; break;
@ -296,6 +310,12 @@ void KissModem::processTx() {
writeHardwareFrame(HW_RESP_TX_DONE, &result, 1); writeHardwareFrame(HW_RESP_TX_DONE, &result, 1);
_has_pending_tx = false; _has_pending_tx = false;
_tx_state = TX_IDLE; _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; break;
} }

2
examples/kiss_modem/KissModem.h

@ -26,6 +26,7 @@
#define KISS_DEFAULT_TXDELAY 50 #define KISS_DEFAULT_TXDELAY 50
#define KISS_DEFAULT_PERSISTENCE 63 #define KISS_DEFAULT_PERSISTENCE 63
#define KISS_DEFAULT_SLOTTIME 10 #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_IDENTITY 0x01
#define HW_CMD_GET_RANDOM 0x02 #define HW_CMD_GET_RANDOM 0x02
@ -71,6 +72,7 @@
#define HW_ERR_MAC_FAILED 0x04 #define HW_ERR_MAC_FAILED 0x04
#define HW_ERR_UNKNOWN_CMD 0x05 #define HW_ERR_UNKNOWN_CMD 0x05
#define HW_ERR_ENCRYPT_FAILED 0x06 #define HW_ERR_ENCRYPT_FAILED 0x06
#define HW_ERR_TX_BUSY 0x07
#define KISS_FIRMWARE_VERSION 1 #define KISS_FIRMWARE_VERSION 1

11
examples/kiss_modem/main.cpp

@ -10,7 +10,10 @@
#include <LittleFS.h> #include <LittleFS.h>
#elif defined(ESP32) #elif defined(ESP32)
#include <SPIFFS.h> #include <SPIFFS.h>
#else
#include <InternalFileSystem.h>
#endif #endif
#if defined(KISS_UART_RX) && defined(KISS_UART_TX) #if defined(KISS_UART_RX) && defined(KISS_UART_TX)
#include <HardwareSerial.h> #include <HardwareSerial.h>
#endif #endif
@ -29,7 +32,7 @@ void halt() {
} }
void loadOrCreateIdentity() { void loadOrCreateIdentity() {
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
InternalFS.begin(); InternalFS.begin();
IdentityStore store(InternalFS, ""); IdentityStore store(InternalFS, "");
#elif defined(ESP32) #elif defined(ESP32)
@ -53,11 +56,11 @@ void loadOrCreateIdentity() {
} }
void onSetRadio(float freq, float bw, uint8_t sf, uint8_t cr) { 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) { void onSetTxPower(uint8_t power) {
radio_set_tx_power(power); radio_driver.setTxPower(power);
} }
float onGetCurrentRssi() { float onGetCurrentRssi() {
@ -79,7 +82,7 @@ void setup() {
radio_driver.begin(); radio_driver.begin();
rng.begin(radio_get_rng_seed()); rng.begin(radio_driver.getRngSeed());
loadOrCreateIdentity(); loadOrCreateIdentity();
sensors.begin(); sensors.begin();

10
examples/simple_repeater/MyMesh.cpp

@ -953,8 +953,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
} }
#endif #endif
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm); radio_driver.setTxPower(_prefs.tx_power_dbm);
radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain);
MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s", MESH_DEBUG_PRINTLN("RX Boosted Gain Mode: %s",
@ -1050,7 +1050,7 @@ void MyMesh::dumpLogFile() {
} }
void MyMesh::setTxPower(int8_t power_dbm) { 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) #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 if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
set_radio_at = 0; // clear timer 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"); MESH_DEBUG_PRINTLN("Temp radio params");
} }
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
revert_radio_at = 0; // clear timer 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"); MESH_DEBUG_PRINTLN("Radio params restored");
} }

12
examples/simple_repeater/UITask.cpp

@ -52,17 +52,25 @@ void UITask::renderCurrScreen() {
int logoWidth = 128; int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); _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 // version info
_display->setColor(DisplayDriver::LIGHT); _display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1); _display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info); 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); _display->print(_version_info);
// node type // node type
const char* node_type = "< Repeater >"; const char* node_type = "< Repeater >";
uint16_t typeWidth = _display->getTextWidth(node_type); 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); _display->print(node_type);
} else { // home screen } else { // home screen
// node name // node name

2
examples/simple_repeater/main.cpp

@ -57,7 +57,7 @@ void setup() {
halt(); halt();
} }
fast_rng.begin(radio_get_rng_seed()); fast_rng.begin(radio_driver.getRngSeed());
FILESYSTEM* fs; FILESYSTEM* fs;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

10
examples/simple_room_server/MyMesh.cpp

@ -691,8 +691,8 @@ void MyMesh::begin(FILESYSTEM *fs) {
} }
} }
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm); radio_driver.setTxPower(_prefs.tx_power_dbm);
updateAdvertTimer(); updateAdvertTimer();
updateFloodAdvertTimer(); updateFloodAdvertTimer();
@ -796,7 +796,7 @@ void MyMesh::dumpLogFile() {
} }
void MyMesh::setTxPower(int8_t power_dbm) { 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) { 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 if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
set_radio_at = 0; // clear timer 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"); MESH_DEBUG_PRINTLN("Temp radio params");
} }
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
revert_radio_at = 0; // clear timer 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"); MESH_DEBUG_PRINTLN("Radio params restored");
} }

12
examples/simple_room_server/UITask.cpp

@ -52,17 +52,25 @@ void UITask::renderCurrScreen() {
int logoWidth = 128; int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); _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 // version info
_display->setColor(DisplayDriver::LIGHT); _display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1); _display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info); 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); _display->print(_version_info);
// node type // node type
const char* node_type = "< Room Server >"; const char* node_type = "< Room Server >";
uint16_t typeWidth = _display->getTextWidth(node_type); 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); _display->print(node_type);
} else { // home screen } else { // home screen
// node name // node name

2
examples/simple_room_server/main.cpp

@ -35,7 +35,7 @@ void setup() {
if (!radio_init()) { halt(); } if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed()); fast_rng.begin(radio_driver.getRngSeed());
FILESYSTEM* fs; FILESYSTEM* fs;
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)

6
examples/simple_secure_chat/main.cpp

@ -562,7 +562,7 @@ void setup() {
if (!radio_init()) { halt(); } if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed()); fast_rng.begin(radio_driver.getRngSeed());
#if defined(NRF52_PLATFORM) #if defined(NRF52_PLATFORM)
InternalFS.begin(); InternalFS.begin();
@ -577,8 +577,8 @@ void setup() {
#error "need to define filesystem" #error "need to define filesystem"
#endif #endif
radio_set_params(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR); radio_driver.setParams(the_mesh.getFreqPref(), LORA_BW, LORA_SF, LORA_CR);
radio_set_tx_power(the_mesh.getTxPowerPref()); radio_driver.setTxPower(the_mesh.getTxPowerPref());
the_mesh.showWelcome(); the_mesh.showWelcome();

10
examples/simple_sensor/SensorMesh.cpp

@ -764,8 +764,8 @@ void SensorMesh::begin(FILESYSTEM* fs) {
} }
} }
radio_set_params(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr);
radio_set_tx_power(_prefs.tx_power_dbm); radio_driver.setTxPower(_prefs.tx_power_dbm);
updateAdvertTimer(); updateAdvertTimer();
updateFloodAdvertTimer(); updateFloodAdvertTimer();
@ -842,7 +842,7 @@ void SensorMesh::updateFloodAdvertTimer() {
} }
void SensorMesh::setTxPower(int8_t power_dbm) { void SensorMesh::setTxPower(int8_t power_dbm) {
radio_set_tx_power(power_dbm); radio_driver.setTxPower(power_dbm);
} }
void SensorMesh::formatStatsReply(char *reply) { 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 if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params
set_radio_at = 0; // clear timer 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"); MESH_DEBUG_PRINTLN("Temp radio params");
} }
if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig
revert_radio_at = 0; // clear timer 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"); MESH_DEBUG_PRINTLN("Radio params restored");
} }

4
examples/simple_sensor/SensorMesh.h

@ -34,11 +34,11 @@
#define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts #define PERM_RECV_ALERTS_HI (1 << 7) // high priority alerts
#ifndef FIRMWARE_BUILD_DATE #ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "20 Mar 2026" #define FIRMWARE_BUILD_DATE "19 Apr 2026"
#endif #endif
#ifndef FIRMWARE_VERSION #ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "v1.14.1" #define FIRMWARE_VERSION "v1.15.0"
#endif #endif
#define FIRMWARE_ROLE "sensor" #define FIRMWARE_ROLE "sensor"

12
examples/simple_sensor/UITask.cpp

@ -52,17 +52,25 @@ void UITask::renderCurrScreen() {
int logoWidth = 128; int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); _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 // version info
_display->setColor(DisplayDriver::LIGHT); _display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1); _display->setTextSize(1);
uint16_t versionWidth = _display->getTextWidth(_version_info); 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); _display->print(_version_info);
// node type // node type
const char* node_type = "< Sensor >"; const char* node_type = "< Sensor >";
uint16_t typeWidth = _display->getTextWidth(node_type); 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); _display->print(node_type);
} else { // home screen } else { // home screen
// node name // node name

2
examples/simple_sensor/main.cpp

@ -68,7 +68,7 @@ void setup() {
if (!radio_init()) { halt(); } if (!radio_init()) { halt(); }
fast_rng.begin(radio_get_rng_seed()); fast_rng.begin(radio_driver.getRngSeed());
FILESYSTEM* fs; FILESYSTEM* fs;
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)

29
platformio.ini

@ -10,8 +10,8 @@
[platformio] [platformio]
extra_configs = extra_configs =
variants/*/platformio.ini variants/*/platformio.ini
platformio.local.ini platformio.local.ini
[arduino_base] [arduino_base]
framework = arduino framework = arduino
@ -81,17 +81,18 @@ platform = https://github.com/pioarduino/platform-espressif32/releases/download/
extends = arduino_base extends = arduino_base
platform = nordicnrf52 platform = nordicnrf52
platform_packages = platform_packages =
framework-arduinoadafruitnrf52 @ 1.10700.0 ; use internal fork that includes patch to ble stack to prevent firmware lockup during rapid connect/disconnect
extra_scripts = ; https://github.com/meshcore-dev/MeshCore/pull/1177
create-uf2.py ; https://github.com/meshcore-dev/MeshCore/pull/1295
arch/nrf52/extra_scripts/patch_bluefruit.py framework-arduinoadafruitnrf52 @ https://github.com/meshcore-dev/Adafruit_nRF52_Arduino#d541301
extra_scripts = create-uf2.py
build_flags = ${arduino_base.build_flags} build_flags = ${arduino_base.build_flags}
-D NRF52_PLATFORM -D NRF52_PLATFORM
-D LFS_NO_ASSERT=1 -D LFS_NO_ASSERT=1
-D EXTRAFS=1 -D EXTRAFS=1
lib_deps = lib_deps =
${arduino_base.lib_deps} ${arduino_base.lib_deps}
https://github.com/oltaco/CustomLFS @ 0.2.1 https://github.com/oltaco/CustomLFS#0.2.1
; ----------------- RP2040 --------------------- ; ----------------- RP2040 ---------------------
[rp2040_base] [rp2040_base]
@ -151,3 +152,17 @@ lib_deps =
stevemarple/MicroNMEA @ ^2.0.6 stevemarple/MicroNMEA @ ^2.0.6
adafruit/Adafruit BME680 Library @ ^2.0.4 adafruit/Adafruit BME680 Library @ ^2.0.4
adafruit/Adafruit BMP085 Library @ ^1.2.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
src/helpers/AutoDiscoverRTCClock.cpp

@ -27,9 +27,11 @@ bool AutoDiscoverRTCClock::i2c_probe(TwoWire& wire, uint8_t addr) {
} }
void AutoDiscoverRTCClock::begin(TwoWire& wire) { void AutoDiscoverRTCClock::begin(TwoWire& wire) {
#if !defined(DISABLE_DS3231_PROBE)
if (i2c_probe(wire, DS3231_ADDRESS)) { if (i2c_probe(wire, DS3231_ADDRESS)) {
ds3231_success = rtc_3231.begin(&wire); ds3231_success = rtc_3231.begin(&wire);
} }
#endif
if (i2c_probe(wire, RV3028_ADDRESS)) { if (i2c_probe(wire, RV3028_ADDRESS)) {
rtc_rv3028.initI2C(wire); rtc_rv3028.initI2C(wire);

10
src/helpers/CommonCLI.cpp

@ -2,6 +2,7 @@
#include "CommonCLI.h" #include "CommonCLI.h"
#include "TxtDataHelpers.h" #include "TxtDataHelpers.h"
#include "AdvertDataHelpers.h" #include "AdvertDataHelpers.h"
#include "TxtDataHelpers.h"
#include <RTClib.h> #include <RTClib.h>
#ifndef BRIDGE_MAX_BAUD #ifndef BRIDGE_MAX_BAUD
@ -285,7 +286,8 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re
// change admin password // change admin password
StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password)); StrHelper::strncpy(_prefs->password, &command[9], sizeof(_prefs->password));
savePrefs(); 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) { } else if (memcmp(command, "clear stats", 11) == 0) {
_callbacks->clearStats(); _callbacks->clearStats();
strcpy(reply, "(OK - stats reset)"); 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"); strcpy(reply, "Error: unsupported by this board");
}; };
} else { } 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) { } else if (memcmp(config, "direct.txdelay", 14) == 0) {
sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor)); sprintf(reply, "> %s", StrHelper::ftoa(_prefs->direct_tx_delay_factor));
} else if (memcmp(config, "owner.info", 10) == 0) { } else if (memcmp(config, "owner.info", 10) == 0) {
auto start = reply;
*reply++ = '>'; *reply++ = '>';
*reply++ = ' '; *reply++ = ' ';
const char* sp = _prefs->owner_info; const char* sp = _prefs->owner_info;
while (*sp) { while (*sp && reply - start < 159) {
*reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|' *reply++ = (*sp == '\n') ? '|' : *sp; // translate newline back to orig '|'
sp++; sp++;
} }

4
src/helpers/esp32/ESPNOWRadio.cpp

@ -120,6 +120,10 @@ void ESPNOWRadio::init() {
} }
} }
uint32_t ESPNOWRadio::getRngSeed() {
return millis() + intID(); // TODO: where to get some entropy?
}
void ESPNOWRadio::setTxPower(uint8_t dbm) { void ESPNOWRadio::setTxPower(uint8_t dbm) {
esp_wifi_set_max_tx_power(dbm * 4); esp_wifi_set_max_tx_power(dbm * 4);
} }

7
src/helpers/esp32/ESPNOWRadio.h

@ -9,6 +9,13 @@ protected:
public: public:
ESPNOWRadio() { n_recv = n_sent = n_recv_errors = 0; } 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(); void init();
int recvRaw(uint8_t* bytes, int sz) override; int recvRaw(uint8_t* bytes, int sz) override;
uint32_t getEstAirtimeFor(int len_bytes) override; uint32_t getEstAirtimeFor(int len_bytes) override;

10
src/helpers/radiolib/CustomLLCC68Wrapper.h

@ -7,6 +7,15 @@
class CustomLLCC68Wrapper : public RadioLibWrapper { class CustomLLCC68Wrapper : public RadioLibWrapper {
public: public:
CustomLLCC68Wrapper(CustomLLCC68& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } 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 { bool isReceivingPacket() override {
return ((CustomLLCC68 *)_radio)->isReceiving(); return ((CustomLLCC68 *)_radio)->isReceiving();
} }
@ -20,6 +29,7 @@ public:
int sf = ((CustomLLCC68 *)_radio)->spreadingFactor; int sf = ((CustomLLCC68 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomLLCC68 *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }

2
src/helpers/radiolib/CustomLR1110.h

@ -36,4 +36,6 @@ class CustomLR1110 : public LR1110 {
bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED)); bool detected = ((irq & RADIOLIB_LR11X0_IRQ_SYNC_WORD_HEADER_VALID) || (irq & RADIOLIB_LR11X0_IRQ_PREAMBLE_DETECTED));
return detected; return detected;
} }
uint8_t getSpreadingFactor() const { return spreadingFactor; }
}; };

13
src/helpers/radiolib/CustomLR1110Wrapper.h

@ -7,6 +7,15 @@
class CustomLR1110Wrapper : public RadioLibWrapper { class CustomLR1110Wrapper : public RadioLibWrapper {
public: public:
CustomLR1110Wrapper(CustomLR1110& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } 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()); } void doResetAGC() override { lr11x0ResetAGC((LR11x0 *)_radio, ((CustomLR1110 *)_radio)->getFreqMHz()); }
bool isReceivingPacket() override { bool isReceivingPacket() override {
return ((CustomLR1110 *)_radio)->isReceiving(); return ((CustomLR1110 *)_radio)->isReceiving();
@ -19,12 +28,14 @@ public:
void onSendFinished() override { void onSendFinished() override {
RadioLibWrapper::onSendFinished(); 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 getLastRSSI() const override { return ((CustomLR1110 *)_radio)->getRSSI(); }
float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); } float getLastSNR() const override { return ((CustomLR1110 *)_radio)->getSNR(); }
uint8_t getSpreadingFactor() const override { return ((CustomLR1110 *)_radio)->getSpreadingFactor(); }
void setRxBoostedGainMode(bool en) override { void setRxBoostedGainMode(bool en) override {
((CustomLR1110 *)_radio)->setRxBoostedGainMode(en); ((CustomLR1110 *)_radio)->setRxBoostedGainMode(en);
} }

10
src/helpers/radiolib/CustomSTM32WLxWrapper.h

@ -8,6 +8,15 @@
class CustomSTM32WLxWrapper : public RadioLibWrapper { class CustomSTM32WLxWrapper : public RadioLibWrapper {
public: public:
CustomSTM32WLxWrapper(CustomSTM32WLx& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } 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 { bool isReceivingPacket() override {
return ((CustomSTM32WLx *)_radio)->isReceiving(); return ((CustomSTM32WLx *)_radio)->isReceiving();
} }
@ -21,6 +30,7 @@ public:
int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor; int sf = ((CustomSTM32WLx *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSTM32WLx *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }
}; };

10
src/helpers/radiolib/CustomSX1262Wrapper.h

@ -11,6 +11,15 @@
class CustomSX1262Wrapper : public RadioLibWrapper { class CustomSX1262Wrapper : public RadioLibWrapper {
public: public:
CustomSX1262Wrapper(CustomSX1262& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } 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 { bool isReceivingPacket() override {
return ((CustomSX1262 *)_radio)->isReceiving(); return ((CustomSX1262 *)_radio)->isReceiving();
} }
@ -24,6 +33,7 @@ public:
int sf = ((CustomSX1262 *)_radio)->spreadingFactor; int sf = ((CustomSX1262 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSX1262 *)_radio)->spreadingFactor; }
virtual void powerOff() override { virtual void powerOff() override {
((CustomSX1262 *)_radio)->sleep(false); ((CustomSX1262 *)_radio)->sleep(false);
} }

10
src/helpers/radiolib/CustomSX1268Wrapper.h

@ -11,6 +11,15 @@
class CustomSX1268Wrapper : public RadioLibWrapper { class CustomSX1268Wrapper : public RadioLibWrapper {
public: public:
CustomSX1268Wrapper(CustomSX1268& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } 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 { bool isReceivingPacket() override {
return ((CustomSX1268 *)_radio)->isReceiving(); return ((CustomSX1268 *)_radio)->isReceiving();
} }
@ -24,6 +33,7 @@ public:
int sf = ((CustomSX1268 *)_radio)->spreadingFactor; int sf = ((CustomSX1268 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSX1268 *)_radio)->spreadingFactor; }
void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); } void doResetAGC() override { sx126xResetAGC((SX126x *)_radio); }

10
src/helpers/radiolib/CustomSX1276Wrapper.h

@ -10,6 +10,15 @@
class CustomSX1276Wrapper : public RadioLibWrapper { class CustomSX1276Wrapper : public RadioLibWrapper {
public: public:
CustomSX1276Wrapper(CustomSX1276& radio, mesh::MainBoard& board) : RadioLibWrapper(radio, board) { } 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 { bool isReceivingPacket() override {
return ((CustomSX1276 *)_radio)->isReceiving(); return ((CustomSX1276 *)_radio)->isReceiving();
} }
@ -23,4 +32,5 @@ public:
int sf = ((CustomSX1276 *)_radio)->spreadingFactor; int sf = ((CustomSX1276 *)_radio)->spreadingFactor;
return packetScoreInt(snr, sf, packet_len); return packetScoreInt(snr, sf, packet_len);
} }
uint8_t getSpreadingFactor() const override { return ((CustomSX1276 *)_radio)->spreadingFactor; }
}; };

10
src/helpers/radiolib/RadioLibWrappers.cpp

@ -26,6 +26,8 @@ void setFlag(void) {
void RadioLibWrapper::begin() { void RadioLibWrapper::begin() {
_radio->setPacketReceivedAction(setFlag); // this is also SentComplete interrupt _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; state = STATE_IDLE;
if (_board->getStartupReason() == BD_STARTUP_RX_PACKET) { // received a LoRa packet (while in deep sleep) 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; _floor_sample_sum = 0;
} }
uint32_t RadioLibWrapper::getRngSeed() {
return _radio->random(0x7FFFFFFF);
}
void RadioLibWrapper::setTxPower(int8_t dbm) {
_radio->setOutputPower(dbm);
}
void RadioLibWrapper::idle() { void RadioLibWrapper::idle() {
_radio->standby(); _radio->standby();
state = STATE_IDLE; // need another startReceive() state = STATE_IDLE; // need another startReceive()

10
src/helpers/radiolib/RadioLibWrappers.h

@ -11,6 +11,7 @@ protected:
int16_t _noise_floor, _threshold; int16_t _noise_floor, _threshold;
uint16_t _num_floor_samples; uint16_t _num_floor_samples;
int32_t _floor_sample_sum; int32_t _floor_sample_sum;
uint8_t _preamble_sf;
void idle(); void idle();
void startRecv(); void startRecv();
@ -27,7 +28,7 @@ protected:
virtual void dispatchPendingIrq() {} virtual void dispatchPendingIrq() {}
public: 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; void begin() override;
virtual void powerOff() { _radio->sleep(); } virtual void powerOff() { _radio->sleep(); }
@ -45,7 +46,14 @@ public:
return isChannelActive(); 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 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; } int getNoiseFloor() const override { return _noise_floor; }
void triggerNoiseFloorCalibrate(int threshold) override; void triggerNoiseFloorCalibrate(int threshold) override;

733
src/helpers/sensors/EnvironmentSensorManager.cpp

@ -1,11 +1,17 @@
#include "EnvironmentSensorManager.h" #include "EnvironmentSensorManager.h"
#include <Wire.h>
#if ENV_PIN_SDA && ENV_PIN_SCL #if ENV_PIN_SDA && ENV_PIN_SCL
#define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors #define TELEM_WIRE &Wire1 // Use Wire1 as the I2C bus for Environment Sensors
#else #else
#define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors #define TELEM_WIRE &Wire // Use default I2C bus for Environment Sensors
#endif #endif
// ============================================================
// Sensor library includes and static driver instances
// ============================================================
#ifdef ENV_INCLUDE_BME680 #ifdef ENV_INCLUDE_BME680
#ifndef TELEM_BME680_ADDRESS #ifndef TELEM_BME680_ADDRESS
#define TELEM_BME680_ADDRESS 0x76 #define TELEM_BME680_ADDRESS 0x76
@ -31,7 +37,7 @@ static Adafruit_AHTX0 AHTX0;
#ifndef TELEM_BME280_ADDRESS #ifndef TELEM_BME280_ADDRESS
#define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address #define TELEM_BME280_ADDRESS 0x76 // BME280 environmental sensor I2C address
#endif #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> #include <Adafruit_BME280.h>
static Adafruit_BME280 BME280; static Adafruit_BME280 BME280;
#endif #endif
@ -40,7 +46,7 @@ static Adafruit_BME280 BME280;
#ifndef TELEM_BMP280_ADDRESS #ifndef TELEM_BMP280_ADDRESS
#define TELEM_BMP280_ADDRESS 0x76 // BMP280 environmental sensor I2C address #define TELEM_BMP280_ADDRESS 0x76 // BMP280 environmental sensor I2C address
#endif #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> #include <Adafruit_BMP280.h>
static Adafruit_BMP280 BMP280(TELEM_WIRE); static Adafruit_BMP280 BMP280(TELEM_WIRE);
#endif #endif
@ -51,7 +57,7 @@ static Adafruit_SHTC3 SHTC3;
#endif #endif
#if ENV_INCLUDE_SHT4X #if ENV_INCLUDE_SHT4X
#define TELEM_SHT4X_ADDRESS 0x44 //0x44 - 0x46 #define TELEM_SHT4X_ADDRESS 0x44
#include <SensirionI2cSht4x.h> #include <SensirionI2cSht4x.h>
static SensirionI2cSht4x SHT4X; static SensirionI2cSht4x SHT4X;
#endif #endif
@ -63,7 +69,7 @@ LPS22HBClass LPS22HB(*TELEM_WIRE);
#if ENV_INCLUDE_INA3221 #if ENV_INCLUDE_INA3221
#ifndef TELEM_INA3221_ADDRESS #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 #endif
#ifndef TELEM_INA3221_SHUNT_VALUE #ifndef TELEM_INA3221_SHUNT_VALUE
#define TELEM_INA3221_SHUNT_VALUE 0.100 // most variants will have a 0.1 ohm shunts #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 #endif
#if ENV_INCLUDE_INA226 #if ENV_INCLUDE_INA226
#define TELEM_INA226_ADDRESS 0x44 #define TELEM_INA226_ADDRESS 0x44
#define TELEM_INA226_SHUNT_VALUE 0.100 #define TELEM_INA226_SHUNT_VALUE 0.100
#define TELEM_INA226_MAX_AMP 0.8 #define TELEM_INA226_MAX_AMP 0.8
#include <INA226.h> #include <INA226.h>
static INA226 INA226(TELEM_INA226_ADDRESS, TELEM_WIRE); static INA226 INA226(TELEM_INA226_ADDRESS, TELEM_WIRE);
#endif #endif
@ -161,381 +167,414 @@ public:
static RAK12500LocationProvider RAK12500_provider; static RAK12500LocationProvider RAK12500_provider;
#endif #endif
bool EnvironmentSensorManager::begin() { // ============================================================
#if ENV_INCLUDE_GPS // I2C bus scanner
#ifdef RAK_WISBLOCK_GPS // Probes every valid address and records which ones ACK.
rakGPSInit(); //probe base board/sockets for GPS // This runs before any sensor library is touched, so a missing
#else // or misbehaving device cannot stall or crash the boot sequence.
initBasicGPS(); // ============================================================
#endif
#endif static void scanI2CBus(TwoWire* wire, bool found[128]) {
for (uint8_t addr = 0x08; addr < 0x78; addr++) {
#if ENV_PIN_SDA && ENV_PIN_SCL wire->beginTransmission(addr);
#ifdef NRF52_PLATFORM found[addr] = (wire->endTransmission() == 0);
Wire1.setPins(ENV_PIN_SDA, ENV_PIN_SCL);
Wire1.setClock(100000);
Wire1.begin();
#else
Wire1.begin(ENV_PIN_SDA, ENV_PIN_SCL, 100000);
#endif
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 }
#if ENV_INCLUDE_BME680 // ============================================================
if (BME680.begin(TELEM_BME680_ADDRESS)) { // Per-sensor init and query functions
MESH_DEBUG_PRINTLN("Found BME680 at address: %02X", TELEM_BME680_ADDRESS); //
BME680_initialized = true; // init(wire, address) — called only when the address was seen
} else { // on the bus. Returns 0 on failure, or the number of
BME680_initialized = false; // telemetry channels the sensor will consume (1 for all
MESH_DEBUG_PRINTLN("BME680 was not found at I2C address %02X", TELEM_BME680_ADDRESS); // single-output sensors; INA3221 returns one per enabled
} // hardware channel; MLX90614 and RAK12035+calibration
#endif // 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_BME280 #if ENV_INCLUDE_AHTX0
if (BME280.begin(TELEM_BME280_ADDRESS, TELEM_WIRE)) { static uint8_t init_ahtx0(TwoWire* wire, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found BME280 at address: %02X", TELEM_BME280_ADDRESS); return AHTX0.begin(wire, 0, addr) ? 1 : 0;
MESH_DEBUG_PRINTLN("BME sensor ID: %02X", BME280.sensorID()); }
// Reduce self-heating: single-shot conversions, light oversampling, long standby. static void query_ahtx0(uint8_t ch, uint8_t, CayenneLPP& lpp) {
BME280.setSampling(Adafruit_BME280::MODE_FORCED, sensors_event_t humidity, temp;
Adafruit_BME280::SAMPLING_X1, // temperature AHTX0.getEvent(&humidity, &temp);
Adafruit_BME280::SAMPLING_X1, // pressure lpp.addTemperature(ch, temp.temperature);
Adafruit_BME280::SAMPLING_X1, // humidity lpp.addRelativeHumidity(ch, humidity.relative_humidity);
Adafruit_BME280::FILTER_OFF, }
Adafruit_BME280::STANDBY_MS_1000); #endif
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 #ifdef ENV_INCLUDE_BME680
if (BMP280.begin(TELEM_BMP280_ADDRESS)) { static uint8_t init_bme680(TwoWire*, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found BMP280 at address: %02X", TELEM_BMP280_ADDRESS); // Wire was set in the static constructor; begin() takes address only.
MESH_DEBUG_PRINTLN("BMP sensor ID: %02X", BMP280.sensorID()); return BME680.begin(addr) ? 1 : 0;
BMP280_initialized = true; }
} else { static void query_bme680(uint8_t ch, uint8_t, CayenneLPP& lpp) {
BMP280_initialized = false; if (BME680.performReading()) {
MESH_DEBUG_PRINTLN("BMP280 was not found at I2C address %02X", TELEM_BMP280_ADDRESS); 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 }
#endif
#if ENV_INCLUDE_SHTC3 #if ENV_INCLUDE_BME280
if (SHTC3.begin(TELEM_WIRE)) { static uint8_t init_bme280(TwoWire* wire, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found sensor: SHTC3"); if (!BME280.begin(addr, wire)) return 0;
SHTC3_initialized = true; BME280.setSampling(Adafruit_BME280::MODE_FORCED,
} else { Adafruit_BME280::SAMPLING_X1,
SHTC3_initialized = false; Adafruit_BME280::SAMPLING_X1,
MESH_DEBUG_PRINTLN("SHTC3 was not found at I2C address %02X", 0x70); 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 }
#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_SHT4X #if ENV_INCLUDE_SHTC3
SHT4X.begin(*TELEM_WIRE, TELEM_SHT4X_ADDRESS); static uint8_t init_shtc3(TwoWire* wire, uint8_t) {
uint32_t serialNumber = 0; // Adafruit_SHTC3::begin() does not accept an address (fixed at 0x70).
int16_t sht4x_error; return SHTC3.begin(wire) ? 1 : 0;
sht4x_error = SHT4X.serialNumber(serialNumber); }
if (sht4x_error == 0) { static void query_shtc3(uint8_t ch, uint8_t, CayenneLPP& lpp) {
MESH_DEBUG_PRINTLN("Found SHT4X at address: %02X", TELEM_SHT4X_ADDRESS); sensors_event_t humidity, temp;
SHT4X_initialized = true; SHTC3.getEvent(&humidity, &temp);
} else { lpp.addTemperature(ch, temp.temperature);
SHT4X_initialized = false; lpp.addRelativeHumidity(ch, humidity.relative_humidity);
MESH_DEBUG_PRINTLN("SHT4X was not found at I2C address %02X", TELEM_SHT4X_ADDRESS); }
} #endif
#endif
#if ENV_INCLUDE_LPS22HB #if ENV_INCLUDE_SHT4X
if (LPS22HB.begin()) { static uint8_t init_sht4x(TwoWire* wire, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found sensor: LPS22HB"); // SensirionI2cSht4x::begin() does not probe the hardware; use serialNumber()
LPS22HB_initialized = true; // as the actual presence check since it performs a real I2C transaction.
} else { SHT4X.begin(*wire, addr);
LPS22HB_initialized = false; uint32_t serial = 0;
MESH_DEBUG_PRINTLN("LPS22HB was not found at I2C address %02X", 0x5C); 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 }
#endif
#if ENV_INCLUDE_INA3221 #if ENV_INCLUDE_LPS22HB
if (INA3221.begin(TELEM_INA3221_ADDRESS, TELEM_WIRE)) { static uint8_t init_lps22hb(TwoWire*, uint8_t) {
MESH_DEBUG_PRINTLN("Found INA3221 at address: %02X", TELEM_INA3221_ADDRESS); // LPS22HBClass is constructed with the wire reference; begin() uses it.
MESH_DEBUG_PRINTLN("%04X %04X", INA3221.getDieID(), INA3221.getManufacturerID()); 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
for(int i = 0; i < 3; i++) { #if ENV_INCLUDE_INA3221
INA3221.setShuntResistance(i, TELEM_INA3221_SHUNT_VALUE); 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++;
} }
INA3221_initialized = true;
} else {
INA3221_initialized = false;
MESH_DEBUG_PRINTLN("INA3221 was not found at I2C address %02X", TELEM_INA3221_ADDRESS);
} }
#endif }
#endif
#if ENV_INCLUDE_INA219 #if ENV_INCLUDE_INA219
if (INA219.begin(TELEM_WIRE)) { static uint8_t init_ina219(TwoWire* wire, uint8_t) {
MESH_DEBUG_PRINTLN("Found INA219 at address: %02X", TELEM_INA219_ADDRESS); // INA219 static instance was constructed with the address; begin() uses it.
INA219_initialized = true; return INA219.begin(wire) ? 1 : 0;
} else { }
INA219_initialized = false; static void query_ina219(uint8_t ch, uint8_t, CayenneLPP& lpp) {
MESH_DEBUG_PRINTLN("INA219 was not found at I2C address %02X", TELEM_INA219_ADDRESS); lpp.addVoltage(ch, INA219.getBusVoltage_V());
} lpp.addCurrent(ch, INA219.getCurrent_mA() / 1000.0f);
#endif lpp.addPower(ch, INA219.getPower_mW() / 1000.0f);
}
#endif
#if ENV_INCLUDE_INA260 #if ENV_INCLUDE_INA260
if (INA260.begin(TELEM_INA260_ADDRESS, TELEM_WIRE)) { static uint8_t init_ina260(TwoWire* wire, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found INA260 at address: %02X", TELEM_INA260_ADDRESS); return INA260.begin(addr, wire) ? 1 : 0;
INA260_initialized = true; }
} else { static void query_ina260(uint8_t ch, uint8_t, CayenneLPP& lpp) {
INA260_initialized = false; lpp.addVoltage(ch, INA260.readBusVoltage() / 1000.0f);
MESH_DEBUG_PRINTLN("INA260 was not found at I2C address %02X", TELEM_INA260_ADDRESS); lpp.addCurrent(ch, INA260.readCurrent() / 1000.0f);
} lpp.addPower(ch, INA260.readPower() / 1000.0f);
#endif }
#endif
#if ENV_INCLUDE_INA226 #if ENV_INCLUDE_INA226
if (INA226.begin()) { static uint8_t init_ina226(TwoWire*, uint8_t) {
MESH_DEBUG_PRINTLN("Found INA226 at address: %02X", TELEM_INA226_ADDRESS); // INA226 static instance was constructed with address and wire.
INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE); if (!INA226.begin()) return 0;
INA226_initialized = true; INA226.setMaxCurrentShunt(TELEM_INA226_MAX_AMP, TELEM_INA226_SHUNT_VALUE);
} else { return 1;
INA226_initialized = false; }
MESH_DEBUG_PRINTLN("INA226 was not found at I2C address %02X", TELEM_INA226_ADDRESS); static void query_ina226(uint8_t ch, uint8_t, CayenneLPP& lpp) {
} lpp.addVoltage(ch, INA226.getBusVoltage());
#endif lpp.addCurrent(ch, INA226.getCurrent_mA() / 1000.0f);
lpp.addPower(ch, INA226.getPower_mW() / 1000.0f);
}
#endif
#if ENV_INCLUDE_MLX90614 #if ENV_INCLUDE_MLX90614
if (MLX90614.begin(TELEM_MLX90614_ADDRESS, TELEM_WIRE)) { static uint8_t init_mlx90614(TwoWire* wire, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found MLX90614 at address: %02X", TELEM_MLX90614_ADDRESS); return MLX90614.begin(addr, wire) ? 2 : 0; // 2 channels: object temp, ambient temp
MLX90614_initialized = true; }
} else { static void query_mlx90614(uint8_t ch, uint8_t sub_ch, CayenneLPP& lpp) {
MLX90614_initialized = false; if (sub_ch == 0)
MESH_DEBUG_PRINTLN("MLX90614 was not found at I2C address %02X", TELEM_MLX90614_ADDRESS); lpp.addTemperature(ch, MLX90614.readObjectTempC());
} else
#endif lpp.addTemperature(ch, MLX90614.readAmbientTempC());
}
#endif
#if ENV_INCLUDE_VL53L0X #if ENV_INCLUDE_VL53L0X
if (VL53L0X.begin(TELEM_VL53L0X_ADDRESS, false, TELEM_WIRE)) { static uint8_t init_vl53l0x(TwoWire* wire, uint8_t addr) {
MESH_DEBUG_PRINTLN("Found VL53L0X at address: %02X", TELEM_VL53L0X_ADDRESS); return VL53L0X.begin(addr, false, wire) ? 1 : 0;
VL53L0X_initialized = true; }
} else { static void query_vl53l0x(uint8_t ch, uint8_t, CayenneLPP& lpp) {
VL53L0X_initialized = false; VL53L0X_RangingMeasurementData_t measure;
MESH_DEBUG_PRINTLN("VL53L0X was not found at I2C address %02X", TELEM_VL53L0X_ADDRESS); VL53L0X.rangingTest(&measure, false);
} lpp.addDistance(ch, measure.RangeStatus != 4 ? measure.RangeMilliMeter / 1000.0f : 0.0f);
#endif }
#endif
#if ENV_INCLUDE_BMP085 #ifdef ENV_INCLUDE_BMP085
// First argument is MODE (aka oversampling) static uint8_t init_bmp085(TwoWire* wire, uint8_t) {
// choose ULTRALOWPOWER return BMP085.begin(0, wire) ? 1 : 0; // mode 0 = ULTRALOWPOWER
if (BMP085.begin(0, TELEM_WIRE)) { }
MESH_DEBUG_PRINTLN("Found sensor BMP085"); static void query_bmp085(uint8_t ch, uint8_t, CayenneLPP& lpp) {
BMP085_initialized = true; lpp.addTemperature(ch, BMP085.readTemperature());
} else { lpp.addBarometricPressure(ch, BMP085.readPressure() / 100);
BMP085_initialized = false; lpp.addAltitude(ch, BMP085.readAltitude(TELEM_BMP085_SEALEVELPRESSURE_HPA * 100));
MESH_DEBUG_PRINTLN("BMP085 was not found at I2C address %02X", 0x77); }
} #endif
#endif
#if ENV_INCLUDE_RAK12035 #if ENV_INCLUDE_RAK12035
RAK12035.setup(*TELEM_WIRE); static uint8_t init_rak12035(TwoWire* wire, uint8_t addr) {
if (RAK12035.begin(TELEM_RAK12035_ADDRESS)) { // RAK12035 requires setup() before begin().
MESH_DEBUG_PRINTLN("Found sensor RAK12035 at address: %02X", TELEM_RAK12035_ADDRESS); RAK12035.setup(*wire);
RAK12035_initialized = true; 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 { } else {
RAK12035_initialized = false; #ifdef ENABLE_RAK12035_CALIBRATION
MESH_DEBUG_PRINTLN("RAK12035 was not found at I2C address %02X", TELEM_RAK12035_ADDRESS); 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
return true;
} }
#endif
bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { // ============================================================
next_available_channel = TELEM_CHANNEL_SELF + 1; // 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);
};
if (requester_permissions & TELEM_PERM_LOCATION && gps_active) { static const SensorDef SENSOR_TABLE[] = {
telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); // allow lat/lon via telemetry even if no GPS is detected #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
};
if (requester_permissions & TELEM_PERM_ENVIRONMENT) { static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TABLE[0])) - 1;
#if ENV_INCLUDE_AHTX0 // ============================================================
if (AHTX0_initialized) { // begin() — scan the I2C bus, then initialize only what was
sensors_event_t humidity, temp; // found. A sensor whose address does not ACK during the scan
AHTX0.getEvent(&humidity, &temp); // is never touched by a library call, preventing hangs or
telemetry.addTemperature(TELEM_CHANNEL_SELF, temp.temperature); // crashes caused by absent or misbehaving hardware.
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, humidity.relative_humidity); // ============================================================
}
#endif
#if ENV_INCLUDE_BME680 bool EnvironmentSensorManager::begin() {
if (BME680_initialized) { #if ENV_INCLUDE_GPS
if (BME680.performReading()) { #ifdef RAK_WISBLOCK_GPS
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME680.temperature); rakGPSInit();
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME680.humidity); #else
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME680.pressure / 100); initBasicGPS();
telemetry.addAltitude(TELEM_CHANNEL_SELF, 44330.0 * (1.0 - pow((BME680.pressure / 100) / TELEM_BME680_SEALEVELPRESSURE_HPA, 0.1903))); #endif
telemetry.addAnalogInput(next_available_channel, BME680.gas_resistance); #endif
next_available_channel++;
}
}
#endif
#if ENV_INCLUDE_BME280 #if ENV_PIN_SDA && ENV_PIN_SCL
if (BME280_initialized) { #ifdef NRF52_PLATFORM
if (BME280.takeForcedMeasurement()) { // trigger a fresh reading in forced mode Wire1.setPins(ENV_PIN_SDA, ENV_PIN_SCL);
telemetry.addTemperature(TELEM_CHANNEL_SELF, BME280.readTemperature()); Wire1.setClock(100000);
telemetry.addRelativeHumidity(TELEM_CHANNEL_SELF, BME280.readHumidity()); Wire1.begin();
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BME280.readPressure()/100); #else
telemetry.addAltitude(TELEM_CHANNEL_SELF, BME280.readAltitude(TELEM_BME280_SEALEVELPRESSURE_HPA)); Wire1.begin(ENV_PIN_SDA, ENV_PIN_SCL, 100000);
}
}
#endif #endif
MESH_DEBUG_PRINTLN("Second I2C initialized on pins SDA: %d SCL: %d", ENV_PIN_SDA, ENV_PIN_SCL);
#endif
#if ENV_INCLUDE_BMP280 // Scan the I2C bus before touching any sensor library.
if (BMP280_initialized) { bool detected[128] = {};
telemetry.addTemperature(TELEM_CHANNEL_SELF, BMP280.readTemperature()); scanI2CBus(TELEM_WIRE, detected);
telemetry.addBarometricPressure(TELEM_CHANNEL_SELF, BMP280.readPressure()/100);
telemetry.addAltitude(TELEM_CHANNEL_SELF, BMP280.readAltitude(TELEM_BMP280_SEALEVELPRESSURE_HPA)); // 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;
} }
#endif uint8_t n = def.init(TELEM_WIRE, def.address);
if (n == 0) {
#if ENV_INCLUDE_SHTC3 MESH_DEBUG_PRINTLN("%s found at %02X but failed to initialize", def.name, def.address);
if (SHTC3_initialized) { continue;
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 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++) {
#if ENV_INCLUDE_SHT4X _active_sensors[_active_sensor_count++] = { def.query, sub };
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 return true;
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) { // querySensors() — GPS stays on channel 1; each active sensor
for(int i = 0; i < TELEM_INA3221_NUM_CHANNELS; i++) { // gets the next available channel in the order it was
// add only enabled INA3221 channels to telemetry // initialized.
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 bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) {
if (INA219_initialized) { next_available_channel = TELEM_CHANNEL_SELF + 1;
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);
next_available_channel++;
}
#endif
#if ENV_INCLUDE_INA260 if (requester_permissions & TELEM_PERM_LOCATION && gps_active) {
if (INA260_initialized) { telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude);
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 (requester_permissions & TELEM_PERM_ENVIRONMENT) {
if (INA226_initialized) { for (int i = 0; i < _active_sensor_count; i++) {
telemetry.addVoltage(next_available_channel, INA226.getBusVoltage()); _active_sensors[i].query(next_available_channel, _active_sensors[i].sub_channel, telemetry);
telemetry.addCurrent(next_available_channel, INA226.getCurrent_mA() / 1000.0);
telemetry.addPower(next_available_channel, INA226.getPower_mW() / 1000.0);
next_available_channel++; 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; return true;
} }
@ -555,8 +594,6 @@ const char* EnvironmentSensorManager::getSettingName(int i) const {
return "gps"; return "gps";
} }
#endif #endif
// convenient way to add params (needed for some tests)
// if (i == settings++) return "param.2";
return NULL; return NULL;
} }
@ -567,8 +604,6 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const {
return gps_active ? "1" : "0"; return gps_active ? "1" : "0";
} }
#endif #endif
// convenient way to add params ...
// if (i == settings++) return "2";
return NULL; return NULL;
} }
@ -584,11 +619,7 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val
} }
if (strcmp(name, "gps_interval") == 0) { if (strcmp(name, "gps_interval") == 0) {
uint32_t interval_seconds = atoi(value); uint32_t interval_seconds = atoi(value);
if (interval_seconds > 0) { gps_update_interval_sec = interval_seconds > 0 ? interval_seconds : 1;
gps_update_interval_sec = interval_seconds;
} else {
gps_update_interval_sec = 1; // Default to 1 second if 0
}
return true; return true;
} }
#endif #endif
@ -652,16 +683,10 @@ void EnvironmentSensorManager::rakGPSInit(){
//search for the correct IO standby pin depending on socket used //search for the correct IO standby pin depending on socket used
if(gpsIsAwake(WB_IO2)){ 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)){ 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)){ else if(gpsIsAwake(WB_IO5)){
// MESH_DEBUG_PRINTLN("RAK base board is RAK19001/11");
// MESH_DEBUG_PRINTLN("GPS is installed on Socket F");
} }
else{ else{
MESH_DEBUG_PRINTLN("No GPS found"); MESH_DEBUG_PRINTLN("No GPS found");
@ -708,15 +733,17 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){
return true; return true;
} else if (Serial1.available()) { } else if (Serial1.available()) {
MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on"); MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on");
#ifdef PIN_GPS_EN
if(PIN_GPS_EN){ if(PIN_GPS_EN){
gpsResetPin = PIN_GPS_EN; gpsResetPin = PIN_GPS_EN;
} }
#endif
serialGPSFlag = true; serialGPSFlag = true;
gps_active = true; gps_active = true;
gps_detected = true; gps_detected = true;
return true; return true;
} }
pinMode(ioPin, INPUT); pinMode(ioPin, INPUT);
MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next"); MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next");
return false; return false;

34
src/helpers/sensors/EnvironmentSensorManager.h

@ -6,27 +6,22 @@
class EnvironmentSensorManager : public SensorManager { class EnvironmentSensorManager : public SensorManager {
protected: protected:
int next_available_channel = TELEM_CHANNEL_SELF + 1; static const int MAX_ACTIVE_SENSORS = 16;
bool AHTX0_initialized = false; // Query function pointer + sub-channel index (for multi-channel sensors like INA3221).
bool BME280_initialized = false; // Sub-channel is 0 for all single-output sensors.
bool BMP280_initialized = false; struct ActiveSensor {
bool INA3221_initialized = false; void (*query)(uint8_t channel, uint8_t sub_channel, CayenneLPP& telemetry);
bool INA219_initialized = false; uint8_t sub_channel;
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;
bool gps_detected = false; ActiveSensor _active_sensors[MAX_ACTIVE_SENSORS];
bool gps_active = false; int _active_sensor_count = 0;
uint32_t gps_update_interval_sec = 1; // Default 1 second 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 #if ENV_INCLUDE_GPS
LocationProvider* _location; LocationProvider* _location;
@ -39,7 +34,6 @@ protected:
#endif #endif
#endif #endif
public: public:
#if ENV_INCLUDE_GPS #if ENV_INCLUDE_GPS
EnvironmentSensorManager(LocationProvider &location): _location(&location){}; EnvironmentSensorManager(LocationProvider &location): _location(&location){};

2
src/helpers/sensors/RAK12035_SoilMoisture.cpp

@ -431,7 +431,7 @@ bool RAK12035_SoilMoisture::sensor_on()
delay(10); // Wait for the sensor code to complete initialization delay(10); // Wait for the sensor code to complete initialization
*/ */
uint8_t v = 0; uint8_t v = 0;
time_t timeout = millis(); uint32_t timeout = millis();
while ((!query_sensor())) //Wait for sensor to respond to I2C commands, while ((!query_sensor())) //Wait for sensor to respond to I2C commands,
{ //indicating it is ready { //indicating it is ready
if ((millis() - timeout) > 50){ //0.5 second timeout for sensor to respond if ((millis() - timeout) > 50){ //0.5 second timeout for sensor to respond

8
src/helpers/ui/ST7789LCDDisplay.cpp

@ -27,12 +27,6 @@ bool ST7789LCDDisplay::begin() {
pinMode(PIN_TFT_LEDA_CTL, OUTPUT); pinMode(PIN_TFT_LEDA_CTL, OUTPUT);
digitalWrite(PIN_TFT_LEDA_CTL, HIGH); 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. // 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) #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT)
@ -166,4 +160,4 @@ uint16_t ST7789LCDDisplay::getTextWidth(const char* str) {
void ST7789LCDDisplay::endFrame() { void ST7789LCDDisplay::endFrame() {
// display.display(); // display.display();
} }

1
src/helpers/ui/buzzer.cpp

@ -12,7 +12,6 @@ void genericBuzzer::begin() {
quiet(false); quiet(false);
pinMode(PIN_BUZZER, OUTPUT); pinMode(PIN_BUZZER, OUTPUT);
digitalWrite(PIN_BUZZER, LOW); // need to pull low by default to avoid extreme power draw digitalWrite(PIN_BUZZER, LOW); // need to pull low by default to avoid extreme power draw
startup();
} }
void genericBuzzer::play(const char *melody) { void genericBuzzer::play(const char *melody) {

13
test/mocks/AES.h

@ -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) {}
};

14
test/mocks/SHA256.h

@ -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) {}
};

10
test/mocks/Stream.h

@ -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) {}
};

57
test/test_utils/test_tohex.cpp

@ -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();
}

5
variants/ebyte_eora_s3/platformio.ini

@ -135,3 +135,8 @@ build_src_filter = ${Ebyte_EoRa-S3.build_src_filter}
lib_deps = lib_deps =
${Ebyte_EoRa-S3.lib_deps} ${Ebyte_EoRa-S3.lib_deps}
densaugeo/base64 @ ~1.4.0 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/>

15
variants/ebyte_eora_s3/target.cpp

@ -64,21 +64,6 @@ bool radio_init() {
return true; // success 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/ebyte_eora_s3/target.h

@ -23,7 +23,4 @@ extern SensorManager sensors;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

4
variants/gat562_30s_mesh_kit/platformio.ini

@ -112,3 +112,7 @@ lib_deps =
densaugeo/base64 @ ~1.4.0 densaugeo/base64 @ ~1.4.0
end2endzone/NonBlockingRTTTL@^1.3.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/>

15
variants/gat562_30s_mesh_kit/target.cpp

@ -38,21 +38,6 @@ bool radio_init() {
return radio.std_init(&SPI); 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/gat562_30s_mesh_kit/target.h

@ -24,7 +24,4 @@ extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors; extern EnvironmentSensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/gat562_mesh_evb_pro/platformio.ini

@ -50,3 +50,8 @@ build_flags =
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
build_src_filter = ${GAT562_Mesh_EVB_Pro.build_src_filter} build_src_filter = ${GAT562_Mesh_EVB_Pro.build_src_filter}
+<../examples/simple_room_server> +<../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/>

15
variants/gat562_mesh_evb_pro/target.cpp

@ -37,21 +37,6 @@ bool radio_init() {
return radio.std_init(&SPI); 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/gat562_mesh_evb_pro/target.h

@ -23,7 +23,4 @@ extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors; extern EnvironmentSensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

4
variants/gat562_mesh_tracker_pro/platformio.ini

@ -106,3 +106,7 @@ lib_deps =
${GAT562_Mesh_Tracker_Pro.lib_deps} ${GAT562_Mesh_Tracker_Pro.lib_deps}
densaugeo/base64 @ ~1.4.0 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/>

15
variants/gat562_mesh_tracker_pro/target.cpp

@ -38,21 +38,6 @@ bool radio_init() {
return radio.std_init(&SPI); 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/gat562_mesh_tracker_pro/target.h

@ -24,7 +24,4 @@ extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors; extern EnvironmentSensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

4
variants/gat562_mesh_watch13/platformio.ini

@ -87,3 +87,7 @@ lib_deps =
${GAT562_Mesh_Watch13.lib_deps} ${GAT562_Mesh_Watch13.lib_deps}
densaugeo/base64 @ ~1.4.0 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/>

15
variants/gat562_mesh_watch13/target.cpp

@ -29,21 +29,6 @@ bool radio_init() {
return radio.std_init(&SPI); 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/gat562_mesh_watch13/target.h

@ -25,7 +25,4 @@ extern AutoDiscoverRTCClock rtc_clock;
extern EnvironmentSensorManager sensors; extern EnvironmentSensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/generic-e22/platformio.ini

@ -95,6 +95,11 @@ lib_deps =
${Generic_E22.lib_deps} ${Generic_E22.lib_deps}
${esp32_ota.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] [env:Generic_E22_sx1268_repeater]
extends = Generic_E22 extends = Generic_E22
build_src_filter = ${Generic_E22.build_src_filter} build_src_filter = ${Generic_E22.build_src_filter}

15
variants/generic-e22/target.cpp

@ -27,21 +27,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/generic-e22/target.h

@ -15,8 +15,5 @@ extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors; extern SensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

12
variants/generic_espnow/target.cpp

@ -17,18 +17,6 @@ bool radio_init() {
return true; // success 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: // 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 // https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/random.html
class ESP_RNG : public mesh::RNG { class ESP_RNG : public mesh::RNG {

3
variants/generic_espnow/target.h

@ -10,7 +10,4 @@ extern ESP32RTCClock rtc_clock;
extern SensorManager sensors; extern SensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/heltec_ct62/platformio.ini

@ -150,3 +150,8 @@ build_src_filter = ${Heltec_ct62.build_src_filter}
lib_deps = lib_deps =
${Heltec_ct62.lib_deps} ${Heltec_ct62.lib_deps}
${esp32_ota.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/>

15
variants/heltec_ct62/target.cpp

@ -16,21 +16,6 @@ bool radio_init() {
return radio.std_init(&SPI); 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_ct62/target.h

@ -14,7 +14,4 @@ extern AutoDiscoverRTCClock rtc_clock;
extern SensorManager sensors; extern SensorManager sensors;
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/heltec_e213/platformio.ini

@ -166,3 +166,8 @@ lib_deps =
${Heltec_E213_base.lib_deps} ${Heltec_E213_base.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0 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/>

15
variants/heltec_e213/target.cpp

@ -33,21 +33,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_e213/target.h

@ -23,7 +23,4 @@ extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/heltec_e290/platformio.ini

@ -162,3 +162,8 @@ lib_deps =
${Heltec_E290_base.lib_deps} ${Heltec_E290_base.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0 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/>

15
variants/heltec_e290/target.cpp

@ -33,21 +33,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_e290/target.h

@ -23,7 +23,4 @@ extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

7
variants/heltec_mesh_solar/platformio.ini

@ -92,4 +92,9 @@ build_src_filter = ${Heltec_mesh_solar.build_src_filter}
+<../examples/companion_radio/*.cpp> +<../examples/companion_radio/*.cpp>
lib_deps = lib_deps =
${Heltec_mesh_solar.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/>

15
variants/heltec_mesh_solar/target.cpp

@ -23,21 +23,6 @@ bool radio_init() {
return radio.std_init(&SPI); 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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_mesh_solar/target.h

@ -40,7 +40,4 @@ extern SolarSensorManager sensors;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

2
variants/heltec_t096/LoRaFEMControl.cpp

@ -9,7 +9,7 @@ void LoRaFEMControl::init(void)
pinMode(P_LORA_KCT8103L_PA_CSD, OUTPUT); pinMode(P_LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH); digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH);
pinMode(P_LORA_KCT8103L_PA_CTX, OUTPUT); 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); setLnaCanControl(true);
} }

9
variants/heltec_t096/platformio.ini

@ -92,7 +92,7 @@ build_src_filter = ${Heltec_t096.build_src_filter}
[env:Heltec_t096_room_server] [env:Heltec_t096_room_server]
extends = Heltec_t096 extends = Heltec_t096
build_src_filter = ${Heltec_t096.build_src_filter} build_src_filter = ${Heltec_t096.build_src_filter}
+<../examples/simple_room_server> +<../examples/simple_room_server>
build_flags = build_flags =
${Heltec_t096.build_flags} ${Heltec_t096.build_flags}
-D ADVERT_NAME='"Heltec_t096 Room"' -D ADVERT_NAME='"Heltec_t096 Room"'
@ -145,4 +145,9 @@ build_src_filter = ${Heltec_t096.build_src_filter}
+<../examples/companion_radio/ui-new/*.cpp> +<../examples/companion_radio/ui-new/*.cpp>
lib_deps = lib_deps =
${Heltec_t096.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/>

15
variants/heltec_t096/target.cpp

@ -43,21 +43,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_t096/target.h

@ -27,7 +27,4 @@ extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

12
variants/heltec_t114/platformio.ini

@ -50,7 +50,6 @@ upload_protocol = nrfutil
extends = Heltec_t114 extends = Heltec_t114
build_src_filter = ${Heltec_t114.build_src_filter} build_src_filter = ${Heltec_t114.build_src_filter}
+<../examples/simple_repeater> +<../examples/simple_repeater>
build_flags = build_flags =
${Heltec_t114.build_flags} ${Heltec_t114.build_flags}
-D ADVERT_NAME='"Heltec_T114 Repeater"' -D ADVERT_NAME='"Heltec_T114 Repeater"'
@ -127,10 +126,6 @@ build_flags =
-D DISPLAY_CLASS=NullDisplayDriver -D DISPLAY_CLASS=NullDisplayDriver
-D MAX_CONTACTS=350 -D MAX_CONTACTS=350
-D MAX_GROUP_CHANNELS=40 -D MAX_GROUP_CHANNELS=40
; -D BLE_PIN_CODE=123456
; -D BLE_DEBUG_LOGGING=1
; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_t114.build_src_filter} build_src_filter = ${Heltec_t114.build_src_filter}
+<helpers/nrf52/*.cpp> +<helpers/nrf52/*.cpp>
+<../examples/companion_radio/*.cpp> +<../examples/companion_radio/*.cpp>
@ -251,4 +246,9 @@ build_src_filter = ${Heltec_t114_with_display.build_src_filter}
+<../examples/companion_radio/ui-new/*.cpp> +<../examples/companion_radio/ui-new/*.cpp>
lib_deps = lib_deps =
${Heltec_t114_with_display.lib_deps} ${Heltec_t114_with_display.lib_deps}
densaugeo/base64 @ ~1.4.0 densaugeo/base64 @ ~1.4.0
[env:Heltec_t114_kiss_modem]
extends = Heltec_t114
build_src_filter = ${Heltec_t114.build_src_filter}
+<../examples/kiss_modem/>

15
variants/heltec_t114/target.cpp

@ -43,21 +43,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_t114/target.h

@ -29,7 +29,4 @@ extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/heltec_t190/platformio.ini

@ -153,3 +153,8 @@ build_src_filter = ${Heltec_T190_base.build_src_filter}
lib_deps = lib_deps =
${Heltec_T190_base.lib_deps} ${Heltec_T190_base.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
[env:Heltec_T190_kiss_modem]
extends = Heltec_T190_base
build_src_filter = ${Heltec_T190_base.build_src_filter}
+<../examples/kiss_modem/>

15
variants/heltec_t190/target.cpp

@ -33,21 +33,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_t190/target.h

@ -23,7 +23,4 @@ extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

5
variants/heltec_tracker/platformio.ini

@ -186,3 +186,8 @@ build_src_filter = ${Heltec_tracker_base.build_src_filter}
lib_deps = lib_deps =
${Heltec_tracker_base.lib_deps} ${Heltec_tracker_base.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
[env:Heltec_Wireless_Tracker_kiss_modem]
extends = Heltec_tracker_base
build_src_filter = ${Heltec_tracker_base.build_src_filter}
+<../examples/kiss_modem/>

15
variants/heltec_tracker/target.cpp

@ -37,21 +37,6 @@ bool radio_init() {
} }
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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_tracker/target.h

@ -42,7 +42,4 @@ extern HWTSensorManager sensors;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

2
variants/heltec_tracker_v2/LoRaFEMControl.cpp

@ -14,7 +14,7 @@ void LoRaFEMControl::init(void)
pinMode(P_LORA_KCT8103L_PA_CSD, OUTPUT); pinMode(P_LORA_KCT8103L_PA_CSD, OUTPUT);
digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH); digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH);
pinMode(P_LORA_KCT8103L_PA_CTX, OUTPUT); 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); setLnaCanControl(true);
} }

7
variants/heltec_tracker_v2/platformio.ini

@ -187,7 +187,7 @@ build_flags =
-D WIFI_DEBUG_LOGGING=1 -D WIFI_DEBUG_LOGGING=1
-D WIFI_SSID='"myssid"' -D WIFI_SSID='"myssid"'
-D WIFI_PWD='"mypwd"' -D WIFI_PWD='"mypwd"'
-D OFFLINE_QUEUE_SIZE=256 -D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
build_src_filter = ${Heltec_tracker_v2.build_src_filter} build_src_filter = ${Heltec_tracker_v2.build_src_filter}
@ -217,3 +217,8 @@ build_src_filter = ${Heltec_tracker_v2.build_src_filter}
lib_deps = lib_deps =
${Heltec_tracker_v2.lib_deps} ${Heltec_tracker_v2.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
[env:heltec_tracker_v2_kiss_modem]
extends = Heltec_tracker_v2
build_src_filter = ${Heltec_tracker_v2.build_src_filter}
+<../examples/kiss_modem/>

15
variants/heltec_tracker_v2/target.cpp

@ -39,21 +39,6 @@ bool radio_init() {
#endif #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() { mesh::LocalIdentity radio_new_identity() {
RadioNoiseListener rng(radio); RadioNoiseListener rng(radio);
return mesh::LocalIdentity(&rng); // create new random identity return mesh::LocalIdentity(&rng); // create new random identity

3
variants/heltec_tracker_v2/target.h

@ -24,8 +24,5 @@ extern EnvironmentSensorManager sensors;
#endif #endif
bool radio_init(); 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(); mesh::LocalIdentity radio_new_identity();

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save