315 lines
8.8 KiB
C
315 lines
8.8 KiB
C
#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;
|
||
}
|