chore: cleanup — .gitignore, track design docs

This commit is contained in:
eddy 2026-08-01 00:52:03 +02:00
commit 45f8f38e81
3 changed files with 1359 additions and 0 deletions

15
.gitignore vendored
View file

@ -1,5 +1,6 @@
# Generated from version.txt
src/version.h
android/app/src/main/java/nl/eddydevink/sylda/SylDaVersions.kt
# PlatformIO
.pio/
@ -46,4 +47,18 @@ codereview.md
DC3-24V*.md
DC3-24V*.pdf
esplibalternatief.md
docs/handleiding.md
release/
releases/
# Test/debug firmware files
src/blink.cpp
src/c1.cpp
src/get_mac.cpp
src/min_cli.cpp
src/min_srv.cpp
src/s1.cpp
src/test_*.cpp
# Python cache
scripts/__pycache__/

File diff suppressed because it is too large Load diff

View 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.