Project_SylDa/docs/superpowers/plans/2026-08-02-led-stable-connection.md

7.1 KiB

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;)

#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:

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:

    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:

    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:

    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:

    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
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;)

#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:

    if (relay1State || relay2State) {
        leds[0] = CRGB::Green;
    } else if (connected) {
        leds[0] = CRGB::Blue;
    } else {
        leds[0] = CRGB::Red;
    }
    FastLED.show();

With:

    // --- 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
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:

#define FIRMWARE_VERSION_SERVER "15"
#define FIRMWARE_VERSION_CLIENT "1"

With:

#define FIRMWARE_VERSION_SERVER "16"
#define FIRMWARE_VERSION_CLIENT "2"
  • Step 2: Commit version bump
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.