chore: remove research docs from git tracking, add to .gitignore
This commit is contained in:
parent
0833327cea
commit
15498d025a
14 changed files with 9 additions and 2778 deletions
|
|
@ -1,127 +0,0 @@
|
|||
# ESP32-PICO-D4 (M5Stack Atom Lite) Port — Design Spec
|
||||
|
||||
> **Goal:** Port the Sylvia Touch Relay project from ESP32-S3-Pico (Atom VoiceS3R) to ESP32-PICO-D4 (Atom Lite), update the Android app for CDC-class USB detection, compile, bundle, and deploy via ADB wireless.
|
||||
|
||||
## Current State
|
||||
|
||||
The project targets the M5Stack Atom VoiceS3R with ESP32-S3-PICO-1-N8R8 (8MB Flash, 8MB PSRAM). The Android app filters USB devices by Espressif vendor ID (`0x303A`) for key exchange and firmware flashing. The `esp32-flash-lib` handles DTR/RTS via the `SerialTransport` interface implemented by `UsbSerialTransport`.
|
||||
|
||||
## Target Hardware
|
||||
|
||||
M5Stack Atom Lite with ESP32-PICO-D4:
|
||||
- 4MB Flash (no PSRAM)
|
||||
- FTDI FT2232C/D USB-to-UART bridge (VID `0x0403`) — appears as `/dev/ttyUSB0`
|
||||
- SK6812 addressable RGB LED on GPIO27
|
||||
- Relay on GPIO32 (G32)
|
||||
- Touch sensor on GPIO33 (G33) — open-drain type, needs pullup
|
||||
|
||||
## Design Decisions
|
||||
|
||||
### 1. Side Branch & Git Worktree
|
||||
|
||||
Create branch `esp32picod4` via git worktree at `.worktrees/esp32picod4` to isolate the port from the main S3 variant.
|
||||
|
||||
### 2. Firmware (`platformio.ini` + `src/main.cpp`)
|
||||
|
||||
**New PlatformIO environment `m5stack-atom`:**
|
||||
|
||||
| Setting | Old (S3) | New (PICO-D4) | Reason |
|
||||
|---------|----------|---------------|--------|
|
||||
| `board` | `esp32-s3-devkitc-1` | `m5stack-atom` | PlatformIO board ID for M5Stack Atom Lite |
|
||||
| `board_build.arduino.memory_type` | `qio_opi` | removed | Not needed for PICO-D4 |
|
||||
| `-DESP32S3` | present | `-DESP32` | Correct chip family |
|
||||
| `-DBOARD_HAS_PSRAM` | present | removed | Atom Lite has no PSRAM |
|
||||
| `-mfix-esp32-psram-cache-issue` | present | removed | Only needed with PSRAM |
|
||||
| `-DARDUINO_USB_CDC_ON_BOOT=1` | present | removed | No native USB on PICO-D4 |
|
||||
| `-DARDUINO_USB_MODE=1` | present | removed | No native USB |
|
||||
| `upload_port` | `/dev/ttyACM0` | `/dev/ttyUSB0` | FTDI serial device |
|
||||
| `lib_deps` | (empty) | `fastled/FastLED@^3.7` | SK6812 Neopixel library |
|
||||
|
||||
**Pin definitions:**
|
||||
|
||||
| Constant | Old | New | Notes |
|
||||
|----------|-----|-----|-------|
|
||||
| `SENSOR_PIN` | 38 | 33 | G33, open-drain, `INPUT_PULLUP` unchanged |
|
||||
| `RELAY_PIN` | 1 | 32 | G32 |
|
||||
| `LED_PIN` | 27 | 27 | SK6812 Neopixel — replaces `digitalWrite` with FastLED API |
|
||||
|
||||
**BLE device name:** `"SyldaServer"` (was `"AtomS3R-Relay"`)
|
||||
|
||||
**Serial banner:** `"Atom Lite BLE Relay"` (was `"Atom VoiceS3R BLE Relay"`)
|
||||
|
||||
**LED code change:** Replace `digitalWrite(LED_PIN, HIGH/LOW)` with FastLED:
|
||||
- Include `FastLED.h`
|
||||
- Define `#define NUM_LEDS 1`
|
||||
- `CRGB leds[NUM_LEDS]`
|
||||
- `FastLED.addLeds<SK6812, LED_PIN, GRB>(leds, NUM_LEDS)` in setup
|
||||
- Startup blink: 3x green (`leds[0] = CRGB::Green; FastLED.show()`)
|
||||
|
||||
No other firmware logic changes — HMAC key exchange, BLE relay control, touch sensor logic remain identical.
|
||||
|
||||
### 3. Android USB Detection
|
||||
|
||||
**Problem:** The Atom Lite's FTDI bridge presents VID `0x0403`, not Espressif's `0x303A`. The app currently filters on VID and won't detect the device.
|
||||
|
||||
**Solution:** Replace VID (`0x303A`) filtering with CDC-ACM class filtering (interface class `0x0A`).
|
||||
|
||||
**Changes in `UsbUtils.kt`:**
|
||||
- Add `hasCdcDataInterface(device: UsbDevice): Boolean` — iterates interfaces, returns true if any has `interfaceClass == 0x0A`
|
||||
|
||||
**Changes in `SettingsActivity.kt`:**
|
||||
- Remove `ESP_VENDOR_ID` constant
|
||||
- Replace all `device?.vendorId == ESP_VENDOR_ID` checks with `UsbUtils.hasCdcDataInterface(device)`
|
||||
|
||||
This detects all CDC-ACM serial devices including:
|
||||
- FTDI FT2232C/D (Atom Lite)
|
||||
- CP210x (some Atom Lite revisions)
|
||||
- ESP32-S3 native USB (current hardware)
|
||||
- CH340/CH9102 (other Atom variants)
|
||||
|
||||
### 4. DTR/RTS — No Changes Needed
|
||||
|
||||
**Research findings:**
|
||||
|
||||
The `esp32-flash-lib`'s `EspFlasherProtocol.enterBootLoader()` implements the standard DTR/RTS download-mode sequence:
|
||||
|
||||
```
|
||||
setControlLines(true, false) // DTR=High → EN released, RTS=Low → GPIO0=Low (download mode)
|
||||
delayMS(100)
|
||||
setControlLines(false, true) // DTR=Low → reset pulse via capacitor, RTS=High → GPIO0=High
|
||||
delayMS(100)
|
||||
setControlLines(true, false) // DTR=High → release reset, RTS=Low → GPIO0=Low → boot in download mode
|
||||
```
|
||||
|
||||
The `UsbSerialTransport.setControlLines()` implementation sends `CDC_SET_CONTROL_LINE_STATE` control transfers with correct DTR/RTS bit flags. This works for FTDI, CP2104, and native USB CDC-ACM.
|
||||
|
||||
The `SettingsActivity.setupCdcControl()` sets DTR|RTS both HIGH for normal serial operation (key exchange), which is correct for all bridge types.
|
||||
|
||||
**Verdict: No changes to `EspFlasher.kt`, `UsbSerialTransport.kt`, or key exchange logic.**
|
||||
|
||||
### 5. Build, Bundle & Deploy
|
||||
|
||||
1. `pio run -e m5stack-atom` to compile firmware
|
||||
2. `tools/copy_firmware.sh` to copy binaries to `android/app/src/main/assets/firmware/` (environment dir is now `m5stack-atom`)
|
||||
3. `./gradlew assembleDebug` in `android/` to build APK
|
||||
4. ADB wireless to connect phone (`adb-wireless` skill)
|
||||
5. `adb install -r app-debug.apk` to deploy
|
||||
|
||||
## Files Changed
|
||||
|
||||
| File | Change |
|
||||
|------|--------|
|
||||
| `platformio.ini` | New environment `m5stack-atom` replacing S3 config |
|
||||
| `src/main.cpp` | New pin definitions, BLE name, FastLED LED code |
|
||||
| `android/.../UsbUtils.kt` | Add `hasCdcDataInterface()` |
|
||||
| `android/.../SettingsActivity.kt` | VID filter → CDC class filter |
|
||||
| `tools/copy_firmware.sh` | Environment name `m5stack-atoms3r` → `m5stack-atom` |
|
||||
|
||||
## Files Not Changed
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `EspFlasher.kt` | DTR/RTS handled by library |
|
||||
| `UsbSerialTransport.kt` | Correct implementation |
|
||||
| `MainActivity.kt` | No BLE changes beyond device name (auto-discovered) |
|
||||
| `PrefsHelper.kt` | No changes |
|
||||
| `AndroidManifest.xml` | No changes |
|
||||
| `build.gradle` | No dependency changes |
|
||||
|
|
@ -1,172 +0,0 @@
|
|||
# BLE Refactor: BleRelayManager extractie
|
||||
|
||||
**Datum:** 2026-07-29
|
||||
**Status:** goedgekeurd
|
||||
|
||||
## Doel
|
||||
|
||||
Extractie van alle BLE-logica uit `MainActivity.kt` naar een aparte `BleRelayManager` klasse. MainActivity wordt verantwoordelijk voor UI en lifecycle only.
|
||||
|
||||
## Scope
|
||||
|
||||
- **In:** BLE scan, GATT connectie, UUID verificatie, HMAC relay commands, key/seq management, auto-retry logica
|
||||
- **Uit:** UI binding (views, touch listeners, string resources), lifecycle (onCreate/onResume/etc), permissions result handling, theme switching
|
||||
- **Niet aangeraakt:** `PrefsHelper`, `SettingsActivity`, `EspFlasher`, `UsbSerialTransport`, firmware
|
||||
|
||||
## Nieuwe bestanden
|
||||
|
||||
### `BleStatus.kt` — enum + callback interface
|
||||
|
||||
```kotlin
|
||||
enum class BleStatus {
|
||||
Scanning,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected,
|
||||
NoKey,
|
||||
WrongDevice,
|
||||
ServiceNotFound,
|
||||
ServiceError,
|
||||
CommError,
|
||||
DeviceNotFound,
|
||||
BluetoothDisabled,
|
||||
PermissionsDenied
|
||||
}
|
||||
|
||||
interface BleCallbacks {
|
||||
fun onStatusChanged(status: BleStatus)
|
||||
}
|
||||
```
|
||||
|
||||
### `BleRelayManager.kt` — publieke API
|
||||
|
||||
```kotlin
|
||||
class BleRelayManager(
|
||||
private val context: Context,
|
||||
private val callbacks: BleCallbacks
|
||||
) {
|
||||
fun startScan()
|
||||
fun stopScan()
|
||||
fun sendRelayCommand(cmd: Byte)
|
||||
fun disconnect()
|
||||
fun updateKeys() // herlaadt keys na settings key exchange
|
||||
fun resetTriedAddresses() // wist triedAddresses (bij onResume)
|
||||
fun hasPermissions(): Boolean
|
||||
fun getRequiredPermissions(): Array<String>
|
||||
fun destroy() // cleanup handler, stop scan, close gatt
|
||||
}
|
||||
```
|
||||
|
||||
### Interne componenten (private)
|
||||
|
||||
1. **State:** `bluetoothGatt`, `relayCharacteristic`, `hmacKey`, `deviceUuid`, `seq`, `triedAddresses`, `isScanning`, `scanTimedOut`, `scanTimeoutRetries`
|
||||
2. **Handler:** `Handler(Looper.getMainLooper())` voor scan scheduling en timeouts
|
||||
3. **ScanCallback:** scan op `SERVICE_UUID`, skip `triedAddresses`, bij match → `stopScan()` + `connectToDevice()`
|
||||
4. **GattCallback:** connectie → check key → discoverServices → requestMtu → read UUID char → verify → enable notifications. Bij disconnect: auto-retry na delay.
|
||||
5. **HMAC:** `hexStringToBytes()`, `hmacSha256()`, `buildPacket()` — ongewijzigd verplaatst
|
||||
6. **Key loading:** `loadState()` leest via `PrefsHelper.getPrefs(context)`
|
||||
|
||||
**Companion constants** (ongewijzigd): `SERVICE_UUID`, `CHARACTERISTIC_UUID`, `UUID_CHAR_UUID`, `CCCD_UUID`, `DEVICE_NAME`, `HMAC_PKT_SIZE`, `HMAC_LEN`, `SCAN_TIMEOUT_MS`, `RECONNECT_DELAY_MS`, `MAX_SCAN_RETRIES`
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MA as MainActivity
|
||||
participant BRM as BleRelayManager
|
||||
participant ESP as ESP32-S3
|
||||
|
||||
MA->>BRM: startScan()
|
||||
BRM->>BRM: loadState() via PrefsHelper
|
||||
BRM-->>MA: onStatusChanged(Scanning)
|
||||
BRM->>ESP: BLE scan (filter SERVICE_UUID)
|
||||
ESP-->>BRM: scan result
|
||||
BRM->>BRM: skip triedAddresses?
|
||||
BRM->>ESP: connectGatt()
|
||||
BRM-->>MA: onStatusChanged(Connecting)
|
||||
ESP-->>BRM: STATE_CONNECTED
|
||||
BRM->>BRM: check hmacKey != null
|
||||
BRM->>ESP: discoverServices → requestMtu(256)
|
||||
BRM->>ESP: readCharacteristic(UUID_CHAR)
|
||||
ESP-->>BRM: UUID value
|
||||
BRM->>BRM: verify against stored deviceUuid
|
||||
BRM->>ESP: setCharacteristicNotification + writeDescriptor(CCCD)
|
||||
BRM-->>MA: onStatusChanged(Connected)
|
||||
|
||||
MA->>BRM: sendRelayCommand(0x01)
|
||||
BRM->>BRM: buildPacket(cmd, hmacKey, seq)
|
||||
BRM->>ESP: writeCharacteristic (NO_RESPONSE)
|
||||
BRM->>Prefs: save seq
|
||||
|
||||
ESP-->>BRM: STATE_DISCONNECTED
|
||||
BRM-->>MA: onStatusChanged(Disconnected)
|
||||
BRM->>BRM: handler.postDelayed(startScan, 2s)
|
||||
```
|
||||
|
||||
## MainActivity — wat blijft
|
||||
|
||||
| Component | Blijft in MainActivity |
|
||||
|---|---|
|
||||
| `onCreate` | View binding, settings button, `BleRelayManager` instantiatie |
|
||||
| `onResume` | Theme check, `manager.resetTriedAddresses()`, `manager.startScan()` of toon NoKey |
|
||||
| `onPause` | `manager.stopScan()` |
|
||||
| `onDestroy` | `manager.destroy()` |
|
||||
| `onActivityResult` | Bij RESULT_SCAN: `manager.disconnect()`, `manager.updateKeys()`, `manager.resetTriedAddresses()`, `manager.startScan()` |
|
||||
| `onRequestPermissionsResult` | Bij granted: `manager.startScan()`; bij denied: UI update |
|
||||
| `setupRelayButton()` | Touch listener → `manager.sendRelayCommand(...)` |
|
||||
| `setupRelay2Button()` | Touch listener → `manager.sendRelayCommand(...)` |
|
||||
| Status mapping | `onStatusChanged` → `statusText.text = getString(R.string.status_*)` + knop enabled/disabled |
|
||||
|
||||
### Status → UI mapping
|
||||
|
||||
```kotlin
|
||||
callbacks = object : BleCallbacks {
|
||||
override fun onStatusChanged(status: BleStatus) {
|
||||
when (status) {
|
||||
BleStatus.Connected -> {
|
||||
statusText.text = getString(R.string.status_connected)
|
||||
setupRelayButton()
|
||||
setupRelay2Button()
|
||||
relay1Button.isEnabled = true
|
||||
relay2Button.isEnabled = true
|
||||
}
|
||||
BleStatus.Disconnected -> {
|
||||
statusText.text = getString(R.string.status_connecting)
|
||||
relay1Button.isEnabled = false
|
||||
relay2Button.isEnabled = false
|
||||
}
|
||||
BleStatus.DeviceNotFound -> {
|
||||
// retry knop op relay1Button (huidig gedrag behouden)
|
||||
relay1Button.setOnTouchListener(null)
|
||||
relay2Button.setOnTouchListener(null)
|
||||
relay1Button.setOnClickListener(null)
|
||||
relay2Button.setOnClickListener(null)
|
||||
relay1Button.text = getString(R.string.button_retry_scan)
|
||||
relay2Button.text = getString(R.string.button_hold)
|
||||
relay2Button.isEnabled = false
|
||||
relay1Button.isEnabled = true
|
||||
relay1Button.setOnClickListener {
|
||||
setupRelayButton()
|
||||
setupRelay2Button()
|
||||
manager.startScan()
|
||||
}
|
||||
}
|
||||
else -> statusText.text = getString(R.string.status_scanning) // fallback
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Risico's & mitigatie
|
||||
|
||||
| Risico | Mitigatie |
|
||||
|---|---|
|
||||
| Race condition bij snelle key-update + scan | `updateKeys()` stopt scan vóór herladen |
|
||||
| Context leak via Handler | `destroy()` roept `handler.removeCallbacksAndMessages(null)` |
|
||||
| GattCallback refs naar oude Activity | Manager instantieert per Activity, geen singleton |
|
||||
| Touch listeners verwijzen naar verouderde manager | `setupRelay*Button()` opnieuw aanroepen bij statuswissel (huidig patroon) |
|
||||
|
||||
## Verificatie
|
||||
|
||||
1. Build: `./gradlew assembleDebug` moet slagen
|
||||
2. Lint: `./gradlew lint` geen nieuwe warnings
|
||||
3. Functioneel: APK deployen via ADB, BLE scan → connect → UUID verify → relay knoppen ON/OFF
|
||||
4. Randgevallen: app herstarten (keys behouden), settings key exchange → rescan, timeout + retry, disconnect + auto-reconnect, permissie deny/accept
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# Device UUID Pairing — Design
|
||||
|
||||
**Datum:** 2026-07-29
|
||||
**Status:** goedgekeurd
|
||||
|
||||
## Doel
|
||||
|
||||
Voorkomen dat twee ESP-telefoon paren in dezelfde ruimte met elkaars ESP verbinden. Elk ESP-telefoon paar deelt een unieke UUID die tijdens key exchange wordt uitgewisseld.
|
||||
|
||||
## Flow
|
||||
|
||||
### Verse ESP (geen UUID, geen HMAC key)
|
||||
|
||||
1. ESP boot, genereert random UUID (16 bytes via `esp_random()`), slaat op in NVS
|
||||
2. ESP start BLE advertising als `"SylDa_Server"` (UUID nog onbekend bij app)
|
||||
3. App opent Settings → Key Exchange
|
||||
4. App stuurt `KEY?` over USB CDC, ESP antwoordt `NO_KEY`
|
||||
5. App genereert random 32-byte HMAC key + 16-byte UUID
|
||||
6. App stuurt `SET_KEY:<64 hex>` → ESP slaat op → `OK`
|
||||
7. App stuurt `SET_ID:<32 hex>` → ESP slaat op → `OK`
|
||||
8. App slaat beide op in EncryptedSharedPreferences
|
||||
9. App keert terug naar main, start scan
|
||||
|
||||
### Bestaand paar (beide hebben UUID + HMAC key)
|
||||
|
||||
1. App stuurt `KEY?`, ESP antwoordt `DEVICE_ID:<32 hex>\nHMAC_KEY:<64 hex>`
|
||||
2. App slaat beide op
|
||||
3. Klaar
|
||||
|
||||
### BLE connectie
|
||||
|
||||
1. App scant op BLE service UUID `4fafc201-...` (niet op naam)
|
||||
2. Bij elke gevonden device: connect, discover services
|
||||
3. Lees UUID characteristic (READ-only)
|
||||
4. UUID match → zet relay knoppen actief
|
||||
5. UUID mismatch → disconnect, voeg address toe aan `triedAddresses`, herstart scan
|
||||
6. Relay commands blijven HMAC-beveiligd zoals voorheen
|
||||
|
||||
## Componenten
|
||||
|
||||
### Firmware (`src/main.cpp`)
|
||||
|
||||
| Item | Details |
|
||||
|------|---------|
|
||||
| NVS key `"uuid"` | 16 bytes, namespace `"hmac"` |
|
||||
| UUID generatie | `esp_random()` per byte, alleen bij eerste boot |
|
||||
| BLE naam | `"SylDa_Server"` |
|
||||
| UUID characteristic | Nieuwe READ-only characteristic, los UUID |
|
||||
| `KEY?` response | `DEVICE_ID:32HEX\nHMAC_KEY:64HEX` of `NO_KEY` |
|
||||
| `SET_ID:32HEX` | Nieuw commando, slaat UUID in NVS |
|
||||
|
||||
### Android — PrefsHelper
|
||||
|
||||
- `PREF_UUID = "device_uuid"` (EncryptedSharedPreferences)
|
||||
|
||||
### Android — SettingsActivity
|
||||
|
||||
- `readKeyFromEsp()`: parse `DEVICE_ID:` lijn
|
||||
- `KeyResult.Success` krijgt `deviceUuid` veld
|
||||
- Bij `NO_KEY`: app genereert UUID + stuurt `SET_KEY` en `SET_ID`
|
||||
|
||||
### Android — MainActivity
|
||||
|
||||
- `loadState()`: laadt UUID
|
||||
- `startScan()`: `ScanFilter` op service UUID
|
||||
- `onScanResult`: connect bij elke match (naam genegeerd)
|
||||
- `gattCallback.onMtuChanged`: lees UUID characteristic, verify
|
||||
- UUID mismatch: disconnect, `triedAddresses.add(address)`, herstart scan
|
||||
Loading…
Reference in a new issue