Improve heartbeat indication and docs

This commit is contained in:
2025-12-16 16:19:33 +02:00
parent f3d5e4018b
commit 89ded8b119
6 changed files with 176 additions and 37 deletions

View File

@@ -6,6 +6,8 @@
#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
@@ -20,36 +22,65 @@
#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;
}
for (size_t i = 0; i < WS2812_STATUS_LED_COUNT; ++i) {
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_active_channel == i) {
g = 60;
} else if (s_led_state[i]) {
g = 18;
} else if (!s_led_state[i]) {
b = 5;
} else if (s_ack_state[i]) {
g = (s_active_channel == i) ? 60 : 25;
} else {
b = 10;
b = (s_active_channel == i) ? 60 : 25;
}
ESP_RETURN_ON_ERROR(led_strip_set_pixel(s_strip, i, r, g, b), TAG,
"led pixel set failed");
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);
}
return led_strip_refresh(s_strip);
xSemaphoreGive(s_ws_lock);
return err;
}
esp_err_t ws2812_status_init(void)
@@ -77,8 +108,16 @@ esp_err_t ws2812_status_init(void)
"Не вдалося створити 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;
@@ -86,12 +125,78 @@ esp_err_t ws2812_status_init(void)
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();
}
@@ -119,6 +224,15 @@ esp_err_t ws2812_status_set_error(bool has_error)
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();
@@ -127,10 +241,15 @@ esp_err_t ws2812_status_refresh_from_dcdc(void)
: WS2812_STATUS_LED_COUNT;
for (size_t i = 0; i < count; ++i) {
s_led_state[i] = dcdc_get_state(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();
}