269 lines
8.1 KiB
Markdown
269 lines
8.1 KiB
Markdown
# SylDa NimBLE + HMAC — Design Spec
|
||
|
||
Datum: 2025-07-31
|
||
Branch: nieuw, van `main`
|
||
|
||
## Doel
|
||
|
||
Stabiele basis: 1× Atom S3 Lite als SylDa_Server + 1× Atom S3 Lite als Sylda_Client + Android app, met NimBLE, UUID identificatie, HMAC-SHA256 authenticatie. Later refactoren naar AES-GCM.
|
||
|
||
## Architectuur
|
||
|
||
```
|
||
┌──────────────────────────────────────────────┐
|
||
│ Android App │
|
||
│ USB CDC: flash + provision (server & client) │
|
||
│ BLE: relay knoppen → HMAC → server │
|
||
│ OTA firmware update → server & client │
|
||
│ Radio buttons: [Server | Client] × [BLE|USB] │
|
||
└────────────────┬──────────────────────────────┘
|
||
│ BLE (client 2) │ USB CDC
|
||
▼ ▼
|
||
┌─────────────────────────┐ ┌─────────────────────────┐
|
||
│ SylDa_Server │ │ Sylda_Client │
|
||
│ AtomS3 Lite │ │ AtomS3 Lite │
|
||
│ │◄─┤ │
|
||
│ Relais G1, G2 (ext) │ │ Touch1 G8, Touch2 G6 │
|
||
│ Touch1 G8, Touch2 G6 │ │ Button G41 │
|
||
│ Button G41 │ │ Relay-state → BLE HMAC │
|
||
│ OR-logica │ │ OTA (BLE+USB) │
|
||
│ OTA (BLE+USB) │ │ │
|
||
│ LED G35 (NeoPixel) │ │ LED G35 (NeoPixel) │
|
||
└─────────────────────────┘ └─────────────────────────┘
|
||
```
|
||
|
||
### Provisioning flow
|
||
|
||
1. Android app flasht firmware via USB CDC (EspFlasher)
|
||
2. Android leest server UUID via BLE READ
|
||
3. Android stuurt `SET_KEY`, `SET_ID`, `SET_SERVER` naar client via USB CDC
|
||
4. Server genereert eigen UUID bij eerste boot (NVS persistent)
|
||
5. Gedeelde HMAC key (32 bytes) = enige authenticatie voor beide clients
|
||
6. Server UUID in client NVS = verificatie bij connect (geen spoofing)
|
||
7. UUIDs + HMAC key overleven NVS bij firmware updates (partities 0x9000-0xDFFF)
|
||
|
||
## Hardware
|
||
|
||
Beide devices: AtomS3 Lite (ESP32-S3), qio_qspi flash.
|
||
|
||
### Server
|
||
|
||
| Pin | Functie | Type |
|
||
|-----|---------|------|
|
||
| G39 | RELAY1 | OUTPUT (externe relais module) |
|
||
| G38 | RELAY2 | OUTPUT (externe relais module) |
|
||
| G8 | TOUCH1 | INPUT_PULLUP |
|
||
| G6 | TOUCH2 | INPUT_PULLUP |
|
||
| G41 | BUTTON | INPUT (active low) |
|
||
| G35 | LED | FastLED NeoPixel |
|
||
|
||
### Client
|
||
|
||
| Pin | Functie | Type |
|
||
|-----|---------|------|
|
||
| G8 | TOUCH1 | INPUT_PULLUP |
|
||
| G6 | TOUCH2 | INPUT_PULLUP |
|
||
| G41 | BUTTON | INPUT (active low) |
|
||
| G35 | LED | FastLED NeoPixel |
|
||
|
||
## Server firmware (`src/main.cpp`)
|
||
|
||
Basis: `main` branch. NimBLE, UUID, HMAC, relays, touch, button, OTA, NVS.
|
||
|
||
### Connection tracking
|
||
|
||
Max 2 verbindingen: client1 (AtomS3 Lite) + client2 (Android app).
|
||
|
||
```cpp
|
||
bool bleRelay1[2] = {false, false};
|
||
bool bleRelay2[2] = {false, false};
|
||
bool connActive[2] = {false, false};
|
||
int connectedCount = 0;
|
||
```
|
||
|
||
### Server callbacks
|
||
|
||
```cpp
|
||
onConnect(pServer, connInfo):
|
||
connActive[cid] = true;
|
||
connectedCount++;
|
||
if (connectedCount < 2) NimBLEDevice::startAdvertising();
|
||
|
||
onDisconnect(pServer, connInfo, reason):
|
||
connActive[cid] = false;
|
||
bleRelay1[cid] = false;
|
||
bleRelay2[cid] = false;
|
||
connectedCount--;
|
||
NimBLEDevice::startAdvertising();
|
||
```
|
||
|
||
CID mapping: via `NimBLEConnInfo::getConnHandle()` in callbacks. ConnHandle → index 0/1 in arrays. Eerste connect = index 0, tweede = index 1. Disconnect wist de entry en reset die index.
|
||
|
||
### Relay logica (OR over alle sources)
|
||
|
||
```cpp
|
||
relay1 = touch1 || button || (connActive[0] && bleRelay1[0]) || (connActive[1] && bleRelay1[1]);
|
||
relay2 = touch2 || (connActive[0] && bleRelay2[0]) || (connActive[1] && bleRelay2[1]);
|
||
```
|
||
|
||
OTA schakelt alle relays uit. LED = FastLED NeoPixel op G35 (geen digitalWrite meer).
|
||
|
||
### BLE characteristics
|
||
|
||
| UUID | Type | Functie |
|
||
|------|------|---------|
|
||
| `4fafc201-...` | Service | |
|
||
| `...f26d8` | WRITE_NR | Relay commands (HMAC) |
|
||
| `...f26d9` | READ | Server UUID (identificatie) |
|
||
| `...f26db` | READ | Firmware version |
|
||
| `...f26da` | WRITE_NR + NOTIFY | OTA |
|
||
|
||
### Protocol
|
||
|
||
HMAC-SHA256, 37-byte packets: `[cmd:1][seq:4][hmac:32]`
|
||
|
||
| Cmd | Actie |
|
||
|-----|-------|
|
||
| 0x00 | Relay1 OFF |
|
||
| 0x01 | Relay1 ON |
|
||
| 0x02 | Relay2 OFF |
|
||
| 0x03 | Relay2 ON |
|
||
|
||
Seq = monotoon per connection, replay protection per client.
|
||
|
||
### USB CDC
|
||
|
||
```
|
||
KEY? → DEVICE_ID:<32 hex> + HMAC_KEY:<64 hex>
|
||
SET_KEY:<64> → "OK" (slaat op in NVS)
|
||
SET_ID:<32> → "OK" (slaat op in NVS)
|
||
VERSION? → BUILD_NR:<n>
|
||
```
|
||
|
||
### FIRMWARE_VERSION
|
||
|
||
Server: "6"
|
||
Client: "1"
|
||
|
||
Single source of truth: `version.txt` in project root.
|
||
|
||
```
|
||
server=6
|
||
client=1
|
||
```
|
||
|
||
Build scripts lezen `version.txt`:
|
||
- PlatformIO: `-DFIRMWARE_VERSION=<n>` via `extra_scripts = pre:tools/pio_version_pre.py`
|
||
- Android: `build.gradle` leest `version.txt` voor `EXPECTED_FIRMWARE_VERSION`
|
||
|
||
## Client firmware (`src/client.cpp`)
|
||
|
||
Nieuw bestand op deze branch. HMAC, geen AES-GCM.
|
||
|
||
### BLE flow
|
||
|
||
```
|
||
1. Scan (actief, interval=100, window=99) → filter op naam "SylDa_Server"
|
||
2. Connect → READ UUID characteristic
|
||
3. Verify: server UUID == geprovisioneerde serverUuid in NVS
|
||
✓ match → HMAC relay commands
|
||
✗ mismatch → disconnect, scan opnieuw
|
||
```
|
||
|
||
### Relay logica (lokaal, stuurt eindresultaat)
|
||
|
||
```cpp
|
||
relay1 = touch1 || button → stuurt 0x01/0x00
|
||
relay2 = touch2 → stuurt 0x02/0x03
|
||
```
|
||
|
||
Debounce: 50ms, identiek aan server.
|
||
|
||
### HMAC packet build
|
||
|
||
Zelfde 37-byte packet: `[cmd:1][seq:4][hmac:32]`, zelfde HMAC key.
|
||
|
||
### NVS
|
||
|
||
```
|
||
namespace "hmac": key "key" (32 bytes HMAC), "uuid" (16 bytes eigen UUID), "server" (16 bytes server UUID)
|
||
```
|
||
|
||
### USB CDC
|
||
|
||
```
|
||
KEY? → DEVICE_ID:<32 hex> + HMAC_KEY:<64 hex> + SERVER_UUID:<32 hex>
|
||
SET_KEY:<64> → "OK"
|
||
SET_ID:<32> → "OK"
|
||
SET_SERVER:<32> → "OK"
|
||
VERSION? → BUILD_NR:<n>
|
||
```
|
||
|
||
### OTA
|
||
|
||
Zelfde mechanisme als server, via BLE OTA characteristic. Chunks: 240 bytes.
|
||
|
||
## Android app
|
||
|
||
Basis: `main` branch. Wijzigingen t.o.v. `main`:
|
||
|
||
### SettingsActivity
|
||
|
||
Radio buttons:
|
||
```
|
||
[Server ▸ Client] × [BLE ▸ USB]
|
||
Server + USB = flash + provision (SET_KEY, SET_ID)
|
||
Server + BLE = relay knoppen + OTA + UUID READ
|
||
Client + USB = flash + provision (SET_KEY, SET_ID, SET_SERVER)
|
||
Client + BLE = OTA (via client UUID)
|
||
```
|
||
|
||
### BleRelayManager
|
||
|
||
Ongewijzigd — HMAC 37-byte packets, commands 0x00-0x03.
|
||
|
||
### EspFlasher
|
||
|
||
Server én client firmware binaries. Assets gesplitst:
|
||
```
|
||
assets/firmware/server/{bootloader,partitions,boot_app0,firmware}.bin
|
||
assets/firmware/client/{bootloader,partitions,boot_app0,firmware}.bin
|
||
```
|
||
|
||
### PrefsHelper
|
||
|
||
Extra keys: `deviceMode` (server/client), `serverUuid`, `clientUuid`.
|
||
|
||
## OTA protocol
|
||
|
||
Blijft ongewijzigd van `main`. Chunk size: 240 bytes (past in MTU 248 single write).
|
||
|
||
| Packet | Grootte | Inhoud |
|
||
|--------|---------|--------|
|
||
| START | 5 bytes | `[0x10][size:4]` |
|
||
| Chunk | 4 + 240 bytes | `[seq:4][data:240]` |
|
||
| Checksum | 32 bytes | SHA256 hash |
|
||
| Response | 5 bytes | `[status:1][seq:4]` via notify |
|
||
|
||
## PlatformIO
|
||
|
||
```ini
|
||
[env:s3lite-server] # AtomS3 Lite, qio_qspi, main.cpp
|
||
[env:s3lite-client] # AtomS3 Lite, qio_qspi, DEVICE_ID=1, client.cpp
|
||
upload_flags = --no-stub # beide envs (QSPI flash)
|
||
```
|
||
|
||
Beide envs identieke `qio_qspi` (beide AtomS3 Lite).
|
||
|
||
## Teststrategie
|
||
|
||
1. Server solo: touch/knop schakelen relais lokaal
|
||
2. Server + client: BLE relay commands via touch/knop op client
|
||
3. Server + client + Android: alle drie bronnen tegelijk, OR-logica
|
||
4. OTA: firmware update server én client via BLE
|
||
5. Reconnect: client + Android herstellen na disconnect
|
||
6. NVS persistentie: HMAC key + UUIDs overleven reboot
|
||
|
||
## Toekomst: AES-GCM refactor
|
||
|
||
Na stabiele NimBLE+HMAC basis: vervang HMAC door AES-256-GCM (18-byte packets, encryptie + authenticatie). Code op `Sylda_Client` branch dient als referentie.
|