8.1 KiB
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
- Android app flasht firmware via USB CDC (EspFlasher)
- Android leest server UUID via BLE READ
- Android stuurt
SET_KEY,SET_ID,SET_SERVERnaar client via USB CDC - Server genereert eigen UUID bij eerste boot (NVS persistent)
- Gedeelde HMAC key (32 bytes) = enige authenticatie voor beide clients
- Server UUID in client NVS = verificatie bij connect (geen spoofing)
- 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).
bool bleRelay1[2] = {false, false};
bool bleRelay2[2] = {false, false};
bool connActive[2] = {false, false};
int connectedCount = 0;
Server callbacks
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)
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>viaextra_scripts = pre:tools/pio_version_pre.py - Android:
build.gradleleestversion.txtvoorEXPECTED_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)
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
[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
- Server solo: touch/knop schakelen relais lokaal
- Server + client: BLE relay commands via touch/knop op client
- Server + client + Android: alle drie bronnen tegelijk, OR-logica
- OTA: firmware update server én client via BLE
- Reconnect: client + Android herstellen na disconnect
- 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.