| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- #include "led.h"
- #include "esp_log.h"
- #include "esp_timer.h"
- #include "freertos/FreeRTOS.h"
- #include "freertos/timers.h"
- #include "rtsp_events.h"
- #include "settings.h"
- #if CONFIG_LED_STATUS_GPIO >= 0 || CONFIG_LED_ERROR_GPIO >= 0
- #include "driver/ledc.h"
- #endif
- #include <math.h>
- // Convert Kconfig boolean values to C macros
- #ifdef CONFIG_LED_STATUS_INVERT
- #define LED_STATUS_INVERT_VAL 1
- #else
- #define LED_STATUS_INVERT_VAL 0
- #endif
- #ifdef CONFIG_LED_ERROR_INVERT
- #define LED_ERROR_INVERT_VAL 1
- #else
- #define LED_ERROR_INVERT_VAL 0
- #endif
- static const char *TAG = "led";
- // Module-level brightness (0–255), shared across all LED types.
- // Loaded from NVS in led_init(); updated by led_set_brightness().
- static uint8_t s_brightness = CONFIG_LED_STATUS_BRIGHTNESS;
- // ============================================================================
- // Configuration helpers - map Kconfig to led_mode_t
- // ============================================================================
- static led_mode_t get_status_mode_playing(void) {
- #if defined(CONFIG_LED_STATUS_PLAYING_VU)
- return LED_VU;
- #elif defined(CONFIG_LED_STATUS_PLAYING_BLINK_FAST)
- return LED_BLINK_FAST;
- #else
- return LED_STEADY;
- #endif
- }
- static led_mode_t get_status_mode_paused(void) {
- #if defined(CONFIG_LED_STATUS_PAUSED_OFF)
- return LED_OFF;
- #elif defined(CONFIG_LED_STATUS_PAUSED_STEADY)
- return LED_STEADY;
- #elif defined(CONFIG_LED_STATUS_PAUSED_BLINK_SLOW)
- return LED_BLINK_SLOW;
- #else
- return LED_BLINK_MEDIUM;
- #endif
- }
- static led_mode_t get_status_mode_standby(void) {
- #if defined(CONFIG_LED_STATUS_STANDBY_OFF)
- return LED_OFF;
- #elif defined(CONFIG_LED_STATUS_STANDBY_BLINK_MEDIUM)
- return LED_BLINK_MEDIUM;
- #else
- return LED_BLINK_SLOW;
- #endif
- }
- static led_mode_t get_rgb_mode_playing(void) {
- #if defined(CONFIG_LED_RGB_PLAYING_OFF)
- return LED_OFF;
- #elif defined(CONFIG_LED_RGB_PLAYING_STEADY)
- return LED_STEADY;
- #else
- return LED_VU;
- #endif
- }
- static led_mode_t get_rgb_mode_paused(void) {
- #if defined(CONFIG_LED_RGB_PAUSED_OFF)
- return LED_OFF;
- #else
- return LED_STEADY;
- #endif
- }
- static led_mode_t get_rgb_mode_standby(void) {
- #if defined(CONFIG_LED_RGB_STANDBY_STEADY)
- return LED_STEADY;
- #else
- return LED_OFF;
- #endif
- }
- // ============================================================================
- // Status LED (single color via LEDC PWM)
- // ============================================================================
- #if CONFIG_LED_STATUS_GPIO >= 0
- #define STATUS_LED_CHANNEL LEDC_CHANNEL_0
- #define STATUS_LED_TIMER LEDC_TIMER_0
- static led_mode_t s_status_mode = LED_OFF;
- static TimerHandle_t s_status_timer = NULL;
- static bool s_status_on = false;
- static uint8_t s_status_duty = CONFIG_LED_STATUS_BRIGHTNESS;
- static void status_led_set_duty(uint8_t duty) {
- ledc_set_duty(LEDC_LOW_SPEED_MODE, STATUS_LED_CHANNEL, duty);
- ledc_update_duty(LEDC_LOW_SPEED_MODE, STATUS_LED_CHANNEL);
- ESP_LOGV(TAG, "Status LED duty set to %d", duty);
- }
- static void status_timer_cb(TimerHandle_t xTimer) {
- (void)xTimer;
- s_status_on = !s_status_on;
- status_led_set_duty(s_status_on ? s_status_duty : 0);
- ESP_LOGD(TAG, "Status LED timer: mode=%d, state=%s", s_status_mode,
- s_status_on ? "ON" : "OFF");
- uint32_t period_ms;
- switch (s_status_mode) {
- case LED_BLINK_SLOW:
- period_ms = s_status_on ? 100 : 2500;
- break;
- case LED_BLINK_MEDIUM:
- period_ms = 500;
- break;
- case LED_BLINK_FAST:
- period_ms = 250;
- break;
- default:
- return;
- }
- TickType_t ticks = pdMS_TO_TICKS(period_ms);
- if (ticks == 0) {
- ticks = 1;
- }
- BaseType_t ret = xTimerChangePeriod(s_status_timer, ticks, 10);
- if (ret != pdPASS) {
- ESP_LOGW(TAG, "Failed to change timer period: %d", ret);
- }
- }
- static void status_led_init(void) {
- s_status_duty = s_brightness;
- ledc_timer_config_t timer_cfg = {
- .speed_mode = LEDC_LOW_SPEED_MODE,
- .timer_num = STATUS_LED_TIMER,
- .duty_resolution = LEDC_TIMER_8_BIT,
- .freq_hz = 1000,
- .clk_cfg = LEDC_AUTO_CLK,
- };
- if (ledc_timer_config(&timer_cfg) != ESP_OK) {
- ESP_LOGE(TAG, "Status LED timer init failed");
- return;
- }
- ledc_channel_config_t ch_cfg = {
- .speed_mode = LEDC_LOW_SPEED_MODE,
- .channel = STATUS_LED_CHANNEL,
- .timer_sel = STATUS_LED_TIMER,
- .intr_type = LEDC_INTR_DISABLE,
- .gpio_num = CONFIG_LED_STATUS_GPIO,
- .duty = 0,
- .hpoint = 0,
- .flags = {.output_invert = LED_STATUS_INVERT_VAL},
- };
- if (ledc_channel_config(&ch_cfg) != ESP_OK) {
- ESP_LOGE(TAG, "Status LED channel init failed");
- return;
- }
- // Explicitly apply initial duty (off)
- ledc_set_duty(LEDC_LOW_SPEED_MODE, STATUS_LED_CHANNEL, 0);
- ledc_update_duty(LEDC_LOW_SPEED_MODE, STATUS_LED_CHANNEL);
- s_status_timer = xTimerCreate("status_led", pdMS_TO_TICKS(500), pdFALSE, NULL,
- status_timer_cb);
- if (s_status_timer == NULL) {
- ESP_LOGE(TAG, "Failed to create status LED timer");
- return;
- }
- ESP_LOGI(TAG, "Status LED initialized on GPIO %d", CONFIG_LED_STATUS_GPIO);
- }
- static void status_led_set_mode(led_mode_t mode) {
- if (mode == s_status_mode) {
- return;
- }
- ESP_LOGD(TAG, "Status LED mode change: %d -> %d", s_status_mode, mode);
- s_status_mode = mode;
- if (s_status_timer && xTimerIsTimerActive(s_status_timer)) {
- BaseType_t ret = xTimerStop(s_status_timer, 10);
- if (ret != pdPASS) {
- ESP_LOGW(TAG, "Failed to stop status LED timer: %d", ret);
- }
- }
- switch (mode) {
- case LED_OFF:
- ESP_LOGD(TAG, "Status LED: OFF");
- status_led_set_duty(0);
- break;
- case LED_STEADY:
- ESP_LOGD(TAG, "Status LED: STEADY (duty=%d)", s_status_duty);
- status_led_set_duty(s_status_duty);
- break;
- case LED_BLINK_SLOW:
- case LED_BLINK_MEDIUM:
- case LED_BLINK_FAST:
- // Reset state and turn LED on for first blink cycle
- s_status_on =
- false; // Will be toggled to true immediately in first timer callback
- if (!s_status_timer) {
- ESP_LOGE(TAG, "Status LED timer not initialized!");
- break;
- }
- BaseType_t ret = xTimerStart(s_status_timer, 10);
- if (ret != pdPASS) {
- ESP_LOGE(TAG, "Failed to start status LED timer: %d", ret);
- } else {
- ESP_LOGD(TAG, "Status LED: BLINK mode %d started", mode);
- // Immediately trigger first state to avoid initial delay
- status_timer_cb(s_status_timer);
- }
- break;
- case LED_VU:
- ESP_LOGD(TAG, "Status LED: VU mode (initial OFF)");
- // Initialize to OFF, will be updated by led_audio_feed()
- status_led_set_duty(0);
- break;
- }
- }
- static void status_led_set_vu(float norm) {
- if (s_status_mode != LED_VU) {
- return;
- }
- uint8_t duty = (uint8_t)(norm * (float)s_status_duty);
- status_led_set_duty(duty);
- }
- #else
- static void status_led_init(void) {
- }
- static void status_led_set_mode(led_mode_t mode) {
- (void)mode;
- }
- static void status_led_set_vu(float norm) {
- (void)norm;
- }
- #endif
- // ============================================================================
- // Error LED (simple on/off via LEDC)
- // ============================================================================
- #if CONFIG_LED_ERROR_GPIO >= 0
- #define ERROR_LED_CHANNEL LEDC_CHANNEL_1
- #define ERROR_LED_TIMER LEDC_TIMER_1
- static void error_led_init(void) {
- ledc_timer_config_t timer_cfg = {
- .speed_mode = LEDC_LOW_SPEED_MODE,
- .timer_num = ERROR_LED_TIMER,
- .duty_resolution = LEDC_TIMER_8_BIT,
- .freq_hz = 1000,
- .clk_cfg = LEDC_AUTO_CLK,
- };
- if (ledc_timer_config(&timer_cfg) != ESP_OK) {
- ESP_LOGE(TAG, "Error LED timer init failed");
- return;
- }
- ledc_channel_config_t ch_cfg = {
- .speed_mode = LEDC_LOW_SPEED_MODE,
- .channel = ERROR_LED_CHANNEL,
- .timer_sel = ERROR_LED_TIMER,
- .intr_type = LEDC_INTR_DISABLE,
- .gpio_num = CONFIG_LED_ERROR_GPIO,
- .duty = 0,
- .hpoint = 0,
- .flags = {.output_invert = LED_ERROR_INVERT_VAL},
- };
- if (ledc_channel_config(&ch_cfg) != ESP_OK) {
- ESP_LOGE(TAG, "Error LED channel init failed");
- return;
- }
- // Explicitly apply initial duty (off)
- ledc_set_duty(LEDC_LOW_SPEED_MODE, ERROR_LED_CHANNEL, 0);
- ledc_update_duty(LEDC_LOW_SPEED_MODE, ERROR_LED_CHANNEL);
- ESP_LOGI(TAG, "Error LED initialized on GPIO %d", CONFIG_LED_ERROR_GPIO);
- }
- static void error_led_set(bool on) {
- ledc_set_duty(LEDC_LOW_SPEED_MODE, ERROR_LED_CHANNEL, on ? s_brightness : 0);
- ledc_update_duty(LEDC_LOW_SPEED_MODE, ERROR_LED_CHANNEL);
- }
- #else
- static void error_led_init(void) {
- }
- static void error_led_set(bool on) {
- (void)on;
- }
- #endif
- // ============================================================================
- // RGB LED (WS2812 via led_strip)
- // ============================================================================
- #if CONFIG_LED_RGB_GPIO >= 0
- #include "led_strip.h"
- static led_strip_handle_t s_rgb_strip = NULL;
- static led_mode_t s_rgb_mode = LED_OFF;
- static void rgb_led_init(void) {
- led_strip_config_t strip_cfg = {
- .strip_gpio_num = CONFIG_LED_RGB_GPIO,
- .max_leds = 1,
- .led_model = LED_MODEL_WS2812,
- .flags.invert_out = false,
- };
- led_strip_rmt_config_t rmt_cfg = {
- .clk_src = RMT_CLK_SRC_DEFAULT,
- .resolution_hz = 10 * 1000 * 1000,
- .flags.with_dma = false,
- };
- if (led_strip_new_rmt_device(&strip_cfg, &rmt_cfg, &s_rgb_strip) != ESP_OK) {
- ESP_LOGE(TAG, "RGB LED init failed");
- s_rgb_strip = NULL;
- return;
- }
- led_strip_clear(s_rgb_strip);
- ESP_LOGI(TAG, "RGB LED initialized on GPIO %d", CONFIG_LED_RGB_GPIO);
- }
- static void rgb_led_set_color(uint8_t r, uint8_t g, uint8_t b) {
- if (!s_rgb_strip) {
- return;
- }
- led_strip_set_pixel(s_rgb_strip, 0, r, g, b);
- led_strip_refresh(s_rgb_strip);
- }
- static void rgb_led_clear(void) {
- if (!s_rgb_strip) {
- return;
- }
- led_strip_clear(s_rgb_strip);
- led_strip_refresh(s_rgb_strip);
- }
- static void rgb_led_set_mode(led_mode_t mode) {
- s_rgb_mode = mode;
- switch (mode) {
- case LED_OFF:
- rgb_led_clear();
- break;
- case LED_STEADY: {
- // Color depends on current state - handled by on_rtsp_event
- break;
- }
- case LED_VU:
- // Handled by led_audio_feed
- break;
- default:
- break;
- }
- }
- static void rgb_led_set_vu(float norm, float bass_ratio) {
- if (s_rgb_mode != LED_VU || !s_rgb_strip) {
- return;
- }
- if (norm <= 0.0f || s_brightness == 0) {
- rgb_led_clear();
- return;
- }
- uint8_t val = (uint8_t)(norm * (float)s_brightness);
- if (val < 1) {
- val = 1;
- }
- // Map to HSV hue: 170 (blue, quiet) -> 85 (green, medium) -> 0 (red, loud)
- uint16_t hue = (uint16_t)(170.0f * (1.0f - norm));
- // Shift towards purple/magenta when bassy
- if (bass_ratio > 0.3f) {
- hue = (uint16_t)(hue + (uint16_t)(bass_ratio * 60.0f));
- if (hue > 255) {
- hue = 255;
- }
- }
- // High saturation, reduce slightly at very high energy for warm white
- uint8_t sat = 255;
- if (norm > 0.85f) {
- sat = (uint8_t)(255 - (uint8_t)((norm - 0.85f) / 0.15f * 80.0f));
- }
- led_strip_set_pixel_hsv(s_rgb_strip, 0, hue, sat, val);
- led_strip_refresh(s_rgb_strip);
- }
- #else
- static void rgb_led_init(void) {
- }
- static void rgb_led_set_mode(led_mode_t mode) {
- (void)mode;
- }
- static void rgb_led_set_color(uint8_t r, uint8_t g, uint8_t b) {
- (void)r;
- (void)g;
- (void)b;
- }
- static void rgb_led_clear(void) {
- }
- static void rgb_led_set_vu(float norm, float bass_ratio) {
- (void)norm;
- (void)bass_ratio;
- }
- #endif
- // ============================================================================
- // RTSP Event Handler
- // ============================================================================
- typedef enum {
- STATE_STANDBY,
- STATE_PAUSED,
- STATE_PLAYING,
- STATE_ERROR,
- } led_state_t;
- static led_state_t s_prev_state = STATE_STANDBY;
- static led_state_t s_current_state = STATE_STANDBY;
- static uint8_t scale_bright(uint8_t v) {
- return (uint8_t)((uint16_t)v * s_brightness / 255);
- }
- static void render_state(led_state_t state) {
- switch (state) {
- case STATE_PLAYING:
- status_led_set_mode(get_status_mode_playing());
- rgb_led_set_mode(get_rgb_mode_playing());
- error_led_set(false);
- break;
- case STATE_PAUSED:
- status_led_set_mode(get_status_mode_paused());
- rgb_led_set_mode(get_rgb_mode_paused());
- if (get_rgb_mode_paused() == LED_STEADY) {
- #ifdef CONFIG_LED_RGB_COLOR_PAUSED
- uint32_t c = CONFIG_LED_RGB_COLOR_PAUSED;
- rgb_led_set_color(scale_bright((c >> 16) & 0xFF),
- scale_bright((c >> 8) & 0xFF), scale_bright(c & 0xFF));
- #else
- rgb_led_set_color(0, 0, scale_bright(0x33));
- #endif
- }
- error_led_set(false);
- break;
- case STATE_STANDBY:
- status_led_set_mode(get_status_mode_standby());
- rgb_led_set_mode(get_rgb_mode_standby());
- if (get_rgb_mode_standby() == LED_STEADY) {
- #ifdef CONFIG_LED_RGB_COLOR_STANDBY
- uint32_t c = CONFIG_LED_RGB_COLOR_STANDBY;
- rgb_led_set_color(scale_bright((c >> 16) & 0xFF),
- scale_bright((c >> 8) & 0xFF), scale_bright(c & 0xFF));
- #else
- rgb_led_set_color(0, scale_bright(0x11), 0);
- #endif
- }
- error_led_set(false);
- break;
- case STATE_ERROR:
- #if CONFIG_LED_ERROR_GPIO >= 0
- // Dedicated error LED - turn off status to avoid mixed signals
- status_led_set_mode(LED_OFF);
- #else
- // No error LED - use status LED to indicate error
- status_led_set_mode(LED_BLINK_FAST);
- #endif
- rgb_led_set_color(scale_bright(0x80), 0, 0);
- error_led_set(true);
- break;
- }
- }
- static void apply_state(led_state_t state) {
- s_prev_state = s_current_state;
- s_current_state = state;
- ESP_LOGI(TAG, "LED state change: %d -> %d", s_prev_state, state);
- render_state(state);
- }
- static void on_rtsp_event(rtsp_event_t event, const rtsp_event_data_t *data,
- void *user_data) {
- ESP_LOGD(TAG, "RTSP event: %d", event);
- switch (event) {
- case RTSP_EVENT_CLIENT_CONNECTED:
- apply_state(STATE_PAUSED);
- break;
- case RTSP_EVENT_PLAYING:
- apply_state(STATE_PLAYING);
- break;
- case RTSP_EVENT_PAUSED:
- apply_state(STATE_PAUSED);
- break;
- case RTSP_EVENT_DISCONNECTED:
- apply_state(STATE_STANDBY);
- break;
- case RTSP_EVENT_METADATA:
- break;
- }
- }
- // ============================================================================
- // Audio VU Processing
- // ============================================================================
- #define SILENCE_THRESH 200
- #define UPDATE_INTERVAL_US (1000000 / 30) // ~30 Hz
- static int64_t s_last_update_us = 0;
- void led_audio_feed(const int16_t *pcm, size_t stereo_samples) {
- if (stereo_samples == 0 || s_current_state != STATE_PLAYING) {
- return;
- }
- // Rate limit to ~30 Hz
- int64_t now = esp_timer_get_time();
- if (now - s_last_update_us < UPDATE_INTERVAL_US) {
- return;
- }
- s_last_update_us = now;
- size_t total = stereo_samples * 2;
- // Compute RMS energy
- uint64_t sum_sq = 0;
- for (size_t i = 0; i < total; i++) {
- int32_t s = pcm[i];
- sum_sq += (uint64_t)(s * s);
- }
- float rms = sqrtf((float)sum_sq / (float)total);
- // Simple bass energy estimate
- uint64_t diff_sum = 0;
- for (size_t i = 2; i < total; i += 2) {
- int32_t d = (int32_t)pcm[i] - (int32_t)pcm[i - 2];
- diff_sum += (uint64_t)(d < 0 ? -d : d);
- }
- float high_energy = (float)diff_sum / ((float)total / 2.0f);
- float bass_ratio = 0.0f;
- if (rms > SILENCE_THRESH) {
- bass_ratio = 1.0f - (high_energy / (rms * 2.0f + 1.0f));
- if (bass_ratio < 0.0f) {
- bass_ratio = 0.0f;
- }
- if (bass_ratio > 1.0f) {
- bass_ratio = 1.0f;
- }
- }
- float norm = 0.0f;
- if (rms >= SILENCE_THRESH) {
- norm = (rms - SILENCE_THRESH) / (16000.0f - SILENCE_THRESH);
- if (norm > 1.0f) {
- norm = 1.0f;
- }
- }
- status_led_set_vu(norm);
- rgb_led_set_vu(norm, bass_ratio);
- }
- // ============================================================================
- // Public API
- // ============================================================================
- void led_init(void) {
- ESP_LOGI(TAG, "Initializing LED subsystem");
- ESP_LOGI(TAG, " Status LED GPIO: %d", CONFIG_LED_STATUS_GPIO);
- ESP_LOGI(TAG, " Error LED GPIO: %d", CONFIG_LED_ERROR_GPIO);
- ESP_LOGI(TAG, " RGB LED GPIO: %d", CONFIG_LED_RGB_GPIO);
- ESP_LOGI(TAG, " LED brightness: %d", CONFIG_LED_STATUS_BRIGHTNESS);
- uint8_t saved;
- if (settings_get_led_brightness(&saved) == ESP_OK) {
- s_brightness = saved;
- }
- status_led_init();
- error_led_init();
- rgb_led_init();
- rtsp_events_register(on_rtsp_event, NULL);
- // Start in standby
- ESP_LOGI(TAG, "Starting in STANDBY state");
- apply_state(STATE_STANDBY);
- ESP_LOGI(TAG, "LED subsystem initialized");
- }
- void led_set_error(bool error) {
- if (error) {
- if (s_current_state != STATE_ERROR) {
- apply_state(STATE_ERROR);
- } else {
- render_state(STATE_ERROR);
- }
- } else if (s_current_state == STATE_ERROR) {
- apply_state(s_prev_state);
- }
- }
- esp_err_t led_set_brightness(uint8_t brightness) {
- esp_err_t err = settings_set_led_brightness(brightness);
- if (err != ESP_OK) {
- return err;
- }
- s_brightness = brightness;
- #if CONFIG_LED_STATUS_GPIO >= 0
- s_status_duty = brightness;
- if (s_status_mode == LED_STEADY ||
- (s_status_mode >= LED_BLINK_SLOW && s_status_on)) {
- status_led_set_duty(s_status_duty);
- }
- #endif
- // Re-render without changing previous/current state history.
- render_state(s_current_state);
- return ESP_OK;
- }
- uint8_t led_get_brightness(void) {
- return s_brightness;
- }
|