mirror of
https://github.com/meshcore-dev/MeshCore.git
synced 2026-04-20 22:13:47 +00:00
Merge branch 'meshcore-dev:dev' into dev
This commit is contained in:
commit
7ecfbfd4e6
16 changed files with 304 additions and 170 deletions
|
|
@ -63,6 +63,12 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### Send a zero-hop advert
|
||||||
|
**Usage:**
|
||||||
|
- `advert.zerohop`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Start an Over-The-Air (OTA) firmware update
|
### Start an Over-The-Air (OTA) firmware update
|
||||||
**Usage:**
|
**Usage:**
|
||||||
- `start ota`
|
- `start ota`
|
||||||
|
|
@ -355,13 +361,25 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### View this node's public key
|
||||||
|
**Usage:** `get public.key`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### View this node's configured role
|
||||||
|
**Usage:** `get role`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### View or change this node's power saving flag (Repeater Only)
|
#### View or change this node's power saving flag (Repeater Only)
|
||||||
**Usage:**
|
**Usage:**
|
||||||
- `powersaving <state>`
|
|
||||||
- `powersaving`
|
- `powersaving`
|
||||||
|
- `powersaving on`
|
||||||
|
- `powersaving off`
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `state`: `on`|`off`
|
- `on`: enable power saving
|
||||||
|
- `off`: disable power saving
|
||||||
|
|
||||||
**Default:** `on`
|
**Default:** `on`
|
||||||
|
|
||||||
|
|
@ -383,6 +401,46 @@ This document provides an overview of CLI commands that can be sent to MeshCore
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### View or change this node's advert path hash size
|
||||||
|
**Usage:**
|
||||||
|
- `get path.hash.mode`
|
||||||
|
- `set path.hash.mode <value>`
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `value`: Path hash size (0-2)
|
||||||
|
- `0`: 1 Byte hash size (256 unique ids)[64 max flood]
|
||||||
|
- `1`: 2 Byte hash size (65,536 unique ids)[32 max flood]
|
||||||
|
- `2`: 3 Byte hash size (16,777,216 unique ids)[21 max flood]
|
||||||
|
- `3`: DO NOT USE (Reserved)
|
||||||
|
|
||||||
|
**Default:** `0`
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### View or change this node's loop detection
|
||||||
|
**Usage:**
|
||||||
|
- `get loop.detect`
|
||||||
|
- `set loop.detect <state>`
|
||||||
|
|
||||||
|
**Parameters:**
|
||||||
|
- `state`:
|
||||||
|
- `off`: no loop detection is performed
|
||||||
|
- `minimal`: packets are dropped if repeater's ID/hash appears 4 or more times (1-byte), 2 or more (2-byte), 1 or more (3-byte)
|
||||||
|
- `moderate`: packets are dropped if repeater's ID/hash appears 2 or more times (1-byte), 1 or more (2-byte), 1 or more (3-byte)
|
||||||
|
- `strict`: packets are dropped if repeater's ID/hash appears 1 or more times (1-byte), 1 or more (2-byte), 1 or more (3-byte)
|
||||||
|
|
||||||
|
**Default:** `off`
|
||||||
|
|
||||||
|
**Note:** When it is enabled, repeaters will now reject flood packets which look like they are in a loop. This has been happening recently in some meshes when there is just a single 'bad' repeater firmware out there (prob some forked or custom firmware). If the payload is messed with, then forwarded, the same packet ends up causing a packet storm, repeated up to the max 64 hops. This feature was added in firmware 1.14
|
||||||
|
|
||||||
|
**Example:** If preference is `loop.detect minimal`, and a 1-byte path size packet is received, the repeater will see if its own ID/hash is already in the path. If it's already encoded 4 times, it will reject the packet. If the packet uses 2-byte path size, and repeater's own ID/hash is already encoded 2 times, it rejects. If the packet uses 3-byte path size, and the repeater's own ID/hash is already encoded 1 time, it rejects.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### View or change the retransmit delay factor for flood traffic
|
#### View or change the retransmit delay factor for flood traffic
|
||||||
**Usage:**
|
**Usage:**
|
||||||
- `get txdelay`
|
- `get txdelay`
|
||||||
|
|
@ -804,6 +862,11 @@ region save
|
||||||
|
|
||||||
### Bridge (When bridge support is compiled in)
|
### Bridge (When bridge support is compiled in)
|
||||||
|
|
||||||
|
#### View the compiled bridge type
|
||||||
|
**Usage:** `get bridge.type`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
#### View or change the bridge enabled flag
|
#### View or change the bridge enabled flag
|
||||||
**Usage:**
|
**Usage:**
|
||||||
- `get bridge.enabled`
|
- `get bridge.enabled`
|
||||||
|
|
@ -841,10 +904,10 @@ region save
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `source`:
|
- `source`:
|
||||||
- `rx`: bridges received packets
|
- `logRx`: bridges received packets
|
||||||
- `tx`: bridges transmitted packets
|
- `logTx`: bridges transmitted packets
|
||||||
|
|
||||||
**Default:** `tx`
|
**Default:** `logTx`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -876,8 +939,39 @@ region save
|
||||||
- `set bridge.secret <secret>`
|
- `set bridge.secret <secret>`
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `secret`: 16-character encryption secret
|
- `secret`: ESP-NOW bridge secret, up to 15 characters
|
||||||
|
|
||||||
**Default:** Varies by board
|
**Default:** Varies by board
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
#### View the bootloader version (nRF52 only)
|
||||||
|
**Usage:** `get bootloader.ver`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### View power management support
|
||||||
|
**Usage:** `get pwrmgt.support`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### View the current power source
|
||||||
|
**Usage:** `get pwrmgt.source`
|
||||||
|
|
||||||
|
**Note:** Returns an error on boards without power management support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### View the boot reset and shutdown reasons
|
||||||
|
**Usage:** `get pwrmgt.bootreason`
|
||||||
|
|
||||||
|
**Note:** Returns an error on boards without power management support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
#### View the boot voltage
|
||||||
|
**Usage:** `get pwrmgt.bootmv`
|
||||||
|
|
||||||
|
**Note:** Returns an error on boards without power management support.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Companion Protocol
|
# Companion Protocol
|
||||||
|
|
||||||
- **Last Updated**: 2026-01-03
|
- **Last Updated**: 2026-03-08
|
||||||
- **Protocol Version**: Companion Firmware v1.12.0+
|
- **Protocol Version**: Companion Firmware v1.12.0+
|
||||||
|
|
||||||
> NOTE: This document is still in development. Some information may be inaccurate.
|
> NOTE: This document is still in development. Some information may be inaccurate.
|
||||||
|
|
@ -100,7 +100,7 @@ When writing commands to the RX characteristic, specify the write type:
|
||||||
|
|
||||||
### MTU (Maximum Transmission Unit)
|
### MTU (Maximum Transmission Unit)
|
||||||
|
|
||||||
The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (66 bytes), you may need to:
|
The default BLE MTU is 23 bytes (20 bytes payload). For larger commands like `SET_CHANNEL` (50 bytes), you may need to:
|
||||||
|
|
||||||
1. **Request Larger MTU**: Request MTU of 512 bytes if supported
|
1. **Request Larger MTU**: Request MTU of 512 bytes if supported
|
||||||
- Android: `gatt.requestMtu(512)`
|
- Android: `gatt.requestMtu(512)`
|
||||||
|
|
@ -167,16 +167,16 @@ The first byte indicates the packet type (see [Response Parsing](#response-parsi
|
||||||
**Command Format**:
|
**Command Format**:
|
||||||
```
|
```
|
||||||
Byte 0: 0x01
|
Byte 0: 0x01
|
||||||
Byte 1: 0x03
|
Bytes 1-7: Reserved (currently ignored by firmware)
|
||||||
Bytes 2-10: "mccli" (ASCII, null-padded to 9 bytes)
|
Bytes 8+: Application name (UTF-8, optional)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example** (hex):
|
**Example** (hex):
|
||||||
```
|
```
|
||||||
01 03 6d 63 63 6c 69 00 00 00 00
|
01 00 00 00 00 00 00 00 6d 63 63 6c 69
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response**: `PACKET_OK` (0x00)
|
**Response**: `PACKET_SELF_INFO` (0x05)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -216,8 +216,6 @@ Byte 1: Channel Index (0-7)
|
||||||
|
|
||||||
**Response**: `PACKET_CHANNEL_INFO` (0x12) with channel details
|
**Response**: `PACKET_CHANNEL_INFO` (0x12) with channel details
|
||||||
|
|
||||||
**Note**: The device does not return channel secrets for security reasons. Store secrets locally when creating channels.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 4. Set Channel
|
### 4. Set Channel
|
||||||
|
|
@ -229,10 +227,10 @@ Byte 1: Channel Index (0-7)
|
||||||
Byte 0: 0x20
|
Byte 0: 0x20
|
||||||
Byte 1: Channel Index (0-7)
|
Byte 1: Channel Index (0-7)
|
||||||
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
|
Bytes 2-33: Channel Name (32 bytes, UTF-8, null-padded)
|
||||||
Bytes 34-65: Secret (32 bytes)
|
Bytes 34-49: Secret (16 bytes)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Total Length**: 66 bytes
|
**Total Length**: 50 bytes
|
||||||
|
|
||||||
**Channel Index**:
|
**Channel Index**:
|
||||||
- Index 0: Reserved for public channels (no secret)
|
- Index 0: Reserved for public channels (no secret)
|
||||||
|
|
@ -243,16 +241,18 @@ Bytes 34-65: Secret (32 bytes)
|
||||||
- Maximum 32 bytes
|
- Maximum 32 bytes
|
||||||
- Padded with null bytes (0x00) if shorter
|
- Padded with null bytes (0x00) if shorter
|
||||||
|
|
||||||
**Secret Field** (32 bytes):
|
**Secret Field** (16 bytes):
|
||||||
- For **private channels**: 32-byte secret
|
- For **private channels**: 16-byte secret
|
||||||
- For **public channels**: All zeros (0x00)
|
- For **public channels**: All zeros (0x00)
|
||||||
|
|
||||||
**Example** (create channel "YourChannelName" at index 1 with secret):
|
**Example** (create channel "YourChannelName" at index 1 with secret):
|
||||||
```
|
```
|
||||||
20 01 53 4D 53 00 00 ... (name padded to 32 bytes)
|
20 01 53 4D 53 00 00 ... (name padded to 32 bytes)
|
||||||
[32 bytes of secret]
|
[16 bytes of secret]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Note**: The 32-byte secret variant is unsupported and returns `PACKET_ERROR`.
|
||||||
|
|
||||||
**Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure
|
**Response**: `PACKET_OK` (0x00) on success, `PACKET_ERROR` (0x01) on failure
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -304,9 +304,9 @@ Byte 0: 0x0A
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 7. Get Battery
|
### 7. Get Battery and Storage
|
||||||
|
|
||||||
**Purpose**: Query device battery level.
|
**Purpose**: Query device battery voltage and storage usage.
|
||||||
|
|
||||||
**Command Format**:
|
**Command Format**:
|
||||||
```
|
```
|
||||||
|
|
@ -318,7 +318,7 @@ Byte 0: 0x14
|
||||||
14
|
14
|
||||||
```
|
```
|
||||||
|
|
||||||
**Response**: `PACKET_BATTERY` (0x0C) with battery percentage
|
**Response**: `PACKET_BATTERY` (0x0C) with battery millivolts and storage information
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -346,7 +346,7 @@ Byte 0: 0x14
|
||||||
1. **Set Channel**:
|
1. **Set Channel**:
|
||||||
- Fetch all channel slots, and find one with empty name and all-zero secret
|
- Fetch all channel slots, and find one with empty name and all-zero secret
|
||||||
- Generate or provide a 16-byte secret
|
- Generate or provide a 16-byte secret
|
||||||
- Send `CMD_SET_CHANNEL` with name and secret
|
- Send `CMD_SET_CHANNEL` with name and a 16-byte secret
|
||||||
2. **Get Channel**:
|
2. **Get Channel**:
|
||||||
- Send `CMD_GET_CHANNEL` with channel index
|
- Send `CMD_GET_CHANNEL` with channel index
|
||||||
- Parse `RESP_CODE_CHANNEL_INFO` response
|
- Parse `RESP_CODE_CHANNEL_INFO` response
|
||||||
|
|
@ -360,7 +360,7 @@ Byte 0: 0x14
|
||||||
|
|
||||||
### Receiving Messages
|
### Receiving Messages
|
||||||
|
|
||||||
Messages are received via the RX characteristic (notifications). The device sends:
|
Messages are received via the TX characteristic (notifications). The device sends:
|
||||||
|
|
||||||
1. **Channel Messages**:
|
1. **Channel Messages**:
|
||||||
- `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format
|
- `PACKET_CHANNEL_MSG_RECV` (0x08) - Standard format
|
||||||
|
|
@ -544,10 +544,10 @@ Byte 1: Error code (optional)
|
||||||
Byte 0: 0x12
|
Byte 0: 0x12
|
||||||
Byte 1: Channel Index
|
Byte 1: Channel Index
|
||||||
Bytes 2-33: Channel Name (32 bytes, null-terminated)
|
Bytes 2-33: Channel Name (32 bytes, null-terminated)
|
||||||
Bytes 34-65: Secret (32 bytes, but device typically only returns 20 bytes total)
|
Bytes 34-49: Secret (16 bytes)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note**: The device may not return the full 66-byte packet. Parse what is available. The secret field is typically not returned for security reasons.
|
**Note**: The device returns the 16-byte channel secret in this response.
|
||||||
|
|
||||||
**PACKET_DEVICE_INFO** (0x0D):
|
**PACKET_DEVICE_INFO** (0x0D):
|
||||||
```
|
```
|
||||||
|
|
@ -562,6 +562,8 @@ Bytes 4-7: BLE PIN (32-bit little-endian)
|
||||||
Bytes 8-19: Firmware Build (12 bytes, UTF-8, null-padded)
|
Bytes 8-19: Firmware Build (12 bytes, UTF-8, null-padded)
|
||||||
Bytes 20-59: Model (40 bytes, UTF-8, null-padded)
|
Bytes 20-59: Model (40 bytes, UTF-8, null-padded)
|
||||||
Bytes 60-79: Version (20 bytes, UTF-8, null-padded)
|
Bytes 60-79: Version (20 bytes, UTF-8, null-padded)
|
||||||
|
Byte 80: Client repeat enabled/preferred (firmware v9+)
|
||||||
|
Byte 81: Path hash mode (firmware v10+)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parsing Pseudocode**:
|
**Parsing Pseudocode**:
|
||||||
|
|
@ -587,9 +589,7 @@ def parse_device_info(data):
|
||||||
**PACKET_BATTERY** (0x0C):
|
**PACKET_BATTERY** (0x0C):
|
||||||
```
|
```
|
||||||
Byte 0: 0x0C
|
Byte 0: 0x0C
|
||||||
Bytes 1-2: Battery Level (16-bit little-endian, percentage 0-100)
|
Bytes 1-2: Battery Voltage (16-bit little-endian, millivolts)
|
||||||
|
|
||||||
Optional (if data size > 3):
|
|
||||||
Bytes 3-6: Used Storage (32-bit little-endian, KB)
|
Bytes 3-6: Used Storage (32-bit little-endian, KB)
|
||||||
Bytes 7-10: Total Storage (32-bit little-endian, KB)
|
Bytes 7-10: Total Storage (32-bit little-endian, KB)
|
||||||
```
|
```
|
||||||
|
|
@ -600,14 +600,12 @@ def parse_battery(data):
|
||||||
if len(data) < 3:
|
if len(data) < 3:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
level = int.from_bytes(data[1:3], 'little')
|
mv = int.from_bytes(data[1:3], 'little')
|
||||||
info = {'level': level}
|
info = {'battery_mv': mv}
|
||||||
|
|
||||||
if len(data) > 3:
|
if len(data) >= 11:
|
||||||
used_kb = int.from_bytes(data[3:7], 'little')
|
info['used_kb'] = int.from_bytes(data[3:7], 'little')
|
||||||
total_kb = int.from_bytes(data[7:11], 'little')
|
info['total_kb'] = int.from_bytes(data[7:11], 'little')
|
||||||
info['used_kb'] = used_kb
|
|
||||||
info['total_kb'] = total_kb
|
|
||||||
|
|
||||||
return info
|
return info
|
||||||
```
|
```
|
||||||
|
|
@ -629,7 +627,7 @@ Bytes 48-51: Radio Frequency (32-bit little-endian, divided by 1000.0)
|
||||||
Bytes 52-55: Radio Bandwidth (32-bit little-endian, divided by 1000.0)
|
Bytes 52-55: Radio Bandwidth (32-bit little-endian, divided by 1000.0)
|
||||||
Byte 56: Radio Spreading Factor
|
Byte 56: Radio Spreading Factor
|
||||||
Byte 57: Radio Coding Rate
|
Byte 57: Radio Coding Rate
|
||||||
Bytes 58+: Device Name (UTF-8, variable length, null-terminated)
|
Bytes 58+: Device Name (UTF-8, variable length, no null terminator required)
|
||||||
```
|
```
|
||||||
|
|
||||||
**Parsing Pseudocode**:
|
**Parsing Pseudocode**:
|
||||||
|
|
@ -680,9 +678,9 @@ def parse_self_info(data):
|
||||||
**PACKET_MSG_SENT** (0x06):
|
**PACKET_MSG_SENT** (0x06):
|
||||||
```
|
```
|
||||||
Byte 0: 0x06
|
Byte 0: 0x06
|
||||||
Byte 1: Message Type
|
Byte 1: Route Flag (0 = direct, 1 = flood)
|
||||||
Bytes 2-5: Expected ACK (4 bytes, hex)
|
Bytes 2-5: Tag / Expected ACK (4 bytes, little-endian)
|
||||||
Bytes 6-9: Suggested Timeout (32-bit little-endian, seconds)
|
Bytes 6-9: Suggested Timeout (32-bit little-endian, milliseconds)
|
||||||
```
|
```
|
||||||
|
|
||||||
**PACKET_ACK** (0x82):
|
**PACKET_ACK** (0x82):
|
||||||
|
|
@ -710,89 +708,32 @@ Bytes 1-6: ACK Code (6 bytes, hex)
|
||||||
|
|
||||||
**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.
|
||||||
|
|
||||||
### Partial Packet Handling
|
### Frame Handling
|
||||||
|
|
||||||
BLE notifications may arrive in chunks, especially for larger packets. Implement buffering:
|
BLE implementations enqueue and deliver one protocol frame per BLE write/notification at the firmware layer.
|
||||||
|
|
||||||
**Implementation**:
|
- Apps should treat each characteristic write/notification as exactly one companion protocol frame
|
||||||
```python
|
- Apps should still validate frame lengths before parsing
|
||||||
class PacketBuffer:
|
- Future transports or firmware revisions may differ, so avoid assuming fixed payload sizes for variable-length responses
|
||||||
def __init__(self):
|
|
||||||
self.buffer = bytearray()
|
|
||||||
self.expected_length = None
|
|
||||||
|
|
||||||
def add_data(self, data):
|
|
||||||
self.buffer.extend(data)
|
|
||||||
|
|
||||||
# Check if we have a complete packet
|
|
||||||
if len(self.buffer) >= 1:
|
|
||||||
packet_type = self.buffer[0]
|
|
||||||
|
|
||||||
# Determine expected length based on packet type
|
|
||||||
expected = self.get_expected_length(packet_type)
|
|
||||||
|
|
||||||
if expected is not None and len(self.buffer) >= expected:
|
|
||||||
# Complete packet
|
|
||||||
packet = bytes(self.buffer[:expected])
|
|
||||||
self.buffer = self.buffer[expected:]
|
|
||||||
return packet
|
|
||||||
elif expected is None:
|
|
||||||
# Variable length packet - try to parse what we have
|
|
||||||
# Some packets have minimum length requirements
|
|
||||||
if self.can_parse_partial(packet_type):
|
|
||||||
return self.try_parse_partial()
|
|
||||||
|
|
||||||
return None # Incomplete packet
|
|
||||||
|
|
||||||
def get_expected_length(self, packet_type):
|
|
||||||
# Fixed-length packets
|
|
||||||
fixed_lengths = {
|
|
||||||
0x00: 5, # PACKET_OK (minimum)
|
|
||||||
0x01: 2, # PACKET_ERROR (minimum)
|
|
||||||
0x0A: 1, # PACKET_NO_MORE_MSGS
|
|
||||||
0x14: 3, # PACKET_BATTERY (minimum)
|
|
||||||
}
|
|
||||||
return fixed_lengths.get(packet_type)
|
|
||||||
|
|
||||||
def can_parse_partial(self, packet_type):
|
|
||||||
# Some packets can be parsed partially
|
|
||||||
return packet_type in [0x12, 0x08, 0x11, 0x07, 0x10, 0x05, 0x0D]
|
|
||||||
|
|
||||||
def try_parse_partial(self):
|
|
||||||
# Try to parse with available data
|
|
||||||
# Return packet if successfully parsed, None otherwise
|
|
||||||
# This is packet-type specific
|
|
||||||
pass
|
|
||||||
```
|
|
||||||
|
|
||||||
**Usage**:
|
|
||||||
```python
|
|
||||||
buffer = PacketBuffer()
|
|
||||||
|
|
||||||
def on_notification_received(data):
|
|
||||||
packet = buffer.add_data(data)
|
|
||||||
if packet:
|
|
||||||
parse_and_handle_packet(packet)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Response Handling
|
### Response Handling
|
||||||
|
|
||||||
1. **Command-Response Pattern**:
|
1. **Command-Response Pattern**:
|
||||||
- Send command via TX characteristic
|
- Send command via RX characteristic
|
||||||
- Wait for response via RX characteristic (notification)
|
- Wait for response via TX characteristic (notification)
|
||||||
- Match response to command using sequence numbers or command type
|
- Match response to command using sequence numbers or command type
|
||||||
- Handle timeout (typically 5 seconds)
|
- Handle timeout (typically 5 seconds)
|
||||||
- Use command queue to prevent concurrent commands
|
- Use command queue to prevent concurrent commands
|
||||||
|
|
||||||
2. **Asynchronous Messages**:
|
2. **Asynchronous Messages**:
|
||||||
- Device may send messages at any time via RX characteristic
|
- Device may send messages at any time via TX characteristic
|
||||||
- Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command
|
- Handle `PACKET_MESSAGES_WAITING` (0x83) by polling `GET_MESSAGE` command
|
||||||
- Parse incoming messages and route to appropriate handlers
|
- Parse incoming messages and route to appropriate handlers
|
||||||
- Buffer partial packets until complete
|
- Validate frame length before decoding
|
||||||
|
|
||||||
3. **Response Matching**:
|
3. **Response Matching**:
|
||||||
- Match responses to commands by expected packet type:
|
- Match responses to commands by expected packet type:
|
||||||
- `APP_START` → `PACKET_OK`
|
- `APP_START` → `PACKET_SELF_INFO`
|
||||||
- `DEVICE_QUERY` → `PACKET_DEVICE_INFO`
|
- `DEVICE_QUERY` → `PACKET_DEVICE_INFO`
|
||||||
- `GET_CHANNEL` → `PACKET_CHANNEL_INFO`
|
- `GET_CHANNEL` → `PACKET_CHANNEL_INFO`
|
||||||
- `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR`
|
- `SET_CHANNEL` → `PACKET_OK` or `PACKET_ERROR`
|
||||||
|
|
@ -825,16 +766,16 @@ device = scan_for_device("MeshCore")
|
||||||
gatt = connect_to_device(device)
|
gatt = connect_to_device(device)
|
||||||
|
|
||||||
# 3. Discover services and characteristics
|
# 3. Discover services and characteristics
|
||||||
service = discover_service(gatt, "0000ff00-0000-1000-8000-00805f9b34fb")
|
service = discover_service(gatt, "6E400001-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||||
rx_char = discover_characteristic(service, "0000ff01-0000-1000-8000-00805f9b34fb")
|
rx_char = discover_characteristic(service, "6E400002-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||||
tx_char = discover_characteristic(service, "0000ff02-0000-1000-8000-00805f9b34fb")
|
tx_char = discover_characteristic(service, "6E400003-B5A3-F393-E0A9-E50E24DCCA9E")
|
||||||
|
|
||||||
# 4. Enable notifications on RX characteristic
|
# 4. Enable notifications on TX characteristic
|
||||||
enable_notifications(rx_char, on_notification_received)
|
enable_notifications(tx_char, on_notification_received)
|
||||||
|
|
||||||
# 5. Send AppStart command
|
# 5. Send AppStart command
|
||||||
send_command(tx_char, build_app_start())
|
send_command(rx_char, build_app_start())
|
||||||
wait_for_response(PACKET_OK)
|
wait_for_response(PACKET_SELF_INFO)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating a Private Channel
|
### Creating a Private Channel
|
||||||
|
|
@ -844,21 +785,16 @@ wait_for_response(PACKET_OK)
|
||||||
secret_16_bytes = generate_secret(16) # Use CSPRNG
|
secret_16_bytes = generate_secret(16) # Use CSPRNG
|
||||||
secret_hex = secret_16_bytes.hex()
|
secret_hex = secret_16_bytes.hex()
|
||||||
|
|
||||||
# 2. Expand secret to 32 bytes using SHA-512
|
# 2. Build SET_CHANNEL command
|
||||||
import hashlib
|
|
||||||
sha512_hash = hashlib.sha512(secret_16_bytes).digest()
|
|
||||||
secret_32_bytes = sha512_hash[:32]
|
|
||||||
|
|
||||||
# 3. Build SET_CHANNEL command
|
|
||||||
channel_name = "YourChannelName"
|
channel_name = "YourChannelName"
|
||||||
channel_index = 1 # Use 1-7 for private channels
|
channel_index = 1 # Use 1-7 for private channels
|
||||||
command = build_set_channel(channel_index, channel_name, secret_32_bytes)
|
command = build_set_channel(channel_index, channel_name, secret_16_bytes)
|
||||||
|
|
||||||
# 4. Send command
|
# 3. Send command
|
||||||
send_command(tx_char, command)
|
send_command(rx_char, command)
|
||||||
response = wait_for_response(PACKET_OK)
|
response = wait_for_response(PACKET_OK)
|
||||||
|
|
||||||
# 5. Store secret locally (device won't return it)
|
# 4. Store secret locally
|
||||||
store_channel_secret(channel_index, secret_hex)
|
store_channel_secret(channel_index, secret_hex)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -872,7 +808,7 @@ timestamp = int(time.time())
|
||||||
command = build_channel_message(channel_index, message, timestamp)
|
command = build_channel_message(channel_index, message, timestamp)
|
||||||
|
|
||||||
# 2. Send command
|
# 2. Send command
|
||||||
send_command(tx_char, command)
|
send_command(rx_char, command)
|
||||||
response = wait_for_response(PACKET_MSG_SENT)
|
response = wait_for_response(PACKET_MSG_SENT)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -887,7 +823,7 @@ def on_notification_received(data):
|
||||||
handle_channel_message(message)
|
handle_channel_message(message)
|
||||||
elif packet_type == PACKET_MESSAGES_WAITING:
|
elif packet_type == PACKET_MESSAGES_WAITING:
|
||||||
# Poll for messages
|
# Poll for messages
|
||||||
send_command(tx_char, build_get_message())
|
send_command(rx_char, build_get_message())
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
10
docs/faq.md
10
docs/faq.md
|
|
@ -221,11 +221,11 @@ MeshCore allows you to manually broadcast your name, position and public encrypt
|
||||||
* Zero hop means your advert is broadcasted out to anyone that can hear it, and that's it.
|
* Zero hop means your advert is broadcasted out to anyone that can hear it, and that's it.
|
||||||
* Flooded means it's broadcasted out and then repeated by all the repeaters that hear it.
|
* Flooded means it's broadcasted out and then repeated by all the repeaters that hear it.
|
||||||
|
|
||||||
MeshCore clients only advertise themselves when the user initiates it. A repeater sends a flood advert once every 3 hours by default. This interval can be configured using the following command:
|
MeshCore clients only advertise themselves when the user initiates it. A repeater sends a flood advert once every 12 hours by default. This interval can be configured using the following command:
|
||||||
|
|
||||||
`set advert.interval {minutes}`
|
`set flood.advert.interval {hours}`
|
||||||
|
|
||||||
As of Aug 20 2025, a pending PR on github will change the flood advert to 12 hours to minimize airtime utilization caused by repeaters' flood adverts.
|
The separate `set advert.interval {minutes}` command controls the local zero-hop advert timer.
|
||||||
|
|
||||||
### 2.5. Q: Is there a hop limit?
|
### 2.5. Q: Is there a hop limit?
|
||||||
|
|
||||||
|
|
@ -260,7 +260,9 @@ Repeater or room server can be administered with one of the options below:
|
||||||
### 3.2. Q: Do I need to set the location for a repeater?
|
### 3.2. Q: Do I need to set the location for a repeater?
|
||||||
**A:** While not required, with location set for a repeater it will show up on the MeshCore map in the future. Set location with the following command:
|
**A:** While not required, with location set for a repeater it will show up on the MeshCore map in the future. Set location with the following command:
|
||||||
|
|
||||||
`set lat <GPS Lat> set long <GPS Lon>`
|
`set lat <GPS Lat>`
|
||||||
|
|
||||||
|
`set lon <GPS Lon>`
|
||||||
|
|
||||||
You can get the latitude and longitude from Google Maps by right-clicking the location you are at on the map.
|
You can get the latitude and longitude from Google Maps by right-clicking the location you are at on the map.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -190,7 +190,7 @@ All values little-endian.
|
||||||
| Field | Size | Description |
|
| Field | Size | Description |
|
||||||
|-------|------|-------------|
|
|-------|------|-------------|
|
||||||
| MAC | 2 bytes | HMAC-SHA256 truncated to 2 bytes |
|
| MAC | 2 bytes | HMAC-SHA256 truncated to 2 bytes |
|
||||||
| Ciphertext | variable | AES-128-CBC encrypted data |
|
| Ciphertext | variable | AES-128 block-encrypted data with zero padding |
|
||||||
|
|
||||||
### Airtime (Airtime response)
|
### Airtime (Airtime response)
|
||||||
|
|
||||||
|
|
@ -268,7 +268,7 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.
|
||||||
|-----------|-----------|
|
|-----------|-----------|
|
||||||
| Identity / Signing / Verification | Ed25519 |
|
| Identity / Signing / Verification | Ed25519 |
|
||||||
| Key Exchange | X25519 (ECDH) |
|
| Key Exchange | X25519 (ECDH) |
|
||||||
| Encryption | AES-128-CBC + HMAC-SHA256 (MAC truncated to 2 bytes) |
|
| Encryption | AES-128 block encryption with zero padding + HMAC-SHA256 (MAC truncated to 2 bytes) |
|
||||||
| Hashing | SHA-256 |
|
| Hashing | SHA-256 |
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
@ -279,4 +279,4 @@ Data returned in CayenneLPP format. See [CayenneLPP documentation](https://docs.
|
||||||
- SNR values in RxMeta are multiplied by 4 for 0.25 dB precision
|
- SNR values in RxMeta are multiplied by 4 for 0.25 dB precision
|
||||||
- TxDone is sent as a SetHardware event after each transmission
|
- TxDone is sent as a SetHardware event after each transmission
|
||||||
- Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames
|
- Standard KISS clients receive only type 0x00 data frames and can safely ignore all SetHardware (0x06) frames
|
||||||
- See [packet_structure.md](./packet_structure.md) for packet format
|
- See [packet_format.md](./packet_format.md) for packet format
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ Shutdown reason codes (stored in GPREGRET2):
|
||||||
| RAK WisMesh Tag | No | No | No |
|
| RAK WisMesh Tag | No | No | No |
|
||||||
| Heltec Mesh Solar | No | No | No |
|
| Heltec Mesh Solar | No | No | No |
|
||||||
| LilyGo T-Echo / T-Echo Lite | No | No | No |
|
| LilyGo T-Echo / T-Echo Lite | No | No | No |
|
||||||
| SenseCAP Solar | No | No | No |
|
| SenseCAP Solar | Yes | Yes | Yes |
|
||||||
| WIO Tracker L1 / L1 E-Ink | No | No | No |
|
| WIO Tracker L1 / L1 E-Ink | No | No | No |
|
||||||
| WIO WM1110 | No | No | No |
|
| WIO WM1110 | No | No | No |
|
||||||
| Mesh Pocket | No | No | No |
|
| Mesh Pocket | No | No | No |
|
||||||
|
|
|
||||||
|
|
@ -90,23 +90,17 @@ Returned path messages provide a description of the route a packet took from the
|
||||||
|
|
||||||
## Request
|
## Request
|
||||||
|
|
||||||
| Field | Size (bytes) | Description |
|
| Field | Size (bytes) | Description |
|
||||||
|--------------|-----------------|----------------------------|
|
|--------------|-----------------|------------------------------------------|
|
||||||
| timestamp | 4 | send time (unix timestamp) |
|
| timestamp | 4 | sender time (unix timestamp) |
|
||||||
| request type | 1 | see below |
|
| request data | rest of payload | application-defined request payload body |
|
||||||
| request data | rest of payload | depends on request type |
|
|
||||||
|
|
||||||
Request type
|
For the common chat/server helpers in `BaseChatMesh`, the current request type values are:
|
||||||
|
|
||||||
| Value | Name | Description |
|
| Value | Name | Description |
|
||||||
|--------|----------------------|---------------------------------------|
|
|--------|----------------------|---------------------------------------|
|
||||||
| `0x01` | get stats | get stats of repeater or room server |
|
| `0x01` | get stats | get stats of repeater or room server |
|
||||||
| `0x02` | keepalive | (deprecated) |
|
| `0x02` | keepalive | keep-alive request used for maintained connections |
|
||||||
| `0x03` | get telemetry data | TODO |
|
|
||||||
| `0x04` | get min,max,avg data | sensor nodes - get min, max, average for given time span |
|
|
||||||
| `0x05` | get access list | get node's approved access list |
|
|
||||||
| `0x06` | get neighbors | get repeater node's neighbors |
|
|
||||||
| `0x07` | get owner info | get repeater firmware-ver/name/owner info |
|
|
||||||
|
|
||||||
### Get stats
|
### Get stats
|
||||||
|
|
||||||
|
|
@ -133,35 +127,36 @@ Gets information about the node, possibly including the following:
|
||||||
|
|
||||||
### Get telemetry data
|
### Get telemetry data
|
||||||
|
|
||||||
Request data about sensors on the node, including battery level.
|
Not defined in `BaseChatMesh`. Sensor- and application-specific request payloads may be implemented by higher-level firmware.
|
||||||
|
|
||||||
### Get Telemetry
|
### Get Telemetry
|
||||||
|
|
||||||
TODO
|
Not defined in `BaseChatMesh`.
|
||||||
|
|
||||||
### Get Min/Max/Ave (Sensor nodes)
|
### Get Min/Max/Ave (Sensor nodes)
|
||||||
|
|
||||||
TODO
|
Not defined in `BaseChatMesh`.
|
||||||
|
|
||||||
### Get Access List
|
### Get Access List
|
||||||
|
|
||||||
TODO
|
Not defined in `BaseChatMesh`.
|
||||||
|
|
||||||
### Get Neighors
|
### Get Neighors
|
||||||
|
|
||||||
TODO
|
Not defined in `BaseChatMesh`.
|
||||||
|
|
||||||
### Get Owner Info
|
### Get Owner Info
|
||||||
|
|
||||||
TODO
|
Not defined in `BaseChatMesh`.
|
||||||
|
|
||||||
|
|
||||||
## Response
|
## Response
|
||||||
|
|
||||||
| Field | Size (bytes) | Description |
|
| Field | Size (bytes) | Description |
|
||||||
|---------|-----------------|-------------|
|
|---------|-----------------|-------------|
|
||||||
| tag | 4 | TODO |
|
| content | rest of payload | application-defined response body |
|
||||||
| content | rest of payload | TODO |
|
|
||||||
|
Response contents are opaque application data. There is no single generic response envelope beyond the encrypted payload wrapper shown above.
|
||||||
|
|
||||||
## Plain text message
|
## Plain text message
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ static char command[160];
|
||||||
unsigned long lastActive = 0; // mark last active time
|
unsigned long lastActive = 0; // mark last active time
|
||||||
unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot
|
unsigned long nextSleepinSecs = 120; // next sleep in seconds. The first sleep (if enabled) is after 2 minutes from boot
|
||||||
|
|
||||||
|
#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
|
||||||
|
static unsigned long userBtnDownAt = 0;
|
||||||
|
#define USER_BTN_HOLD_OFF_MILLIS 1500
|
||||||
|
#endif
|
||||||
|
|
||||||
void setup() {
|
void setup() {
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
|
|
@ -127,6 +132,21 @@ void loop() {
|
||||||
command[0] = 0; // reset command buffer
|
command[0] = 0; // reset command buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_)
|
||||||
|
// Hold the user button to power off the SenseCAP Solar repeater.
|
||||||
|
int btnState = digitalRead(PIN_USER_BTN);
|
||||||
|
if (btnState == LOW) {
|
||||||
|
if (userBtnDownAt == 0) {
|
||||||
|
userBtnDownAt = millis();
|
||||||
|
} else if ((unsigned long)(millis() - userBtnDownAt) >= USER_BTN_HOLD_OFF_MILLIS) {
|
||||||
|
Serial.println("Powering off...");
|
||||||
|
board.powerOff(); // does not return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userBtnDownAt = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
the_mesh.loop();
|
the_mesh.loop();
|
||||||
sensors.loop();
|
sensors.loop();
|
||||||
#ifdef DISPLAY_CLASS
|
#ifdef DISPLAY_CLASS
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
#include "AdvertDataHelpers.h"
|
#include "AdvertDataHelpers.h"
|
||||||
#include <RTClib.h>
|
#include <RTClib.h>
|
||||||
|
|
||||||
|
#ifndef BRIDGE_MAX_BAUD
|
||||||
|
#define BRIDGE_MAX_BAUD 115200
|
||||||
|
#endif
|
||||||
|
|
||||||
// Believe it or not, this std C function is busted on some platforms!
|
// Believe it or not, this std C function is busted on some platforms!
|
||||||
static uint32_t _atoi(const char* sp) {
|
static uint32_t _atoi(const char* sp) {
|
||||||
uint32_t n = 0;
|
uint32_t n = 0;
|
||||||
|
|
@ -103,7 +107,7 @@ void CommonCLI::loadPrefsInt(FILESYSTEM* fs, const char* filename) {
|
||||||
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
_prefs->bridge_enabled = constrain(_prefs->bridge_enabled, 0, 1);
|
||||||
_prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
|
_prefs->bridge_delay = constrain(_prefs->bridge_delay, 0, 10000);
|
||||||
_prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
|
_prefs->bridge_pkt_src = constrain(_prefs->bridge_pkt_src, 0, 1);
|
||||||
_prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, 115200);
|
_prefs->bridge_baud = constrain(_prefs->bridge_baud, 9600, BRIDGE_MAX_BAUD);
|
||||||
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
|
_prefs->bridge_channel = constrain(_prefs->bridge_channel, 0, 14);
|
||||||
|
|
||||||
_prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1);
|
_prefs->powersaving_enabled = constrain(_prefs->powersaving_enabled, 0, 1);
|
||||||
|
|
@ -199,7 +203,9 @@ uint8_t CommonCLI::buildAdvertData(uint8_t node_type, uint8_t* app_data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
|
void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, char* reply) {
|
||||||
if (memcmp(command, "reboot", 6) == 0) {
|
if (memcmp(command, "poweroff", 8) == 0 || memcmp(command, "shutdown", 8) == 0) {
|
||||||
|
_board->powerOff(); // doesn't return
|
||||||
|
} else if (memcmp(command, "reboot", 6) == 0) {
|
||||||
_board->reboot(); // doesn't return
|
_board->reboot(); // doesn't return
|
||||||
} else if (memcmp(command, "clkreboot", 9) == 0) {
|
} else if (memcmp(command, "clkreboot", 9) == 0) {
|
||||||
// Reset clock
|
// Reset clock
|
||||||
|
|
@ -639,13 +645,13 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, const char* command, ch
|
||||||
#ifdef WITH_RS232_BRIDGE
|
#ifdef WITH_RS232_BRIDGE
|
||||||
} else if (memcmp(config, "bridge.baud ", 12) == 0) {
|
} else if (memcmp(config, "bridge.baud ", 12) == 0) {
|
||||||
uint32_t baud = atoi(&config[12]);
|
uint32_t baud = atoi(&config[12]);
|
||||||
if (baud >= 9600 && baud <= 115200) {
|
if (baud >= 9600 && baud <= BRIDGE_MAX_BAUD) {
|
||||||
_prefs->bridge_baud = (uint32_t)baud;
|
_prefs->bridge_baud = (uint32_t)baud;
|
||||||
_callbacks->restartBridge();
|
_callbacks->restartBridge();
|
||||||
savePrefs();
|
savePrefs();
|
||||||
strcpy(reply, "OK");
|
strcpy(reply, "OK");
|
||||||
} else {
|
} else {
|
||||||
strcpy(reply, "Error: baud rate must be between 9600-115200");
|
sprintf(reply, "Error: baud rate must be between 9600-%d",BRIDGE_MAX_BAUD);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef WITH_ESPNOW_BRIDGE
|
#ifdef WITH_ESPNOW_BRIDGE
|
||||||
|
|
|
||||||
|
|
@ -246,6 +246,7 @@ void SerialBLEInterface::enable() {
|
||||||
clearBuffers();
|
clearBuffers();
|
||||||
_last_health_check = millis();
|
_last_health_check = millis();
|
||||||
|
|
||||||
|
Bluefruit.Advertising.restartOnDisconnect(true);
|
||||||
Bluefruit.Advertising.start(0);
|
Bluefruit.Advertising.start(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -259,8 +260,9 @@ void SerialBLEInterface::disable() {
|
||||||
_isEnabled = false;
|
_isEnabled = false;
|
||||||
BLE_DEBUG_PRINTLN("SerialBLEInterface: disable");
|
BLE_DEBUG_PRINTLN("SerialBLEInterface: disable");
|
||||||
|
|
||||||
disconnect();
|
Bluefruit.Advertising.restartOnDisconnect(false);
|
||||||
Bluefruit.Advertising.stop();
|
Bluefruit.Advertising.stop();
|
||||||
|
disconnect();
|
||||||
_last_health_check = 0;
|
_last_health_check = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@ class CustomLLCC68 : public LLCC68 {
|
||||||
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||||
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
|
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
|
||||||
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
|
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
|
||||||
#define SX126X_DIO3_TCXO_VOLTAGE (0.0f);
|
tcxo = 0.0f;
|
||||||
tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
|
||||||
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||||
}
|
}
|
||||||
if (status != RADIOLIB_ERR_NONE) {
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@ class CustomSX1262 : public SX1262 {
|
||||||
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||||
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
|
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
|
||||||
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
|
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
|
||||||
#define SX126X_DIO3_TCXO_VOLTAGE (0.0f);
|
tcxo = 0.0f;
|
||||||
tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
|
||||||
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||||
}
|
}
|
||||||
if (status != RADIOLIB_ERR_NONE) {
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,7 @@ class CustomSX1268 : public SX1268 {
|
||||||
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
int status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||||
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
|
// if radio init fails with -707/-706, try again with tcxo voltage set to 0.0f
|
||||||
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
|
if (status == RADIOLIB_ERR_SPI_CMD_FAILED || status == RADIOLIB_ERR_SPI_CMD_INVALID) {
|
||||||
#define SX126X_DIO3_TCXO_VOLTAGE (0.0f);
|
tcxo = 0.0f;
|
||||||
tcxo = SX126X_DIO3_TCXO_VOLTAGE;
|
|
||||||
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
status = begin(LORA_FREQ, LORA_BW, LORA_SF, cr, RADIOLIB_SX126X_SYNC_WORD_PRIVATE, LORA_TX_POWER, 16, tcxo);
|
||||||
}
|
}
|
||||||
if (status != RADIOLIB_ERR_NONE) {
|
if (status != RADIOLIB_ERR_NONE) {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,43 @@
|
||||||
|
|
||||||
#include "SenseCapSolarBoard.h"
|
#include "SenseCapSolarBoard.h"
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
const PowerMgtConfig power_config = {
|
||||||
|
.lpcomp_ain_channel = PWRMGT_LPCOMP_AIN,
|
||||||
|
.lpcomp_refsel = PWRMGT_LPCOMP_REFSEL,
|
||||||
|
.voltage_bootlock = PWRMGT_VOLTAGE_BOOTLOCK
|
||||||
|
};
|
||||||
|
|
||||||
|
void SenseCapSolarBoard::initiateShutdown(uint8_t reason) {
|
||||||
|
bool enable_lpcomp = (reason == SHUTDOWN_REASON_LOW_VOLTAGE ||
|
||||||
|
reason == SHUTDOWN_REASON_BOOT_PROTECT);
|
||||||
|
|
||||||
|
pinMode(VBAT_ENABLE, OUTPUT);
|
||||||
|
digitalWrite(VBAT_ENABLE, enable_lpcomp ? LOW : HIGH);
|
||||||
|
|
||||||
|
if (enable_lpcomp) {
|
||||||
|
configureVoltageWake(power_config.lpcomp_ain_channel, power_config.lpcomp_refsel);
|
||||||
|
}
|
||||||
|
|
||||||
|
enterSystemOff(reason);
|
||||||
|
}
|
||||||
|
#endif // NRF52_POWER_MANAGEMENT
|
||||||
|
|
||||||
void SenseCapSolarBoard::begin() {
|
void SenseCapSolarBoard::begin() {
|
||||||
NRF52Board::begin();
|
NRF52BoardDCDC::begin();
|
||||||
|
|
||||||
|
pinMode(BATTERY_PIN, INPUT);
|
||||||
|
pinMode(VBAT_ENABLE, OUTPUT);
|
||||||
|
digitalWrite(VBAT_ENABLE, LOW);
|
||||||
|
analogReadResolution(12);
|
||||||
|
analogReference(AR_INTERNAL_3_0);
|
||||||
|
delay(50);
|
||||||
|
|
||||||
|
#ifdef PIN_USER_BTN
|
||||||
|
pinMode(PIN_USER_BTN, INPUT_PULLUP);
|
||||||
|
#elif defined(PIN_BUTTON1)
|
||||||
|
pinMode(PIN_BUTTON1, INPUT_PULLUP);
|
||||||
|
#endif
|
||||||
|
|
||||||
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
#if defined(PIN_WIRE_SDA) && defined(PIN_WIRE_SCL)
|
||||||
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
Wire.setPins(PIN_WIRE_SDA, PIN_WIRE_SCL);
|
||||||
|
|
@ -12,10 +47,23 @@ void SenseCapSolarBoard::begin() {
|
||||||
|
|
||||||
Wire.begin();
|
Wire.begin();
|
||||||
|
|
||||||
|
#ifdef LED_GREEN
|
||||||
|
pinMode(LED_GREEN, OUTPUT);
|
||||||
|
digitalWrite(LED_GREEN, HIGH);
|
||||||
|
#endif
|
||||||
|
#ifdef LED_BLUE
|
||||||
|
pinMode(LED_BLUE, OUTPUT);
|
||||||
|
digitalWrite(LED_BLUE, LOW);
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef P_LORA_TX_LED
|
#ifdef P_LORA_TX_LED
|
||||||
pinMode(P_LORA_TX_LED, OUTPUT);
|
pinMode(P_LORA_TX_LED, OUTPUT);
|
||||||
digitalWrite(P_LORA_TX_LED, LOW);
|
digitalWrite(P_LORA_TX_LED, LOW);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
checkBootVoltage(&power_config);
|
||||||
|
#endif
|
||||||
|
|
||||||
delay(10); // give sx1262 some time to power up
|
delay(10); // give sx1262 some time to power up
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,11 @@
|
||||||
#include <helpers/NRF52Board.h>
|
#include <helpers/NRF52Board.h>
|
||||||
|
|
||||||
class SenseCapSolarBoard : public NRF52BoardDCDC {
|
class SenseCapSolarBoard : public NRF52BoardDCDC {
|
||||||
|
protected:
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
void initiateShutdown(uint8_t reason) override;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SenseCapSolarBoard() : NRF52Board("SENSECAP_SOLAR_OTA") {}
|
SenseCapSolarBoard() : NRF52Board("SENSECAP_SOLAR_OTA") {}
|
||||||
void begin();
|
void begin();
|
||||||
|
|
@ -31,4 +36,25 @@ public:
|
||||||
const char* getManufacturerName() const override {
|
const char* getManufacturerName() const override {
|
||||||
return "Seeed SenseCap Solar";
|
return "Seeed SenseCap Solar";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void powerOff() override {
|
||||||
|
digitalWrite(LED_GREEN, LOW);
|
||||||
|
digitalWrite(LED_BLUE, LOW);
|
||||||
|
|
||||||
|
#ifdef PIN_USER_BTN
|
||||||
|
while (digitalRead(PIN_USER_BTN) == LOW);
|
||||||
|
// Keep pull-up enabled in system-off so the wake line doesn't float low.
|
||||||
|
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_USER_BTN]), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
|
||||||
|
#elif defined(PIN_BUTTON1)
|
||||||
|
while (digitalRead(PIN_BUTTON1) == LOW);
|
||||||
|
// Keep pull-up enabled in system-off so the wake line doesn't float low.
|
||||||
|
nrf_gpio_cfg_sense_input(digitalPinToInterrupt(g_ADigitalPinMap[PIN_BUTTON1]), NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef NRF52_POWER_MANAGEMENT
|
||||||
|
initiateShutdown(SHUTDOWN_REASON_USER);
|
||||||
|
#else
|
||||||
|
sd_power_system_off();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,15 @@ build_flags = ${nrf52_base.build_flags}
|
||||||
-I variants/sensecap_solar
|
-I variants/sensecap_solar
|
||||||
-I src/helpers/nrf52
|
-I src/helpers/nrf52
|
||||||
-D NRF52_PLATFORM=1
|
-D NRF52_PLATFORM=1
|
||||||
|
-D NRF52_POWER_MANAGEMENT
|
||||||
-D RADIO_CLASS=CustomSX1262
|
-D RADIO_CLASS=CustomSX1262
|
||||||
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
-D WRAPPER_CLASS=CustomSX1262Wrapper
|
||||||
-D P_LORA_TX_LED=12
|
-D P_LORA_TX_LED=11
|
||||||
-D P_LORA_DIO_1=1
|
-D P_LORA_DIO_1=1
|
||||||
-D P_LORA_RESET=2
|
-D P_LORA_RESET=2
|
||||||
-D P_LORA_BUSY=3
|
-D P_LORA_BUSY=3
|
||||||
-D P_LORA_NSS=4
|
-D P_LORA_NSS=4
|
||||||
|
-D PIN_USER_BTN=PIN_BUTTON1
|
||||||
-D LORA_TX_POWER=22
|
-D LORA_TX_POWER=22
|
||||||
-D SX126X_RXEN=5
|
-D SX126X_RXEN=5
|
||||||
-D SX126X_TXEN=RADIOLIB_NC
|
-D SX126X_TXEN=RADIOLIB_NC
|
||||||
|
|
@ -96,4 +98,4 @@ build_src_filter = ${SenseCap_Solar.build_src_filter}
|
||||||
+<../examples/companion_radio/*.cpp>
|
+<../examples/companion_radio/*.cpp>
|
||||||
lib_deps =
|
lib_deps =
|
||||||
${SenseCap_Solar.lib_deps}
|
${SenseCap_Solar.lib_deps}
|
||||||
densaugeo/base64 @ ~1.4.0
|
densaugeo/base64 @ ~1.4.0
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@
|
||||||
// Buttons
|
// Buttons
|
||||||
#define PIN_BUTTON1 (13)
|
#define PIN_BUTTON1 (13)
|
||||||
#define PIN_BUTTON2 (20)
|
#define PIN_BUTTON2 (20)
|
||||||
|
#define PIN_USER_BTN PIN_BUTTON1
|
||||||
|
|
||||||
#define VBAT_ENABLE (19) // Output LOW to enable reading of the BAT voltage.
|
#define VBAT_ENABLE (19) // Output LOW to enable reading of the BAT voltage.
|
||||||
|
|
||||||
|
|
@ -41,6 +42,11 @@
|
||||||
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
#define ADC_MULTIPLIER (3.0F) // 1M, 512k divider bridge
|
||||||
#define ADC_RESOLUTION (12)
|
#define ADC_RESOLUTION (12)
|
||||||
|
|
||||||
|
// nRF52 power management settings
|
||||||
|
#define PWRMGT_VOLTAGE_BOOTLOCK (3300) // Won't boot below this voltage (mV)
|
||||||
|
#define PWRMGT_LPCOMP_AIN (7) // AIN7 = P0.31 = BATTERY_PIN
|
||||||
|
#define PWRMGT_LPCOMP_REFSEL (2) // 3/8 VDD (~3.38-3.71V)
|
||||||
|
|
||||||
// Serial interfaces
|
// Serial interfaces
|
||||||
#define PIN_SERIAL1_RX (7)
|
#define PIN_SERIAL1_RX (7)
|
||||||
#define PIN_SERIAL1_TX (6)
|
#define PIN_SERIAL1_TX (6)
|
||||||
|
|
@ -82,4 +88,4 @@
|
||||||
#define EXTERNAL_FLASH_DEVICES P25Q16H
|
#define EXTERNAL_FLASH_DEVICES P25Q16H
|
||||||
#define EXTERNAL_FLASH_USE_QSPI
|
#define EXTERNAL_FLASH_USE_QSPI
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue