chore: cleanup — .gitignore, track design docs
This commit is contained in:
parent
92c79ac93f
commit
45f8f38e81
3 changed files with 1359 additions and 0 deletions
269
docs/superpowers/specs/2025-07-31-sylda-nimble-hmac-design.md
Normal file
269
docs/superpowers/specs/2025-07-31-sylda-nimble-hmac-design.md
Normal file
|
|
@ -0,0 +1,269 @@
|
|||
# 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.
|
||||
Loading…
Reference in a new issue