No description
  • Kotlin 58%
  • C++ 31.3%
  • Python 5.2%
  • Shell 5.2%
  • C 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
eddy daa2600bde
All checks were successful
CI / build (push) Successful in 1m7s
refactor: extract duplicate isHex/parseHex into shared serial_utils.h
Fixes #19
2026-08-05 08:23:34 +02:00
.forgejo/workflows Remove version check from CI (needs Gradle, not available) 2026-08-03 16:55:32 +02:00
android Merge pull request 'fix: critical issues from code review' (#20) from fix/code-review-critical-issues into main 2026-08-04 18:44:55 +02:00
docs docs: add implementation plan for issue #16 2026-08-04 19:01:57 +02:00
scripts Merge pull request 'fix: remove outdated ble_client.py, add test_ota_notify.py to tools' (#22) from fix/cleanup-outdated-scripts into main 2026-08-04 18:48:54 +02:00
src refactor: extract duplicate isHex/parseHex into shared serial_utils.h 2026-08-05 08:23:34 +02:00
tools fix(script): filter ID response in query_id() 2026-08-04 19:02:51 +02:00
.gitignore chore: cleanup — .gitignore, track design docs 2026-08-01 00:52:03 +02:00
AGENTS.md docs: HS-S43A actief LOW, !t1Active logica gedocumenteerd 2026-08-04 01:21:36 +02:00
icon.png Replace HMAC-SHA256 with AES-256-GCM + ECDH key exchange for BLE relay commands (issue #2) 2026-08-03 04:34:26 +02:00
icon.svg Replace HMAC-SHA256 with AES-256-GCM + ECDH key exchange for BLE relay commands (issue #2) 2026-08-03 04:34:26 +02:00
LICENSE Convert Thread+Handler to lifecycle-aware coroutines, add GPL-3.0 license 2026-07-26 20:43:37 +02:00
platformio.ini Revert "Add auto-detected upload_port for both ESP32s" 2026-08-03 23:34:31 +02:00
README.md docs: remove non-existent features from README (broker, client OTA server mode) 2026-08-05 07:28:46 +02:00
version.txt Fix BLE scan + touch logic + UUID + version read 2026-08-03 16:38:26 +02:00

Dit project is vibecoded met DeepSeek v4 Pro en OpenCode 1.18.5

Project SylDa

BLE-gestuurde relais-switch voor de M5Stack Atom S3 Lite (ESP32-S3) met een bijbehorende Android-app. De relais kunnen worden bediend via capacitieve touch-sensoren (HS-S43A) óf via Bluetooth Low Energy vanaf een Android-telefoon.

De BLE-verbinding is beveiligd met ECDH (secp256r1) sleuteluitwisseling en AES-256-GCM encryptie (authenticiteit + confidentiality). Een PSK (pre-shared key, via USB provisioning) wordt gebruikt voor ECDH pubkey HMAC-verificatie, niet direct voor command authenticatie. Sequence counters bieden replay-protectie. Eerdere versies gebruikten HMAC-SHA256 zonder encryptie — ECDH+AES-GCM is vanaf firmware server v16 / client v3.

Hardware

Component Details
Relais 1 Op G1
Relais 2 Op G2
Touch sensor 1 HS-S43A Digital Capacitive Switch Module op G8 (actief LOW)
Touch sensor 2 HS-S43A Digital Capacitive Switch Module op G6 (actief LOW)
Knop Op G41 (active low, interne Atom S3 Lite knop)
Status LED Op G35 (WS2812 RGB extern, rood = geen verbinding, geel = verbinding < 60 s, uit = stabiel)

Pinout

G1 = RELAY1
G2 = RELAY2
G6 = TOUCH2 (INPUT_PULLUP, actief LOW)
G8 = TOUCH1 (INPUT_PULLUP, actief LOW)
G41 = BUTTON (INPUT, active low)
G35 = LED (WS2812 RGB, OUTPUT)

Aansluiting

Touch sensor GND → GND
Touch sensor OUT → G8 / G6
Touch sensor VCC → 3.3V

Relais + → G1 / G2
Relais - → GND

HS-S43A touch sensor: Digitale output (actief LOW — pin naar GND bij aanraking). Gebruik INPUT_PULLUP modus. De sensor trekt de pin actief laag bij detectie.

LED-signalering

De LED fungeert als debug-indicator en schakelt uit na 60 seconden stabiele verbinding:

State LED Prioriteit
OTA in progress Groen knipperend (100 ms) 1 (hoogst)
OTA completed, wacht op confirm Geel vast 1
Geen verbinding Rood 2
Verbinding actief (< 60 s) Geel 3
Verbinding stabiel (>= 60 s) Uit 4 (laagst)
Relais-flash overlay Groen 150 ms overlay

De relais-flash toont kort groen bij een verandering in relais-status, waarna de LED terugkeert naar de verbindingskleur.

Beveiliging

De relay is beveiligd tegen ongeautoriseerde toegang via ECDH (Elliptic Curve Diffie-Hellman over secp256r1) met AES-256-GCM encryptie op applicatieniveau.

ECDH + AES-256-GCM

  • Zowel de firmware als de Android app delen een 32-byte PSK (pre-shared key), die via USB provisioning op beide apparaten terechtkomt
  • Bij BLE-connectie wordt een ECDH sleuteluitwisseling uitgevoerd:
    1. Client en server genereren elk een secp256r1 sleutelpaar
    2. Ze sturen hun public key (65 bytes) + HMAC-SHA256(PSK, public key) (32 bytes) naar elkaar (ECDH packet: 97 bytes)
    3. De ontvanger verifieert de HMAC met de PSK — ongeautoriseerde apparaten worden hier al geweerd
    4. Uit de eigen private key + peer public key wordt een shared secret berekend
    5. SHA-256(shared secret) levert de AES-256 sleutel op
  • Elk relay-commando wordt daarna versleuteld met AES-256-GCM (authenticity + confidentiality)
  • Een sequence counter (uint32, big-endian) voorkomt replay-aanvallen: elk packet moet een strikt oplopend volgnummer hebben
  • GCM biedt zowel encryptie als integriteitsverificatie (AEAD)

AES-GCM packet formaat (29 bytes)

Offset  Grootte  Veld            Beschrijving
0       4 bytes  sequence        Big-endian uint32, strikt oplopend (start bij 1)
4       8 bytes  nonce           Random nonce voor AES-GCM
12      1 byte   ciphertext      1 byte plaintext (command), versleuteld
13      16 bytes tag             GCM authenticatie-tag

ECDH packet formaat (97 bytes)

Offset  Grootte  Veld            Beschrijving
0       65 bytes public key      Uncompressed secp256r1 public key
65      32 bytes hmac            HMAC-SHA256(PSK, public key)

Commando codes

0x00 = relay1 OFF
0x01 = relay1 ON
0x02 = relay2 ON
0x03 = relay2 OFF

De sequence counter reset naar 0 bij elke (re)connectie op de module. De app start bij 1 om te zorgen dat het eerste packet altijd geaccepteerd wordt (1 > 0). De sequence counter wordt gepersisteerd in SharedPreferences en overleeft app-herstarts.

Key management

  • De PSK wordt niet meegecompileerd — de module start zonder key en wordt geprovisioneerd via USB
  • De PSK en device UUID worden opgeslagen in NVS (namespace syl, keys psk en uuid, partitie op 0x9000-0xDFFF)
  • De app slaat PSK, device UUID, server UUID en client UUID op in SharedPreferences
  • USB provisioning: Open Instellingen → Module bijwerken. De app checkt eerst de firmware versie (VERSION?) en flasht indien nodig. Na het flashen (of als versie al actueel is) volgt automatisch de key exchange:
    • Als de module al keys heeft → module antwoordt DEVICE_ID:<hex> en PSK:<hex> → app slaat lokaal op
    • Als de module nog geen keys heeft → module antwoordt NO_KEY → app genereert willekeurige 32-byte PSK via SecureRandom → stuurt SET_KEY:<hex>\n, SET_ID:<hex>\n, en voor client devices SET_SERVER:<hex>\n → module slaat op in NVS → app slaat lokaal op

USB Serial Protocol

App → module:  VERSION?\n
module → App:  BUILD_NR:<version>\n              (firmware versie check)

App → module:  ID?\n
module → App:  SylDa_Server\n of SylDa_Client\n  (device type)

App → module:  KEY?\n
module → App:  DEVICE_ID:<hex>\n                 (module UUID, 16 bytes = 32 hex)
module → App:  PSK:<hex>\n                        (PSK, 32 bytes = 64 hex)
module → App:  SERVER_UUID:<hex>\n               (alleen client: opgeslagen server UUID, 32 hex)
module → App:  NO_KEY\n                           (als module nog geen key heeft)

App → module:  SET_KEY:<hex>\n                    (nieuwe PSK schrijven, 64 hex)
module → App:  OK\n

App → module:  SET_ID:<hex>\n                    (UUID schrijven, 32 hex)
module → App:  OK\n

App → module:  SET_SERVER:<hex>\n                (server UUID voor client, 32 hex)
module → App:  OK\n

Multi-client ondersteuning

De server ondersteunt tot 2 gelijktijdige BLE-clients (MAX_CLIENTS 2). Elke client krijgt een onafhankelijke ECDH-handshake, AES-sleutel en sequence counter. Relais-commando's van alle clients worden gecombineerd: een relais gaat aan als minimaal één client het aan-commando stuurt (OR-logica).

Beveiligingsanalyse

Aanval Hoe geweerd
Replay Sequence counter — zelfde packet werkt niet 2× (seq <= lastSeq geweerd)
Ongeautoriseerd device ECDH pubkey HMAC vereist PSK — verkeerde PSK = afgewezen public key
BLE spoofing (client) Client gebruikt service UUID + manufacturer data voor scanning, full 16-byte UUID + ECDH PSK-HMAC voor verificatie na connect. 4-byte manufacturer data scan-filter (2^32 combinaties) kan tot onnodige connectiepogingen leiden — deze falen alsnog bij 16-byte UUID check of ECDH handshake. Voorheen (issue #10) werd alleen op naam "SylDa_Server" gescant zonder verdere verificatie.
Commando vervalsing AES-GCM versleutelt en authenticeert command byte + nonce — wijzigen maakt tag ongeldig
Afluisteren AES-256-GCM biedt confidentiality — commando's zijn onleesbaar zonder AES-sleutel
Brute-force PSK 32-byte PSK = 2^256 mogelijkheden — onhaalbaar
Reverse engineering van PSK PSK staat niet in de APK — wordt pas bij USB provisioning gegenereerd en in SharedPreferences opgeslagen.
PSK extractie via USB KEY?\n geeft de huidige PSK terug. Fysieke USB-toegang is nodig — bescherm het board.
NVS wiping pio run -t erase wist alle flash waaronder NVS — module start daarna zonder keys.

Firmware (OTA via BLE)

De firmware kan over-the-air worden geüpdatet via BLE (zonder USB):

  1. De app scant en verbindt zoals normaal
  2. Instellingen → Module bijwerken (Server mode + BLE transport)
  3. De app leest de VERSION characteristic — als de versie actueel is, wordt overgeslagen
  4. Firmware wordt in 500-byte chunks verzonden via de OTA characteristic (WRITE_NR + NOTIFY)
  5. Elke chunk krijgt een ACK/NAK response
  6. Na alle chunks wordt de SHA-256 hash van de ontvangen firmware geverifieerd
  7. Bij match: 0xFF confirmatie → esp_ota_set_boot_partition → module reboot met nieuwe firmware
  8. 10-seconden timeout op 0xFF confirm — geen confirm = update wordt weggegooid

Tijdens OTA zijn relais geforceerd UIT en knippert de LED groen. Na afloop (wachtend op confirm) is de LED geel.

Firmware

De firmware is geschreven in C++ met PlatformIO en de Arduino-framework. BLE: ESP32 NimBLE library, crypto: mbedtls (ingebouwd in ESP-IDF).

Environment Bronbestand Rol
s3lite-server src/main.cpp Ontvangt BLE-verbindingen, bedient relais
s3lite-client src/client.cpp Verbindt als BLE-central met server, stuurt relais-commando's

Firmwareversies: version.txt (single source of truth) → src/version.h (gegenereerd door tools/pio_version_pre.py) + SylDaVersions.kt (Android). Versiebeheer via tools/check_versions.sh.

Logica

Elk relais gaat aan als één van beide bronnen actief is:

Relais Bron Voorwaarde Resultaat
Relais 1 Touch sensor 1 G8 = LOW (aanraken, actief LOW) Relais 1 aan
Relais 1 BLE/AES command App stuurt AES-GCM 0x01 Relais 1 aan
Relais 1 BLE/AES command (client) Client stuurt AES-GCM 0x01 Relais 1 aan
Relais 1 Interne knop (G41) Knop ingedrukt (LOW) Relais 1 aan
Relais 2 Touch sensor 2 G6 = LOW (aanraken, actief LOW) Relais 2 aan
Relais 2 BLE/AES command App stuurt AES-GCM 0x02 Relais 2 aan
Relais 2 BLE/AES command (client) Client stuurt AES-GCM 0x02 Relais 2 aan
Beide Geen van beide Geen touch + geen BLE command Relais uit

De touch sensoren, BLE, en interne knop werken onafhankelijk (OR-logica). De interne knop (G41) activeert alleen relais 1. Bij meerdere BLE-clients worden hun commando's samengevoegd.

BLE GATT

Eigenschap UUID Properties Beschrijving
Service 4fafc201-1fb5-459e-8fcc-c5c9c331914b SylDa service
Relay beb5483e-36e1-4688-b7f5-ea07361f26d8 WRITE_NR AES-GCM relay commands (29 bytes)
UUID beb5483e-36e1-4688-b7f5-ea07361f26d9 READ Device UUID (16 bytes)
VERSION beb5483e-36e1-4688-b7f5-ea07361f26db READ Firmware versie string
OTA beb5483e-36e1-4688-b7f5-ea07361f26da WRITE_NR, NOTIFY BLE OTA firmware update
ECDH beb5483e-36e1-4688-b7f5-ea07361f26dc WRITE_NR, NOTIFY ECDH sleuteluitwisseling

Key provisioning

De firmware wordt zonder PSK gecompileerd. Bij de eerste opstart print de module:

No PSK — connect USB and run SET_KEY

Sluit de module via USB aan op de telefoon met de app geïnstalleerd. De app genereert automatisch een willekeurige PSK en stuurt deze naar de module. Na provisioning print de module bij opstarten:

PSK loaded from NVS

Key wissen (factory reset): pio run -t erase wist alle flash inclusief NVS.

Serial monitor

Baudrate: 115200 (USB CDC). De firmware stuurt heartbeats elke 60 seconden, logt statuswijzigingen, en print de PSK-status bij opstarten.

Met geprovisioneerde PSK (server):

=== AtomS3 Lite BLE Relay v20 ===
PSK loaded from NVS
BLE: advertising started
Setup done.
Relays: R1=ON R2=OFF (t1=YES t2=no btn=no ble1=OFF ble2=OFF)
[heartbeat] R1=ON R2=OFF t1=YES t2=no btn=no ble1=OFF ble2=OFF clients=1

Zonder PSK (nieuwe/gewiste module):

=== AtomS3 Lite BLE Relay v20 ===
No PSK — connect USB and run SET_KEY
BLE: advertising started
No PSK — use USB SET_KEY to provision
Setup done.

Client firmware log (geprovisioneerd):

=== SylDa Client v5 ===
PSK loaded from NVS
UUID loaded from NVS
BLE: scanning...
BLE: connected to xx:xx:xx:xx:xx:xx
[hb] t1=no t2=no btn=no r1=OFF r2=OFF conn=yes ecdh=yes

Uploaden

Download-mode: Houd de reset-knop ~2 seconden ingedrukt tot de groene LED aangaat, laat dan los.

# Flash server firmware (behoudt NVS/PSK)
pio run -e s3lite-server --target upload

# Flash client firmware (behoudt NVS/PSK)
pio run -e s3lite-client --target upload

# Volledig wissen (incl. NVS/PSK) + opnieuw flashen
pio run -t erase && pio run -e s3lite-server --target upload

De upload port wordt automatisch gedetecteerd door tools/pio_upload_port.py (post-build script). Deze script zoekt de ESP32 via ID? over de seriële poort en past upload_port aan in platformio.ini.

Direct flashen (handmatig):

~/.platformio/packages/tool-esptoolpy/esptool.py --no-stub --chip esp32s3 --port /dev/ttyACM1 --baud 115200 --before default_reset --after hard_reset write_flash --flash_mode keep --flash_size keep 0x0 .pio/build/s3lite-server/bootloader.bin 0x8000 .pio/build/s3lite-server/partitions.bin 0xE000 ~/.platformio/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 .pio/build/s3lite-server/firmware.bin

Firmware flashen via de app

De firmware kan rechtstreeks via de Android-app worden geflashed (USB) of geüpdatet (BLE OTA):

  1. Sluit de module via USB-OTG aan op de telefoon (USB) of zorg dat deze verbonden is via BLE (OTA)
  2. Open de app → Instellingen
  3. Kies Device Mode (Server/Client) en Transport Mode (BLE/USB)
  4. Tik op Module bijwerken
  5. USB transport: App checkt versie, flasht bootloader/partitions/firmware indien verouderd, wisselt daarna PSK uit
  6. BLE transport (alleen server mode): App checkt VERSION characteristic, voert OTA update uit zonder USB

Firmware binaries: tools/copy_firmware.sh kopieert .bin bestanden naar assets/firmware/server/ en assets/firmware/client/.

Android App

Features

  • Auto-connect: Scant bij opstarten naar BLE service UUID 4fafc201-1fb5-459e-8fcc-c5c9c331914b ("SylDa_Server")
  • Manufacturer data matching: Scant op manufacturer ID 0xFFFF die de eerste 4 bytes van de device UUID bevat — directe UUID-match bij scan
  • Device UUID verificatie: Leest UUID_CHAR_UUID en vergelijkt met opgeslagen UUID — alleen correcte apparaten worden geaccepteerd
  • Firmware versie check: Bij connect wordt VERSION characteristic gelezen — toont "Update beschikbaar" als app-versie nieuwer is
  • Auto-reconnect: Bij wegvallen verbinding na 2 seconden opnieuw scannen
  • Scan retry: Bij timeout 3× automatisch herhalen, daarna "Opnieuw zoeken" knop
  • Hold-to-activate: Knop ingedrukt = relais aan, loslaten = relais uit
  • Twee onafhankelijke relais: Twee knoppen, elk met eigen AES-GCM commando
  • Write Without Response: WRITE_TYPE_NO_RESPONSE voor minimale latency
  • MTU negotiatie: Vraagt MTU 512 aan na service discovery
  • ECDH + AES-256-GCM: ECDH sleuteluitwisseling bij connectie, daarna AES-GCM encryptie voor elk commando
  • BLE OTA update: Firmware updaten via BLE zonder USB (server mode + BLE transport)
  • Key exchange geïntegreerd: Module bijwerken = versie check → flashen (indien nodig) → PSK-uitwisseling — volledig automatisch
  • Thema-selector: Instellingen → Weergave met Systeem, Licht of Donker
  • Permissies: Vraagt BLUETOOTH_SCAN en BLUETOOTH_CONNECT. Bij weigeren wordt verwezen naar Instellingen

Instellingenscherm

  1. Open de app → tik op instellingen (tandwiel-icoon rechtsboven)
  2. Device Mode: Kies Server of Client (bepaalt welke firmware wordt geflashed / provisioning flow)
  3. Transport Mode: Kies BLE of USB (bepaalt hoe firmware wordt geüpdatet: OTA via BLE of USB flash)
  4. Module bijwerken: Versie check → flash/OTA → PSK-uitwisseling — één knop voor de hele flow
  5. Weergave: Kies Systeem (volgt telefoon), Licht of Donker
  6. Relais labels: Stel eigen namen in voor de twee relais knoppen

De PSK en UUIDs worden opgeslagen in SharedPreferences en overleven app-herstarts. Bij geen PSK toont de hoofdapp "Geen sleutel — open Instellingen".

Hoofdscherm UI

  • instellingen tekst + tandwiel-icoon rechtsboven → opent instellingenscherm
  • Twee knoppen in het midden → hold-to-activate voor relais 1 en 2 (labels aanpasbaar)
  • Status tekst linksonder (14sp) → toont verbindingsstatus

Bouwen

Vereisten:

  • Android SDK (API 34+)
  • Java 17 target (JDK 21 voor Gradle — Java 26 werkt niet)
  • Gradle 8.14 (via wrapper)
  • AGP 8.5.2, Kotlin 2.0.21 (K2 compiler)
  • USB host support (raw Android USB API, geen externe serial library)
  • PlatformIO (voor firmware compilatie)
# Firmware compileren en naar app assets kopiëren
pio run
bash tools/copy_firmware.sh

# App bouwen en installeren
cd android
JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew installDebug

APK: android/app/build/outputs/apk/debug/app-debug.apk.

Installeren

adb -s <ip>:5555 install -r android/app/build/outputs/apk/debug/app-debug.apk

Eerste keer setup

  1. App op de telefoon installeren:

    cd android
    JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew installDebug
    
  2. Firmware flashen & sleutel uitwisselen (één flow):

    • Sluit de module via USB-OTG aan op de telefoon
    • Open de app → Instellingen → kies Device Mode (Server/Client) → kies Transport (USB) → Module bijwerken
    • De app checkt versie, flasht indien nodig, en wisselt PSK uit — volledig automatisch
  3. Gebruiken: De app scant automatisch, verbindt, en toont "Verbonden". Houd een knop ingedrukt om het relais te schakelen.

Nieuwe module, bestaande telefoon

  • Sluit nieuwe module via USB aan → Instellingen → Module bijwerken → app flasht en provisioneert

Bestaande module, nieuwe telefoon

  • Sluit module via USB-OTG aan op nieuwe telefoon → Instellingen → Module bijwerken → app leest bestaande PSK uit (antwoordt PSK:<hex>)

Projectstructuur

Project_SylDa/
├── platformio.ini              # PlatformIO configuratie (ESP32-S3, server + client)
├── version.txt                 # Single source of truth voor firmwareversies
├── icon.png / icon.svg         # Project icon
├── src/
│   ├── main.cpp                # Server firmware (BLE + ECDH/AES + NVS + USB + touch + relais + OTA)
│   ├── client.cpp              # Client firmware (BLE central + ECDH/AES + touch + relais)
│   ├── ecdh_helper.h           # ECDH crypto helper (secp256r1, AES-sleutel afleiding, pubkey HMAC)
│   └── version.h               # Auto-gegenereerd uit version.txt — niet handmatig bewerken
├── tools/
│   ├── copy_firmware.sh        # Kopieert firmware binaries naar Android app assets
│   ├── pio_version_pre.py      # PlatformIO pre-build script (genereert version.h)
│   ├── pio_upload_port.py      # PlatformIO post-build script (auto-detecteert upload port)
│   ├── check_versions.sh       # Valideert version.txt vs version.h vs SylDaVersions.kt
│   └── test_ota_notify.py      # Test BLE OTA notificatie flow
├── scripts/
│   └── release.sh              # Release automatisering: firmware + APK/AAB + Forgejo release
├── android/
│   ├── build.gradle             # Root Gradle config
│   ├── settings.gradle          # Gradle settings
│   ├── gradle.properties
│   ├── gradlew                  # Gradle wrapper
│   └── app/
│       ├── build.gradle         # App module (nl.eddydevink.sylda, minSdk 26)
│       └── src/main/
│           ├── AndroidManifest.xml
│           ├── assets/firmware/
│           │   ├── server/
│           │   │   ├── bootloader.bin
│           │   │   ├── partitions.bin
│           │   │   ├── firmware.bin
│           │   │   └── boot_app0.bin
│           │   └── client/
│           │       ├── bootloader.bin
│           │       ├── partitions.bin
│           │       ├── firmware.bin
│           │       └── boot_app0.bin
│           ├── java/nl/eddydevink/sylda/
│           │   ├── MainActivity.kt
│           │   ├── SettingsActivity.kt
│           │   ├── BleRelayManager.kt
│           │   ├── BleStatus.kt
│           │   ├── EspFlasher.kt
│           │   ├── OtaManager.kt
│           │   ├── SylDaVersions.kt
│           │   ├── UsbSerialTransport.kt
│           │   ├── UsbUtils.kt
│           │   └── PrefsHelper.kt
│           ├── res/
│           │   ├── drawable/
│           │   │   └── ic_settings.xml
│           │   ├── layout/
│           │   │   ├── activity_main.xml
│           │   │   ├── activity_settings.xml
│           │   │   ├── dialog_waiting.xml
│           │   │   └── dialog_progress.xml
│           │   ├── values/strings.xml
│           │   └── xml/
│           │       └── device_filter.xml
├── docs/                        # Documentatie (handleiding, ontwerpplannen)
├── release/                     # Release artifacts
├── .gitignore
├── LICENSE
└── README.md

Ontwerpbeslissingen

WRITE_TYPE_NO_RESPONSE (geen BLE write ACK)

De app gebruikt WRITE_TYPE_NO_RESPONSE in plaats van WRITE_TYPE_DEFAULT. Eerdere versies gebruikten ACK's, maar de BLE write acknowledgement duurde lang genoeg dat de gebruiker de knop kon loslaten vóór de ACK arriveerde. Als ACTION_UP plaatsvond tijdens een pending write, werd het loslaten genegeerd en bleven relais en app hangen in ON-status. WRITE_TYPE_NO_RESPONSE verwijdert die latency en maakt directe ON→OFF sequenties mogelijk.

compileSdk/targetSdk 34 (niet 35 / Android 15)

De doelgroep (lichamelijk gehandicapte gebruikers) is terughoudend met telefoonupdates. API 34 (Android 14) maximaliseert compatibiliteit. Heroverweeg bij voldoende migratie.

Thema-selector en DayNight

Theme.AppCompat.DayNight met 3 opties: Systeem, Licht, Donker. Android's sp-eenheden en hoog-contrast modus (Android 13+) volstaan voor visueel beperkte gebruikers — eigen implementatie zou onnodige complexiteit toevoegen.

ECDH + AES-256-GCM i.p.v. HMAC-only

HMAC-SHA256 (vroegere versies) bood authenticatie maar geen confidentiality. ECDH+AES-GCM voegt encryptie toe zonder de complexiteit van BLE-link bonding (die Android sliding bugs veroorzaakt: write queues, GATT_INSUFFICIENT_AUTHENTICATION). De PSK wordt alleen gebruikt voor ECDH pubkey HMAC-verificatie — daarna is alle communicatie versleuteld met afgeleide AES-256-GCM sleutels. GCM is AEAD: één operatie voor zowel encryptie als authenticatie.

Technische notities

  • De Atom S3 Lite heeft GPIO G1, G2, G5-G8, G38, G39 beschikbaar op de headers. G41 is de interne knop (active low). G35 wordt gebruikt voor een externe WS2812 LED (niet de interne G46 SK6812).
  • G1 en G2 zitten op de HY2.0-4P Grove poort. G5-G8, G38, G39 op de onderste expansie-header.
  • De firmware gebruikt geen M5Unified library — alleen Arduino core + ESP32 NimBLE + mbedtls + FastLED.
  • ARDUINO_USB_CDC_ON_BOOT=1 is ingeschakeld voor virtuele serial via USB.
  • NVS: namespace syl, PSK in key psk, UUID in key uuid, server UUID in key server, BLE-adres in key addr. Partitie op 0x9000-0xDFFF.
  • pio run -t erase wist alle NVS. tools/pio_upload_port.py auto-detecteert de upload port via ID? query.
  • Firmwareversies: version.txtsrc/version.h (gegenereerd) + SylDaVersions.kt (Android). tools/check_versions.sh valideert consistentie.
  • De app gebruikt raw Android USB API (UsbDeviceConnection.bulkTransfer) — geen externe serial library.
  • minSdk is 26 (Android 8.0). compileSdk/targetSdk blijft op 34.
  • Sequence counters starten bij 1 (niet 0), wrappen bij 1.000.000.
  • BLE MTU: server stelt 248 in (ESP32 NimBLE limiet), app vraagt 512. OTA/ECDH chunks passen zich aan aan negotiatedMtu - 7.
  • Server krijgt maximaal 2 gelijktijdige BLE-clients. Client slaat server BLE-adres op in NVS voor snelle reconnect (valt terug op scan als adres niet werkt).
  • Server advertising bevat manufacturer data (0xFFFF) met eerste 4 bytes van device UUID voor snelle identificatie.
  • OTA: 500-byte chunks (aangepast aan MTU), 3 retries per chunk, SHA-256 hash verificatie, 10s 0xFF confirm timeout.

Release

# Volledige release (firmware + APK + AAB + Forgejo)
bash scripts/release.sh

Deze script:

  1. Compileert server + client firmware (pio run)
  2. Kopieert firmware naar Android assets (copy_firmware.sh)
  3. Bouwt release APK + AAB (gradlew assembleRelease assembleAndroidTest bundleRelease)
  4. Publiceert de release op Forgejo (met APK/AAB als attachments)

Licentie

GNU General Public License v3.0 or later (GPL-3.0-or-later).

Vereist omdat de app esp32-flash-lib (GPL-3.0) gebruikt voor ESP32 firmware flashing. Zie het LICENSE bestand.