@@ -480,6 +562,17 @@
busy: false,
configMsg: '',
configMsgOk: false,
+ led: {
+ mode: 'color',
+ r: 0,
+ g: 120,
+ b: 255,
+ intensity: 0,
+ progress: 50,
+ digit: 0,
+ blinkMs: 350,
+ blinkCount: 1
+ },
connect() {
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
const url = proto + '//' + location.host + '/ws';
@@ -853,6 +946,49 @@
this.busy = false;
}
},
+ async ledRing(opts = {}) {
+ const clientId = opts.clientId ?? 0;
+ const mode = opts.mode ?? this.led.mode;
+ this.busy = true;
+ try {
+ const r = await fetch('/api/led-ring', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ mode,
+ client_id: clientId,
+ all_clients: !!opts.allClients,
+ slaves_only: !!opts.slavesOnly,
+ r: this.led.r,
+ g: this.led.g,
+ b: this.led.b,
+ intensity: this.led.intensity,
+ progress: this.led.progress,
+ digit: this.led.digit,
+ blink_ms: this.led.blinkMs,
+ blink_count: this.led.blinkCount
+ })
+ });
+ const data = await r.json();
+ if (!r.ok || !data.success) {
+ this.flash(data.error || 'LED-Ring fehlgeschlagen', false);
+ return;
+ }
+ let label = 'Master';
+ if (opts.allClients) {
+ label = opts.slavesOnly
+ ? `Alle Slaves (${data.slaves_updated})`
+ : `Alle + Master (${data.slaves_updated} Slaves)`;
+ } else if (clientId > 0) {
+ label = `Slave ${clientId}`;
+ }
+ this.flash(`LED ${mode} → ${label}`, true);
+ } catch (e) {
+ this.flash(String(e), false);
+ } finally {
+ this.busy = false;
+ }
+ },
async findMe(clientId = 0) {
this.busy = true;
try {
diff --git a/main/README.md b/main/README.md
index 2370200..955e54c 100644
--- a/main/README.md
+++ b/main/README.md
@@ -378,14 +378,19 @@ Control the 95-LED ring from the host. The firmware **does not** animate digits
| Field | Meaning |
|-------|---------|
-| `mode` | `0` = clear, `1` = progress bar, `2` = digit, `3` = blink full ring, `4` = find-me (R/G/B ×3 @ full brightness) |
+| `mode` | `0` = clear, `1` = progress, `2` = digit (0–10), `3` = blink, `4` = find-me, `5` = solid color (all LEDs) |
| `progress` | 0–100 (% of ring lit, mode `1`) |
-| `digit` | 0–10 (mode `2`, same segment maps as built-in digits) |
+| `digit` | 0–10 (mode `2`, segment maps in `led_ring.c`) |
| `r`, `g`, `b` | Color 0–255 |
| `intensity` | Brightness 0–255 (scaled into RGB; `0` → firmware default ~5 %) |
| `blink_ms`, `blink_count` | Pulse length and count (mode `3`; defaults 350 ms, 1) |
+| `client_id` | `0` = master ring only; `>0` = ESP-NOW unicast to one slave |
+| `all_clients` | Broadcast to all registered slaves |
+| `slaves_only` | With `all_clients`: do not change master ring |
-**Response:** `led_ring_progress_response` (`success`, `mode`, `progress`, `digit`).
+**Response:** `led_ring_progress_response` (`success`, `mode`, `progress`, `digit`, `client_id`, `slaves_updated`).
+
+Slaves receive the same command via ESP-NOW `ESPNOW_LED_RING` and run it locally.
```bash
go run . -port /dev/ttyUSB0 led-ring -mode progress -progress 75 -g 80 -b 255
@@ -395,6 +400,8 @@ go run . -port /dev/ttyUSB0 led-ring -mode blink -g 255 -blink-count 2
go run . -port /dev/ttyUSB0 find-me
go run . -port /dev/ttyUSB0 find-me -client 16
go run . -port /dev/ttyUSB0 led-ring -mode find-me
+go run . -port /dev/ttyUSB0 led-ring -mode color -r 255 -g 0 -b 0 -client 16
+go run . -port /dev/ttyUSB0 led-ring -mode digit -digit 5 -all
```
### CLIENT_INFO command
diff --git a/main/cmd/cmd_led_ring.c b/main/cmd/cmd_led_ring.c
index 5672ecd..c3874f5 100644
--- a/main/cmd/cmd_led_ring.c
+++ b/main/cmd/cmd_led_ring.c
@@ -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) {
diff --git a/main/cmd/cmd_led_ring.h b/main/cmd/cmd_led_ring.h
index 89b2487..f35f686 100644
--- a/main/cmd/cmd_led_ring.h
+++ b/main/cmd/cmd_led_ring.h
@@ -1,6 +1,12 @@
#ifndef CMD_LED_RING_H
#define CMD_LED_RING_H
+#include
+#include "uart_messages.pb.h"
+
+/** Apply LED ring command locally (master or slave). */
+bool cmd_led_ring_apply(const alox_LedRingProgressRequest *req);
+
void cmd_led_ring_register(void);
#endif
diff --git a/main/esp_now_comm.c b/main/esp_now_comm.c
index ec7a758..9f417b8 100644
--- a/main/esp_now_comm.c
+++ b/main/esp_now_comm.c
@@ -1,6 +1,7 @@
#include "bosch456.h"
#include "client_registry.h"
#include "esp_now_comm.h"
+#include "cmd_led_ring.h"
#include "led_ring.h"
#include "ota_espnow.h"
#include "pod_reboot.h"
@@ -209,6 +210,30 @@ static esp_err_t send_find_me(const uint8_t *dest_mac, uint32_t client_id) {
return send_message(dest_mac, &msg);
}
+static esp_err_t send_led_ring(const uint8_t *dest_mac, uint32_t client_id,
+ const alox_LedRingProgressRequest *req) {
+ if (req == NULL) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
+
+ msg.type = alox_EspNowMessageType_ESPNOW_LED_RING;
+ msg.which_payload = alox_EspNowMessage_led_ring_tag;
+ msg.payload.led_ring.client_id = client_id;
+ msg.payload.led_ring.mode = req->mode;
+ msg.payload.led_ring.progress = req->progress;
+ msg.payload.led_ring.digit = req->digit;
+ msg.payload.led_ring.r = req->r;
+ msg.payload.led_ring.g = req->g;
+ msg.payload.led_ring.b = req->b;
+ msg.payload.led_ring.intensity = req->intensity;
+ msg.payload.led_ring.blink_ms = req->blink_ms;
+ msg.payload.led_ring.blink_count = req->blink_count;
+
+ return send_message(dest_mac, &msg);
+}
+
static esp_err_t send_restart(const uint8_t *dest_mac, uint32_t client_id) {
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
@@ -347,6 +372,26 @@ esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN],
return err;
}
+esp_err_t esp_now_comm_send_led_ring(const uint8_t mac[CLIENT_MAC_LEN],
+ uint32_t client_id,
+ const alox_LedRingProgressRequest *req) {
+ if (mac == NULL || !s_config.master || req == NULL) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ char mac_str[18];
+ mac_to_str(mac, mac_str, sizeof(mac_str));
+ esp_err_t err = send_led_ring(mac, client_id, req);
+ if (err == ESP_OK) {
+ ESP_LOGI(TAG, "unicast LED_RING mode %lu to %s client_id=%lu",
+ (unsigned long)req->mode, mac_str, (unsigned long)client_id);
+ } else {
+ ESP_LOGW(TAG, "unicast LED_RING to %s failed: %s", mac_str,
+ esp_err_to_name(err));
+ }
+ return err;
+}
+
esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t seq) {
if (mac == NULL || !s_config.master) {
@@ -454,6 +499,36 @@ static void handle_slave_restart(const uint8_t *master_mac,
pod_schedule_restart();
}
+static void handle_slave_led_ring(const uint8_t *master_mac,
+ const alox_EspNowLedRing *msg) {
+ uint32_t my_id = s_own_mac[5];
+
+ if (msg->client_id != 0 && msg->client_id != my_id) {
+ return;
+ }
+
+ if (s_slave_joined && !mac_equal(master_mac, s_master_mac)) {
+ return;
+ }
+
+ alox_LedRingProgressRequest req = alox_LedRingProgressRequest_init_zero;
+ req.mode = msg->mode;
+ req.progress = msg->progress;
+ req.digit = msg->digit;
+ req.r = msg->r;
+ req.g = msg->g;
+ req.b = msg->b;
+ req.intensity = msg->intensity;
+ req.blink_ms = msg->blink_ms;
+ req.blink_count = msg->blink_count;
+
+ char mac_str[18];
+ mac_to_str(master_mac, mac_str, sizeof(mac_str));
+ ESP_LOGI(TAG, "LED_RING mode %lu from master %s (id=%lu)",
+ (unsigned long)req.mode, mac_str, (unsigned long)my_id);
+ cmd_led_ring_apply(&req);
+}
+
static void handle_slave_find_me(const uint8_t *master_mac,
const alox_EspNowFindMe *req) {
uint32_t my_id = s_own_mac[5];
@@ -704,6 +779,12 @@ static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data,
}
handle_slave_accel_stream(info->src_addr, &msg.payload.accel_stream);
break;
+ case alox_EspNowMessage_led_ring_tag:
+ if (!s_slave_joined || !mac_equal(info->src_addr, s_master_mac)) {
+ break;
+ }
+ handle_slave_led_ring(info->src_addr, &msg.payload.led_ring);
+ break;
case alox_EspNowMessage_find_me_tag:
if (!s_slave_joined || !mac_equal(info->src_addr, s_master_mac)) {
break;
diff --git a/main/esp_now_comm.h b/main/esp_now_comm.h
index b34252e..51a7906 100644
--- a/main/esp_now_comm.h
+++ b/main/esp_now_comm.h
@@ -4,6 +4,7 @@
#include "app_config.h"
#include "client_registry.h"
#include "esp_err.h"
+#include "uart_messages.pb.h"
esp_err_t esp_now_comm_init(const app_config_t *config);
@@ -23,6 +24,11 @@ esp_err_t esp_now_comm_send_unicast_test(const uint8_t mac[CLIENT_MAC_LEN],
esp_err_t esp_now_comm_send_find_me(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id);
+/** Master: LED ring command on one slave. */
+esp_err_t esp_now_comm_send_led_ring(const uint8_t mac[CLIENT_MAC_LEN],
+ uint32_t client_id,
+ const alox_LedRingProgressRequest *req);
+
/** Master: request reboot on one slave. */
esp_err_t esp_now_comm_send_restart(const uint8_t mac[CLIENT_MAC_LEN],
uint32_t client_id);
diff --git a/main/led_ring.c b/main/led_ring.c
index e0641b6..9ed05c5 100644
--- a/main/led_ring.c
+++ b/main/led_ring.c
@@ -116,6 +116,8 @@ void vTaskLedRing(void *pvParameters) {
for (int i = 0; i < digit.count; i++) {
led_strip_set_pixel(led_ring, RING_LEDS - digit.leds[i], r, g, b);
}
+ } else if (cmd.mode == LED_CMD_SET_COLOR) {
+ ring_fill_color(r, g, b);
} else if (cmd.mode == LED_CMD_PROGRESS) {
uint32_t lit = ((uint32_t)cmd.progress * RING_LEDS + 50) / 100;
if (lit > RING_LEDS) {
diff --git a/main/proto/esp_now_messages.pb.c b/main/proto/esp_now_messages.pb.c
index aca1be1..955bd86 100644
--- a/main/proto/esp_now_messages.pb.c
+++ b/main/proto/esp_now_messages.pb.c
@@ -30,6 +30,9 @@ PB_BIND(alox_EspNowAccelStream, alox_EspNowAccelStream, AUTO)
PB_BIND(alox_EspNowAccelSample, alox_EspNowAccelSample, AUTO)
+PB_BIND(alox_EspNowLedRing, alox_EspNowLedRing, AUTO)
+
+
PB_BIND(alox_EspNowOtaStart, alox_EspNowOtaStart, AUTO)
diff --git a/main/proto/esp_now_messages.pb.h b/main/proto/esp_now_messages.pb.h
index be8e98e..8fe2f31 100644
--- a/main/proto/esp_now_messages.pb.h
+++ b/main/proto/esp_now_messages.pb.h
@@ -24,7 +24,8 @@ typedef enum _alox_EspNowMessageType {
alox_EspNowMessageType_ESPNOW_FIND_ME = 10,
alox_EspNowMessageType_ESPNOW_RESTART = 11,
alox_EspNowMessageType_ESPNOW_ACCEL_SAMPLE = 12,
- alox_EspNowMessageType_ESPNOW_SET_ACCEL_STREAM = 13
+ alox_EspNowMessageType_ESPNOW_SET_ACCEL_STREAM = 13,
+ alox_EspNowMessageType_ESPNOW_LED_RING = 14
} alox_EspNowMessageType;
/* Struct definitions */
@@ -75,6 +76,20 @@ typedef struct _alox_EspNowAccelSample {
int32_t z;
} alox_EspNowAccelSample;
+/* * Master → slave: LED ring command (same modes as UART LedRingProgressRequest). */
+typedef struct _alox_EspNowLedRing {
+ uint32_t client_id;
+ uint32_t mode;
+ uint32_t progress;
+ uint32_t digit;
+ uint32_t r;
+ uint32_t g;
+ uint32_t b;
+ uint32_t intensity;
+ uint32_t blink_ms;
+ uint32_t blink_count;
+} alox_EspNowLedRing;
+
/* Master → slave: begin OTA (erase inactive slot; slave replies ESPNOW_OTA_STATUS). */
typedef struct _alox_EspNowOtaStart {
uint32_t total_size;
@@ -116,6 +131,7 @@ typedef struct _alox_EspNowMessage {
alox_EspNowRestart restart;
alox_EspNowAccelSample accel_sample;
alox_EspNowAccelStream accel_stream;
+ alox_EspNowLedRing led_ring;
} payload;
} alox_EspNowMessage;
@@ -126,8 +142,9 @@ extern "C" {
/* Helper constants for enums */
#define _alox_EspNowMessageType_MIN alox_EspNowMessageType_ESPNOW_UNKNOWN
-#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_SET_ACCEL_STREAM
-#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_SET_ACCEL_STREAM+1))
+#define _alox_EspNowMessageType_MAX alox_EspNowMessageType_ESPNOW_LED_RING
+#define _alox_EspNowMessageType_ARRAYSIZE ((alox_EspNowMessageType)(alox_EspNowMessageType_ESPNOW_LED_RING+1))
+
@@ -153,6 +170,7 @@ extern "C" {
#define alox_EspNowAccelDeadzone_init_default {0, 0}
#define alox_EspNowAccelStream_init_default {0, 0}
#define alox_EspNowAccelSample_init_default {0, 0, 0, 0}
+#define alox_EspNowLedRing_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define alox_EspNowOtaStart_init_default {0}
#define alox_EspNowOtaPayload_init_default {0, {0, {0}}}
#define alox_EspNowOtaEnd_init_default {0}
@@ -166,6 +184,7 @@ extern "C" {
#define alox_EspNowAccelDeadzone_init_zero {0, 0}
#define alox_EspNowAccelStream_init_zero {0, 0}
#define alox_EspNowAccelSample_init_zero {0, 0, 0, 0}
+#define alox_EspNowLedRing_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
#define alox_EspNowOtaStart_init_zero {0}
#define alox_EspNowOtaPayload_init_zero {0, {0, {0}}}
#define alox_EspNowOtaEnd_init_zero {0}
@@ -191,6 +210,16 @@ extern "C" {
#define alox_EspNowAccelSample_x_tag 2
#define alox_EspNowAccelSample_y_tag 3
#define alox_EspNowAccelSample_z_tag 4
+#define alox_EspNowLedRing_client_id_tag 1
+#define alox_EspNowLedRing_mode_tag 2
+#define alox_EspNowLedRing_progress_tag 3
+#define alox_EspNowLedRing_digit_tag 4
+#define alox_EspNowLedRing_r_tag 5
+#define alox_EspNowLedRing_g_tag 6
+#define alox_EspNowLedRing_b_tag 7
+#define alox_EspNowLedRing_intensity_tag 8
+#define alox_EspNowLedRing_blink_ms_tag 9
+#define alox_EspNowLedRing_blink_count_tag 10
#define alox_EspNowOtaStart_total_size_tag 1
#define alox_EspNowOtaPayload_seq_tag 1
#define alox_EspNowOtaPayload_data_tag 2
@@ -211,6 +240,7 @@ extern "C" {
#define alox_EspNowMessage_restart_tag 12
#define alox_EspNowMessage_accel_sample_tag 13
#define alox_EspNowMessage_accel_stream_tag 14
+#define alox_EspNowMessage_led_ring_tag 15
/* Struct field encoding specification for nanopb */
#define alox_EspNowUnicastTest_FIELDLIST(X, a) \
@@ -263,6 +293,20 @@ X(a, STATIC, SINGULAR, SINT32, z, 4)
#define alox_EspNowAccelSample_CALLBACK NULL
#define alox_EspNowAccelSample_DEFAULT NULL
+#define alox_EspNowLedRing_FIELDLIST(X, a) \
+X(a, STATIC, SINGULAR, UINT32, client_id, 1) \
+X(a, STATIC, SINGULAR, UINT32, mode, 2) \
+X(a, STATIC, SINGULAR, UINT32, progress, 3) \
+X(a, STATIC, SINGULAR, UINT32, digit, 4) \
+X(a, STATIC, SINGULAR, UINT32, r, 5) \
+X(a, STATIC, SINGULAR, UINT32, g, 6) \
+X(a, STATIC, SINGULAR, UINT32, b, 7) \
+X(a, STATIC, SINGULAR, UINT32, intensity, 8) \
+X(a, STATIC, SINGULAR, UINT32, blink_ms, 9) \
+X(a, STATIC, SINGULAR, UINT32, blink_count, 10)
+#define alox_EspNowLedRing_CALLBACK NULL
+#define alox_EspNowLedRing_DEFAULT NULL
+
#define alox_EspNowOtaStart_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, total_size, 1)
#define alox_EspNowOtaStart_CALLBACK NULL
@@ -300,7 +344,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,ota_status,payload.ota_status), 10)
X(a, STATIC, ONEOF, MESSAGE, (payload,find_me,payload.find_me), 11) \
X(a, STATIC, ONEOF, MESSAGE, (payload,restart,payload.restart), 12) \
X(a, STATIC, ONEOF, MESSAGE, (payload,accel_sample,payload.accel_sample), 13) \
-X(a, STATIC, ONEOF, MESSAGE, (payload,accel_stream,payload.accel_stream), 14)
+X(a, STATIC, ONEOF, MESSAGE, (payload,accel_stream,payload.accel_stream), 14) \
+X(a, STATIC, ONEOF, MESSAGE, (payload,led_ring,payload.led_ring), 15)
#define alox_EspNowMessage_CALLBACK NULL
#define alox_EspNowMessage_DEFAULT NULL
#define alox_EspNowMessage_payload_discover_MSGTYPE alox_EspNowDiscover
@@ -316,6 +361,7 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,accel_stream,payload.accel_stream),
#define alox_EspNowMessage_payload_restart_MSGTYPE alox_EspNowRestart
#define alox_EspNowMessage_payload_accel_sample_MSGTYPE alox_EspNowAccelSample
#define alox_EspNowMessage_payload_accel_stream_MSGTYPE alox_EspNowAccelStream
+#define alox_EspNowMessage_payload_led_ring_MSGTYPE alox_EspNowLedRing
extern const pb_msgdesc_t alox_EspNowUnicastTest_msg;
extern const pb_msgdesc_t alox_EspNowFindMe_msg;
@@ -325,6 +371,7 @@ extern const pb_msgdesc_t alox_EspNowSlavePresence_msg;
extern const pb_msgdesc_t alox_EspNowAccelDeadzone_msg;
extern const pb_msgdesc_t alox_EspNowAccelStream_msg;
extern const pb_msgdesc_t alox_EspNowAccelSample_msg;
+extern const pb_msgdesc_t alox_EspNowLedRing_msg;
extern const pb_msgdesc_t alox_EspNowOtaStart_msg;
extern const pb_msgdesc_t alox_EspNowOtaPayload_msg;
extern const pb_msgdesc_t alox_EspNowOtaEnd_msg;
@@ -340,6 +387,7 @@ extern const pb_msgdesc_t alox_EspNowMessage_msg;
#define alox_EspNowAccelDeadzone_fields &alox_EspNowAccelDeadzone_msg
#define alox_EspNowAccelStream_fields &alox_EspNowAccelStream_msg
#define alox_EspNowAccelSample_fields &alox_EspNowAccelSample_msg
+#define alox_EspNowLedRing_fields &alox_EspNowLedRing_msg
#define alox_EspNowOtaStart_fields &alox_EspNowOtaStart_msg
#define alox_EspNowOtaPayload_fields &alox_EspNowOtaPayload_msg
#define alox_EspNowOtaEnd_fields &alox_EspNowOtaEnd_msg
@@ -355,6 +403,7 @@ extern const pb_msgdesc_t alox_EspNowMessage_msg;
#define alox_EspNowAccelStream_size 8
#define alox_EspNowDiscover_size 6
#define alox_EspNowFindMe_size 6
+#define alox_EspNowLedRing_size 60
#define alox_EspNowOtaEnd_size 0
#define alox_EspNowOtaPayload_size 209
#define alox_EspNowOtaStart_size 6
diff --git a/main/proto/esp_now_messages.proto b/main/proto/esp_now_messages.proto
index def3642..ca6c6c4 100644
--- a/main/proto/esp_now_messages.proto
+++ b/main/proto/esp_now_messages.proto
@@ -19,6 +19,7 @@ enum EspNowMessageType {
ESPNOW_RESTART = 11;
ESPNOW_ACCEL_SAMPLE = 12;
ESPNOW_SET_ACCEL_STREAM = 13;
+ ESPNOW_LED_RING = 14;
}
message EspNowUnicastTest {
@@ -68,6 +69,20 @@ message EspNowAccelSample {
sint32 z = 4;
}
+/** Master → slave: LED ring command (same modes as UART LedRingProgressRequest). */
+message EspNowLedRing {
+ uint32 client_id = 1;
+ uint32 mode = 2;
+ uint32 progress = 3;
+ uint32 digit = 4;
+ uint32 r = 5;
+ uint32 g = 6;
+ uint32 b = 7;
+ uint32 intensity = 8;
+ uint32 blink_ms = 9;
+ uint32 blink_count = 10;
+}
+
// Master → slave: begin OTA (erase inactive slot; slave replies ESPNOW_OTA_STATUS).
message EspNowOtaStart {
uint32 total_size = 1;
@@ -105,5 +120,6 @@ message EspNowMessage {
EspNowRestart restart = 12;
EspNowAccelSample accel_sample = 13;
EspNowAccelStream accel_stream = 14;
+ EspNowLedRing led_ring = 15;
}
}
diff --git a/main/proto/uart_messages.pb.h b/main/proto/uart_messages.pb.h
index 950d8a6..0ad43df 100644
--- a/main/proto/uart_messages.pb.h
+++ b/main/proto/uart_messages.pb.h
@@ -139,8 +139,8 @@ typedef struct _alox_EspNowUnicastTestResponse {
uint32_t seq;
} alox_EspNowUnicastTestResponse;
-/* Host → device: LED ring display (progress bar, digit, clear, blink, or find-me).
- mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink full ring, 4=find-me (R/G/B ×3 @ full brightness). */
+/* Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
+ mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color. */
typedef struct _alox_LedRingProgressRequest {
uint32_t mode;
/* * 0–100: fraction of ring LEDs to light (mode=progress) */
@@ -156,6 +156,12 @@ typedef struct _alox_LedRingProgressRequest {
uint32_t blink_ms;
/* * Number of pulses (mode=blink, default 1) */
uint32_t blink_count;
+ /* * 0 = master ring only; >0 = one slave; ignored when all_clients */
+ uint32_t client_id;
+ /* * Broadcast to all registered slaves (and optionally master unless slaves_only) */
+ bool all_clients;
+ /* * With all_clients: do not change master ring */
+ bool slaves_only;
} alox_LedRingProgressRequest;
typedef struct _alox_LedRingProgressResponse {
@@ -163,6 +169,8 @@ typedef struct _alox_LedRingProgressResponse {
uint32_t mode;
uint32_t progress;
uint32_t digit;
+ uint32_t client_id;
+ uint32_t slaves_updated;
} alox_LedRingProgressResponse;
/* * Host → master: find-me on local ring (client_id=0) or ESP-NOW unicast to one slave. */
@@ -326,8 +334,8 @@ extern "C" {
#define alox_AccelSnapshotResponse_init_default {0, {alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default, alox_AccelSample_init_default}}
#define alox_EspNowUnicastTestRequest_init_default {0, 0}
#define alox_EspNowUnicastTestResponse_init_default {0, 0}
-#define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0}
-#define alox_LedRingProgressResponse_init_default {0, 0, 0, 0}
+#define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define alox_LedRingProgressResponse_init_default {0, 0, 0, 0, 0, 0}
#define alox_EspNowFindMeRequest_init_default {0}
#define alox_EspNowFindMeResponse_init_default {0, 0}
#define alox_RestartRequest_init_default {0}
@@ -356,8 +364,8 @@ extern "C" {
#define alox_AccelSnapshotResponse_init_zero {0, {alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero, alox_AccelSample_init_zero}}
#define alox_EspNowUnicastTestRequest_init_zero {0, 0}
#define alox_EspNowUnicastTestResponse_init_zero {0, 0}
-#define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0}
-#define alox_LedRingProgressResponse_init_zero {0, 0, 0, 0}
+#define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
+#define alox_LedRingProgressResponse_init_zero {0, 0, 0, 0, 0, 0}
#define alox_EspNowFindMeRequest_init_zero {0}
#define alox_EspNowFindMeResponse_init_zero {0, 0}
#define alox_RestartRequest_init_zero {0}
@@ -426,10 +434,15 @@ extern "C" {
#define alox_LedRingProgressRequest_intensity_tag 7
#define alox_LedRingProgressRequest_blink_ms_tag 8
#define alox_LedRingProgressRequest_blink_count_tag 9
+#define alox_LedRingProgressRequest_client_id_tag 10
+#define alox_LedRingProgressRequest_all_clients_tag 11
+#define alox_LedRingProgressRequest_slaves_only_tag 12
#define alox_LedRingProgressResponse_success_tag 1
#define alox_LedRingProgressResponse_mode_tag 2
#define alox_LedRingProgressResponse_progress_tag 3
#define alox_LedRingProgressResponse_digit_tag 4
+#define alox_LedRingProgressResponse_client_id_tag 5
+#define alox_LedRingProgressResponse_slaves_updated_tag 6
#define alox_EspNowFindMeRequest_client_id_tag 1
#define alox_EspNowFindMeResponse_success_tag 1
#define alox_EspNowFindMeResponse_client_id_tag 2
@@ -660,7 +673,10 @@ X(a, STATIC, SINGULAR, UINT32, g, 5) \
X(a, STATIC, SINGULAR, UINT32, b, 6) \
X(a, STATIC, SINGULAR, UINT32, intensity, 7) \
X(a, STATIC, SINGULAR, UINT32, blink_ms, 8) \
-X(a, STATIC, SINGULAR, UINT32, blink_count, 9)
+X(a, STATIC, SINGULAR, UINT32, blink_count, 9) \
+X(a, STATIC, SINGULAR, UINT32, client_id, 10) \
+X(a, STATIC, SINGULAR, BOOL, all_clients, 11) \
+X(a, STATIC, SINGULAR, BOOL, slaves_only, 12)
#define alox_LedRingProgressRequest_CALLBACK NULL
#define alox_LedRingProgressRequest_DEFAULT NULL
@@ -668,7 +684,9 @@ X(a, STATIC, SINGULAR, UINT32, blink_count, 9)
X(a, STATIC, SINGULAR, BOOL, success, 1) \
X(a, STATIC, SINGULAR, UINT32, mode, 2) \
X(a, STATIC, SINGULAR, UINT32, progress, 3) \
-X(a, STATIC, SINGULAR, UINT32, digit, 4)
+X(a, STATIC, SINGULAR, UINT32, digit, 4) \
+X(a, STATIC, SINGULAR, UINT32, client_id, 5) \
+X(a, STATIC, SINGULAR, UINT32, slaves_updated, 6)
#define alox_LedRingProgressResponse_CALLBACK NULL
#define alox_LedRingProgressResponse_DEFAULT NULL
@@ -826,8 +844,8 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
#define alox_EspNowFindMeResponse_size 8
#define alox_EspNowUnicastTestRequest_size 12
#define alox_EspNowUnicastTestResponse_size 8
-#define alox_LedRingProgressRequest_size 54
-#define alox_LedRingProgressResponse_size 20
+#define alox_LedRingProgressRequest_size 64
+#define alox_LedRingProgressResponse_size 32
#define alox_OtaEndPayload_size 0
#define alox_OtaPayload_size 209
#define alox_OtaSlaveProgressEntry_size 30
diff --git a/main/proto/uart_messages.proto b/main/proto/uart_messages.proto
index 20b63e0..1d2b89f 100644
--- a/main/proto/uart_messages.proto
+++ b/main/proto/uart_messages.proto
@@ -160,8 +160,8 @@ message EspNowUnicastTestResponse {
uint32 seq = 2;
}
-// Host → device: LED ring display (progress bar, digit, clear, blink, or find-me).
-// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink full ring, 4=find-me (R/G/B ×3 @ full brightness).
+// Host → master: LED ring on master (client_id=0) and/or slaves via ESP-NOW.
+// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink, 4=find-me, 5=all LEDs solid color.
message LedRingProgressRequest {
uint32 mode = 1;
/** 0–100: fraction of ring LEDs to light (mode=progress) */
@@ -177,6 +177,12 @@ message LedRingProgressRequest {
uint32 blink_ms = 8;
/** Number of pulses (mode=blink, default 1) */
uint32 blink_count = 9;
+ /** 0 = master ring only; >0 = one slave; ignored when all_clients */
+ uint32 client_id = 10;
+ /** Broadcast to all registered slaves (and optionally master unless slaves_only) */
+ bool all_clients = 11;
+ /** With all_clients: do not change master ring */
+ bool slaves_only = 12;
}
message LedRingProgressResponse {
@@ -184,6 +190,8 @@ message LedRingProgressResponse {
uint32 mode = 2;
uint32 progress = 3;
uint32 digit = 4;
+ uint32 client_id = 5;
+ uint32 slaves_updated = 6;
}
/** Host → master: find-me on local ring (client_id=0) or ESP-NOW unicast to one slave. */