# ESP32-PICO-D4 (M5Stack Atom Lite) Port — Implementation Plan > **For 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:** Port the Sylvia Touch Relay project from ESP32-S3-Pico to ESP32-PICO-D4 (Atom Lite) with relay on G32, touch on G33 (pullup), SK6812 LED on G27, CDC-class USB detection in the Android app, and deploy via ADB wireless. **Architecture:** Create side branch `esp32picod4` via git worktree. Replace platformio.ini S3 config with `m5stack-atom` board + FastLED library. Update pin definitions and LED code in main.cpp. Replace Android USB VID filter with CDC-ACM class filter. No DTR/RTS changes needed — the esp32-flash-lib handles it correctly. **Tech Stack:** C++/Arduino (FastLED), PlatformIO (espressif32@6.7.0), Android/Kotlin (AGP 8.5.2, Gradle 8.14), ADB (wireless debugging) --- ### Task 1: Create sidebranch using git worktree **Files:** - Create: `.worktrees/esp32picod4/` (git worktree directory) - [ ] **Step 1: Create and switch to sidebranch in worktree** ```bash git worktree add -b esp32picod4 .worktrees/esp32picod4 ``` - [ ] **Step 2: Verify branch is active** ```bash git -C .worktrees/esp32picod4 branch --show-current ``` Expected: `esp32picod4` --- ### Task 2: Update PlatformIO configuration for PICO-D4 **Files:** - Modify: `platformio.ini` **Worktree path:** `.worktrees/esp32picod4/platformio.ini` - [ ] **Step 1: Replace entire platformio.ini content** ```ini [env:m5stack-atom] platform = espressif32@6.7.0 board = m5stack-atom framework = arduino build_flags = -DESP32 -DCORE_DEBUG_LEVEL=5 lib_deps = fastled/FastLED@^3.7.0 upload_port = /dev/ttyUSB0 upload_speed = 115200 ``` - [ ] **Step 2: Verify m5stack-atom board exists in PlatformIO** ```bash pio boards --json-output 2>/dev/null | python3 -c "import json,sys; boards=[b['id'] for b in json.load(sys.stdin) if 'm5stack-atom' in b['id']]; print('\n'.join(boards) if boards else 'NOT FOUND')" ``` Expected: `m5stack-atom` (or similar variant) - [ ] **Step 3: Commit platformio.ini** ```bash git -C .worktrees/esp32picod4 add platformio.ini git -C .worktrees/esp32picod4 commit -m "feat: switch platformio config to m5stack-atom (ESP32-PICO-D4)" ``` --- ### Task 3: Update firmware for Atom Lite (pins, LED, BLE name) **Files:** - Modify: `src/main.cpp` **Worktree path:** `.worktrees/esp32picod4/src/main.cpp` - [ ] **Step 1: Replace pin definitions (lines 10-12)** Replace: ```cpp #define SENSOR_PIN 38 #define RELAY_PIN 1 #define LED_PIN 46 ``` With: ```cpp #include #define SENSOR_PIN 33 #define RELAY_PIN 32 #define LED_PIN 27 #define NUM_LEDS 1 CRGB leds[NUM_LEDS]; ``` - [ ] **Step 2: Replace setup() LED init and blink code (lines 139-149)** Replace: ```cpp pinMode(SENSOR_PIN, INPUT_PULLUP); pinMode(RELAY_PIN, OUTPUT); pinMode(LED_PIN, OUTPUT); for (int i = 0; i < 3; i++) { digitalWrite(LED_PIN, HIGH); delay(200); digitalWrite(LED_PIN, LOW); delay(200); } Serial.println("LED blink done, starting BLE..."); ``` With: ```cpp pinMode(SENSOR_PIN, INPUT_PULLUP); pinMode(RELAY_PIN, OUTPUT); FastLED.addLeds(leds, NUM_LEDS); FastLED.setBrightness(32); for (int i = 0; i < 3; i++) { leds[0] = CRGB::Green; FastLED.show(); delay(200); leds[0] = CRGB::Black; FastLED.show(); delay(200); } Serial.println("LED blink done, starting BLE..."); ``` - [ ] **Step 3: Replace Serial banner and BLE device name** Replace (line 125): ```cpp Serial.println("=== SylDa BLE Relay ==="); ``` With: ```cpp Serial.println("=== Atom Lite BLE Relay ==="); ``` Replace (line 151): ```cpp BLEDevice::init("SylDa_Server"); ``` With: ```cpp BLEDevice::init("SyldaServer"); ``` Replace (line 174): ```cpp Serial.println("BLE: advertising started as 'SylDa_Server'"); ``` With: ```cpp Serial.println("BLE: advertising started as 'SyldaServer'"); ``` - [ ] **Step 4: Verify main.cpp has no remaining S3-specific references** ```bash grep -n "S3\|Atom Voice\|46\|G38\|G1\b" .worktrees/esp32picod4/src/main.cpp ``` Expected: no output (no matches) - [ ] **Step 5: Commit main.cpp** ```bash git -C .worktrees/esp32picod4 add src/main.cpp git -C .worktrees/esp32picod4 commit -m "feat: port firmware to Atom Lite (G32 relais, G33 touch, G27 SK6812 LED, SyldaServer BLE)" ``` --- ### Task 4: Update copy_firmware.sh for new environment name **Files:** - Modify: `tools/copy_firmware.sh` **Worktree path:** `.worktrees/esp32picod4/tools/copy_firmware.sh` - [ ] **Step 1: Change environment directory name (line 4)** Replace: ```bash BINARY_DIR="$(cd "$(dirname "$0")/.." && pwd)/.pio/build/s3lite-server" ``` With: ```bash BINARY_DIR="$(cd "$(dirname "$0")/.." && pwd)/.pio/build/m5stack-atom" ``` - [ ] **Step 2: Commit** ```bash git -C .worktrees/esp32picod4 add tools/copy_firmware.sh git -C .worktrees/esp32picod4 commit -m "chore: update copy_firmware.sh for m5stack-atom environment" ``` --- ### Task 5: Add CDC-class USB detection to UsbUtils.kt **Files:** - Modify: `android/app/src/main/java/com/eddy/atomrelay/UsbUtils.kt` **Worktree path:** `.worktrees/esp32picod4/android/app/src/main/java/com/eddy/atomrelay/UsbUtils.kt` - [ ] **Step 1: Add hasCdcDataInterface function** After the `findBulkEndpoint` function (before the closing `}` of `object UsbUtils`), add: ```kotlin fun hasCdcDataInterface(device: UsbDevice): Boolean { for (i in 0 until device.interfaceCount) { if (device.getInterface(i).interfaceClass == CDC_DATA_INTERFACE_CLASS) { return true } } return false } ``` - [ ] **Step 2: Commit** ```bash git -C .worktrees/esp32picod4 add android/app/src/main/java/com/eddy/atomrelay/UsbUtils.kt git -C .worktrees/esp32picod4 commit -m "feat: add CDC-class USB detection for universal bridge support" ``` --- ### Task 6: Replace VID filter with CDC-class filter in SettingsActivity.kt **Files:** - Modify: `android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt` **Worktree path:** `.worktrees/esp32picod4/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt` - [ ] **Step 1: Remove ESP_VENDOR_ID constant (line 39)** Delete: ```kotlin private const val ESP_VENDOR_ID = 0x303a ``` - [ ] **Step 2: Replace VID check in usbAttachedReceiver (line 67)** Replace: ```kotlin if (device?.vendorId == ESP_VENDOR_ID) { ``` With: ```kotlin if (device != null && UsbUtils.hasCdcDataInterface(device)) { ``` - [ ] **Step 3: Replace VID check in usbPermissionReceiver (line 85)** Replace: ```kotlin val device = usbManager.deviceList.values.firstOrNull { it.vendorId == ESP_VENDOR_ID } ``` With: ```kotlin val device = usbManager.deviceList.values.firstOrNull { UsbUtils.hasCdcDataInterface(it) } ``` - [ ] **Step 4: Replace VID check in usbDetachedReceiver (line 101)** Replace: ```kotlin if (device?.vendorId == ESP_VENDOR_ID) { ``` With: ```kotlin if (device != null && UsbUtils.hasCdcDataInterface(device)) { ``` - [ ] **Step 5: Replace VID check in startKeyExchange (line 208)** Replace: ```kotlin val device = usbManager.deviceList.values.firstOrNull { it.vendorId == ESP_VENDOR_ID } ``` With: ```kotlin val device = usbManager.deviceList.values.firstOrNull { UsbUtils.hasCdcDataInterface(it) } ``` - [ ] **Step 6: Replace VID check in startFlashing (line 256)** Replace: ```kotlin val device = usbManager.deviceList.values.firstOrNull { it.vendorId == ESP_VENDOR_ID } ``` With: ```kotlin val device = usbManager.deviceList.values.firstOrNull { UsbUtils.hasCdcDataInterface(it) } ``` - [ ] **Step 7: Verify no remaining ESP_VENDOR_ID references** ```bash grep -n "ESP_VENDOR_ID\|vendorId.*==" .worktrees/esp32picod4/android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt ``` Expected: no output - [ ] **Step 8: Commit** ```bash git -C .worktrees/esp32picod4 add android/app/src/main/java/com/eddy/atomrelay/SettingsActivity.kt git -C .worktrees/esp32picod4 commit -m "feat: replace USB VID filter with CDC-ACM class detection" ``` --- ### Task 7: Compile firmware **Files:** N/A (build artifacts in `.pio/build/m5stack-atom/`) **Work tree path:** `.worktrees/esp32picod4/` - [ ] **Step 1: Clean and compile** ```bash pio run -d .worktrees/esp32picod4 -e m5stack-atom -t clean pio run -d .worktrees/esp32picod4 -e m5stack-atom ``` Expected: `SUCCESS` at end of output - [ ] **Step 2: Verify build output files** ```bash ls -la .worktrees/esp32picod4/.pio/build/m5stack-atom/firmware.bin \ .worktrees/esp32picod4/.pio/build/m5stack-atom/bootloader.bin \ .worktrees/esp32picod4/.pio/build/m5stack-atom/partitions.bin ``` Expected: all 3 files exist with non-zero size --- ### Task 8: Copy firmware binaries to Android app assets **Files:** - Modify: `android/app/src/main/assets/firmware/` (destination) **Worktree path:** `.worktrees/esp32picod4/` - [ ] **Step 1: Run copy script** ```bash bash .worktrees/esp32picod4/tools/copy_firmware.sh ``` Expected: lists 4 files (bootloader.bin, partitions.bin, firmware.bin, boot_app0.bin) - [ ] **Step 2: Verify boot_app0.bin is also present** ```bash ls -la .worktrees/esp32picod4/android/app/src/main/assets/firmware/boot_app0.bin ``` Expected: file exists - [ ] **Step 3: Commit binary assets** ```bash git -C .worktrees/esp32picod4 add android/app/src/main/assets/firmware/ git -C .worktrees/esp32picod4 commit -m "feat: add Atom Lite firmware binaries to Android assets" ``` --- ### Task 9: Build Android APK **Files:** N/A (build output `android/app/build/outputs/apk/debug/app-debug.apk`) **Worktree path:** `.worktrees/esp32picod4/android/` - [ ] **Step 1: Build debug APK** ```bash JAVA_HOME=/home/linuxbrew/.linuxbrew/opt/openjdk@21/libexec ./gradlew assembleDebug ``` Expected: `BUILD SUCCESSFUL` - [ ] **Step 2: Verify APK exists** ```bash ls -la app/build/outputs/apk/debug/app-debug.apk ``` Expected: file exists with size > 1MB --- ### Task 10: Connect Android phone via ADB wireless **Files:** N/A (ADB system tool) - [ ] **Step 1: Load adb-wireless skill** Use the `adb-wireless` skill to establish a wireless ADB connection to the Android phone. - [ ] **Step 2: Verify ADB connection** ```bash adb devices ``` Expected: `:5555` listed as `device` --- ### Task 11: Install APK on phone **Files:** N/A (app deployment) - [ ] **Step 1: Install APK** ```bash adb install -r .worktrees/esp32picod4/android/app/build/outputs/apk/debug/app-debug.apk ``` Expected: `Success` - [ ] **Step 2: Launch the app** ```bash adb shell am start -n com.eddy.atomrelay/.MainActivity ``` --- ### Task 12: Flash firmware to Atom Lite via Android app **Files:** N/A (manual device action) - [ ] **Step 1: Connect Atom Lite via USB-OTG to the phone** - [ ] **Step 2: Open the app, tap settings gear → "Firmware Flashen"** - [ ] **Step 3: If app doesn't auto-detect, unplug/replug the Atom Lite while the dialog is open** - [ ] **Step 4: Wait for flash to complete (progress bar reaches 100%)** - [ ] **Step 5: After flash, disconnect USB — the Atom Lite should reboot with green LED blink 3x** --- ### Task 13: Provision HMAC key via app **Files:** N/A (manual device action) - [ ] **Step 1: Reconnect Atom Lite via USB-OTG to phone (NOT in download mode — just power on normally)** - [ ] **Step 2: Open app → Settings → "Sleutel Uitwisselen"** - [ ] **Step 3: Wait for toast "Beveiligingssleutel gelezen" or provisioning** - [ ] **Step 4: If key was newly generated, verify Serial monitor shows "HMAC key stored in NVS"** --- ### Task 14: Verify BLE connection and relay control **Files:** N/A (manual verification) - [ ] **Step 1: Power on the Atom Lite (with relay and touch sensor connected)** - [ ] **Step 2: Open the Android app — verify it auto-connects to "SyldaServer"** - [ ] **Step 3: Verify status shows "Connected"** - [ ] **Step 4: Hold HOLD button — verify relay activates (click or multimeter)** - [ ] **Step 5: Release — verify relay deactivates** - [ ] **Step 6: Touch sensor on G33 — verify relay also activates** --- ## Self-Review **1. Spec coverage:** - Sidebranch `esp32picod4`: Task 1 ✓ - Compile for ESP32-PICO-D4: Task 2 ✓ - Pins G32/G33/G27: Task 3 ✓ - SK6812 LED with FastLED: Task 3 ✓ - BLE name "SyldaServer": Task 3 ✓ - CDC-class USB detection: Tasks 5, 6 ✓ - DTR/RTS no changes: Confirmed in research, no task needed ✓ - Bundle binary with Android app: Task 8 ✓ - Build APK: Task 9 ✓ - ADB wireless deploy: Tasks 10, 11 ✓ - Flash + provision + test: Tasks 12-14 ✓ **2. Placeholder scan:** No TBD, TODO, or incomplete sections. All commands are concrete with expected output. **3. Type consistency:** `hasCdcDataInterface(device: UsbDevice): Boolean` defined in Task 5, used consistently in Task 6 with `UsbUtils.hasCdcDataInterface(device/it)`.