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
2026-07-27 13:12:31 +02:00
android Extract magic numbers to named constants (review 4.3) 2026-07-27 12:39:41 +02:00
docs/superpowers/plans Add code review fixes implementation plan 2026-07-26 12:54:11 +02:00
src Replace BLE encryption with HMAC-only auth, add buildnr, fix post-flash flow 2026-07-26 15:46:28 +02:00
tools Replace BLE encryption with HMAC-only auth, add buildnr, fix post-flash flow 2026-07-26 15:46:28 +02:00
.gitignore Add .worktrees/ to .gitignore 2026-07-27 12:32:40 +02:00
appstore.md Convert Thread+Handler to lifecycle-aware coroutines, add GPL-3.0 license 2026-07-26 20:43:37 +02:00
codereview.md Voeg thema-selector toe (DayNight, Radiogroep, teal accent) 2026-07-27 00:22:43 +02:00
esplibalternatief.md Convert Thread+Handler to lifecycle-aware coroutines, add GPL-3.0 license 2026-07-26 20:43:37 +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 Initial commit: BLE relay controller for Atom VoiceS3R 2026-07-26 01:17:31 +02:00
README.md Voeg thema-selector toe (DayNight, Radiogroep, teal accent) 2026-07-27 00:22:43 +02:00

Sylvia Touch Relay

BLE-gestuurde relais-switch voor de M5Stack Atom VoiceS3R (ESP32-S3) met een bijbehorende Android-app. Het relais kan worden bediend via een capacitieve touch-sensor (open-drain) óf via Bluetooth Low Energy vanaf een Android-telefoon.

De BLE-verbinding is beveiligd met HMAC-SHA256 authenticatie (application-level handtekening met replay-protectie). BLE-link encryptie/bonding is bewust niet toegepast — HMAC biedt authenticatie en replay-bescherming op applicatieniveau, voldoende voor een relais-aan/uit commando.

Hardware

Component Details
Board M5Stack Atom VoiceS3R (SKU: C126-ECHO)
SoC ESP32-S3-PICO-1-N8R8 (8MB Flash, 8MB PSRAM)
Touch sensor Open-drain sensor op G38 (3.3V, interne pullup)
Relais / LED Op G1 (3.3V GPIO output)
Status LED Op G46 (knippert 3x bij opstarten)

Pinout (Atom VoiceS3R onderste header)

G38  G39  3.3V  GND  5V  G5  G6  G7  G8
  • G38 — Touch sensor input (active LOW, interne pullup ingeschakeld)
  • G1 — Relais output (HIGH = aan, LOW = uit)
  • G46 — Status LED (3x knipperen bij opstarten, daarna uit)

Aansluiting

Sensor GND  →  GND
Sensor OUT →  G38
Sensor VCC →  3.3V

Relais +  →  G1
Relais -  →  GND

Let op: De ESP32-S3 interne pullup is ~45kΩ. Bij lange draden of elektrische ruis een externe 4.7kΩ–10kΩ pullup naar 3.3V plaatsen.

Beveiliging

De relay is beveiligd tegen ongeautoriseerde toegang via HMAC-SHA256 authenticatie op applicatieniveau.

HMAC-SHA256 Authenticatie (application-level)

  • Zowel de ESP firmware als de Android app delen een 32-byte shared secret (HMAC key), die via USB provisioning op beide apparaten terechtkomt
  • Elk commando wordt ondertekend met HMAC-SHA256(key, command || sequence)
  • De ESP verifieert de handtekening voordat het relais schakelt
  • Een sequence counter voorkomt replay-aanvallen: elk packet moet een strikt oplopend volgnummer hebben

Packet formaat (37 bytes)

Offset  Grootte  Veld         Beschrijving
0       1 byte   command      0x01 = relais aan, 0x00 = relais uit
1       4 bytes  sequence     Big-endian uint32, strikt oplopend (start bij 1)
5       32 bytes hmac         HMAC-SHA256(key, command || sequence)

De sequence counter reset naar 0 bij elke (re)connectie op de ESP. De app start bij 1 om te zorgen dat het eerste packet altijd geaccepteerd wordt (1 > 0). Replay van een eerder packet wordt geweerd omdat seq <= lastSeq. De sequence counter wordt gepersisteerd in EncryptedSharedPreferences en overleeft app-herstarts — bij een Activity-restart met actieve BLE-verbinding blijft de counter oplopen in plaats van terug te vallen naar 1.

Key management

  • De HMAC key wordt niet meegecompileerd — de ESP start zonder key en wordt geprovisioneerd via USB
  • De key wordt opgeslagen in NVS (Non-Volatile Storage) op de ESP via de Arduino Preferences library (survives reboots en power-cycles)
  • De app slaat de key op in EncryptedSharedPreferences (AES256-GCM versleuteld, overleeft app-herstarts)
  • USB provisioning via Instellingen: Open Instellingen → Sleutel Uitwisselen. Sluit de ESP via USB-OTG aan. De app stuurt KEY?\n:
    • Als de ESP al een key heeft → ESP antwoordt HMAC_KEY:<hex> → app slaat op lokaal op
    • Als de ESP nog geen key heeft → ESP antwoordt NO_KEY → app genereert willekeurige 32-byte key via SecureRandom → stuurt SET_KEY:<hex>\n → ESP slaat op in NVS en antwoordt OK → app slaat op lokaal op
    • Beide paden tonen dezelfde toast: "Beveiligingssleutel gelezen"
  • Zo kan dezelfde app gebruikt worden voor meerdere ESP modules — elk met een eigen key

USB Serial Protocol

App → ESP:  KEY?\n
ESP → App:  HMAC_KEY:C3F17B...AF\n     (als key bekend)
ESP → App:  NO_KEY\n                    (als ESP nog geen key heeft)

App → ESP:  SET_KEY:C3F17B...AF\n       (nieuwe key schrijven)
ESP → App:  OK\n                        (bevestiging)

De ESP luistert in loop() voor USB serial data. Bij KEY?\n antwoordt hij met de hex key (HMAC_KEY:<hex>) of NO_KEY. Bij SET_KEY:<64-hex>\n slaat hij de key op in NVS en antwoordt OK.

Provisioning flow (via Instellingen)

  Android app                         ESP32 (USB CDC)
  ───────────                         ───────────────
  User taps "Sleutel Uitwisselen"
  Request USB permission ◄────── Android dialog
  Open CDC-ACM connection
  Send KEY?\n ──────────────────► 
                                 ┌─ if key in NVS ──► HMAC_KEY:<hex>\n
                                 │                   App saves to prefs → toast
                                 │
                                 └─ if no key ──────► NO_KEY\n
  Generate 32 random bytes              │
  Send SET_KEY:<hex>\n ─────────► (store in NVS)
  ◄──────────────────────────── OK\n
  App saves to prefs → toast

Beveiligingsanalyse

Aanval Hoe geweerd
Replay Sequence counter — zelfde packet werkt niet 2x (seq <= lastSeq wordt geweerd)
Ongeautoriseerd device HMAC vereist geheime sleutel (32-byte random via USB provisioning)
Commando vervalsing HMAC dekt command byte + sequence — wijzigen maakt handtekening ongeldig
Brute-force key 32-byte key = 2^256 mogelijkheden — onhaalbaar
Sniffen Pakketten zijn onversleuteld; geen confidentiality. Alleen commando's (aan/uit) zijn zichtbaar, niet de key.
Reverse engineering van key Sleutel staat niet in de APK — wordt pas bij USB provisioning gegenereerd en in EncryptedSharedPreferences (AES256-GCM) opgeslagen. Extractie vereist fysieke toegang tot de telefoon én root.
Key extractie via USB Fysieke USB-toegang tot de ESP geeft KEY?\n de huidige key terug. Bescherming: fysieke toegang tot het board beperken.
NVS wiping pio run -t erase wist alle flash waaronder NVS — ESP is daarna leeg en kan opnieuw geprovisioneerd worden.

BLE-link encryptie (bonding/Just Works) vereist complexe pairing flows die op Android sliding bugs veroorzaken (write queues, GATT_INSUFFICIENT_AUTHENTICATION handling, state leaks). HMAC biedt authenticatie en replay-bescherming op applicatieniveau — voldoende voor een relais. Het enige wat link-encryptie extra biedt is confidentiality (afluisteren), wat voor een aan/uit commando irrelevant is. De HMAC key wordt fysiek via USB uitgewisseld, nooit via BLE.

Firmware

De firmware is geschreven in C++ met behulp van PlatformIO en de Arduino-framework. BLE wordt verzorgd door de ESP32 BLE library, HMAC door mbedtls (ingebouwd in ESP-IDF).

Logica

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

Bron Voorwaarde Resultaat
Touch sensor G38 = LOW (aanraken) Relais aan
BLE command App stuurt HMAC-ondertekend 0x01 Relais aan
Geen van beide G38 = HIGH + geen BLE command Relais uit

De touch sensor en BLE werken onafhankelijk. Als de sensor losgelaten wordt en de BLE-knop niet ingedrukt is, gaat het relais uit.

BLE GATT

Eigenschap UUID
Service 4fafc201-1fb5-459e-8fcc-c5c9c331914b
Characteristic beb5483e-36e1-4688-b7f5-ea07361f26d8

De characteristic ondersteunt WRITE_NR (Write Without Response) en NOTIFY. Writes worden geaccepteerd zonder bonding — de HMAC-handtekening op applicatieniveau verzorgt de authenticatie. WRITE_NR zorgt voor minimale latency: de app kan direct na ON ook OFF sturen zonder te wachten op een ACK.

Key provisioning

De firmware wordt zonder HMAC key gecompileerd. Bij de eerste opstart print de ESP:

No HMAC key provisioned — connect USB and run SET_KEY

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

HMAC key loaded from NVS
HMAC Key: C3F17B...AF

Key wissen (factory reset): Om de ESP terug te zetten naar ongeprovisioneerd:

pio run -t erase

Dit wist alle flash inclusief NVS. Na opnieuw flashen start de ESP weer zonder key.

Serial monitor

Baudrate: 115200 (USB CDC). De firmware stuurt heartbeat-statussen elke 5 seconden, logt statuswijzigingen, en print de HMAC key (of de afwezigheid ervan) bij opstarten.

Met geprovisioneerde key:

=== Atom VoiceS3R BLE Relay ===
HMAC key loaded from NVS
LED blink done, starting BLE...
BLE: advertising started as 'AtomS3R-Relay'
HMAC Key: C3F17B612B93E82C56B055FA9B04E84664ACE1EC4E889F2763F271B65A5B54AF
Setup done.
[heartbeat] relay=OFF touch=no ble=disconnected
BLE: device connected
Relay ON (touch=YES, ble=connected)
BLE: relay ON (seq=1)
BLE: relay OFF (seq=2)

Zonder key (nieuwe/gewiste ESP):

=== Atom VoiceS3R BLE Relay ===
No HMAC key provisioned — connect USB and run SET_KEY
LED blink done, starting BLE...
BLE: advertising started as 'AtomS3R-Relay'
No HMAC key — use USB SET_KEY to provision
Setup done.

In deze staat accepteert de ESP wel BLE-verbindingen, maar elk HMAC-packet faalt verificatie omdat er geen key is om mee te vergelijken.

Uploaden

Download-mode: Houd de reset-knop ~2 seconden ingedrukt tot de groene LED aangaat, laat dan los. Het board is nu in download-mode.

# Flash firmware (behoudt NVS/key)
pio run --target upload

# Volledig wissen (incl. NVS/key) + opnieuw flashen
pio run -t erase && pio run -t upload

Na het uploaden treedt er een harde reset op. Als het board in download-mode blijft, USB eruit/erin of kort op reset tikken om in run-mode te komen.

Firmware flashen via de app

De firmware kan ook rechtstreeks via de Android-app worden geflashed, zonder computer:

  1. Zet de ESP in download-modus (houd reset ~2s in tot groene LED)
  2. Sluit de ESP via USB-OTG aan op de telefoon
  3. Open de app → Instellingen → Firmware Flashen
  4. De app flasht bootloader, partitions, en firmware via USB
  5. Dialog verdwijnt automatisch wanneer de USB wordt losgekoppeld
  6. Sluit de ESP opnieuw aan → Sleutel Uitwisselen

De firmware binaries worden via tools/copy_firmware.sh naar de app assets gekopieerd.

Android App

Features

  • Auto-connect: De app scant automatisch bij opstarten naar het BLE-apparaat "AtomS3R-Relay"
  • Auto-reconnect: Bij wegvallen van de verbinding wordt na 2 seconden opnieuw gescant
  • Scan retry: Bij scan timeout wordt de HOLD-knop een "Opnieuw scannen" knop — tik om opnieuw te scannen zonder de app te herstarten
  • Hold-to-activate: Houd de knop ingedrukt voor relais aan, loslaten voor relais uit (zelfde gedrag als de sensor)
  • Write Without Response: Gebruikt WRITE_TYPE_NO_RESPONSE voor minimale latency — ON en OFF kunnen direct achter elkaar worden verstuurd zonder ACK-wachttijd
  • MTU negotiatie: Vraagt MTU 256 aan na service discovery (voor 37-byte HMAC packets)
  • HMAC authenticatie: Elk commando wordt ondertekend met HMAC-SHA256 + sequence counter
  • USB provisioning via Instellingen: Via Instellingen → Sleutel Uitwisselen leest de app de bestaande key uit, of genereert een nieuwe key als de ESP er nog geen heeft. Beide scenario's tonen de toast "Beveiligingssleutel gelezen"
  • Firmware flashen via app: De app kan de ESP32 firmware rechtstreeks via USB flashen (download-modus), geen computer nodig
  • Instellingenscherm: Losse Activity voor sleutel uitwisseling, firmware flashen, en buildnummer weergave
  • Permissies: Vraagt BLUETOOTH_SCAN, BLUETOOTH_CONNECT en ACCESS_FINE_LOCATION aan (Android 12+). Bij weigeren wordt de knop uitgeschakeld en wordt de gebruiker doorverwezen naar Instellingen
  • BLE write feedback: onCharacteristicWrite callback logt succes/falen; bij falen wordt "Communicatiefout" getoond
  • GATT status logging: Verbindingsstatus, service discovery en MTU-negotiatie loggen foutstatussen (GATT_SUCCESS check)
  • Scan annulering: BLE-scan wordt betrouwbaar gestopt in onPause via gedeelde scanRunnable + removeCallbacks
  • Cancellable firmware flash: Flash-proces kan geannuleerd worden via de annuleerknop; EspFlasher gebruikt een @Volatile cancelled flag
  • Key exchange robuustheid: 5 retries (was 3), 1000ms delay (was 500ms), duidelijkere foutmelding bij falen
  • Thema-selector: Instellingen → Weergave kiest Systeem (volgt telefoon), Licht of Donker. App gebruikt Theme.AppCompat.DayNight met teal accent-kleuren. Android's systeem-lettergrootte (sp) en hoog-contrast instellingen worden gerespecteerd

Instellingenscherm

  1. Open de app
  2. Tik op het tandwiel-icoon rechtsboven
  3. De app toont twee acties: Sleutel Uitwisselen en Firmware Flashen
  4. Sleutel Uitwisselen: Sluit de ESP via USB-OTG aan. De app leest de key uit (als de ESP er al een heeft) of genereert een nieuwe key en provisioneert de ESP. Bij falen: toast "Sleutel uitwisselen mislukt — probeer opnieuw". Bij succes: "Beveiligingssleutel gelezen"
  5. Firmware Flashen: Zet de ESP in download-modus (houd reset 2s in tot groene LED). Sluit aan via USB. De app flasht de firmware met voortgangsbalk. Het flash-proces kan geannuleerd worden. Dialog verdwijnt automatisch bij loskoppelen USB.
  6. Weergave: Kies Systeem (volgt telefoon), Licht of Donker. De app herstart met het gekozen thema.
  7. Onderaan staat het Buildnummer (timestamp van de build)

De key wordt opgeslagen in EncryptedSharedPreferences (AES256-GCM versleuteld) en overleeft app-herstarts. Bij geen key toont de hoofdapp "Geen HMAC sleutel - open Instellingen" en wordt er niet gescant.

Hoofdscherm UI

  • Tandwiel-icoon rechtsboven → opent instellingenscherm
  • HOLD-knop centrum → houd ingedrukt voor relais aan
  • Status tekst linksonder (14sp) → toont verbindingsstatus

Bouwen

Vereisten:

  • Android SDK (API 34+)
  • Java 21 (Java 26 werkt niet met Gradle)
  • 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

De APK staat na build in 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. Firmware compileren:

    pio run
    bash tools/copy_firmware.sh
    
  2. App op de telefoon installeren:

    cd android
    JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew installDebug
    
  3. Firmware flashen (kies één):

    Optie A — Via de app (aanbevolen, geen computer nodig na eerste installatie):

    • Zet de ESP in download-modus (houd reset ~2s in tot groene LED)
    • Sluit de ESP via USB-OTG aan op de telefoon
    • Open de app → Instellingen → Firmware Flashen
    • Dialog verdwijnt automatisch bij loskoppelen USB

    Optie B — Via computer:

    pio run -t upload
    
  4. Sleutel uitwisselen:

    • Sluit de ESP via USB-OTG aan op de telefoon (niet in download-modus)
    • Open de app → Instellingen → Sleutel Uitwisselen
    • De app leest de bestaande key uit of genereert een nieuwe
    • Toast: "Beveiligingssleutel gelezen"
  5. Gebruiken: De app scant automatisch, verbindt, en toont "Connected". Houd de HOLD-knop ingedrukt om het relais te schakelen.

Nieuwe ESP, bestaande telefoon

  • Sluit de nieuwe ESP via USB aan → app genereert nieuwe key (overschrijft vorige)
  • Flash eerst de firmware (download-modus → Firmware Flashen), daarna sleutel uitwisselen

Bestaande ESP, nieuwe telefoon

  • Sluit de ESP via USB-OTG aan op de nieuwe telefoon
  • De app leest de bestaande key uit de ESP en slaat lokaal op
  • Toast: "Beveiligingssleutel gelezen"

Projectstructuur

sylvia-touch-relay/
├── platformio.ini              # PlatformIO configuratie (ESP32-S3)
├── src/
│   └── main.cpp                # Firmware (BLE + HMAC + NVS + USB serial + sensor + relais)
├── tools/
│   └── copy_firmware.sh        # Kopieert firmware binaries naar app assets
├── android/
│   ├── build.gradle             # Root Gradle config
│   ├── settings.gradle          # Gradle settings
│   ├── gradle.properties
│   ├── gradlew                  # Gradle wrapper
│   ├── gradle/wrapper/
│   │   └── gradle-wrapper.properties
│   └── app/
│       ├── build.gradle         # App module config (esp32-flash-lib, minSdk 26)
│       └── src/main/
│           ├── AndroidManifest.xml
│           ├── assets/firmware/      # Gecompileerde firmware binaries
│           │   ├── bootloader.bin
│           │   ├── partitions.bin
│           │   ├── firmware.bin
│           │   └── boot_app0.bin
│           ├── java/com/eddy/atomrelay/
│           │   ├── MainActivity.kt        # BLE client + HMAC signing + write feedback
│           │   ├── SettingsActivity.kt    # Key exchange + firmware flash UI
│           │   ├── EspFlasher.kt          # Cancellable ESP32 firmware flasher via USB
│           │   ├── UsbSerialTransport.kt  # USB serial transport voor flasher
│           │   ├── UsbUtils.kt            # Gedeelde USB utility functions
│           │   ├── PrefsHelper.kt         # EncryptedSharedPreferences utility (AES256-GCM)
│           │   └── BuildConfig            # Buildnummer (timestamp)
│           └── res/
│               ├── drawable/
│               │   ├── ic_eye_open.xml        # Oog open icoon
│               │   ├── ic_eye_closed.xml      # Oog dicht icoon
│               │   └── ic_settings.xml        # Tandwiel icoon
│               ├── layout/
│               │   ├── activity_main.xml        # Hoofd UI (FrameLayout)
│               │   ├── activity_settings.xml    # Key exchange + flash UI
│               │   ├── dialog_waiting.xml       # Spinner dialog (vervangt ProgressDialog)
│               │   └── dialog_progress.xml      # Voortgangsbalk dialog (vervangt ProgressDialog)
│               └── values/strings.xml
├── .gitignore
└── README.md

Ontwerpbeslissingen

WRITE_TYPE_NO_RESPONSE (geen BLE write ACK)

De app gebruikt WRITE_TYPE_NO_RESPONSE (Write Without Response) in plaats van WRITE_TYPE_DEFAULT (Write With Response). 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 het relais en de app hangen in de ON-status. WRITE_TYPE_NO_RESPONSE verwijdert die latency en maakt directe ON→OFF sequenties mogelijk. De UI reflecteert de intentie van de gebruiker ("ingedrukt" / "losgelaten") in plaats van bevestigde relais-state — een aanvaardbare afweging voor een hold-to-activate interactie.

compileSdk/targetSdk 34 (niet 35 / Android 15)

De doelgroep bestaat uit lichamelijk gehandicapte gebruikers die zeer terughoudend zijn met het updaten van hun telefoon. Door te blijven op API 34 (Android 14) wordt de compatibiliteit met zoveel mogelijk apparaten gemaximaliseerd. Heroverweeg wanneer de gebruikersbasis voldoende is gemigreerd.

Thema-selector en DayNight

De app gebruikt Theme.AppCompat.DayNight in plaats van het eerder geforceerde Theme.AppCompat.Light. De instellingen bieden 3 opties: Systeem (volgt telefoon), Licht en Donker. Gekozen is voor de standaard Android-best-practice: geen eigen font-slider of contrast-kleurenkiezer, omdat Android's systeem-lettergrootte-instelling (sp-eenheden schalen automatisch) en hoog-contrast modus (Android 13+) al voldoen voor de doelgroep van visueel beperkte gebruikers. Een eigen implementatie zou het wiel opnieuw uitvinden en onnodige complexiteit toevoegen.

Technische notities

  • De Atom VoiceS3R gebruikt de ES8311 audio codec op G45/G0/G48/G4/G3/G17/G11, IR TX op G47, en de knop op G41. Deze pinnen zijn niet beschikbaar voor eigen gebruik.
  • G1 en G2 zitten op de HY2.0-4P Grove poort. G5-G8, G38, G39 zitten op de onderste expansie-header.
  • De firmware gebruikt geen M5Unified library — alleen Arduino core + ESP32 BLE + mbedtls. Dit maakt de serial output betrouwbaarder via USB CDC.
  • ARDUINO_USB_CDC_ON_BOOT=1 is ingeschakeld voor virtuele serial via USB.
  • De HMAC key wordt opgeslagen in NVS (Non-Volatile Storage) via de Arduino Preferences library. De key wordt niet meegecompileerd maar via USB provisioning door de Android app op de ESP gezet. Sleuteloverdracht werkt in beide richtingen: de app kan een key naar de ESP schrijven (nieuwe ESP) of een bestaande key uitlezen (bestaande ESP op nieuwe telefoon).
  • De app gebruikt WRITE_TYPE_NO_RESPONSE (Write Without Response) voor minimale latency. De ESP characteristic heeft PROPERTY_WRITE_NR zodat writes zonder ACK worden geaccepteerd. Dit maakt directe ON/OFF sequenties mogelijk zonder write queue. Zie Ontwerpbeslissingen voor de rationale.
  • De sequence counter start bij 1 (niet 0) om te voorkomen dat het eerste packet na connectie als replay wordt geweerd (0 <= 0 is true in de seq <= lastSeq check). De counter wordt gepersisteerd in EncryptedSharedPreferences — bij app-herstart (zonder BLE reconnect) gaat de counter verder waar hij gebleven was. Bij BLE reconnect wordt de in-memory counter teruggezet naar 1 (de ESP reset zijn lastSeq ook).
  • Voor meerdere ESP modules met verschillende keys: sluit elke ESP één voor één aan op de telefoon. De app genereert per ESP een eigen key en slaat deze op. De app ondersteunt momenteel slechts één key tegelijk (opgeslagen in EncryptedSharedPreferences). Bij wisselen van ESP: wis app data of sluit de nieuwe ESP aan (overschrijft vorige key).
  • De app gebruikt raw Android USB API (UsbDeviceConnection.bulkTransfer) in plaats van een externe serial library. Dit vermijdt compatibiliteitsproblemen met de ESP32-S3 USB-JTAG/serial controller die geen CDC get_status requests ondersteunt.
  • CDC control interface setup: line coding (115200 8N1, parity NONE) via SET_LINE_CODING request + DTR/RTS via SET_CONTROL_LINE_STATE. Dit activeert de data-flow op de ESP32-S3 CDC-ACM interface voordat commando's worden gestuurd.
  • Het buildnummer wordt via BuildConfig.BUILD_NUMBER in build.gradle gegenereerd als timestamp (yyyyMMddHHmmss). Elke build produceert een uniek buildnummer dat onderaan het instellingenscherm staat.
  • minSdk is 26 (Android 8.0). Android 7 en lager worden niet ondersteund.
  • ProgressDialog is vervangen door eigen AlertDialog layouts (dialog_waiting.xml met spinner, dialog_progress.xml met ProgressBar). ProgressDialog is deprecated sinds API 26.
  • De HMAC key wordt in de app opgeslagen via EncryptedSharedPreferences (AES256-GCM) via PrefsHelper. Dit versleutelt de key at-rest, in tegenstelling tot gewone SharedPreferences waarbij waarden plaintext op schijf staan.
  • Bij geweigerde BLE-permissies wordt de HOLD-knop uitgeschakeld en toont de app "Permissies geweigerd — open Instellingen" met een link naar de systeem-instellingen.
  • De BLE-scan wordt in onPause betrouwbaar gestopt via handler.removeCallbacks(scanRunnable), wat voorkomt dat scans doordraaien op de achtergrond.
  • Het firmware flash-proces is annuleerbaar via coroutine Job cancellation. De annuleerknop en onDestroy roepen flasherJob?.cancel() aan, wat de CancellationException doorgeeft aan de actieve coroutine. Checkpoints via ensureActive() in de flash-lus stoppen het proces direct.
  • USB systeem-broadcasts (ACTION_USB_DEVICE_ATTACHED/DETACHED) worden geregistreerd met RECEIVER_EXPORTED voor compatibiliteit met Android 14+ (API 34). De custom ACTION_USB_PERMISSION broadcast gebruikt RECEIVER_NOT_EXPORTED (app-intern).
  • De app gebruikt Theme.AppCompat.DayNight in plaats van Theme.AppCompat.Light. Het thema wordt gekozen via Instellingen → Weergave: Systeem (volgt telefoon via AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), Licht (MODE_NIGHT_NO) of Donker (MODE_NIGHT_YES). Het thema wordt toegepast in elke onCreate vóór super.onCreate(). Bij wijziging in de Instellingen wordt de Activity herstart via setDefaultNightMode. De onResume-methode detecteert of het thema gewijzigd is sinds de Activity voor het laatst zichtbaar was en herstart dan.

Licentie

Dit project is gelicentieerd onder de GNU General Public License v3.0 of later (GPL-3.0-or-later).

Deze licentie is vereist omdat de app gebruik maakt van esp32-flash-lib (GPL-3.0), een library voor het flashen van ESP32 firmware. De GPL-3.0 copyleft-clausule bepaalt dat een werk dat aan een GPL-3.0 werk linkt zelf ook onder GPL-3.0 (of een compatibele licentie) moet worden gedistribueerd.

Zie het LICENSE bestand voor de volledige licentietekst.