Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ downloads/
eggs/
.eggs/
lib/
!scripts/lib/
!scripts/lib/raw-csi.js
lib64/
parts/
sdist/
Expand Down
42 changes: 38 additions & 4 deletions docs/adr/ADR-018-esp32-dev-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,25 +35,59 @@ This ADR answers *how* to build it — the concrete development sequence, the sp

### Binary Frame Format (implemented in `esp32_parser.rs`)

#### V1 Format (magic `0xC5110001`, 20-byte header)

```
Offset Size Field
0 4 Magic: 0xC5110001 (LE)
4 1 Node ID (0-255)
5 1 Number of antennas
6 2 Number of subcarriers (LE u16)
8 4 Frequency Hz (LE u32, e.g. 2412 for 2.4 GHz ch1)
8 4 Frequency MHz (LE u32, e.g. 2437 for ch6)
12 4 Sequence number (LE u32)
16 1 RSSI (i8, dBm)
17 1 Noise floor (i8, dBm)
18 2 Reserved (zero)
20 N*2 I/Q pairs: (i8, i8) per subcarrier, repeated per antenna
```

Total frame size: 20 + (n_antennas × n_subcarriers × 2) bytes.
Total frame size: 20 + (n_antennas x n_subcarriers x 2) bytes.

#### V2 Format (magic `0xC5110003`, 26-byte header)

V2 extends V1 with a 6-byte source MAC address at offset 20, identifying which WiFi transmitter's frame triggered the CSI measurement. This enables per-device CSI tracking.

```
Offset Size Field
0 4 Magic: 0xC5110003 (LE)
4 1 Node ID (0-255)
5 1 Number of antennas
6 2 Number of subcarriers (LE u16)
8 4 Frequency MHz (LE u32, e.g. 2437 for ch6)
12 4 Sequence number (LE u32)
16 1 RSSI (i8, dBm)
17 1 Noise floor (i8, dBm)
18 2 Reserved (zero)
20 6 Source MAC address (network byte order)
26 N*2 I/Q pairs: (i8, i8) per subcarrier, repeated per antenna
```

Total frame size: 26 + (n_antennas x n_subcarriers x 2) bytes.

#### Backward Compatibility

All parsers accept both V1 and V2 frames. V1 frames produce `source_mac = None`. During mixed-firmware rollout, nodes running old firmware send V1 and nodes running new firmware send V2 — both parse correctly.

#### Magic Number Family

For 3 antennas, 56 subcarriers: 20 + 336 = 356 bytes per frame.
| Magic | Format | Description |
|-------|--------|-------------|
| `0xC5110001` | CSI V1 | 20-byte header, no source MAC |
| `0xC5110002` | Vitals | Edge-computed vital signs (ADR-039) |
| `0xC5110003` | CSI V2 | 26-byte header, includes source MAC |
| `0xC5110004` | WASM | WASM module events (ADR-040) |

The firmware must write frames in this exact format. The parser already validates magic, bounds-checks `n_subcarriers` (≤512), and resyncs the stream on magic search for `parse_stream()`.
The firmware must write frames in the V2 format. The parser validates magic, bounds-checks `n_subcarriers` (<=256), and resyncs the stream on magic search for `parse_stream()`.

## Decision

Expand Down
26 changes: 18 additions & 8 deletions firmware/esp32-csi-node/main/csi_collector.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ static uint8_t s_hop_index = 0;
static esp_timer_handle_t s_hop_timer = NULL;

/**
* Serialize CSI data into ADR-018 binary frame format.
* Serialize CSI data into ADR-018 V2 binary frame format.
*
* Layout:
* [0..3] Magic: 0xC5110001 (LE)
* V2 layout (26-byte header):
* [0..3] Magic: 0xC5110006 (LE) — V2
* [4] Node ID
* [5] Number of antennas (rx_ctrl.rx_ant + 1 if available, else 1)
* [6..7] Number of subcarriers (LE u16) = len / (2 * n_antennas)
Expand All @@ -82,7 +82,8 @@ static esp_timer_handle_t s_hop_timer = NULL;
* [16] RSSI (i8)
* [17] Noise floor (i8)
* [18..19] Reserved
* [20..] I/Q data (raw bytes from ESP-IDF callback)
* [20..25] Source MAC address (6 bytes, network byte order)
* [26..] I/Q data (raw bytes from ESP-IDF callback)
*/
size_t csi_serialize_frame(const wifi_csi_info_t *info, uint8_t *buf, size_t buf_len)
{
Expand All @@ -94,7 +95,7 @@ size_t csi_serialize_frame(const wifi_csi_info_t *info, uint8_t *buf, size_t buf
uint16_t iq_len = (uint16_t)info->len;
uint16_t n_subcarriers = iq_len / (2 * n_antennas);

size_t frame_size = CSI_HEADER_SIZE + iq_len;
size_t frame_size = CSI_HEADER_SIZE_V2 + iq_len;
if (frame_size > buf_len) {
ESP_LOGW(TAG, "Buffer too small: need %u, have %u", (unsigned)frame_size, (unsigned)buf_len);
return 0;
Expand All @@ -113,8 +114,8 @@ size_t csi_serialize_frame(const wifi_csi_info_t *info, uint8_t *buf, size_t buf
freq_mhz = 0;
}

/* Magic (LE) */
uint32_t magic = CSI_MAGIC;
/* Magic (LE) — V2 includes source MAC */
uint32_t magic = CSI_MAGIC_V2;
memcpy(&buf[0], &magic, 4);

/* Node ID (from NVS runtime config, not compile-time Kconfig) */
Expand Down Expand Up @@ -143,8 +144,17 @@ size_t csi_serialize_frame(const wifi_csi_info_t *info, uint8_t *buf, size_t buf
buf[18] = 0;
buf[19] = 0;

/* Source MAC address (6 bytes, network byte order) — V2 extension */
memcpy(&buf[20], info->mac, 6);

ESP_LOGD(TAG, "V2 frame: node=%u mac=%02x:%02x:%02x:%02x:%02x:%02x seq=%u",
g_nvs_config.node_id,
info->mac[0], info->mac[1], info->mac[2],
info->mac[3], info->mac[4], info->mac[5],
seq);

/* I/Q data */
memcpy(&buf[CSI_HEADER_SIZE], info->buf, iq_len);
memcpy(&buf[CSI_HEADER_SIZE_V2], info->buf, iq_len);

return frame_size;
}
Expand Down
14 changes: 10 additions & 4 deletions firmware/esp32-csi-node/main/csi_collector.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,20 @@
#include "esp_err.h"
#include "esp_wifi_types.h"

/** ADR-018 magic number. */
/** ADR-018 V1 magic number (no source MAC). */
#define CSI_MAGIC 0xC5110001

/** ADR-018 header size in bytes. */
/** ADR-018 V2 magic number (includes 6-byte source MAC). */
#define CSI_MAGIC_V2 0xC5110006

/** ADR-018 V1 header size in bytes. */
#define CSI_HEADER_SIZE 20

/** Maximum frame buffer size (header + 4 antennas * 256 subcarriers * 2 bytes). */
#define CSI_MAX_FRAME_SIZE (CSI_HEADER_SIZE + 4 * 256 * 2)
/** ADR-018 V2 header size in bytes (V1 + 6-byte source MAC). */
#define CSI_HEADER_SIZE_V2 26

/** Maximum frame buffer size (V2 header + 4 antennas * 256 subcarriers * 2 bytes). */
#define CSI_MAX_FRAME_SIZE (CSI_HEADER_SIZE_V2 + 4 * 256 * 2)

/** Maximum number of channels in the hop table (ADR-029). */
#define CSI_HOP_CHANNELS_MAX 6
Expand Down
Loading
Loading