# Stateful ESP-NOW Light Control
Model: gemma-4-e4b-it
Created: 6/8/2026, 10:26:35 AM
Exported from: LM Studio 0.4.15+2

### User
hi, can you cahnge this code please? its for an esp32c3. wireless comms. reciever code

cuttently this is what i have

#include &lt;esp_now.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;Adafruit_NeoPixel.h&gt;


// --- Configuration ---
#define LED_PIN     10
#define LED_COUNT   10
// Removed PAUSE_TIME from here, as the timing logic will be handled by millis() in the callback.



// Structure definition (MUST MATCH THE SENDER)
typedef struct struct_message {
    char a[32]; // For status text
    int b;       // Integer (e.g., scaled reading)
    float c;     // Float (e.g., magnitude or raw float data)
    bool d;      // Boolean (Movement detected flag)
} struct_message;



struct_message myData;


// --- Object Declarations ---
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);


// Variable to track how many pixels have been lit (This is our persistent counter)
int activePixelCount = 0;


// *** NEW STATE VARIABLES FOR TIMING CONTROL ***
unsigned long lastTriggerTime = 0;
const unsigned long COOLDOWN_MS = 5000; // 5 seconds cooldown period



//=============================================================
// FUNCTION DEFINITIONS (Helpers must be defined outside setup/loop)
//=============================================================


/**
 * @brief Handles lighting the next pixel and cycling the state when capacity is reached.
 * NOTE: This function should only be called if the time cooldown has passed.
 */
void activateNextPixel() {
    // 1. Check if we need to reset (Cycle condition)
    if (activePixelCount &gt;= LED_COUNT) {
        Serial.println("--- CYCLE COMPLETE! Resetting all pixels for next cycle. ---");
        strip.clear(); // Turn ALL lights off
        strip.show();  // Update the physical LEDs
        activePixelCount = 0; // Reset the counter to start over from Pixel 0
    }


    // 2. Light up the current pixel (This runs whether we just reset or not)
    if (activePixelCount &lt; LED_COUNT) {
        // Increment the count *before* lighting, so it reflects the index being lit
        activePixelCount++; 
        
        Serial.print("Lighting Pixel Index: ");
        Serial.println(activePixelCount - 1);



        // --- COLOR MAPPING START HERE (Using your specified colors) ---
        uint32_t color;
        int current_index = activePixelCount - 1; // The physical index being lit (0, 1, 2...)



        if (current_index == 0) {
            color = strip.Color(255, 50, 50); // Reddish
        } else if (current_index == 1) {
            color = strip.Color(255, 255, 50); // Yellowish
        } else if (current_index == 2) {
            color = strip.Color(50, 255, 50); // Green
        } else if (current_index == 3) {
            color = strip.Color(50, 255, 255); // Cyan/Light Blue
        } else if (current_index == 4) {
            color = strip.Color(50, 50, 255); // Blue
        } else if (current_index == 5) {
            color = strip.Color(255, 50, 255); // Magenta/Purple
        } else if (current_index == 6) {
            color = strip.Color(255, 255, 100); // Light Yellow/Cream
        } else if (current_index == 7) {
            color = strip.Color(250, 100, 50); // Orange/Peach
        } else if (current_index == 8) {
            color = strip.Color(100, 200, 150); // Light Teal/Mint
        } else if (current_index == 9) {
            color = strip.Color(200, 80, 10); // Brownish Orange
        } else {
             // Fallback color just in case something goes wrong
             color = strip.Color(255, 0, 0); 
        }



        strip.setPixelColor(current_index, color);
        strip.show(); // Update the physical LEDs
    }
}


/**
 * @brief Callback function executed when data is received via ESP-NOW.
 */
void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len) {
    // 1. Copy the received data into our local structure variable
    memcpy(&myData, incomingData, sizeof(myData));
    
    Serial.println("\n=========================================");
    Serial.print("Packet Received from MAC: ");
    for (int i = 0; i &lt; 6; i++) {
        Serial.printf("%02X", info-&gt;src_addr[i]);
        if (i &lt; 5) Serial.print(":");
    }
    Serial.println();



    // Display the received data fields (for debugging)
    Serial.print("Char: ");
    Serial.println(myData.a);
    Serial.print("Int: ");
    Serial.println(myData.b);
    Serial.print("Float: ");
    Serial.println(myData.c);
    Serial.print("Bool: ");
    Serial.println(myData.d ? "TRUE" : "FALSE");


    // 2. CHECK THE MOVEMENT FLAG AND ENFORCE COOLDOWN (THE CORE CHANGE)
    if (myData.d == true) {
        unsigned long currentTime = millis();
        
        // Check if enough time has passed since the last successful trigger
        if (currentTime - lastTriggerTime &gt;= COOLDOWN_MS) {
            Serial.println("\n*** MOVEMENT DETECTED! Cooldown elapsed. Starting timed sequence... ***");
            activateNextPixel(); // This starts the process by lighting Pixel 0
            lastTriggerTime = currentTime; // IMPORTANT: Update the timer ONLY when action is taken
        } else {
            // If movement is detected but cooldown hasn't passed, we ignore it.
            Serial.printf("Movement detected, BUT ignored. Still in cooldown period (%.1f seconds remaining).\n", 
                          (float)(COOLDOWN_MS - (millis() - lastTriggerTime)) / 1000.0);
        }
    } else {
        // If movement is NOT detected, we do nothing to the lights, 
        // allowing previously lit pixels to stay lit until a reset/cycle.
        Serial.println("Movement flag is FALSE. Lights remain as they are.");
    }
}



void setup() {
    // Initialize Serial Monitor (Use 115200 baud rate)
    Serial.begin(115200);


    // Setup the NeoPixels
    strip.begin();          
    strip.show();           // Turn OFF all pixels initially
    strip.setBrightness(50); // Set brightness to a reasonable level (0-255)


    // Initialize timing variables
    lastTriggerTime = 0; // Ensure it starts ready to receive the first trigger


    // --- ESP-NOW Initialization ---
    WiFi.mode(WIFI_STA);



    if (esp_now_init() != ESP_OK) {
        Serial.println("Error initializing ESP-NOW");
        return;
    }
    
    // Register the Receive Callback Function
    esp_now_register_recv_cb(OnDataRecv);


    Serial.println("\n=========================================");
    Serial.println("Receiver Initialized and Listening for ESP-NOW Data...");
    Serial.print("Cooldown set to: ");
    Serial.print(COOLDOWN_MS / 1000);
    Serial.println(" seconds.");
}



void loop() {
    // The loop is now empty because all logic runs asynchronously in the callback function.
    delay(20); 
}


when i sense movemnt, it turns on the next neopixel in line

can you make an alternate state where if it recieves the message "TOO FAST" it turns on all the neopixels red. and then if the next message is movement detected or movement subsided it goes back to resume the normal count from where it was before?

### Assistant
This is a great request that requires adding **state management** to your existing logic. We need to introduce a new state variable to track whether the system is in "Normal Operation" (counting pixels) or "Emergency State" (all red).

Here is the modified code incorporating the "TOO FAST" emergency trigger and the logic to resume counting afterward.

### Key Changes Implemented:

1.  **New State Variable:** `SystemState` enum/variable to track operational mode.
2.  **New Logic in `OnDataRecv`:** Checks for `"TOO FAST"` message first. If found, it triggers the emergency state (all red) and updates the state.
3.  **Modified Pixel Activation:** The `activateNextPixel()` function is now aware of the current state and will only proceed with counting if the state allows it.
4.  **Resumption Logic:** When a non-emergency trigger arrives, the system checks its previous state to decide whether to continue counting or reset.

---

### Modified Receiver Code (ESP32C3)

```cpp
#include &lt;esp_now.h&gt;
#include &lt;WiFi.h&gt;
#include &lt;Adafruit_NeoPixel.h&gt;

// --- Configuration ---
#define LED_PIN    10
#define LED_COUNT  10
// Removed PAUSE_TIME from here, as the timing logic will be handled by millis() in the callback.


// Structure definition (MUST MATCH THE SENDER)
typedef struct struct_message {
    char a[32]; // For status text
    int b;       // Integer (e.g., scaled reading)
    float c;     // Float (e.g., magnitude or raw float data)
    bool d;      // Boolean (Movement detected flag)
} struct_message;

struct_message myData;


// --- Object Declarations ---
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

// Variable to track how many pixels have been lit (This is our persistent counter)
int activePixelCount = 0;

// *** NEW STATE VARIABLES FOR TIMING CONTROL & MODE MANAGEMENT ***
unsigned long lastTriggerTime = 0;
const unsigned long COOLDOWN_MS = 5000; // 5 seconds cooldown period

// Define System States
enum SystemState {
    STATE_IDLE,       // Waiting for any trigger
    STATE_COUNTING,   // Normal operation: Counting pixels sequentially
    STATE_EMERGENCY   // Triggered by "TOO FAST": All red flash/solid state
};

SystemState currentState = STATE_IDLE;


//=============================================================
// FUNCTION DEFINITIONS (Helpers must be defined outside setup/loop)
//=============================================================


/**
 * @brief Sets all pixels to a specific color and updates the strip.
 * @param color The color to set.
 */
void setAllPixelsColor(uint32_t color) {
    for (int i = 0; i &lt; LED_COUNT; i++) {
        strip.setPixelColor(i, color);
    }
    strip.show();
}

/**
 * @brief Handles lighting the next pixel and cycling the state when capacity is reached.
 * NOTE: This function should only be called if the time cooldown has passed AND we are in COUNTING mode.
 */
void activateNextPixel() {
    // Only proceed with counting logic if we are currently in STATE_COUNTING
    if (currentState != STATE_COUNTING) {
        Serial.println("Cannot activate pixel: Not in Counting State.");
        return;
    }

    // 1. Check if we need to reset (Cycle condition)
    if (activePixelCount &gt;= LED_COUNT) {
        Serial.println("\n--- CYCLE COMPLETE! Resetting all pixels for next cycle. ---");
        setAllPixelsColor(0); // Turn ALL lights off
        activePixelCount = 0; // Reset the counter to start over from Pixel 0
    }

    // 2. Light up the current pixel (This runs whether we just reset or not)
    if (activePixelCount &lt; LED_COUNT) {
        // Increment the count *before* lighting, so it reflects the index being lit
        activePixelCount++; 
        int current_index = activePixelCount - 1; // The physical index being lit (0, 1, 2...)

        Serial.print("Lighting Pixel Index: ");
        Serial.println(current_index);

        // --- COLOR MAPPING START HERE (Using your specified colors) ---
        uint32_t color;

        if (current_index == 0) {
            color = strip.Color(255, 50, 50); // Reddish
        } else if (current_index == 1) {
            color = strip.Color(255, 255, 50); // Yellowish
        } else if (current_index == 2) {
            color = strip.Color(50, 255, 50); // Green
        } else if (current_index == 3) {
            color = strip.Color(50, 255, 255); // Cyan/Light Blue
        } else if (current_index == 4) {
            color = strip.Color(50, 50, 255); // Blue
        } else if (current_index == 5) {
            color = strip.Color(255, 50, 255); // Magenta/Purple
        } else if (current_index == 6) {
            color = strip.Color(255, 255, 100); // Light Yellow/Cream
        } else if (current_index == 7) {
            color = strip.Color(250, 100, 50); // Orange/Peach
        } else if (current_index == 8) {
            color = strip.Color(100, 200, 150); // Light Teal/Mint
        } else if (current_index == 9) {
            color = strip.Color(200, 80, 10); // Brownish Orange
        } else {
             // Fallback color just in case something goes wrong
             color = strip.Color(255, 0, 0); 
        }

        strip.setPixelColor(current_index, color);
        strip.show(); // Update the physical LEDs
    }
}


/**
 * @brief Triggers the emergency state: all pixels turn red.
 */
void triggerEmergencyState() {
    Serial.println("\n!!! EMERGENCY STATE TRIGGERED: ALL RED !!!");
    setAllPixelsColor(strip.Color(255, 0, 0)); // Set all to solid Red
    currentState = STATE_EMERGENCY;
}

/**
 * @brief Resets the system back to normal counting mode after an event.
 */
void resetToCountingState() {
    Serial.println("--- Resetting state to NORMAL COUNTING MODE ---");
    setAllPixelsColor(0); // Turn all lights off first
    activePixelCount = 0; // Start count from zero
    currentState = STATE_COUNTING;
}


/**
 * @brief Callback function executed when data is received via ESP-NOW.
 */
void OnDataRecv(const esp_now_recv_info_t *info, const uint8_t *incomingData, int len) {
    // 1. Copy the received data into our local structure variable
    memcpy(&myData, incomingData, sizeof(myData));
    
    Serial.println("\n=========================================");
    Serial.print("Packet Received from MAC: ");
    for (int i = 0; i &lt; 6; i++) {
        Serial.printf("%02X", info-&gt;src_addr[i]);
        if (i &lt; 5) Serial.print(":");
    }
    Serial.println();

    // --- DEBUG PRINTING ---
    Serial.print("Char: ");
    Serial.println(myData.a);
    Serial.print("Int: ");
    Serial.println(myData.b);
    Serial.print("Float: ");
    Serial.println(myData.c);
    Serial.print("Bool: ");
    Serial.println(myData.d ? "TRUE" : "FALSE");

    // ==============================================================
    // 2. STATE MACHINE LOGIC HANDLER
    // ==============================================================

    // --- A. CHECK FOR EMERGENCY TRIGGER ("TOO FAST") ---
    if (strcmp(myData.a, "TOO FAST") == 0) {
        triggerEmergencyState();
        lastTriggerTime = millis(); // Reset cooldown timer upon emergency trigger
        return; // Stop processing other triggers for this packet
    }

    // --- B. CHECK FOR MOVEMENT TRIGGER (d == true) ---
    if (myData.d == true) {
        unsigned long currentTime = millis();
        
        // Check if enough time has passed since the last successful trigger
        if (currentTime - lastTriggerTime &gt;= COOLDOWN_MS) {
            Serial.println("\n*** MOVEMENT DETECTED! Cooldown elapsed. ***");

            if (currentState == STATE_EMERGENCY) {
                // If we were in emergency mode, movement detection means the event has passed.
                resetToCountingState(); 
            } else if (currentState == STATE_COUNTING) {
                Serial.println("Resuming normal counting sequence...");
                activateNextPixel(); // Continue counting from where we left off
            } else { // STATE_IDLE or initial state
                // Start the process normally
                resetToCountingState(); 
                activateNextPixel(); // This will light Pixel 0 and set the timer
            }

            lastTriggerTime = currentTime; // Update the timer ONLY when action is taken
        } else {
            // Cooldown active, ignore movement trigger unless we are in emergency mode (which handles its own timing)
            Serial.printf("Movement detected, BUT ignored. Still in cooldown period.\n");
        }
    } 
    // --- C. CHECK FOR MOVEMENT SUBSIDING (d == false AND not triggered by "TOO FAST") ---
    else if (myData.d == false && currentState != STATE_EMERGENCY) {
        Serial.println("Movement flag is FALSE. System remains in current state.");

        if (currentState == STATE_COUNTING) {
            // If movement subsides during counting, we might want to pause or hold the last pixel lit.
            // For this implementation, we will just stop advancing the count but keep the lights on until a new trigger.
            Serial.println("Movement subsided. Pausing pixel advancement.");
        } else if (currentState == STATE_IDLE) {
             Serial.println("No movement detected and system is idle.");
        }
    }
}


void setup() {
    // Initialize Serial Monitor (Use 115200 baud rate)
    Serial.begin(115200);

    // Setup the NeoPixels
    strip.begin();          
    setAllPixelsColor(0);   // Turn OFF all pixels initially
    strip.setBrightness(50); // Set brightness to a reasonable level (0-255)

    // Initialize timing variables and state
    lastTriggerTime = 0; 
    currentState = STATE_IDLE;


    // --- ESP-NOW Initialization ---
    WiFi.mode(WIFI_STA);

    if (esp_now_init() != ESP_OK) {
        Serial.println("Error initializing ESP-NOW");
        return;
    }
    
    // Register the Receive Callback Function
    esp_now_register_recv_cb(OnDataRecv);


    Serial.println("\n=========================================");
    Serial.println("Receiver Initialized and Listening for ESP-NOW Data...");
    Serial.print("Cooldown set to: ");
    Serial.print(COOLDOWN_MS / 1000);
    Serial.println(" seconds.");
}


void loop() {
    // The loop remains empty as all logic runs asynchronously in the callback function.
    delay(20); 
}
```

### How to Test and Understand the New Logic:

To test this, you need a sender that can send three distinct types of messages:

1.  **Normal Movement:** `d = true`, `a` is anything else (e.g., "MOVE"). This triggers counting if cooldown allows.
2.  **Emergency Trigger:** `a = "TOO FAST"`. This should immediately turn all lights red, regardless of the current state or cooldown.
3.  **Movement Subsides/Reset Signal:** `d = false` and `a` is anything else (e.g., "STOP").

#### Scenario Walkthrough:

1.  **Start:** System is in `STATE_IDLE`. Lights are off.
2.  **Trigger 1 (Normal Move):** Sender sends `d=true`, `a="MOVE"`.
    *   System checks cooldown, finds it clear.
    *   State changes to `STATE_COUNTING`.
    *   `activateNextPixel()` runs: Lights Pixel 0 red. `lastTriggerTime` is set.
3.  **Trigger 2 (Normal Move):** Sender sends `d=true`, `a="MOVE"` again (within cooldown).
    *   System detects movement but ignores it due to cooldown. No change in lights or state.
4.  **Trigger 3 (Emergency):** Sender sends `a="TOO FAST"`.
    *   The code hits the `"TOO FAST"` check first.
    *   `triggerEmergencyState()` runs: All pixels turn **RED**.
    *   State changes to `STATE_EMERGENCY`. `lastTriggerTime` is updated.
5.  **Trigger 4 (Movement after Emergency):** Sender sends `d=true`, `a="MOVE"` again (after the cooldown period from Trigger 3).
    *   System detects movement, checks cooldown (it's clear).
    *   It sees `currentState == STATE_EMERGENCY`.
    *   The logic executes: **`resetToCountingState()`** runs. Lights turn off, count resets to 0. State changes to `STATE_COUNTING`.
    *   Then, `activateNextPixel()` runs, lighting Pixel 0 red and starting the normal cycle again.
