#include "ws2812_status.h" #include "dcdc_controller.h" #include "led_strip.h" #include "esp_check.h" #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/timers.h" #include "freertos/semphr.h" #include "sdkconfig.h" #ifndef CONFIG_WATCH_WS2812_LED_COUNT #define CONFIG_WATCH_WS2812_LED_COUNT 5 #endif #ifndef CONFIG_WATCH_WS2812_GPIO #define CONFIG_WATCH_WS2812_GPIO 8 #endif #ifndef CONFIG_WATCH_WS2812_RMT_RESOLUTION #define CONFIG_WATCH_WS2812_RMT_RESOLUTION (10 * 1000 * 1000) #endif #define WS2812_STATUS_GPIO ((gpio_num_t)CONFIG_WATCH_WS2812_GPIO) #define WS2812_STATUS_RESOLUTION_HZ CONFIG_WATCH_WS2812_RMT_RESOLUTION #define WS2812_POLL_BLINK_PERIOD pdMS_TO_TICKS(250) static const char *TAG = "ws2812"; static led_strip_handle_t s_strip; static bool s_led_state[WS2812_STATUS_LED_COUNT]; static bool s_ack_state[WS2812_STATUS_LED_COUNT]; static bool s_error_state; static size_t s_active_channel = SIZE_MAX; static bool s_polling_active[WS2812_STATUS_LED_COUNT]; static bool s_polling_visible[WS2812_STATUS_LED_COUNT]; static TickType_t s_polling_expire_tick[WS2812_STATUS_LED_COUNT]; static TimerHandle_t s_polling_timers[WS2812_STATUS_LED_COUNT]; static SemaphoreHandle_t s_ws_lock; static void ws2812_polling_timer_cb(TimerHandle_t timer); static esp_err_t ws2812_status_apply(void) { if (!s_strip) { return ESP_ERR_INVALID_STATE; } if (!s_ws_lock) { return ESP_ERR_INVALID_STATE; } if (xSemaphoreTake(s_ws_lock, portMAX_DELAY) != pdTRUE) { return ESP_ERR_TIMEOUT; } esp_err_t err = ESP_OK; for (size_t i = 0; i < WS2812_STATUS_LED_COUNT && err == ESP_OK; ++i) { uint8_t r = 0, g = 0, b = 0; if (s_error_state) { r = 40; } else if (!s_led_state[i]) { b = 5; } else if (s_ack_state[i]) { g = (s_active_channel == i) ? 60 : 25; } else { b = (s_active_channel == i) ? 60 : 25; } if (s_polling_active[i] && !s_error_state && s_led_state[i]) { if (!s_polling_visible[i]) { r /= 4; g /= 4; b /= 4; } } // Swap R/G to match physical RGB order (driver outputs GRB) err = led_strip_set_pixel(s_strip, i, g, r, b); } if (err == ESP_OK) { err = led_strip_refresh(s_strip); } xSemaphoreGive(s_ws_lock); return err; } esp_err_t ws2812_status_init(void) { if (s_strip) { return ESP_OK; } led_strip_config_t strip_config = { .strip_gpio_num = WS2812_STATUS_GPIO, .max_leds = WS2812_STATUS_LED_COUNT, .led_model = LED_MODEL_WS2812, .flags.invert_out = false, }; led_strip_rmt_config_t rmt_cfg = { .clk_src = RMT_CLK_SRC_DEFAULT, .resolution_hz = WS2812_STATUS_RESOLUTION_HZ, .flags.with_dma = false, }; ESP_RETURN_ON_ERROR( led_strip_new_rmt_device(&strip_config, &rmt_cfg, &s_strip), TAG, "Не вдалося створити RMT пристрій для WS2812"); ESP_RETURN_ON_ERROR(led_strip_clear(s_strip), TAG, "clear fail"); s_ws_lock = xSemaphoreCreateMutex(); ESP_RETURN_ON_FALSE(s_ws_lock, ESP_ERR_NO_MEM, TAG, "mutex alloc failed"); for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) { s_led_state[i] = false; s_ack_state[i] = false; s_polling_active[i] = false; s_polling_visible[i] = false; s_polling_expire_tick[i] = 0; s_polling_timers[i] = NULL; } s_active_channel = SIZE_MAX; s_error_state = false; return ws2812_status_apply(); } static void ws2812_polling_timer_cb(TimerHandle_t timer) { size_t channel = (size_t)pvTimerGetTimerID(timer); if (channel >= WS2812_STATUS_LED_COUNT) { return; } TickType_t now = xTaskGetTickCount(); if (now >= s_polling_expire_tick[channel]) { s_polling_active[channel] = false; s_polling_visible[channel] = false; s_polling_expire_tick[channel] = 0; xTimerStop(timer, 0); } else { s_polling_visible[channel] = !s_polling_visible[channel]; } ws2812_status_apply(); } esp_err_t ws2812_status_indicate_polling(size_t channel, uint32_t duration_ms) { if (channel >= WS2812_STATUS_LED_COUNT) { return ESP_ERR_INVALID_ARG; } if (!s_strip) { return ESP_ERR_INVALID_STATE; } if (duration_ms == 0) { duration_ms = 2000; } TickType_t duration_ticks = pdMS_TO_TICKS(duration_ms); if (duration_ticks == 0) { duration_ticks = 1; } if (!s_polling_timers[channel]) { s_polling_timers[channel] = xTimerCreate("ws2812_poll", WS2812_POLL_BLINK_PERIOD, pdTRUE, (void *)channel, ws2812_polling_timer_cb); if (!s_polling_timers[channel]) { return ESP_ERR_NO_MEM; } } s_polling_active[channel] = true; s_polling_visible[channel] = true; s_polling_expire_tick[channel] = xTaskGetTickCount() + duration_ticks; esp_err_t err = ws2812_status_apply(); if (err != ESP_OK) { return err; } BaseType_t res; if (xTimerIsTimerActive(s_polling_timers[channel]) == pdTRUE) { res = xTimerReset(s_polling_timers[channel], 0); } else { res = xTimerStart(s_polling_timers[channel], 0); } return (res == pdPASS) ? ESP_OK : ESP_FAIL; } esp_err_t ws2812_status_set_channel_state(size_t channel, bool enabled) { if (channel >= WS2812_STATUS_LED_COUNT) { return ESP_ERR_INVALID_ARG; } s_led_state[channel] = enabled; if (!enabled) { s_ack_state[channel] = false; } return ws2812_status_apply(); } esp_err_t ws2812_status_mark_active(size_t channel) { if (channel >= WS2812_STATUS_LED_COUNT) { return ESP_ERR_INVALID_ARG; } s_active_channel = channel; return ws2812_status_apply(); } esp_err_t ws2812_status_clear_active(void) { s_active_channel = SIZE_MAX; return ws2812_status_apply(); } esp_err_t ws2812_status_set_error(bool has_error) { s_error_state = has_error; if (has_error) { s_active_channel = SIZE_MAX; } return ws2812_status_apply(); } esp_err_t ws2812_status_set_ack_state(size_t channel, bool received) { if (channel >= WS2812_STATUS_LED_COUNT) { return ESP_ERR_INVALID_ARG; } s_ack_state[channel] = received; return ws2812_status_apply(); } esp_err_t ws2812_status_refresh_from_dcdc(void) { const size_t available_channels = dcdc_channel_count(); const size_t count = available_channels < WS2812_STATUS_LED_COUNT ? available_channels : WS2812_STATUS_LED_COUNT; for (size_t i = 0; i < count; ++i) { bool enabled = dcdc_get_state(i); s_led_state[i] = enabled; if (!enabled) { s_ack_state[i] = false; } } for (size_t i = count; i < WS2812_STATUS_LED_COUNT; ++i) { s_led_state[i] = false; s_ack_state[i] = false; } return ws2812_status_apply(); } esp_err_t ws2812_status_play_bringup_animation(size_t cycles, uint32_t step_delay_ms) { if (!s_strip) { return ESP_ERR_INVALID_STATE; } if (cycles == 0) { cycles = 1; } if (step_delay_ms == 0) { step_delay_ms = 150; } bool saved_led_state[WS2812_STATUS_LED_COUNT]; for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) { saved_led_state[i] = s_led_state[i]; } const bool saved_error = s_error_state; const size_t saved_active = s_active_channel; s_error_state = false; s_active_channel = SIZE_MAX; const TickType_t delay_ticks = pdMS_TO_TICKS(step_delay_ms); esp_err_t err = ESP_OK; for (size_t cycle = 0; cycle < cycles && err == ESP_OK; ++cycle) { for (size_t led = 0; led < WS2812_STATUS_LED_COUNT; ++led) { for (size_t idx = 0; idx < WS2812_STATUS_LED_COUNT; ++idx) { const uint8_t g = (idx == led) ? 35 : 0; err = led_strip_set_pixel(s_strip, idx, 0, g, 0); if (err != ESP_OK) { break; } } if (err != ESP_OK) { break; } err = led_strip_refresh(s_strip); if (err != ESP_OK) { break; } vTaskDelay(delay_ticks); } } for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) { s_led_state[i] = saved_led_state[i]; } s_error_state = saved_error; s_active_channel = saved_active; esp_err_t restore_err = ws2812_status_apply(); if (err != ESP_OK) { return err; } return restore_err; }