Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion firmware/esp32-csi-node/main/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ set(SRCS
"main.c" "csi_collector.c" "stream_sender.c" "nvs_config.c"
"edge_processing.c" "ota_update.c" "power_mgmt.c"
"wasm_runtime.c" "wasm_upload.c" "rvf_parser.c"
"mmwave_sensor.c"
"mmwave_sensor.c" "led_indicator.c"
"swarm_bridge.c"
)

Expand Down
19 changes: 19 additions & 0 deletions firmware/esp32-csi-node/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,25 @@ menu "WASM Programmable Sensing (ADR-040)"

endmenu

menu "RGB Status Indicator LED"

config RGB_LED_ENABLED
bool "Enable onboard WS2812 status LED"
default y
help
Compiles the LED state machine to indicate boot, wifi,
and streaming status using the ESP32's onboard RGB LED.

config RGB_LED_GPIO
int "RGB LED GPIO Pin"
depends on RGB_LED_ENABLED
default 38
help
The GPIO pin connected to the NeoPixel (WS2812/SK6812).
Commonly 38 on generic S3 boards or 48 on older boards.

endmenu

menu "Mock CSI (QEMU Testing)"
config CSI_MOCK_ENABLED
bool "Enable mock CSI generator (for QEMU testing)"
Expand Down
3 changes: 3 additions & 0 deletions firmware/esp32-csi-node/main/idf_component.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@ dependencies:

## LCD touch abstraction
espressif/esp_lcd_touch: "^1.0"

## WS2812 LED Strip Driver
espressif/led_strip: "^3.0.0"
165 changes: 165 additions & 0 deletions firmware/esp32-csi-node/main/led_indicator.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
/**
* @file led_indicator.c
* @brief Configurable RGB LED Status Indicator for ESP32 CSI Node
*/

#include "led_indicator.h"
#include "sdkconfig.h"
#include "nvs_config.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#ifdef CONFIG_RGB_LED_ENABLED
#include "led_strip.h"

static const char *TAG = "led_indicator";
extern nvs_config_t g_nvs_config;

static led_strip_handle_t s_led_strip = NULL;
static led_indicator_state_t s_current_state = LED_STATE_BOOTING;
static TaskHandle_t s_led_task = NULL;

static void led_task(void *arg)
{
uint8_t pulse = 0;
int8_t dir = 5;
bool toggle = false;

while (1) {
if (!s_led_strip) {
vTaskDelay(pdMS_TO_TICKS(100));
continue;
}

switch (s_current_state) {
case LED_STATE_BOOTING:
/* Solid White */
led_strip_set_pixel(s_led_strip, 0, 50, 50, 50);
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(100));
break;

case LED_STATE_WIFI_CONNECTING:
/* Fast Blinking Blue */
toggle = !toggle;
if (toggle) {
led_strip_set_pixel(s_led_strip, 0, 0, 0, 100);
} else {
led_strip_clear(s_led_strip);
}
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(200));
break;

case LED_STATE_WIFI_ERROR:
/* Solid Red */
led_strip_set_pixel(s_led_strip, 0, 100, 0, 0);
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(100));
break;

case LED_STATE_CONNECTED:
/* Slow Pulsing Green */
pulse += dir;
if (pulse >= 100 || pulse <= 0) {
dir = -dir;
}
led_strip_set_pixel(s_led_strip, 0, 0, pulse, 0);
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(50));
break;

case LED_STATE_MOCK_MODE:
/* Blinking Yellow */
toggle = !toggle;
if (toggle) {
led_strip_set_pixel(s_led_strip, 0, 100, 100, 0);
} else {
led_strip_clear(s_led_strip);
}
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(500));
break;

case LED_STATE_MMWAVE_ERROR:
/* Slow Blinking Yellow */
toggle = !toggle;
if (toggle) {
led_strip_set_pixel(s_led_strip, 0, 100, 100, 0);
} else {
led_strip_clear(s_led_strip);
}
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(1000));
break;

case LED_STATE_SWARM_ERROR:
/* Slow Blinking Magenta */
toggle = !toggle;
if (toggle) {
led_strip_set_pixel(s_led_strip, 0, 100, 0, 100);
} else {
led_strip_clear(s_led_strip);
}
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(1000));
break;

case LED_STATE_SWARM_ACTIVE:
/* Quick Blip Magenta */
led_strip_set_pixel(s_led_strip, 0, 100, 0, 100);
led_strip_refresh(s_led_strip);
vTaskDelay(pdMS_TO_TICKS(100));
led_strip_clear(s_led_strip);
led_strip_refresh(s_led_strip);
s_current_state = LED_STATE_CONNECTED;
break;
}
}
}

void led_indicator_init(void)
{
led_strip_config_t strip_config = {
.strip_gpio_num = CONFIG_RGB_LED_GPIO,
.max_leds = 1,
.led_model = LED_MODEL_WS2812,
.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB,
.flags.invert_out = false,
};
led_strip_rmt_config_t rmt_config = {
.resolution_hz = 10 * 1000 * 1000, /* 10MHz */
.flags.with_dma = false,
};

if (led_strip_new_rmt_device(&strip_config, &rmt_config, &s_led_strip) == ESP_OK) {
led_strip_clear(s_led_strip);

if (!g_nvs_config.status_led) {
ESP_LOGI(TAG, "Status LED disabled by NVS configuration. Cleared and stopped.");
return;
}

xTaskCreate(led_task, "led_indicator_task", 2048, NULL, 5, &s_led_task);
ESP_LOGI(TAG, "RGB LED Indicator initialized on GPIO %d", CONFIG_RGB_LED_GPIO);
} else {
ESP_LOGE(TAG, "Failed to initialize RGB LED on GPIO %d", CONFIG_RGB_LED_GPIO);
}
}

void led_indicator_set_state(led_indicator_state_t state)
{
if (!g_nvs_config.status_led || !s_led_strip) {
return;
}
s_current_state = state;
}

#else

/* Stubs when disabled via Kconfig */
void led_indicator_init(void) {}
void led_indicator_set_state(led_indicator_state_t state) {}

#endif
41 changes: 41 additions & 0 deletions firmware/esp32-csi-node/main/led_indicator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* @file led_indicator.h
* @brief Configurable RGB LED Status Indicator for ESP32 CSI Node
*/

#ifndef LED_INDICATOR_H
#define LED_INDICATOR_H

#ifdef __cplusplus
extern "C" {
#endif

/** State of the system to indicate via the LED */
typedef enum {
LED_STATE_BOOTING = 0,
LED_STATE_WIFI_CONNECTING,
LED_STATE_CONNECTED,
LED_STATE_WIFI_ERROR,
LED_STATE_MOCK_MODE,
LED_STATE_MMWAVE_ERROR,
LED_STATE_SWARM_ERROR,
LED_STATE_SWARM_ACTIVE,
} led_indicator_state_t;

/**
* Initializes the LED indicator system if enabled via NVS and Kconfig.
* Starts the background FreeRTOS task to drive the NeoPixel animations.
*/
void led_indicator_init(void);

/**
* Updates the current system state, changing the LED animation.
* @param state The new system state to indicate.
*/
void led_indicator_set_state(led_indicator_state_t state);

#ifdef __cplusplus
}
#endif

#endif /* LED_INDICATOR_H */
14 changes: 13 additions & 1 deletion firmware/esp32-csi-node/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "mock_csi.h"
#endif

#include "led_indicator.h"
#include "esp_timer.h"

static const char *TAG = "main";
Expand All @@ -56,19 +57,23 @@ static void event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
led_indicator_set_state(LED_STATE_WIFI_CONNECTING);
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < MAX_RETRY) {
led_indicator_set_state(LED_STATE_WIFI_CONNECTING);
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "Retrying WiFi connection (%d/%d)", s_retry_num, MAX_RETRY);
} else {
led_indicator_set_state(LED_STATE_WIFI_ERROR);
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data;
ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
led_indicator_set_state(LED_STATE_CONNECTED);
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
Expand Down Expand Up @@ -137,6 +142,9 @@ void app_main(void)
/* Load runtime config (NVS overrides Kconfig defaults) */
nvs_config_load(&g_nvs_config);

/* Initialize visual status indicator subsystem */
led_indicator_init();

ESP_LOGI(TAG, "ESP32-S3 CSI Node (ADR-018) — Node ID: %d", g_nvs_config.node_id);

/* Initialize WiFi STA (skip entirely under QEMU mock — no RF hardware) */
Expand All @@ -159,6 +167,7 @@ void app_main(void)
/* Initialize CSI collection */
#ifdef CONFIG_CSI_MOCK_ENABLED
/* ADR-061: Start mock CSI generator (replaces real WiFi CSI in QEMU) */
led_indicator_set_state(LED_STATE_MOCK_MODE);
esp_err_t mock_ret = mock_csi_init(CONFIG_CSI_MOCK_SCENARIO);
if (mock_ret != ESP_OK) {
ESP_LOGE(TAG, "Mock CSI init failed: %s", esp_err_to_name(mock_ret));
Expand Down Expand Up @@ -237,8 +246,11 @@ void app_main(void)
ESP_LOGI(TAG, "mmWave sensor: %s (caps=0x%04x)",
mmwave_type_name(mw.type), mw.capabilities);
}
} else {
} else if (mmwave_ret == ESP_ERR_NOT_FOUND) {
ESP_LOGI(TAG, "No mmWave sensor detected (CSI-only mode)");
} else {
ESP_LOGE(TAG, "mmWave sensor init error: %s", esp_err_to_name(mmwave_ret));
led_indicator_set_state(LED_STATE_MMWAVE_ERROR);
}

/* ADR-066: Initialize swarm bridge to Cognitum Seed (if configured). */
Expand Down
10 changes: 10 additions & 0 deletions firmware/esp32-csi-node/main/nvs_config.c
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ void nvs_config_load(nvs_config_t *cfg)
cfg->filter_mac_set = 0;
memset(cfg->filter_mac, 0, 6);

/* Indicator defaults */
cfg->status_led = 1;

/* Try to override from NVS */
nvs_handle_t handle;
esp_err_t err = nvs_open("csi_cfg", NVS_READONLY, &handle);
Expand Down Expand Up @@ -302,6 +305,13 @@ void nvs_config_load(nvs_config_t *cfg)
cfg->filter_mac[3], cfg->filter_mac[4], cfg->filter_mac[5]);
}

/* Indicator LED override */
uint8_t status_led_val;
if (nvs_get_u8(handle, "status_led", &status_led_val) == ESP_OK) {
cfg->status_led = status_led_val ? 1 : 0;
ESP_LOGI(TAG, "NVS override: status_led=%u", (unsigned)cfg->status_led);
}

/* ADR-066: Swarm bridge */
len = sizeof(cfg->seed_url);
if (nvs_get_str(handle, "seed_url", cfg->seed_url, &len) != ESP_OK) {
Expand Down
3 changes: 3 additions & 0 deletions firmware/esp32-csi-node/main/nvs_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ typedef struct {
uint8_t filter_mac[6]; /**< MAC address to filter CSI frames. */
uint8_t filter_mac_set; /**< 1 if filter_mac was loaded from NVS. */

/* Generic Utility Settings */
uint8_t status_led; /**< 1 to enable the RGB status LED, 0 to disable. */

/* ADR-066: Swarm bridge configuration */
char seed_url[64]; /**< Cognitum Seed base URL (empty = disabled). */
char seed_token[64]; /**< Seed Bearer token (from pairing). */
Expand Down
Loading