Skip to content

Commit 6d32193

Browse files
author
Scott Powell
committed
Merge branch 'dev'
2 parents 9405e8b + e054597 commit 6d32193

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+2358
-157
lines changed

boards/keepteen_lt1.json

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
{
2+
"build": {
3+
"arduino":{
4+
"ldscript": "nrf52840_s140_v6.ld"
5+
},
6+
"core": "nRF5",
7+
"cpu": "cortex-m4",
8+
"extra_flags": "-DARDUINO_NRF52840_FEATHER -DNRF52840_XXAA",
9+
"f_cpu": "64000000L",
10+
"hwids": [
11+
[
12+
"0x239A",
13+
"0x00B3"
14+
],
15+
[
16+
"0x239A",
17+
"0x8029"
18+
],
19+
[
20+
"0x239A",
21+
"0x0029"
22+
],
23+
[
24+
"0x239A",
25+
"0x002A"
26+
],
27+
[
28+
"0x239A",
29+
"0x802A"
30+
]
31+
],
32+
"usb_product": "Keepteen LT1",
33+
"mcu": "nrf52840",
34+
"variant": "Keepteen LT1",
35+
"variants_dir": "variants",
36+
"bsp": {
37+
"name": "adafruit"
38+
},
39+
"softdevice": {
40+
"sd_flags": "-DS140",
41+
"sd_name": "s140",
42+
"sd_version": "6.1.1",
43+
"sd_fwid": "0x00B6"
44+
},
45+
"bootloader": {
46+
"settings_addr": "0xFF000"
47+
}
48+
},
49+
"connectivity": [
50+
"bluetooth"
51+
],
52+
"debug": {
53+
"jlink_device": "nRF52840_xxAA",
54+
"svd_path": "nrf52840.svd",
55+
"openocd_target": "nrf52.cfg"
56+
},
57+
"frameworks": [
58+
"arduino",
59+
"zephyr"
60+
],
61+
"name": "Keepteen LT1",
62+
"upload": {
63+
"maximum_ram_size": 248832,
64+
"maximum_size": 815104,
65+
"speed": 115200,
66+
"protocol": "nrfutil",
67+
"protocols": [
68+
"jlink",
69+
"nrfjprog",
70+
"nrfutil",
71+
"stlink"
72+
],
73+
"use_1200bps_touch": true,
74+
"require_upload_port": true,
75+
"wait_for_upload_port": true
76+
},
77+
"url": "http://www.keepteen.com/",
78+
"vendor": "Keepteen"
79+
}

docs/stats_binary_frames.md

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
# Stats Binary Frame Structures
2+
3+
Binary frame structures for companion radio stats commands. All multi-byte integers use little-endian byte order.
4+
5+
## Command Codes
6+
7+
| Command | Code | Description |
8+
|---------|------|-------------|
9+
| `CMD_GET_STATS` | 56 | Get statistics (2-byte command: code + sub-type) |
10+
11+
### Stats Sub-Types
12+
13+
The `CMD_GET_STATS` command uses a 2-byte frame structure:
14+
- **Byte 0:** `CMD_GET_STATS` (56)
15+
- **Byte 1:** Stats sub-type:
16+
- `STATS_TYPE_CORE` (0) - Get core device statistics
17+
- `STATS_TYPE_RADIO` (1) - Get radio statistics
18+
- `STATS_TYPE_PACKETS` (2) - Get packet statistics
19+
20+
## Response Codes
21+
22+
| Response | Code | Description |
23+
|----------|------|-------------|
24+
| `RESP_CODE_STATS` | 24 | Statistics response (2-byte response: code + sub-type) |
25+
26+
### Stats Response Sub-Types
27+
28+
The `RESP_CODE_STATS` response uses a 2-byte header structure:
29+
- **Byte 0:** `RESP_CODE_STATS` (24)
30+
- **Byte 1:** Stats sub-type (matches command sub-type):
31+
- `STATS_TYPE_CORE` (0) - Core device statistics response
32+
- `STATS_TYPE_RADIO` (1) - Radio statistics response
33+
- `STATS_TYPE_PACKETS` (2) - Packet statistics response
34+
35+
---
36+
37+
## RESP_CODE_STATS + STATS_TYPE_CORE (24, 0)
38+
39+
**Total Frame Size:** 11 bytes
40+
41+
| Offset | Size | Type | Field Name | Description | Range/Notes |
42+
|--------|------|------|------------|-------------|-------------|
43+
| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
44+
| 1 | 1 | uint8_t | stats_type | Always `0x00` (STATS_TYPE_CORE) | - |
45+
| 2 | 2 | uint16_t | battery_mv | Battery voltage in millivolts | 0 - 65,535 |
46+
| 4 | 4 | uint32_t | uptime_secs | Device uptime in seconds | 0 - 4,294,967,295 |
47+
| 8 | 2 | uint16_t | errors | Error flags bitmask | - |
48+
| 10 | 1 | uint8_t | queue_len | Outbound packet queue length | 0 - 255 |
49+
50+
### Example Structure (C/C++)
51+
52+
```c
53+
struct StatsCore {
54+
uint8_t response_code; // 0x18
55+
uint8_t stats_type; // 0x00 (STATS_TYPE_CORE)
56+
uint16_t battery_mv;
57+
uint32_t uptime_secs;
58+
uint16_t errors;
59+
uint8_t queue_len;
60+
} __attribute__((packed));
61+
```
62+
63+
---
64+
65+
## RESP_CODE_STATS + STATS_TYPE_RADIO (24, 1)
66+
67+
**Total Frame Size:** 14 bytes
68+
69+
| Offset | Size | Type | Field Name | Description | Range/Notes |
70+
|--------|------|------|------------|-------------|-------------|
71+
| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
72+
| 1 | 1 | uint8_t | stats_type | Always `0x01` (STATS_TYPE_RADIO) | - |
73+
| 2 | 2 | int16_t | noise_floor | Radio noise floor in dBm | -140 to +10 |
74+
| 4 | 1 | int8_t | last_rssi | Last received signal strength in dBm | -128 to +127 |
75+
| 5 | 1 | int8_t | last_snr | SNR scaled by 4 | Divide by 4.0 for dB |
76+
| 6 | 4 | uint32_t | tx_air_secs | Cumulative transmit airtime in seconds | 0 - 4,294,967,295 |
77+
| 10 | 4 | uint32_t | rx_air_secs | Cumulative receive airtime in seconds | 0 - 4,294,967,295 |
78+
79+
### Example Structure (C/C++)
80+
81+
```c
82+
struct StatsRadio {
83+
uint8_t response_code; // 0x18
84+
uint8_t stats_type; // 0x01 (STATS_TYPE_RADIO)
85+
int16_t noise_floor;
86+
int8_t last_rssi;
87+
int8_t last_snr; // Divide by 4.0 to get actual SNR in dB
88+
uint32_t tx_air_secs;
89+
uint32_t rx_air_secs;
90+
} __attribute__((packed));
91+
```
92+
93+
---
94+
95+
## RESP_CODE_STATS + STATS_TYPE_PACKETS (24, 2)
96+
97+
**Total Frame Size:** 26 bytes
98+
99+
| Offset | Size | Type | Field Name | Description | Range/Notes |
100+
|--------|------|------|------------|-------------|-------------|
101+
| 0 | 1 | uint8_t | response_code | Always `0x18` (24) | - |
102+
| 1 | 1 | uint8_t | stats_type | Always `0x02` (STATS_TYPE_PACKETS) | - |
103+
| 2 | 4 | uint32_t | recv | Total packets received | 0 - 4,294,967,295 |
104+
| 6 | 4 | uint32_t | sent | Total packets sent | 0 - 4,294,967,295 |
105+
| 10 | 4 | uint32_t | flood_tx | Packets sent via flood routing | 0 - 4,294,967,295 |
106+
| 14 | 4 | uint32_t | direct_tx | Packets sent via direct routing | 0 - 4,294,967,295 |
107+
| 18 | 4 | uint32_t | flood_rx | Packets received via flood routing | 0 - 4,294,967,295 |
108+
| 22 | 4 | uint32_t | direct_rx | Packets received via direct routing | 0 - 4,294,967,295 |
109+
110+
### Notes
111+
112+
- Counters are cumulative from boot and may wrap.
113+
- `recv = flood_rx + direct_rx`
114+
- `sent = flood_tx + direct_tx`
115+
116+
### Example Structure (C/C++)
117+
118+
```c
119+
struct StatsPackets {
120+
uint8_t response_code; // 0x18
121+
uint8_t stats_type; // 0x02 (STATS_TYPE_PACKETS)
122+
uint32_t recv;
123+
uint32_t sent;
124+
uint32_t flood_tx;
125+
uint32_t direct_tx;
126+
uint32_t flood_rx;
127+
uint32_t direct_rx;
128+
} __attribute__((packed));
129+
```
130+
131+
---
132+
133+
## Command Usage Example (Python)
134+
135+
```python
136+
# Send CMD_GET_STATS command
137+
def send_get_stats_core(serial_interface):
138+
"""Send command to get core stats"""
139+
cmd = bytes([56, 0]) # CMD_GET_STATS (56) + STATS_TYPE_CORE (0)
140+
serial_interface.write(cmd)
141+
142+
def send_get_stats_radio(serial_interface):
143+
"""Send command to get radio stats"""
144+
cmd = bytes([56, 1]) # CMD_GET_STATS (56) + STATS_TYPE_RADIO (1)
145+
serial_interface.write(cmd)
146+
147+
def send_get_stats_packets(serial_interface):
148+
"""Send command to get packet stats"""
149+
cmd = bytes([56, 2]) # CMD_GET_STATS (56) + STATS_TYPE_PACKETS (2)
150+
serial_interface.write(cmd)
151+
```
152+
153+
---
154+
155+
## Response Parsing Example (Python)
156+
157+
```python
158+
import struct
159+
160+
def parse_stats_core(frame):
161+
"""Parse RESP_CODE_STATS + STATS_TYPE_CORE frame (11 bytes)"""
162+
response_code, stats_type, battery_mv, uptime_secs, errors, queue_len = \
163+
struct.unpack('<B B H I H B', frame)
164+
assert response_code == 24 and stats_type == 0, "Invalid response type"
165+
return {
166+
'battery_mv': battery_mv,
167+
'uptime_secs': uptime_secs,
168+
'errors': errors,
169+
'queue_len': queue_len
170+
}
171+
172+
def parse_stats_radio(frame):
173+
"""Parse RESP_CODE_STATS + STATS_TYPE_RADIO frame (14 bytes)"""
174+
response_code, stats_type, noise_floor, last_rssi, last_snr, tx_air_secs, rx_air_secs = \
175+
struct.unpack('<B B h b b I I', frame)
176+
assert response_code == 24 and stats_type == 1, "Invalid response type"
177+
return {
178+
'noise_floor': noise_floor,
179+
'last_rssi': last_rssi,
180+
'last_snr': last_snr / 4.0, # Unscale SNR
181+
'tx_air_secs': tx_air_secs,
182+
'rx_air_secs': rx_air_secs
183+
}
184+
185+
def parse_stats_packets(frame):
186+
"""Parse RESP_CODE_STATS + STATS_TYPE_PACKETS frame (26 bytes)"""
187+
response_code, stats_type, recv, sent, flood_tx, direct_tx, flood_rx, direct_rx = \
188+
struct.unpack('<B B I I I I I I', frame)
189+
assert response_code == 24 and stats_type == 2, "Invalid response type"
190+
return {
191+
'recv': recv,
192+
'sent': sent,
193+
'flood_tx': flood_tx,
194+
'direct_tx': direct_tx,
195+
'flood_rx': flood_rx,
196+
'direct_rx': direct_rx
197+
}
198+
```
199+
200+
---
201+
202+
## Command Usage Example (JavaScript/TypeScript)
203+
204+
```typescript
205+
// Send CMD_GET_STATS command
206+
const CMD_GET_STATS = 56;
207+
const STATS_TYPE_CORE = 0;
208+
const STATS_TYPE_RADIO = 1;
209+
const STATS_TYPE_PACKETS = 2;
210+
211+
function sendGetStatsCore(serialInterface: SerialPort): void {
212+
const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_CORE]);
213+
serialInterface.write(cmd);
214+
}
215+
216+
function sendGetStatsRadio(serialInterface: SerialPort): void {
217+
const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_RADIO]);
218+
serialInterface.write(cmd);
219+
}
220+
221+
function sendGetStatsPackets(serialInterface: SerialPort): void {
222+
const cmd = new Uint8Array([CMD_GET_STATS, STATS_TYPE_PACKETS]);
223+
serialInterface.write(cmd);
224+
}
225+
```
226+
227+
---
228+
229+
## Response Parsing Example (JavaScript/TypeScript)
230+
231+
```typescript
232+
interface StatsCore {
233+
battery_mv: number;
234+
uptime_secs: number;
235+
errors: number;
236+
queue_len: number;
237+
}
238+
239+
interface StatsRadio {
240+
noise_floor: number;
241+
last_rssi: number;
242+
last_snr: number;
243+
tx_air_secs: number;
244+
rx_air_secs: number;
245+
}
246+
247+
interface StatsPackets {
248+
recv: number;
249+
sent: number;
250+
flood_tx: number;
251+
direct_tx: number;
252+
flood_rx: number;
253+
direct_rx: number;
254+
}
255+
256+
function parseStatsCore(buffer: ArrayBuffer): StatsCore {
257+
const view = new DataView(buffer);
258+
const response_code = view.getUint8(0);
259+
const stats_type = view.getUint8(1);
260+
if (response_code !== 24 || stats_type !== 0) {
261+
throw new Error('Invalid response type');
262+
}
263+
return {
264+
battery_mv: view.getUint16(2, true),
265+
uptime_secs: view.getUint32(4, true),
266+
errors: view.getUint16(8, true),
267+
queue_len: view.getUint8(10)
268+
};
269+
}
270+
271+
function parseStatsRadio(buffer: ArrayBuffer): StatsRadio {
272+
const view = new DataView(buffer);
273+
const response_code = view.getUint8(0);
274+
const stats_type = view.getUint8(1);
275+
if (response_code !== 24 || stats_type !== 1) {
276+
throw new Error('Invalid response type');
277+
}
278+
return {
279+
noise_floor: view.getInt16(2, true),
280+
last_rssi: view.getInt8(4),
281+
last_snr: view.getInt8(5) / 4.0, // Unscale SNR
282+
tx_air_secs: view.getUint32(6, true),
283+
rx_air_secs: view.getUint32(10, true)
284+
};
285+
}
286+
287+
function parseStatsPackets(buffer: ArrayBuffer): StatsPackets {
288+
const view = new DataView(buffer);
289+
const response_code = view.getUint8(0);
290+
const stats_type = view.getUint8(1);
291+
if (response_code !== 24 || stats_type !== 2) {
292+
throw new Error('Invalid response type');
293+
}
294+
return {
295+
recv: view.getUint32(2, true),
296+
sent: view.getUint32(6, true),
297+
flood_tx: view.getUint32(10, true),
298+
direct_tx: view.getUint32(14, true),
299+
flood_rx: view.getUint32(18, true),
300+
direct_rx: view.getUint32(22, true)
301+
};
302+
}
303+
```
304+
305+
---
306+
307+
## Field Size Considerations
308+
309+
- Packet counters (uint32_t): May wrap after extended high-traffic operation.
310+
- Time fields (uint32_t): Max ~136 years.
311+
- SNR (int8_t, scaled by 4): Range -32 to +31.75 dB, 0.25 dB precision.
312+

0 commit comments

Comments
 (0)