Add LED ring control per client and broadcast over REST and WebSocket.
Solid color mode fills all ring LEDs; master routes UART commands to slaves via ESPNOW_LED_RING. goTool exposes POST /api/led-ring, WebSocket set_led_ring, and a dashboard LED panel with master/slave/all targets. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+129
-72
@@ -1,5 +1,7 @@
|
||||
#include "cmd_led_ring.h"
|
||||
#include "client_registry.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_now_comm.h"
|
||||
#include "led_ring.h"
|
||||
#include "uart_cmd.h"
|
||||
|
||||
@@ -10,6 +12,7 @@ static const char *TAG = "[LED_RING_CMD]";
|
||||
#define LED_RING_MODE_DIGIT 2
|
||||
#define LED_RING_MODE_BLINK 3
|
||||
#define LED_RING_MODE_FIND_ME 4
|
||||
#define LED_RING_MODE_COLOR 5
|
||||
|
||||
static uint8_t clamp_u8(uint32_t v) {
|
||||
if (v > 255) {
|
||||
@@ -32,7 +35,82 @@ static uint8_t resolve_intensity(uint32_t intensity) {
|
||||
return clamp_u8(intensity);
|
||||
}
|
||||
|
||||
static void reply(bool success, uint32_t mode, uint32_t progress, uint32_t digit) {
|
||||
bool cmd_led_ring_apply(const alox_LedRingProgressRequest *req) {
|
||||
if (req == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t mode = req->mode;
|
||||
uint8_t r = clamp_u8(req->r);
|
||||
uint8_t g = clamp_u8(req->g);
|
||||
uint8_t b = clamp_u8(req->b);
|
||||
uint8_t intensity = resolve_intensity(req->intensity);
|
||||
led_command_t cmd = {0};
|
||||
|
||||
switch (mode) {
|
||||
case LED_RING_MODE_CLEAR:
|
||||
cmd.mode = LED_CMD_CLEAR;
|
||||
led_ring_send_command(&cmd);
|
||||
return true;
|
||||
|
||||
case LED_RING_MODE_COLOR:
|
||||
cmd.mode = LED_CMD_SET_COLOR;
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
led_ring_send_command(&cmd);
|
||||
return true;
|
||||
|
||||
case LED_RING_MODE_PROGRESS: {
|
||||
cmd.mode = LED_CMD_PROGRESS;
|
||||
cmd.progress = clamp_progress(req->progress);
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
led_ring_send_command(&cmd);
|
||||
return true;
|
||||
}
|
||||
|
||||
case LED_RING_MODE_DIGIT:
|
||||
if (req->digit > 10) {
|
||||
return false;
|
||||
}
|
||||
cmd.mode = LED_CMD_SET_DIGIT;
|
||||
cmd.value = (uint8_t)req->digit;
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
led_ring_send_command(&cmd);
|
||||
return true;
|
||||
|
||||
case LED_RING_MODE_FIND_ME:
|
||||
led_ring_find_me();
|
||||
return true;
|
||||
|
||||
case LED_RING_MODE_BLINK:
|
||||
cmd.mode = LED_CMD_BLINK;
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
cmd.blink_ms = (uint16_t)(req->blink_ms > 0 ? req->blink_ms : 350);
|
||||
cmd.blink_count = req->blink_count > 0 ? (uint8_t)req->blink_count : 1;
|
||||
if (cmd.blink_count == 0) {
|
||||
cmd.blink_count = 1;
|
||||
}
|
||||
led_ring_send_command(&cmd);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void reply(bool success, uint32_t mode, uint32_t progress, uint32_t digit,
|
||||
uint32_t client_id, uint32_t slaves_updated) {
|
||||
alox_UartMessage response;
|
||||
uart_cmd_init_response(&response, alox_MessageType_LED_RING,
|
||||
alox_UartMessage_led_ring_progress_response_tag);
|
||||
@@ -40,16 +118,26 @@ static void reply(bool success, uint32_t mode, uint32_t progress, uint32_t digit
|
||||
response.payload.led_ring_progress_response.mode = mode;
|
||||
response.payload.led_ring_progress_response.progress = progress;
|
||||
response.payload.led_ring_progress_response.digit = digit;
|
||||
response.payload.led_ring_progress_response.client_id = client_id;
|
||||
response.payload.led_ring_progress_response.slaves_updated = slaves_updated;
|
||||
uart_cmd_send(&response, TAG);
|
||||
}
|
||||
|
||||
static esp_err_t push_led_ring_to_slave(const client_info_t *client,
|
||||
const alox_LedRingProgressRequest *req) {
|
||||
if (client == NULL || req == NULL) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
return esp_now_comm_send_led_ring(client->mac, client->id, req);
|
||||
}
|
||||
|
||||
static void handle_led_ring(const uint8_t *data, size_t len) {
|
||||
alox_UartMessage uart_msg;
|
||||
alox_LedRingProgressRequest req = alox_LedRingProgressRequest_init_zero;
|
||||
|
||||
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||
ESP_LOGW(TAG, "decode failed");
|
||||
reply(false, 0, 0, 0);
|
||||
reply(false, 0, 0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,84 +149,53 @@ static void handle_led_ring(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
uint32_t mode = req.mode;
|
||||
uint8_t r = clamp_u8(req.r);
|
||||
uint8_t g = clamp_u8(req.g);
|
||||
uint8_t b = clamp_u8(req.b);
|
||||
uint8_t intensity = resolve_intensity(req.intensity);
|
||||
|
||||
led_command_t cmd = {0};
|
||||
|
||||
switch (mode) {
|
||||
case LED_RING_MODE_CLEAR:
|
||||
cmd.mode = LED_CMD_CLEAR;
|
||||
led_ring_send_command(&cmd);
|
||||
ESP_LOGI(TAG, "clear");
|
||||
reply(true, mode, 0, 0);
|
||||
return;
|
||||
|
||||
case LED_RING_MODE_PROGRESS: {
|
||||
uint8_t progress = clamp_progress(req.progress);
|
||||
cmd.mode = LED_CMD_PROGRESS;
|
||||
cmd.progress = progress;
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
led_ring_send_command(&cmd);
|
||||
ESP_LOGI(TAG, "progress %u%% rgb=%u,%u,%u", (unsigned)progress,
|
||||
(unsigned)r, (unsigned)g, (unsigned)b);
|
||||
reply(true, mode, progress, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
case LED_RING_MODE_DIGIT: {
|
||||
if (req.digit > 10) {
|
||||
ESP_LOGW(TAG, "digit %lu out of range", (unsigned long)req.digit);
|
||||
reply(false, mode, 0, req.digit);
|
||||
return;
|
||||
if (req.all_clients) {
|
||||
size_t n = client_registry_count();
|
||||
uint32_t sent = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
const client_info_t *client = client_registry_at(i);
|
||||
if (client == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (push_led_ring_to_slave(client, &req) == ESP_OK) {
|
||||
sent++;
|
||||
}
|
||||
}
|
||||
cmd.mode = LED_CMD_SET_DIGIT;
|
||||
cmd.value = (uint8_t)req.digit;
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
led_ring_send_command(&cmd);
|
||||
ESP_LOGI(TAG, "digit %u rgb=%u,%u,%u", (unsigned)cmd.value, (unsigned)r,
|
||||
(unsigned)g, (unsigned)b);
|
||||
reply(true, mode, 0, req.digit);
|
||||
return;
|
||||
}
|
||||
|
||||
case LED_RING_MODE_FIND_ME:
|
||||
led_ring_find_me();
|
||||
ESP_LOGI(TAG, "find-me");
|
||||
reply(true, mode, 0, 0);
|
||||
return;
|
||||
|
||||
case LED_RING_MODE_BLINK: {
|
||||
cmd.mode = LED_CMD_BLINK;
|
||||
cmd.r = r;
|
||||
cmd.g = g;
|
||||
cmd.b = b;
|
||||
cmd.intensity = intensity;
|
||||
cmd.blink_ms = (uint16_t)(req.blink_ms > 0 ? req.blink_ms : 350);
|
||||
cmd.blink_count = req.blink_count > 0 ? (uint8_t)req.blink_count : 1;
|
||||
if (cmd.blink_count == 0) {
|
||||
cmd.blink_count = 1;
|
||||
bool local_ok = true;
|
||||
if (!req.slaves_only) {
|
||||
local_ok = cmd_led_ring_apply(&req);
|
||||
}
|
||||
led_ring_send_command(&cmd);
|
||||
ESP_LOGI(TAG, "blink x%u %u ms rgb=%u,%u,%u", (unsigned)cmd.blink_count,
|
||||
(unsigned)cmd.blink_ms, (unsigned)r, (unsigned)g, (unsigned)b);
|
||||
reply(true, mode, 0, 0);
|
||||
ESP_LOGI(TAG, "LED ring mode %lu → %u/%u slaves%s", (unsigned long)mode,
|
||||
(unsigned)sent, (unsigned)n, req.slaves_only ? "" : " + master");
|
||||
reply(local_ok || sent > 0, mode, req.progress, req.digit, 0, sent);
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
ESP_LOGW(TAG, "unknown mode %lu", (unsigned long)mode);
|
||||
reply(false, mode, 0, 0);
|
||||
if (req.client_id == 0) {
|
||||
bool ok = cmd_led_ring_apply(&req);
|
||||
ESP_LOGI(TAG, "LED ring mode %lu on master", (unsigned long)mode);
|
||||
reply(ok, mode, req.progress, req.digit, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const client_info_t *client = client_registry_find_by_id(req.client_id);
|
||||
if (client == NULL) {
|
||||
ESP_LOGW(TAG, "client id %lu not in registry", (unsigned long)req.client_id);
|
||||
reply(false, mode, req.progress, req.digit, req.client_id, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t err = push_led_ring_to_slave(client, &req);
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "LED ring mode %lu → slave %lu", (unsigned long)mode,
|
||||
(unsigned long)req.client_id);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "LED ring to slave %lu failed: %s",
|
||||
(unsigned long)req.client_id, esp_err_to_name(err));
|
||||
}
|
||||
reply(err == ESP_OK, mode, req.progress, req.digit, req.client_id,
|
||||
err == ESP_OK ? 1 : 0);
|
||||
}
|
||||
|
||||
void cmd_led_ring_register(void) {
|
||||
|
||||
Reference in New Issue
Block a user