diff --git a/android/app/src/main/assets/firmware/client/firmware.bin b/android/app/src/main/assets/firmware/client/firmware.bin index edd2861..2dd9d81 100644 Binary files a/android/app/src/main/assets/firmware/client/firmware.bin and b/android/app/src/main/assets/firmware/client/firmware.bin differ diff --git a/docs/superpowers/plans/2026-08-04-client-ble-scan.md b/docs/superpowers/plans/2026-08-04-client-ble-scan.md deleted file mode 100644 index b901d4a..0000000 --- a/docs/superpowers/plans/2026-08-04-client-ble-scan.md +++ /dev/null @@ -1,400 +0,0 @@ -# Client BLE scan-based connection Implementation Plan - -> **Voor agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Client verbindt even snel en betrouwbaar als de Android app — via BLE-scan met UUID-prefix matching + ECDH-handshake via de bestaande `connectToServer()` functie. - -**Architecture:** Vervang hardcoded MAC-connectie door BLE-scan (service UUID + manufacturer data UUID-prefix), bewaar gevonden server-MAC in NVS voor snelle herconnectie, roep de reeds correcte `connectToServer()` aan voor UUID-verificatie + ECDH. - -**Tech Stack:** ESP32-S3, NimBLE-Arduino, Preferences (NVS) - ---- - -## Scope - -Één bestand: `src/client.cpp`. Geen nieuwe bestanden. - -**Wat verandert er:** -- `setup()`: alleen BLE init, geen direct connect meer -- `scan`-logica: nieuwe statemachine in `loop()` — scannen → gevonden → connectToServer() → herconnectie -- `MyScanCallbacks`: uitgebreid met UUID-prefix matching uit manufacturer data -- NVS: `server_addr` key voor opgeslagen BLE-adres (48-bit MAC als 6 bytes, little-endian in `NimBLEAddress` formaat) -- Dode code: `SCAN_TIMEOUT` define (ongebruikt, lijn 34) en `SCAN_INTERVAL` vervangen door nieuwe constanten - -**Wat blijft:** -- `connectToServer()` (lijn 239-293): ongewijzigd, hier gebeurt UUID-verify + ECDH -- `performEcdhHandshake()` (lijn 158-237): ongewijzigd, hier gebeurt de ECDH-handshake -- Alle serial-command handlers, touch-logica, relay-command sending - ---- - -### Task 1: Definieer nieuwe constanten en voeg NVS-key + globals toe - -**File:** `src/client.cpp` - -- [ ] **Step 1: Vervang ongebruikte constanten en voeg nieuwe toe** - -Verwijder `#define SCAN_TIMEOUT 5000` (ongebruikt) en voeg nieuwe constanten toe direct eronder: - -```cpp -#define SCAN_INTERVAL 6000 -#define SCAN_TIMEOUT 5000 // <-- verwijder deze regel -``` - -Vervang door: - -```cpp -#define SCAN_DURATION_MS 5000 // hoe lang scannen per poging -#define SCAN_INTERVAL_MS 8000 // tijd tussen scanpogingen -#define RECONNECT_DELAY_MS 1000 // wachttijd na disconnect voor herscan -``` - -- [ ] **Step 2: Voeg NVS-key en globals toe na `#define SCAN_INTERVAL` (lijn 33)** - -Voeg toe onder lijn 35 (`#define NVS_SERVER_NAME "server"`): - -```cpp -#define NVS_ADDR_NAME "addr" -``` - -Voeg toe onder `bool allProvisioned = false;` (lijn 42): - -```cpp -bool serverAddrStored = false; -NimBLEAddress storedServerAddr = NimBLEAddress(""); -``` - -Voeg toe onder `static bool serverFound = false;` (lijn 63): - -```cpp -static bool scanning = false; -static uint32_t scanStarted = 0; -static uint32_t disconnectTime = 0; -``` - -- [ ] **Step 3: Bouw en verifieer** - -```bash -pio run -e s3lite-client -``` - -Expected: Compileert (gebruikt nog niet, maar geen syntaxfouten). - -- [ ] **Step 4: Commit** - -```bash -git add src/client.cpp -git commit -m "feat(client): add scan constants, NVS addr key, and state globals" -``` - ---- - -### Task 2: Breid MyScanCallbacks uit met UUID-prefix matching - -**File:** `src/client.cpp` - -- [ ] **Step 1: Vervang MyScanCallbacks (lijn 70-78)** - -Vervang de bestaande klasse: - -```cpp -class MyScanCallbacks : public NimBLEScanCallbacks { - void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { - if (scanning && advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) { - std::string mfgData = advertisedDevice->getManufacturerData(); - if (mfgData.length() >= 4) { - if (memcmp(mfgData.data(), deviceUuid, 4) == 0) { - serverAddress = advertisedDevice->getAddress(); - serverFound = true; - ESP_LOGI(TAG, "BLE: server found at %s", serverAddress.toString().c_str()); - NimBLEDevice::getScan()->stop(); - } - } - } - } -}; -``` - -De UUID-prefix check (`memcmp(mfgData.data(), deviceUuid, 4)`) matcht exact met Android's `scanRecord.getManufacturerSpecificData(0xFFFF)` + `advPrefix.contentEquals(storedPrefix)` (BleRelayManager.kt lijn 122-129). - -- [ ] **Step 2: Bouw en verifieer** - -```bash -pio run -e s3lite-client -``` - -Expected: Compileert. - -- [ ] **Step 3: Commit** - -```bash -git add src/client.cpp -git commit -m "feat(client): extend MyScanCallbacks with UUID prefix matching" -``` - ---- - -### Task 3: scan + connect statemachine in loop() - -**File:** `src/client.cpp` - -- [ ] **Step 1: Herschrijf setup() lijn 346-363 — alleen BLE init, geen connect** - -Vervang het `if (allProvisioned)` blok in `setup()`: - -```cpp - if (allProvisioned) { - NimBLEDevice::init("SylDa_Client"); - bleReady = true; - - preferences.begin(NVS_NAMESPACE, true); - uint8_t addrBuf[6]; - size_t addrLen = preferences.getBytes(NVS_ADDR_NAME, addrBuf, 6); - preferences.end(); - if (addrLen == 6) { - storedServerAddr = NimBLEAddress(addrBuf, BLE_ADDR_RANDOM); - serverAddrStored = true; - ESP_LOGI(TAG, "BLE: stored server address %s", storedServerAddr.toString().c_str()); - } - - ESP_LOGI(TAG, "BLE: initialized, will scan for server"); - } -``` - -- [ ] **Step 2: Voeg scan-start functie toe vóór loop()** - -Voeg toe direct vóór `void loop()` (vóór lijn 368): - -```cpp -static void startScan() { - if (scanning) return; - NimBLEScan *pScan = NimBLEDevice::getScan(); - pScan->setScanCallbacks(new MyScanCallbacks()); - pScan->setActiveScan(true); - pScan->setInterval(32); - pScan->setWindow(16); - serverFound = false; - scanning = true; - scanStarted = millis(); - pScan->start(SCAN_DURATION_MS / 100, false); // duration in 10ms units - ESP_LOGI(TAG, "BLE: scanning started"); -} -``` - -- [ ] **Step 3: Vervang het reconnect-blok in loop() (lijn 550-568)** - -Vervang het `if (!connected && bleReady)` blok met de volledige scan+connect statemachine. Het oude blok (lijn 550-568) en de `statusLog` log (lijn 541-548) worden vervangen: - -```cpp - static uint32_t lastScanAttempt = 0; - static uint32_t statusLog = 0; - - if (bleReady && millis() - statusLog > 2000) { - ESP_LOGI(TAG, "BLE: connected=%d scanning=%d ecdh=%d serverFound=%d", - connected, scanning, ecdhReady, serverFound); - statusLog = millis(); - } - - if (!connected && bleReady) { - if (!scanning) { - uint32_t delayNeeded = disconnectTime > 0 ? RECONNECT_DELAY_MS : 0; - if (millis() - lastScanAttempt > SCAN_INTERVAL_MS) { - if (serverAddrStored) { - ESP_LOGI(TAG, "BLE: trying stored address %s", storedServerAddr.toString().c_str()); - if (pClient == nullptr) { - pClient = NimBLEDevice::createClient(); - pClient->setConnectTimeout(10); - pClient->setClientCallbacks(&clientCallbacks); - } - if (pClient->connect(storedServerAddr)) { - serverAddress = storedServerAddr; - if (connectToServer()) { - connected = true; - ESP_LOGI(TAG, "BLE: connected via stored address"); - } - } else { - ESP_LOGI(TAG, "BLE: stored address failed, starting scan"); - serverAddrStored = false; - startScan(); - } - } else { - startScan(); - } - lastScanAttempt = millis(); - } - } else { - if (serverFound) { - scanning = false; - ESP_LOGI(TAG, "BLE: connecting to found server at %s", serverAddress.toString().c_str()); - if (pClient != nullptr && pClient->isConnected()) { - pClient->disconnect(); - delay(100); - } - pClient = NimBLEDevice::createClient(); - pClient->setConnectTimeout(10); - pClient->setClientCallbacks(&clientCallbacks); - if (connectToServer()) { - connected = true; - uint8_t addrBuf[6]; - memcpy(addrBuf, serverAddress.getNative(), 6); - preferences.begin(NVS_NAMESPACE, false); - preferences.putBytes(NVS_ADDR_NAME, addrBuf, 6); - preferences.end(); - storedServerAddr = serverAddress; - serverAddrStored = true; - ESP_LOGI(TAG, "BLE: server address stored in NVS"); - } else { - ESP_LOGW(TAG, "BLE: connectToServer failed"); - } - } else if (millis() - scanStarted > SCAN_DURATION_MS) { - NimBLEDevice::getScan()->stop(); - scanning = false; - ESP_LOGI(TAG, "BLE: scan timeout, no server found"); - } - } - } -``` - -- [ ] **Step 4: Update disconnect handler — zet disconnectTime** - -In `onDisconnect` (lijn 85-93), voeg `disconnectTime = millis();` toe: - -```cpp - void onDisconnect(NimBLEClient *client, int reason) override { - connected = false; - disconnectTime = millis(); - relay1SendState = false; - relay2SendState = false; - pCmdChar = nullptr; - ecdhReady = false; - memset(aesKey, 0, AES_KEY_LEN); - ESP_LOGW(TAG, "BLE: disconnected (reason=%d)", reason); - } -``` - -- [ ] **Step 5: Bouw en verifieer** - -```bash -pio run -e s3lite-client -``` - -Expected: Compileert. - -- [ ] **Step 6: Commit** - -```bash -git add src/client.cpp -git commit -m "feat(client): replace hardcoded connect with scan-based discovery + connectToServer()" -``` - ---- - -### Task 4: Verwijder dode code - -**File:** `src/client.cpp` - -- [ ] **Step 1: Verwijder ongebruikte defines** - -Verwijder lijn 34 (`#define SCAN_TIMEOUT 5000`) als dat nog niet gebeurd is. - -- [ ] **Step 2: Verwijder ongebruikte variabele `lastConnectAttempt`** - -De oude `static uint32_t lastConnectAttempt = 0;` in `loop()` (lijn 541) is niet meer nodig — vervangen door `lastScanAttempt` in de nieuwe code. - -- [ ] **Step 3: Verwijder ongebruikte `MyScanCallbacks` instantiatie in `onResult`** - -De klasse `MyScanCallbacks` moet nu een constructor hebben of via `new` aangemaakt worden in `startScan()`. Check: NimBLE verwacht raw pointer, we gebruiken `new` (geheugenlek is acceptabel — callbacks leven de hele uptime). - -- [ ] **Step 4: Bouw en verifieer** - -```bash -pio run -e s3lite-client -``` - -Expected: Compileert zonder warnings over ongebruikte variabelen. - -- [ ] **Step 5: Commit** - -```bash -git add src/client.cpp -git commit -m "chore(client): remove dead code, unused defines and variables" -``` - ---- - -### Task 5: Bouw, flash, test - -- [ ] **Step 1: Bouw** - -```bash -pio run -e s3lite-client -``` - -- [ ] **Step 2: Flash client ESP32** - -Sluit de client USB aan, check poort: - -```bash -ls /dev/serial/by-id/ | grep espressif -``` - -Flash: - -```bash -pio run -e s3lite-client -t upload --upload-port /dev/ttyACM -``` - -- [ ] **Step 3: Monitor serial output** - -```bash -cat /dev/ttyACM -``` - -Expected: `BLE: scanning started`, daarna `BLE: server found at `, `BLE: server UUID verified, ECDH done, ready`. - -- [ ] **Step 4: Test relay commands** - -Druk op touch-sensors op de client. Verifieer dat relais op server schakelen. - -- [ ] **Step 5: Test herconnectie** - -Zet server uit en weer aan. Verifieer dat client automatisch herverbindt na server weer adverteert — via opgeslagen adres (snel) of via new scan. - -- [ ] **Step 6: Kopieer firmware naar Android assets** - -```bash -cp .pio/build/s3lite-client/bootloader.bin .pio/build/s3lite-client/partitions.bin .pio/build/s3lite-client/firmware.bin android/app/src/main/assets/firmware/client/ -``` - -- [ ] **Step 7: Commit en push** - -```bash -git add src/client.cpp android/app/src/main/assets/firmware/client/ -git commit -m "chore: update client firmware in Android assets" -git push -``` - ---- - -## Flow overzicht - -``` -setup() - └─ BLE init, laad stored server addr uit NVS - -loop() [niet connected] - ├─ stored addr beschikbaar? → direct connect poging - │ ├─ success → connectToServer() → UUID-verify + ECDH → connected! - │ └─ fail → startScan() - └─ geen stored addr → startScan() - -loop() [scanning] - ├─ serverFound → stop scan → pClient->connect() → connectToServer() - │ └─ success → sla MAC op in NVS → connected! - └─ scan timeout → stop scan, wacht SCAN_INTERVAL_MS, herstart - -onDisconnect() - └─ zet disconnectTime, cleanup state → herstart scan na RECONNECT_DELAY_MS -``` - -Dit is exact wat de Android app doet: scan → UUID-prefix match → connect → UUID-verify → ECDH. diff --git a/src/client.cpp b/src/client.cpp index 99a4d99..1b9ae2a 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -29,11 +29,9 @@ CRGB leds[NUM_LEDS]; #define NVS_PSK_NAME "psk" #define NVS_UUID_NAME "uuid" #define NVS_SERVER_NAME "server" -#define NVS_ADDR_NAME "addr" #define DEBOUNCE_MS 50 -#define SCAN_DURATION_MS 5000 -#define SCAN_INTERVAL_MS 8000 -#define RECONNECT_DELAY_MS 1000 +#define SCAN_INTERVAL 6000 +#define SCAN_TIMEOUT 5000 uint8_t psk[PSK_LEN]; uint8_t deviceUuid[ESP_UUID_LEN]; @@ -42,8 +40,6 @@ bool pskProvisioned = false; bool uuidProvisioned = false; bool serverProvisioned = false; bool allProvisioned = false; -bool serverAddrStored = false; -NimBLEAddress storedServerAddr; Preferences preferences; static bool connected = false; @@ -66,10 +62,6 @@ bool otaEnded = false; static bool bleReady = false; static bool serverFound = false; static NimBLEAddress serverAddress; -static NimBLEAddress foundAddr; -static bool scanning = false; -static uint32_t scanStarted = 0; -static uint32_t disconnectTime = 0; static bool ecdhNotifyReceived = false; static uint8_t ecdhNotifyBuf[ECDH_PKT_SIZE]; @@ -77,21 +69,12 @@ static size_t ecdhNotifyLen = 0; class MyScanCallbacks : public NimBLEScanCallbacks { void onResult(const NimBLEAdvertisedDevice *advertisedDevice) override { - if (scanning && advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) { - std::string mfgData = advertisedDevice->getManufacturerData(); - if (mfgData.length() >= 6) { - if (memcmp(mfgData.data() + 2, serverUuid, 4) == 0) { - foundAddr = advertisedDevice->getAddress(); - serverFound = true; - ESP_LOGI(TAG, "BLE: server found at %s", foundAddr.toString().c_str()); - } - } + if (advertisedDevice->isAdvertisingService(NimBLEUUID(SERVICE_UUID))) { + serverAddress = advertisedDevice->getAddress(); + serverFound = true; + NimBLEDevice::getScan()->stop(); } } - void onScanEnd(const NimBLEScanResults &results, int reason) override { - scanning = false; - ESP_LOGI(TAG, "BLE: scan ended (reason=%d, found=%d)", reason, serverFound); - } }; class MyClientCallbacks : public NimBLEClientCallbacks { @@ -101,7 +84,6 @@ class MyClientCallbacks : public NimBLEClientCallbacks { } void onDisconnect(NimBLEClient *client, int reason) override { connected = false; - disconnectTime = millis(); relay1SendState = false; relay2SendState = false; pCmdChar = nullptr; @@ -257,7 +239,7 @@ static bool performEcdhHandshake(NimBLERemoteService *pSvc) { static bool connectToServer() { if (pClient == nullptr) { pClient = NimBLEDevice::createClient(); - pClient->setConnectTimeout(5000); + pClient->setConnectTimeout(10); pClient->setClientCallbacks(&clientCallbacks); } @@ -364,40 +346,25 @@ void setup() { if (allProvisioned) { NimBLEDevice::init("SylDa_Client"); bleReady = true; + ESP_LOGI(TAG, "BLE: initialized, connecting..."); - preferences.begin(NVS_NAMESPACE, true); - uint8_t addrBuf[6]; - size_t addrLen = preferences.getBytes(NVS_ADDR_NAME, addrBuf, 6); - preferences.end(); - if (addrLen == 6) { - ble_addr_t addr; - addr.type = BLE_ADDR_PUBLIC; - memcpy(addr.val, addrBuf, 6); - storedServerAddr = NimBLEAddress(addr); - serverAddrStored = true; - ESP_LOGI(TAG, "BLE: stored server address %s", storedServerAddr.toString().c_str()); + serverAddress = NimBLEAddress("44:1b:f6:f4:cf:15", BLE_ADDR_PUBLIC); + pClient = NimBLEDevice::createClient(); + pClient->setConnectTimeout(10); + pClient->setClientCallbacks(&clientCallbacks); + if (pClient->connect(serverAddress)) { + connected = true; + NimBLERemoteService *pSvc = pClient->getService(SERVICE_UUID); + if (pSvc) pCmdChar = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + ESP_LOGI(TAG, "BLE: connected!"); + } else { + ESP_LOGE(TAG, "BLE: connect failed"); } - - ESP_LOGI(TAG, "BLE: initialized, will scan for server"); } ESP_LOGI(TAG, "Setup done."); } -static void startScan() { - if (scanning) return; - NimBLEScan *pScan = NimBLEDevice::getScan(); - pScan->setScanCallbacks(new MyScanCallbacks()); - pScan->setActiveScan(true); - pScan->setInterval(32); - pScan->setWindow(16); - serverFound = false; - scanning = true; - scanStarted = millis(); - pScan->start(SCAN_DURATION_MS / 100, false); - ESP_LOGI(TAG, "BLE: scanning started"); -} - void loop() { if (Serial.available()) { static String serialBuf; @@ -571,57 +538,32 @@ void loop() { } } - static uint32_t lastScanAttempt = 0; + static uint32_t lastConnectAttempt = 0; static uint32_t statusLog = 0; if (bleReady && millis() - statusLog > 2000) { - ESP_LOGI(TAG, "BLE: connected=%d scanning=%d ecdh=%d serverFound=%d", - connected, scanning, ecdhReady, serverFound); + ESP_LOGI(TAG, "BLE: connected=%d, pClient=%p, isConn=%d", + connected, (void*)pClient, pClient ? pClient->isConnected() : 0); statusLog = millis(); } if (!connected && bleReady) { - if (serverFound && !scanning) { - serverFound = false; - serverAddress = foundAddr; - ESP_LOGI(TAG, "BLE: connecting to %s type=%d", serverAddress.toString().c_str(), serverAddress.getType()); - if (connectToServer()) { + if (millis() - lastConnectAttempt > SCAN_INTERVAL) { + ESP_LOGI(TAG, "BLE: connecting to server..."); + if (pClient == nullptr) { + pClient = NimBLEDevice::createClient(); + pClient->setConnectTimeout(10); + pClient->setClientCallbacks(&clientCallbacks); + } + if (pClient->connect(serverAddress)) { connected = true; - uint8_t addrBuf[6]; - memcpy(addrBuf, serverAddress.getVal(), 6); - preferences.begin(NVS_NAMESPACE, false); - preferences.putBytes(NVS_ADDR_NAME, addrBuf, 6); - preferences.end(); - storedServerAddr = serverAddress; - serverAddrStored = true; - ESP_LOGI(TAG, "BLE: server address stored in NVS"); + NimBLERemoteService *pSvc = pClient->getService(SERVICE_UUID); + if (pSvc) pCmdChar = pSvc->getCharacteristic(CHARACTERISTIC_UUID); + ESP_LOGI(TAG, "BLE: connected!"); } else { - ESP_LOGW(TAG, "BLE: connectToServer failed"); - } - } else if (!scanning) { - if (millis() - lastScanAttempt > SCAN_INTERVAL_MS) { - if (serverAddrStored) { - ESP_LOGI(TAG, "BLE: trying stored address %s", storedServerAddr.toString().c_str()); - serverAddress = storedServerAddr; - if (connectToServer()) { - connected = true; - ESP_LOGI(TAG, "BLE: connected via stored address"); - } else { - ESP_LOGI(TAG, "BLE: stored address failed, starting scan"); - serverAddrStored = false; - startScan(); - } - } else { - startScan(); - } - lastScanAttempt = millis(); - } - } else { - if (millis() - scanStarted > SCAN_DURATION_MS) { - NimBLEDevice::getScan()->stop(); - scanning = false; - ESP_LOGI(TAG, "BLE: scan timeout, no server found"); + ESP_LOGW(TAG, "BLE: connect failed"); } + lastConnectAttempt = millis(); } }