Files
watch-watch/main/ws2812_status.c

315 lines
8.8 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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;
}