# LED Stable Connection 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:** Uniform LED signaling for server and client — LED acts as debug indicator, turns off after 60s stable connection. **Architecture:** Shared LED state machine: OTA overrides all, then relay-flash overlay, then connection-status base color (Red=disconnected, Yellow=connected<60s, Off=stable>=60s). Both firmwares use identical logic. **Tech Stack:** C++/Arduino, FastLED, NimBLE, ESP32-S3 (PlatformIO) --- ## File Map | File | Action | Purpose | |------|--------|---------| | `src/main.cpp` | Modify | Server LED logic, new globals, remove setup blinks | | `src/client.cpp` | Modify | Client LED logic, new globals, OTA placeholders | | `src/version.h` | Modify | Version bumps (server 15→16, client 1→2) | --- ### Task 1: Server LED logic **Files:** - Modify: `src/main.cpp` - [ ] **Step 1: Add new global variables after line 56 (after `int connectedCount = 0;`)** ```cpp #define STABLE_LED_TIMEOUT_MS 60000 uint32_t connectionStableMs = 0; uint32_t relayFlashUntil = 0; ``` - [ ] **Step 2: Remove `ledOtaBusy()` function (lines 181-183)** Delete this function — it will be inlined in the new LED logic: ```cpp static void ledOtaBusy() { leds[0] = (millis() / 100) % 2 ? CRGB::Green : CRGB::Black; } ``` Remove all 3 lines. - [ ] **Step 3: Replace setup LED blinks (lines 414-422) with solid red** Replace: ```cpp 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..."); ``` With: ```cpp leds[0] = CRGB::Red; FastLED.show(); Serial.println("LED red (no connection), starting BLE..."); ``` - [ ] **Step 4: Remove `leds[0] = CRGB::Yellow;` from otaReady handler (line 553)** In the otaReady block (lines 542-558), remove line 553 `leds[0] = CRGB::Yellow;`. The wait-for-confirm LED is now handled by the main LED logic checking `otaInProgress && otaEnded`. - [ ] **Step 5: Replace the LED block in loop() (lines 599-615) with new logic** Replace lines 599-615: ```cpp if (!otaInProgress) { relay1State = t1Active || btnActive || (connActive[0] && bleRelay1[0]) || (connActive[1] && bleRelay1[1]); relay2State = t2Active || (connActive[0] && bleRelay2[0]) || (connActive[1] && bleRelay2[1]); leds[0] = (connectedCount > 0) ? CRGB::Green : CRGB::Black; } else { relay1State = false; relay2State = false; ledOtaBusy(); } digitalWrite(RELAY1_PIN, relay1State ? HIGH : LOW); digitalWrite(RELAY2_PIN, relay2State ? HIGH : LOW); FastLED.show(); ``` With: ```cpp if (!otaInProgress) { relay1State = t1Active || btnActive || (connActive[0] && bleRelay1[0]) || (connActive[1] && bleRelay1[1]); relay2State = t2Active || (connActive[0] && bleRelay2[0]) || (connActive[1] && bleRelay2[1]); } else { relay1State = false; relay2State = false; } // --- LED logic --- CRGB ledColor; if (otaInProgress) { ledColor = otaEnded ? CRGB::Yellow : (((millis() / 100) % 2) ? CRGB::Green : CRGB::Black); } else { if (connectedCount > 0) { if (connectionStableMs == 0) connectionStableMs = millis(); } else { connectionStableMs = 0; } if (connectedCount > 0) { ledColor = (millis() - connectionStableMs >= STABLE_LED_TIMEOUT_MS) ? CRGB::Black : CRGB::Yellow; } else { ledColor = CRGB::Red; } static bool lastRelayActive = false; bool relayActive = relay1State || relay2State; if (relayActive != lastRelayActive) { relayFlashUntil = millis() + 150; } lastRelayActive = relayActive; if (millis() < relayFlashUntil) { ledColor = CRGB::Green; } } leds[0] = ledColor; digitalWrite(RELAY1_PIN, relay1State ? HIGH : LOW); digitalWrite(RELAY2_PIN, relay2State ? HIGH : LOW); FastLED.show(); ``` - [ ] **Step 6: Commit server changes** ```bash git add src/main.cpp git commit -m "feat(server): uniform LED signaling — off after 60s stable connection" ``` --- ### Task 2: Client LED logic **Files:** - Modify: `src/client.cpp` - [ ] **Step 1: Add new global variables after line 48 (after `static uint32_t seq = 0;`)** ```cpp #define STABLE_LED_TIMEOUT_MS 60000 uint32_t connectionStableMs = 0; uint32_t relayFlashUntil = 0; bool otaInProgress = false; bool otaEnded = false; ``` - [ ] **Step 2: Replace the LED block in loop() (lines 368-375) with new logic** Replace lines 368-375: ```cpp if (relay1State || relay2State) { leds[0] = CRGB::Green; } else if (connected) { leds[0] = CRGB::Blue; } else { leds[0] = CRGB::Red; } FastLED.show(); ``` With: ```cpp // --- LED logic --- CRGB ledColor; if (otaInProgress) { ledColor = otaEnded ? CRGB::Yellow : (((millis() / 100) % 2) ? CRGB::Green : CRGB::Black); } else { if (connected) { if (connectionStableMs == 0) connectionStableMs = millis(); } else { connectionStableMs = 0; } if (connected) { ledColor = (millis() - connectionStableMs >= STABLE_LED_TIMEOUT_MS) ? CRGB::Black : CRGB::Yellow; } else { ledColor = CRGB::Red; } static bool lastRelayActive = false; bool relayActive = relay1State || relay2State; if (relayActive != lastRelayActive) { relayFlashUntil = millis() + 150; } lastRelayActive = relayActive; if (millis() < relayFlashUntil) { ledColor = CRGB::Green; } } leds[0] = ledColor; FastLED.show(); ``` - [ ] **Step 3: Commit client changes** ```bash git add src/client.cpp git commit -m "feat(client): uniform LED signaling — off after 60s stable connection" ``` --- ### Task 3: Version bumps **Files:** - Modify: `src/version.h` - [ ] **Step 1: Bump versions** Replace: ```cpp #define FIRMWARE_VERSION_SERVER "15" #define FIRMWARE_VERSION_CLIENT "1" ``` With: ```cpp #define FIRMWARE_VERSION_SERVER "16" #define FIRMWARE_VERSION_CLIENT "2" ``` - [ ] **Step 2: Commit version bump** ```bash git add src/version.h git commit -m "chore: bump server v16, client v2" ``` --- ### Task 4: Build verification **Files:** - None (build only) - [ ] **Step 1: Build server firmware** Run: `pio run -e s3lite-server` Expected: SUCCESS, no errors. - [ ] **Step 2: Build client firmware** Run: `pio run -e s3lite-client` Expected: SUCCESS, no errors. - [ ] **Step 3: Verify both targets compile cleanly** Run: `pio run` Expected: Both environments compile SUCCESS.