Dim LED ring, add blink mode, and signal OTA outcome on the ring.
Default brightness is ~5%; UART blink mode and green/red pulses mark OTA success or failure. Failed UART uploads skip ESP-NOW distribution. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
508b684fdf
commit
8931912583
@ -29,7 +29,7 @@ go run . -port /dev/ttyUSB0 clients
|
|||||||
| `serve` | — | Web dashboard at `http://localhost:8080` (WebSocket live updates) |
|
| `serve` | — | Web dashboard at `http://localhost:8080` (WebSocket live updates) |
|
||||||
| `ota` | 16–19 | UART firmware upload to master; firmware then pushes to slaves via ESP-NOW |
|
| `ota` | 16–19 | UART firmware upload to master; firmware then pushes to slaves via ESP-NOW |
|
||||||
| `ota-progress` | 21 | Query per-slave ESP-NOW OTA progress on the master (`-client N`, default all) |
|
| `ota-progress` | 21 | Query per-slave ESP-NOW OTA progress on the master (`-client N`, default all) |
|
||||||
| `led-ring` | 8 | LED ring: `-mode clear\|progress\|digit`, `-progress`, `-digit`, RGB, `-intensity` |
|
| `led-ring` | 8 | LED ring: `-mode clear\|progress\|digit\|blink`, `-progress`, `-digit`, RGB, `-intensity` (0 = ~5 %), `-blink-ms`, `-blink-count` |
|
||||||
|
|
||||||
`clients` requires slaves to have responded to master discover broadcasts first.
|
`clients` requires slaves to have responded to master discover broadcasts first.
|
||||||
|
|
||||||
|
|||||||
@ -11,17 +11,20 @@ const (
|
|||||||
ledRingModeClear = 0
|
ledRingModeClear = 0
|
||||||
ledRingModeProgress = 1
|
ledRingModeProgress = 1
|
||||||
ledRingModeDigit = 2
|
ledRingModeDigit = 2
|
||||||
|
ledRingModeBlink = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
func runLedRing(sp *serialPort, args []string) error {
|
func runLedRing(sp *serialPort, args []string) error {
|
||||||
fs := flag.NewFlagSet("led-ring", flag.ExitOnError)
|
fs := flag.NewFlagSet("led-ring", flag.ExitOnError)
|
||||||
mode := fs.String("mode", "progress", "clear, progress, or digit")
|
mode := fs.String("mode", "progress", "clear, progress, digit, or blink")
|
||||||
progress := fs.Uint("progress", 0, "fill level 0–100 (mode=progress)")
|
progress := fs.Uint("progress", 0, "fill level 0–100 (mode=progress)")
|
||||||
digit := fs.Uint("digit", 0, "digit 0–10 (mode=digit)")
|
digit := fs.Uint("digit", 0, "digit 0–10 (mode=digit)")
|
||||||
r := fs.Uint("r", 0, "red 0–255")
|
r := fs.Uint("r", 0, "red 0–255")
|
||||||
g := fs.Uint("g", 255, "green 0–255")
|
g := fs.Uint("g", 255, "green 0–255")
|
||||||
b := fs.Uint("b", 0, "blue 0–255")
|
b := fs.Uint("b", 0, "blue 0–255")
|
||||||
intensity := fs.Uint("intensity", 255, "brightness 0–255")
|
intensity := fs.Uint("intensity", 0, "brightness 0–255 (0 = device default ~5%)")
|
||||||
|
blinkMs := fs.Uint("blink-ms", 350, "pulse length in ms (mode=blink)")
|
||||||
|
blinkCount := fs.Uint("blink-count", 1, "number of pulses (mode=blink)")
|
||||||
if err := fs.Parse(args); err != nil {
|
if err := fs.Parse(args); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -34,8 +37,10 @@ func runLedRing(sp *serialPort, args []string) error {
|
|||||||
modeVal = ledRingModeProgress
|
modeVal = ledRingModeProgress
|
||||||
case "digit":
|
case "digit":
|
||||||
modeVal = ledRingModeDigit
|
modeVal = ledRingModeDigit
|
||||||
|
case "blink":
|
||||||
|
modeVal = ledRingModeBlink
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown -mode %q (clear, progress, digit)", *mode)
|
return fmt.Errorf("unknown -mode %q (clear, progress, digit, blink)", *mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, err := sp.ledRingProgress(&pb.LedRingProgressRequest{
|
resp, err := sp.ledRingProgress(&pb.LedRingProgressRequest{
|
||||||
@ -46,6 +51,8 @@ func runLedRing(sp *serialPort, args []string) error {
|
|||||||
G: uint32(*g),
|
G: uint32(*g),
|
||||||
B: uint32(*b),
|
B: uint32(*b),
|
||||||
Intensity: uint32(*intensity),
|
Intensity: uint32(*intensity),
|
||||||
|
BlinkMs: uint32(*blinkMs),
|
||||||
|
BlinkCount: uint32(*blinkCount),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@ -1068,8 +1068,8 @@ func (x *EspNowUnicastTestResponse) GetSeq() uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host → device: LED ring display (progress bar, digit, or clear).
|
// Host → device: LED ring display (progress bar, digit, clear, or blink).
|
||||||
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10, same layout as firmware digit maps).
|
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink full ring.
|
||||||
type LedRingProgressRequest struct {
|
type LedRingProgressRequest struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Mode uint32 `protobuf:"varint,1,opt,name=mode,proto3" json:"mode,omitempty"`
|
Mode uint32 `protobuf:"varint,1,opt,name=mode,proto3" json:"mode,omitempty"`
|
||||||
@ -1080,8 +1080,12 @@ type LedRingProgressRequest struct {
|
|||||||
R uint32 `protobuf:"varint,4,opt,name=r,proto3" json:"r,omitempty"`
|
R uint32 `protobuf:"varint,4,opt,name=r,proto3" json:"r,omitempty"`
|
||||||
G uint32 `protobuf:"varint,5,opt,name=g,proto3" json:"g,omitempty"`
|
G uint32 `protobuf:"varint,5,opt,name=g,proto3" json:"g,omitempty"`
|
||||||
B uint32 `protobuf:"varint,6,opt,name=b,proto3" json:"b,omitempty"`
|
B uint32 `protobuf:"varint,6,opt,name=b,proto3" json:"b,omitempty"`
|
||||||
// * 0–255 brightness scale applied to r/g/b
|
// * 0–255 brightness scale; 0 = firmware default (~5 %)
|
||||||
Intensity uint32 `protobuf:"varint,7,opt,name=intensity,proto3" json:"intensity,omitempty"`
|
Intensity uint32 `protobuf:"varint,7,opt,name=intensity,proto3" json:"intensity,omitempty"`
|
||||||
|
// * Pulse length in ms (mode=blink, default 350)
|
||||||
|
BlinkMs uint32 `protobuf:"varint,8,opt,name=blink_ms,json=blinkMs,proto3" json:"blink_ms,omitempty"`
|
||||||
|
// * Number of pulses (mode=blink, default 1)
|
||||||
|
BlinkCount uint32 `protobuf:"varint,9,opt,name=blink_count,json=blinkCount,proto3" json:"blink_count,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
@ -1165,6 +1169,20 @@ func (x *LedRingProgressRequest) GetIntensity() uint32 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *LedRingProgressRequest) GetBlinkMs() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.BlinkMs
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *LedRingProgressRequest) GetBlinkCount() uint32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.BlinkCount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type LedRingProgressResponse struct {
|
type LedRingProgressResponse struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
|
Success bool `protobuf:"varint,1,opt,name=success,proto3" json:"success,omitempty"`
|
||||||
@ -1707,7 +1725,7 @@ const file_uart_messages_proto_rawDesc = "" +
|
|||||||
"\x03seq\x18\x02 \x01(\rR\x03seq\"G\n" +
|
"\x03seq\x18\x02 \x01(\rR\x03seq\"G\n" +
|
||||||
"\x19EspNowUnicastTestResponse\x12\x18\n" +
|
"\x19EspNowUnicastTestResponse\x12\x18\n" +
|
||||||
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x10\n" +
|
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x10\n" +
|
||||||
"\x03seq\x18\x02 \x01(\rR\x03seq\"\xa6\x01\n" +
|
"\x03seq\x18\x02 \x01(\rR\x03seq\"\xe2\x01\n" +
|
||||||
"\x16LedRingProgressRequest\x12\x12\n" +
|
"\x16LedRingProgressRequest\x12\x12\n" +
|
||||||
"\x04mode\x18\x01 \x01(\rR\x04mode\x12\x1a\n" +
|
"\x04mode\x18\x01 \x01(\rR\x04mode\x12\x1a\n" +
|
||||||
"\bprogress\x18\x02 \x01(\rR\bprogress\x12\x14\n" +
|
"\bprogress\x18\x02 \x01(\rR\bprogress\x12\x14\n" +
|
||||||
@ -1715,7 +1733,10 @@ const file_uart_messages_proto_rawDesc = "" +
|
|||||||
"\x01r\x18\x04 \x01(\rR\x01r\x12\f\n" +
|
"\x01r\x18\x04 \x01(\rR\x01r\x12\f\n" +
|
||||||
"\x01g\x18\x05 \x01(\rR\x01g\x12\f\n" +
|
"\x01g\x18\x05 \x01(\rR\x01g\x12\f\n" +
|
||||||
"\x01b\x18\x06 \x01(\rR\x01b\x12\x1c\n" +
|
"\x01b\x18\x06 \x01(\rR\x01b\x12\x1c\n" +
|
||||||
"\tintensity\x18\a \x01(\rR\tintensity\"y\n" +
|
"\tintensity\x18\a \x01(\rR\tintensity\x12\x19\n" +
|
||||||
|
"\bblink_ms\x18\b \x01(\rR\ablinkMs\x12\x1f\n" +
|
||||||
|
"\vblink_count\x18\t \x01(\rR\n" +
|
||||||
|
"blinkCount\"y\n" +
|
||||||
"\x17LedRingProgressResponse\x12\x18\n" +
|
"\x17LedRingProgressResponse\x12\x18\n" +
|
||||||
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x12\n" +
|
"\asuccess\x18\x01 \x01(\bR\asuccess\x12\x12\n" +
|
||||||
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x1a\n" +
|
"\x04mode\x18\x02 \x01(\rR\x04mode\x12\x1a\n" +
|
||||||
|
|||||||
@ -253,6 +253,8 @@ Inactive app partition is selected with `esp_ota_get_next_update_partition()`; `
|
|||||||
|
|
||||||
`OTA_END` can take a long time on the wire (slave flash + ESP-NOW); the host should use a generous read timeout.
|
`OTA_END` can take a long time on the wire (slave flash + ESP-NOW); the host should use a generous read timeout.
|
||||||
|
|
||||||
|
During OTA the LED ring shows progress at ~5 % brightness: **blue** while the image is written (UART on master, ESP-NOW on slaves), **green** on the master while it forwards the image to slaves over ESP-NOW. On **success** the ring gives one short **green** blink; on **failure** one **red** blink and ESP-NOW distribution is not started (failed UART upload / `OTA_END` validation).
|
||||||
|
|
||||||
`OTA_START_ESPNOW` (type `20`): re-run ESP-NOW distribution from the last staged image without a new UART upload (no-op if nothing staged).
|
`OTA_START_ESPNOW` (type `20`): re-run ESP-NOW distribution from the last staged image without a new UART upload (no-op if nothing staged).
|
||||||
|
|
||||||
Implementation: `ota_uart.c` (4 KiB buffer, `esp_ota_write`), `ota_espnow.c`, `cmd_ota.c`.
|
Implementation: `ota_uart.c` (4 KiB buffer, `esp_ota_write`), `ota_espnow.c`, `cmd_ota.c`.
|
||||||
@ -319,11 +321,12 @@ Control the 95-LED ring from the host. The firmware **does not** animate digits
|
|||||||
|
|
||||||
| Field | Meaning |
|
| Field | Meaning |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `mode` | `0` = clear, `1` = progress bar, `2` = digit |
|
| `mode` | `0` = clear, `1` = progress bar, `2` = digit, `3` = blink full ring |
|
||||||
| `progress` | 0–100 (% of ring lit, mode `1`) |
|
| `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`, same segment maps as built-in digits) |
|
||||||
| `r`, `g`, `b` | Color 0–255 |
|
| `r`, `g`, `b` | Color 0–255 |
|
||||||
| `intensity` | Brightness 0–255 (scaled into RGB; `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) |
|
||||||
|
|
||||||
**Response:** `led_ring_progress_response` (`success`, `mode`, `progress`, `digit`).
|
**Response:** `led_ring_progress_response` (`success`, `mode`, `progress`, `digit`).
|
||||||
|
|
||||||
@ -331,6 +334,7 @@ Control the 95-LED ring from the host. The firmware **does not** animate digits
|
|||||||
go run . -port /dev/ttyUSB0 led-ring -mode progress -progress 75 -g 80 -b 255
|
go run . -port /dev/ttyUSB0 led-ring -mode progress -progress 75 -g 80 -b 255
|
||||||
go run . -port /dev/ttyUSB0 led-ring -mode digit -digit 7 -r 255 -g 200
|
go run . -port /dev/ttyUSB0 led-ring -mode digit -digit 7 -r 255 -g 200
|
||||||
go run . -port /dev/ttyUSB0 led-ring -mode clear
|
go run . -port /dev/ttyUSB0 led-ring -mode clear
|
||||||
|
go run . -port /dev/ttyUSB0 led-ring -mode blink -g 255 -blink-count 2
|
||||||
```
|
```
|
||||||
|
|
||||||
### CLIENT_INFO command
|
### CLIENT_INFO command
|
||||||
|
|||||||
@ -8,6 +8,7 @@ static const char *TAG = "[LED_RING_CMD]";
|
|||||||
#define LED_RING_MODE_CLEAR 0
|
#define LED_RING_MODE_CLEAR 0
|
||||||
#define LED_RING_MODE_PROGRESS 1
|
#define LED_RING_MODE_PROGRESS 1
|
||||||
#define LED_RING_MODE_DIGIT 2
|
#define LED_RING_MODE_DIGIT 2
|
||||||
|
#define LED_RING_MODE_BLINK 3
|
||||||
|
|
||||||
static uint8_t clamp_u8(uint32_t v) {
|
static uint8_t clamp_u8(uint32_t v) {
|
||||||
if (v > 255) {
|
if (v > 255) {
|
||||||
@ -23,13 +24,11 @@ static uint8_t clamp_progress(uint32_t v) {
|
|||||||
return (uint8_t)v;
|
return (uint8_t)v;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void apply_intensity(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t intensity) {
|
static uint8_t resolve_intensity(uint32_t intensity) {
|
||||||
if (intensity == 0) {
|
if (intensity == 0) {
|
||||||
return;
|
return LED_RING_DEFAULT_INTENSITY;
|
||||||
}
|
}
|
||||||
*r = (uint16_t)(*r) * intensity / 255;
|
return clamp_u8(intensity);
|
||||||
*g = (uint16_t)(*g) * intensity / 255;
|
|
||||||
*b = (uint16_t)(*b) * intensity / 255;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reply(bool success, uint32_t mode, uint32_t progress, uint32_t digit) {
|
static void reply(bool success, uint32_t mode, uint32_t progress, uint32_t digit) {
|
||||||
@ -64,11 +63,7 @@ static void handle_led_ring(const uint8_t *data, size_t len) {
|
|||||||
uint8_t r = clamp_u8(req.r);
|
uint8_t r = clamp_u8(req.r);
|
||||||
uint8_t g = clamp_u8(req.g);
|
uint8_t g = clamp_u8(req.g);
|
||||||
uint8_t b = clamp_u8(req.b);
|
uint8_t b = clamp_u8(req.b);
|
||||||
uint8_t intensity = clamp_u8(req.intensity);
|
uint8_t intensity = resolve_intensity(req.intensity);
|
||||||
if (intensity == 0) {
|
|
||||||
intensity = 255;
|
|
||||||
}
|
|
||||||
apply_intensity(&r, &g, &b, intensity);
|
|
||||||
|
|
||||||
led_command_t cmd = {0};
|
led_command_t cmd = {0};
|
||||||
|
|
||||||
@ -106,6 +101,7 @@ static void handle_led_ring(const uint8_t *data, size_t len) {
|
|||||||
cmd.r = r;
|
cmd.r = r;
|
||||||
cmd.g = g;
|
cmd.g = g;
|
||||||
cmd.b = b;
|
cmd.b = b;
|
||||||
|
cmd.intensity = intensity;
|
||||||
led_ring_send_command(&cmd);
|
led_ring_send_command(&cmd);
|
||||||
ESP_LOGI(TAG, "digit %u rgb=%u,%u,%u", (unsigned)cmd.value, (unsigned)r,
|
ESP_LOGI(TAG, "digit %u rgb=%u,%u,%u", (unsigned)cmd.value, (unsigned)r,
|
||||||
(unsigned)g, (unsigned)b);
|
(unsigned)g, (unsigned)b);
|
||||||
@ -113,6 +109,24 @@ static void handle_led_ring(const uint8_t *data, size_t len) {
|
|||||||
return;
|
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;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
ESP_LOGW(TAG, "unknown mode %lu", (unsigned long)mode);
|
ESP_LOGW(TAG, "unknown mode %lu", (unsigned long)mode);
|
||||||
reply(false, mode, 0, 0);
|
reply(false, mode, 0, 0);
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
#include "cmd_ota.h"
|
#include "cmd_ota.h"
|
||||||
|
#include "led_ring.h"
|
||||||
#include "ota_espnow.h"
|
#include "ota_espnow.h"
|
||||||
#include "ota_uart.h"
|
#include "ota_uart.h"
|
||||||
#include "uart_cmd.h"
|
#include "uart_cmd.h"
|
||||||
@ -15,6 +16,15 @@ static const char *TAG = "[OTA_CMD]";
|
|||||||
#define OTA_DIST_STACK 8192
|
#define OTA_DIST_STACK 8192
|
||||||
#define OTA_DIST_PRIO 5
|
#define OTA_DIST_PRIO 5
|
||||||
|
|
||||||
|
/** UART OTA upload to this node (master). */
|
||||||
|
#define OTA_LED_UART_R 0
|
||||||
|
#define OTA_LED_UART_G 0
|
||||||
|
#define OTA_LED_UART_B 255
|
||||||
|
/** ESP-NOW distribution from master to slaves. */
|
||||||
|
#define OTA_LED_ESPNOW_TX_R 0
|
||||||
|
#define OTA_LED_ESPNOW_TX_G 255
|
||||||
|
#define OTA_LED_ESPNOW_TX_B 0
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint32_t written;
|
uint32_t written;
|
||||||
int slot;
|
int slot;
|
||||||
@ -32,6 +42,11 @@ static void send_ota_status(ota_uart_status_t status, uint32_t err_code) {
|
|||||||
uart_cmd_send(&response, TAG);
|
uart_cmd_send(&response, TAG);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void send_ota_failed(uint32_t err_code) {
|
||||||
|
led_ring_ota_failed();
|
||||||
|
send_ota_status(OTA_UART_ST_FAILED, err_code);
|
||||||
|
}
|
||||||
|
|
||||||
static void send_ota_distributing(uint32_t kind, uint32_t bytes_done,
|
static void send_ota_distributing(uint32_t kind, uint32_t bytes_done,
|
||||||
uint32_t target_slot) {
|
uint32_t target_slot) {
|
||||||
alox_UartMessage response;
|
alox_UartMessage response;
|
||||||
@ -46,7 +61,9 @@ static void send_ota_distributing(uint32_t kind, uint32_t bytes_done,
|
|||||||
|
|
||||||
static void ota_dist_aggregate(uint32_t bytes_done, uint32_t total_bytes,
|
static void ota_dist_aggregate(uint32_t bytes_done, uint32_t total_bytes,
|
||||||
uint8_t slave_count) {
|
uint8_t slave_count) {
|
||||||
(void)total_bytes;
|
(void)slave_count;
|
||||||
|
led_ring_show_ota_progress(bytes_done, total_bytes, OTA_LED_ESPNOW_TX_R,
|
||||||
|
OTA_LED_ESPNOW_TX_G, OTA_LED_ESPNOW_TX_B);
|
||||||
send_ota_distributing(OTA_DIST_AGGREGATE, bytes_done, (uint32_t)slave_count);
|
send_ota_distributing(OTA_DIST_AGGREGATE, bytes_done, (uint32_t)slave_count);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +85,7 @@ static void ota_prepare_task(void *param) {
|
|||||||
|
|
||||||
int slot = ota_uart_prepare(total_size);
|
int slot = ota_uart_prepare(total_size);
|
||||||
if (slot < 0) {
|
if (slot < 0) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 1);
|
send_ota_failed(1);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -82,6 +99,9 @@ static void ota_prepare_task(void *param) {
|
|||||||
response.payload.ota_status.error = 0;
|
response.payload.ota_status.error = 0;
|
||||||
uart_cmd_send(&response, TAG);
|
uart_cmd_send(&response, TAG);
|
||||||
|
|
||||||
|
led_ring_show_ota_progress(0, total_size, OTA_LED_UART_R, OTA_LED_UART_G,
|
||||||
|
OTA_LED_UART_B);
|
||||||
|
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +110,7 @@ static void handle_ota_start(const uint8_t *data, size_t len) {
|
|||||||
alox_OtaStartPayload req = alox_OtaStartPayload_init_zero;
|
alox_OtaStartPayload req = alox_OtaStartPayload_init_zero;
|
||||||
|
|
||||||
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 2);
|
send_ota_failed( 2);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,13 +122,13 @@ static void handle_ota_start(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
if (req.total_size == 0) {
|
if (req.total_size == 0) {
|
||||||
ESP_LOGW(TAG, "OTA_START: total_size required");
|
ESP_LOGW(TAG, "OTA_START: total_size required");
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 3);
|
send_ota_failed( 3);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ota_uart_is_active()) {
|
if (ota_uart_is_active()) {
|
||||||
ESP_LOGW(TAG, "OTA_START while session active");
|
ESP_LOGW(TAG, "OTA_START while session active");
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 4);
|
send_ota_failed( 4);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -116,7 +136,7 @@ static void handle_ota_start(const uint8_t *data, size_t len) {
|
|||||||
(void *)(uintptr_t)req.total_size, OTA_PREPARE_PRIO,
|
(void *)(uintptr_t)req.total_size, OTA_PREPARE_PRIO,
|
||||||
NULL) != pdPASS) {
|
NULL) != pdPASS) {
|
||||||
ESP_LOGE(TAG, "failed to create ota_prepare task");
|
ESP_LOGE(TAG, "failed to create ota_prepare task");
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 5);
|
send_ota_failed( 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,7 +145,7 @@ static void handle_ota_payload(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
if (uart_cmd_decode(data, len, &uart_msg) != ESP_OK) {
|
||||||
ESP_LOGW(TAG, "OTA_PAYLOAD decode failed");
|
ESP_LOGW(TAG, "OTA_PAYLOAD decode failed");
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 10);
|
send_ota_failed( 10);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,34 +154,47 @@ static void handle_ota_payload(const uint8_t *data, size_t len) {
|
|||||||
if (req_ptr == NULL) {
|
if (req_ptr == NULL) {
|
||||||
ESP_LOGW(TAG, "OTA_PAYLOAD: missing ota_payload (which=%u)",
|
ESP_LOGW(TAG, "OTA_PAYLOAD: missing ota_payload (which=%u)",
|
||||||
(unsigned)uart_msg.which_payload);
|
(unsigned)uart_msg.which_payload);
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 11);
|
send_ota_failed( 11);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req_ptr->data.size == 0) {
|
if (req_ptr->data.size == 0) {
|
||||||
ESP_LOGW(TAG, "OTA_PAYLOAD: empty data (seq=%lu)",
|
ESP_LOGW(TAG, "OTA_PAYLOAD: empty data (seq=%lu)",
|
||||||
(unsigned long)req_ptr->seq);
|
(unsigned long)req_ptr->seq);
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 11);
|
send_ota_failed( 11);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ota_uart_is_active()) {
|
if (!ota_uart_is_active()) {
|
||||||
ESP_LOGW(TAG, "OTA_PAYLOAD without active session (seq=%lu)",
|
ESP_LOGW(TAG, "OTA_PAYLOAD without active session (seq=%lu)",
|
||||||
(unsigned long)req_ptr->seq);
|
(unsigned long)req_ptr->seq);
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 12);
|
send_ota_failed( 12);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ota_feed_result_t r =
|
ota_feed_result_t r =
|
||||||
ota_uart_feed(req_ptr->data.bytes, req_ptr->data.size);
|
ota_uart_feed(req_ptr->data.bytes, req_ptr->data.size);
|
||||||
if (r == OTA_FEED_ERROR) {
|
if (r == OTA_FEED_ERROR) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 13);
|
send_ota_failed( 13);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (r == OTA_FEED_BLOCK_WRITTEN) {
|
if (r == OTA_FEED_BLOCK_WRITTEN) {
|
||||||
|
uint32_t total = ota_uart_total_size();
|
||||||
|
uint32_t done = ota_uart_bytes_written();
|
||||||
ESP_LOGI(TAG, "OTA block ack (%lu bytes in flash)",
|
ESP_LOGI(TAG, "OTA block ack (%lu bytes in flash)",
|
||||||
(unsigned long)ota_uart_bytes_written());
|
(unsigned long)done);
|
||||||
|
led_ring_show_ota_progress(done, total, OTA_LED_UART_R, OTA_LED_UART_G,
|
||||||
|
OTA_LED_UART_B);
|
||||||
send_ota_status(OTA_UART_ST_BLOCK_ACK, 0);
|
send_ota_status(OTA_UART_ST_BLOCK_ACK, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == OTA_FEED_OK) {
|
||||||
|
uint32_t total = ota_uart_total_size();
|
||||||
|
if (total > 0) {
|
||||||
|
led_ring_show_ota_progress(ota_uart_bytes_received(), total,
|
||||||
|
OTA_LED_UART_R, OTA_LED_UART_G, OTA_LED_UART_B);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -175,19 +208,21 @@ static void ota_distribute_task(void *param) {
|
|||||||
const esp_partition_t *part = NULL;
|
const esp_partition_t *part = NULL;
|
||||||
uint32_t image_size = 0;
|
uint32_t image_size = 0;
|
||||||
if (!ota_uart_get_staged_image(&part, &image_size)) {
|
if (!ota_uart_get_staged_image(&part, &image_size)) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 30);
|
send_ota_failed( 30);
|
||||||
free(job);
|
free(job);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
led_ring_show_ota_progress(0, image_size, OTA_LED_ESPNOW_TX_R, OTA_LED_ESPNOW_TX_G,
|
||||||
|
OTA_LED_ESPNOW_TX_B);
|
||||||
send_ota_distributing(OTA_DIST_AGGREGATE, 0, 0);
|
send_ota_distributing(OTA_DIST_AGGREGATE, 0, 0);
|
||||||
|
|
||||||
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
ESP_LOGE(TAG, "slave OTA distribution failed: %s", esp_err_to_name(err));
|
ESP_LOGE(TAG, "slave OTA distribution failed: %s", esp_err_to_name(err));
|
||||||
ota_uart_clear_staged();
|
ota_uart_clear_staged();
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 31);
|
send_ota_failed(31);
|
||||||
free(job);
|
free(job);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
return;
|
return;
|
||||||
@ -195,12 +230,15 @@ static void ota_distribute_task(void *param) {
|
|||||||
|
|
||||||
err = ota_uart_apply_boot();
|
err = ota_uart_apply_boot();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, (uint32_t)err);
|
send_ota_failed((uint32_t)err);
|
||||||
free(job);
|
free(job);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
led_ring_show_ota_progress(image_size, image_size, OTA_LED_ESPNOW_TX_R,
|
||||||
|
OTA_LED_ESPNOW_TX_G, OTA_LED_ESPNOW_TX_B);
|
||||||
|
|
||||||
alox_UartMessage response;
|
alox_UartMessage response;
|
||||||
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
|
uart_cmd_init_response(&response, alox_MessageType_OTA_STATUS,
|
||||||
alox_UartMessage_ota_status_tag);
|
alox_UartMessage_ota_status_tag);
|
||||||
@ -211,6 +249,7 @@ static void ota_distribute_task(void *param) {
|
|||||||
response.payload.ota_status.error = 0;
|
response.payload.ota_status.error = 0;
|
||||||
uart_cmd_send(&response, TAG);
|
uart_cmd_send(&response, TAG);
|
||||||
|
|
||||||
|
led_ring_ota_success();
|
||||||
free(job);
|
free(job);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
@ -220,30 +259,36 @@ static void handle_ota_end(const uint8_t *data, size_t len) {
|
|||||||
(void)len;
|
(void)len;
|
||||||
|
|
||||||
if (!ota_uart_is_active()) {
|
if (!ota_uart_is_active()) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 20);
|
send_ota_failed( 20);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ota_dist_job_t *job = calloc(1, sizeof(*job));
|
ota_dist_job_t *job = calloc(1, sizeof(*job));
|
||||||
if (job == NULL) {
|
if (job == NULL) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 21);
|
send_ota_failed( 21);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
job->written = ota_uart_bytes_written();
|
job->written = ota_uart_bytes_written();
|
||||||
job->slot = ota_uart_target_slot();
|
job->slot = ota_uart_target_slot();
|
||||||
|
uint32_t uart_total = ota_uart_total_size();
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
esp_err_t err = ota_uart_finish(false, &success);
|
esp_err_t err = ota_uart_finish(false, &success);
|
||||||
if (err != ESP_OK || !success) {
|
if (err != ESP_OK || !success) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, (uint32_t)err);
|
send_ota_failed((uint32_t)err);
|
||||||
free(job);
|
free(job);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uart_total > 0) {
|
||||||
|
led_ring_show_ota_progress(job->written, uart_total, OTA_LED_UART_R,
|
||||||
|
OTA_LED_UART_G, OTA_LED_UART_B);
|
||||||
|
}
|
||||||
|
|
||||||
if (xTaskCreate(ota_distribute_task, "ota_dist", OTA_DIST_STACK, job,
|
if (xTaskCreate(ota_distribute_task, "ota_dist", OTA_DIST_STACK, job,
|
||||||
OTA_DIST_PRIO, NULL) != pdPASS) {
|
OTA_DIST_PRIO, NULL) != pdPASS) {
|
||||||
ESP_LOGE(TAG, "failed to create ota_dist task");
|
ESP_LOGE(TAG, "failed to create ota_dist task");
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 22);
|
send_ota_failed( 22);
|
||||||
free(job);
|
free(job);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -253,26 +298,26 @@ static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
|
|||||||
(void)len;
|
(void)len;
|
||||||
|
|
||||||
if (ota_uart_is_active()) {
|
if (ota_uart_is_active()) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 40);
|
send_ota_failed( 40);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const esp_partition_t *part = NULL;
|
const esp_partition_t *part = NULL;
|
||||||
uint32_t image_size = 0;
|
uint32_t image_size = 0;
|
||||||
if (!ota_uart_get_staged_image(&part, &image_size)) {
|
if (!ota_uart_get_staged_image(&part, &image_size)) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 41);
|
send_ota_failed( 41);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
esp_err_t err = ota_espnow_distribute(part, image_size, &s_dist_progress);
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, 42);
|
send_ota_failed( 42);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ota_uart_apply_boot();
|
err = ota_uart_apply_boot();
|
||||||
if (err != ESP_OK) {
|
if (err != ESP_OK) {
|
||||||
send_ota_status(OTA_UART_ST_FAILED, (uint32_t)err);
|
send_ota_failed( (uint32_t)err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,6 +328,7 @@ static void handle_ota_start_espnow(const uint8_t *data, size_t len) {
|
|||||||
response.payload.ota_status.bytes_written = image_size;
|
response.payload.ota_status.bytes_written = image_size;
|
||||||
response.payload.ota_status.error = 0;
|
response.payload.ota_status.error = 0;
|
||||||
uart_cmd_send(&response, TAG);
|
uart_cmd_send(&response, TAG);
|
||||||
|
led_ring_ota_success();
|
||||||
}
|
}
|
||||||
|
|
||||||
void cmd_ota_register(void) {
|
void cmd_ota_register(void) {
|
||||||
|
|||||||
108
main/led_ring.c
108
main/led_ring.c
@ -3,6 +3,8 @@
|
|||||||
#include "driver/i2c_types.h"
|
#include "driver/i2c_types.h"
|
||||||
#include "esp_err.h"
|
#include "esp_err.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
#include "led_strip.h"
|
#include "led_strip.h"
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
@ -16,6 +18,8 @@ static led_strip_handle_t led_ring;
|
|||||||
|
|
||||||
#define RING_LEDS 95
|
#define RING_LEDS 95
|
||||||
#define LED_RING_PIN 7
|
#define LED_RING_PIN 7
|
||||||
|
#define LED_RING_BLINK_ON_MS 350
|
||||||
|
#define LED_RING_BLINK_OFF_MS 150
|
||||||
|
|
||||||
static QueueHandle_t led_queue;
|
static QueueHandle_t led_queue;
|
||||||
|
|
||||||
@ -43,20 +47,33 @@ const uint8_t d9[] = {19, 20, 21, 22, 23, 24, 25, 26, 27, 46, 47, 58,
|
|||||||
const uint8_t d10[] = {46, 50, 57, 61, 65, 72, 76, 78, 80,
|
const uint8_t d10[] = {46, 50, 57, 61, 65, 72, 76, 78, 80,
|
||||||
82, 84, 86, 88, 90, 92, 93, 94, 95};
|
82, 84, 86, 88, 90, 92, 93, 94, 95};
|
||||||
|
|
||||||
// Lookup Array for the Digits
|
|
||||||
const digit_definition_t digit_lookup[] = {
|
const digit_definition_t digit_lookup[] = {
|
||||||
{d0, sizeof(d0)}, {d1, sizeof(d1)}, {d2, sizeof(d2)}, {d3, sizeof(d3)},
|
{d0, sizeof(d0)}, {d1, sizeof(d1)}, {d2, sizeof(d2)}, {d3, sizeof(d3)},
|
||||||
{d4, sizeof(d4)}, {d5, sizeof(d5)}, {d6, sizeof(d6)}, {d7, sizeof(d7)},
|
{d4, sizeof(d4)}, {d5, sizeof(d5)}, {d6, sizeof(d6)}, {d7, sizeof(d7)},
|
||||||
{d8, sizeof(d8)}, {d9, sizeof(d9)}, {d10, sizeof(d10)}};
|
{d8, sizeof(d8)}, {d9, sizeof(d9)}, {d10, sizeof(d10)}};
|
||||||
|
|
||||||
|
void led_ring_scale_rgb(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t intensity) {
|
||||||
|
if (intensity == 0) {
|
||||||
|
intensity = LED_RING_DEFAULT_INTENSITY;
|
||||||
|
}
|
||||||
|
*r = (uint16_t)(*r) * intensity / 255;
|
||||||
|
*g = (uint16_t)(*g) * intensity / 255;
|
||||||
|
*b = (uint16_t)(*b) * intensity / 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ring_fill_color(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
for (uint32_t i = 0; i < RING_LEDS; i++) {
|
||||||
|
led_strip_set_pixel(led_ring, i, r, g, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void vTaskLedRing(void *pvParameters) {
|
void vTaskLedRing(void *pvParameters) {
|
||||||
/* LED Ring config */
|
|
||||||
led_strip_config_t ring_config = {
|
led_strip_config_t ring_config = {
|
||||||
.strip_gpio_num = LED_RING_PIN,
|
.strip_gpio_num = LED_RING_PIN,
|
||||||
.max_leds = RING_LEDS,
|
.max_leds = RING_LEDS,
|
||||||
};
|
};
|
||||||
led_strip_rmt_config_t rmt_ring_config = {
|
led_strip_rmt_config_t rmt_ring_config = {
|
||||||
.resolution_hz = 10 * 1000 * 1000, // 10 MHz
|
.resolution_hz = 10 * 1000 * 1000,
|
||||||
};
|
};
|
||||||
esp_err_t err =
|
esp_err_t err =
|
||||||
led_strip_new_rmt_device(&ring_config, &rmt_ring_config, &led_ring);
|
led_strip_new_rmt_device(&ring_config, &rmt_ring_config, &led_ring);
|
||||||
@ -67,17 +84,19 @@ void vTaskLedRing(void *pvParameters) {
|
|||||||
led_command_t cmd;
|
led_command_t cmd;
|
||||||
while (1) {
|
while (1) {
|
||||||
if (xQueueReceive(led_queue, &cmd, portMAX_DELAY)) {
|
if (xQueueReceive(led_queue, &cmd, portMAX_DELAY)) {
|
||||||
|
uint8_t r = cmd.r;
|
||||||
|
uint8_t g = cmd.g;
|
||||||
|
uint8_t b = cmd.b;
|
||||||
|
led_ring_scale_rgb(&r, &g, &b, cmd.intensity);
|
||||||
|
|
||||||
led_strip_clear(led_ring);
|
led_strip_clear(led_ring);
|
||||||
|
|
||||||
if (cmd.mode == LED_CMD_CLEAR) {
|
if (cmd.mode == LED_CMD_CLEAR) {
|
||||||
/* ring already cleared */
|
/* ring already cleared */
|
||||||
} else if (cmd.mode == LED_CMD_SET_DIGIT && cmd.value <= 10) {
|
} else if (cmd.mode == LED_CMD_SET_DIGIT && cmd.value <= 10) {
|
||||||
digit_definition_t digit = digit_lookup[cmd.value];
|
digit_definition_t digit = digit_lookup[cmd.value];
|
||||||
|
|
||||||
for (int i = 0; i < digit.count; i++) {
|
for (int i = 0; i < digit.count; i++) {
|
||||||
// Invert LED Counting for Now
|
led_strip_set_pixel(led_ring, RING_LEDS - digit.leds[i], r, g, b);
|
||||||
led_strip_set_pixel(led_ring, RING_LEDS - digit.leds[i], cmd.r, cmd.g,
|
|
||||||
cmd.b);
|
|
||||||
}
|
}
|
||||||
} else if (cmd.mode == LED_CMD_PROGRESS) {
|
} else if (cmd.mode == LED_CMD_PROGRESS) {
|
||||||
uint32_t lit = ((uint32_t)cmd.progress * RING_LEDS + 50) / 100;
|
uint32_t lit = ((uint32_t)cmd.progress * RING_LEDS + 50) / 100;
|
||||||
@ -85,8 +104,23 @@ void vTaskLedRing(void *pvParameters) {
|
|||||||
lit = RING_LEDS;
|
lit = RING_LEDS;
|
||||||
}
|
}
|
||||||
for (uint32_t i = 0; i < lit; i++) {
|
for (uint32_t i = 0; i < lit; i++) {
|
||||||
led_strip_set_pixel(led_ring, i, cmd.r, cmd.g, cmd.b);
|
led_strip_set_pixel(led_ring, i, r, g, b);
|
||||||
}
|
}
|
||||||
|
} else if (cmd.mode == LED_CMD_BLINK) {
|
||||||
|
uint16_t on_ms = cmd.blink_ms > 0 ? cmd.blink_ms : LED_RING_BLINK_ON_MS;
|
||||||
|
uint8_t count = cmd.blink_count > 0 ? cmd.blink_count : 1;
|
||||||
|
|
||||||
|
for (uint8_t n = 0; n < count; n++) {
|
||||||
|
ring_fill_color(r, g, b);
|
||||||
|
led_strip_refresh(led_ring);
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(on_ms));
|
||||||
|
led_strip_clear(led_ring);
|
||||||
|
led_strip_refresh(led_ring);
|
||||||
|
if (n + 1 < count) {
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(LED_RING_BLINK_OFF_MS));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
led_strip_refresh(led_ring);
|
led_strip_refresh(led_ring);
|
||||||
}
|
}
|
||||||
@ -103,3 +137,61 @@ void led_ring_send_command(led_command_t *cmd) {
|
|||||||
xQueueSend(led_queue, cmd, portMAX_DELAY);
|
xQueueSend(led_queue, cmd, portMAX_DELAY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void led_ring_show_ota_clear(void) {
|
||||||
|
led_command_t cmd = {.mode = LED_CMD_CLEAR};
|
||||||
|
led_ring_send_command(&cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_ring_show_ota_progress(uint32_t bytes_done, uint32_t total_bytes,
|
||||||
|
uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
static struct {
|
||||||
|
uint8_t pct;
|
||||||
|
uint8_t r, g, b;
|
||||||
|
} last = {255, 0, 0, 0};
|
||||||
|
|
||||||
|
if (total_bytes == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pct32 = (bytes_done * 100u + total_bytes / 2) / total_bytes;
|
||||||
|
if (pct32 > 100) {
|
||||||
|
pct32 = 100;
|
||||||
|
}
|
||||||
|
uint8_t pct = (uint8_t)pct32;
|
||||||
|
|
||||||
|
if (pct == last.pct && r == last.r && g == last.g && b == last.b) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
last.pct = pct;
|
||||||
|
last.r = r;
|
||||||
|
last.g = g;
|
||||||
|
last.b = b;
|
||||||
|
|
||||||
|
led_command_t cmd = {
|
||||||
|
.mode = LED_CMD_PROGRESS,
|
||||||
|
.progress = pct,
|
||||||
|
.r = r,
|
||||||
|
.g = g,
|
||||||
|
.b = b,
|
||||||
|
.intensity = LED_RING_DEFAULT_INTENSITY,
|
||||||
|
};
|
||||||
|
led_ring_send_command(&cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_ring_blink_once(uint8_t r, uint8_t g, uint8_t b) {
|
||||||
|
led_command_t cmd = {
|
||||||
|
.mode = LED_CMD_BLINK,
|
||||||
|
.r = r,
|
||||||
|
.g = g,
|
||||||
|
.b = b,
|
||||||
|
.intensity = LED_RING_DEFAULT_INTENSITY,
|
||||||
|
.blink_ms = LED_RING_BLINK_ON_MS,
|
||||||
|
.blink_count = 1,
|
||||||
|
};
|
||||||
|
led_ring_send_command(&cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_ring_ota_success(void) { led_ring_blink_once(0, 255, 0); }
|
||||||
|
|
||||||
|
void led_ring_ota_failed(void) { led_ring_blink_once(255, 0, 0); }
|
||||||
|
|||||||
@ -1,10 +1,17 @@
|
|||||||
|
#ifndef LED_RING_H
|
||||||
|
#define LED_RING_H
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/** Default RGB scale (~5 % of full brightness). */
|
||||||
|
#define LED_RING_DEFAULT_INTENSITY 13
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
LED_CMD_CLEAR,
|
LED_CMD_CLEAR,
|
||||||
LED_CMD_SET_DIGIT,
|
LED_CMD_SET_DIGIT,
|
||||||
LED_CMD_SET_COLOR,
|
LED_CMD_SET_COLOR,
|
||||||
LED_CMD_PROGRESS
|
LED_CMD_PROGRESS,
|
||||||
|
LED_CMD_BLINK
|
||||||
} led_mode_t;
|
} led_mode_t;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@ -13,7 +20,23 @@ typedef struct {
|
|||||||
uint8_t r, g, b;
|
uint8_t r, g, b;
|
||||||
uint8_t intensity;
|
uint8_t intensity;
|
||||||
uint8_t progress;
|
uint8_t progress;
|
||||||
|
uint16_t blink_ms;
|
||||||
|
uint8_t blink_count;
|
||||||
} led_command_t;
|
} led_command_t;
|
||||||
|
|
||||||
void led_ring_send_command(led_command_t *cmd);
|
void led_ring_send_command(led_command_t *cmd);
|
||||||
void led_ring_init(void);
|
void led_ring_init(void);
|
||||||
|
|
||||||
|
void led_ring_scale_rgb(uint8_t *r, uint8_t *g, uint8_t *b, uint8_t intensity);
|
||||||
|
|
||||||
|
/** OTA feedback: ring fill 0–100 % with RGB. */
|
||||||
|
void led_ring_show_ota_progress(uint32_t bytes_done, uint32_t total_bytes,
|
||||||
|
uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
void led_ring_show_ota_clear(void);
|
||||||
|
|
||||||
|
/** Single pulse on the full ring (blocking in LED task). */
|
||||||
|
void led_ring_blink_once(uint8_t r, uint8_t g, uint8_t b);
|
||||||
|
void led_ring_ota_success(void);
|
||||||
|
void led_ring_ota_failed(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
#include "ota_espnow.h"
|
#include "ota_espnow.h"
|
||||||
#include "app_config.h"
|
#include "app_config.h"
|
||||||
|
#include "led_ring.h"
|
||||||
#include "client_registry.h"
|
#include "client_registry.h"
|
||||||
#include "esp_log.h"
|
#include "esp_log.h"
|
||||||
#include "esp_now_comm.h"
|
#include "esp_now_comm.h"
|
||||||
@ -27,6 +28,11 @@ static const char *TAG = "[OTA_ESPNOW]";
|
|||||||
#define OTA_ST_SUCCESS 4u
|
#define OTA_ST_SUCCESS 4u
|
||||||
#define OTA_ST_FAILED 5u
|
#define OTA_ST_FAILED 5u
|
||||||
|
|
||||||
|
/** ESP-NOW OTA receive on slave (blue progress bar). */
|
||||||
|
#define OTA_LED_ESPNOW_RX_R 0
|
||||||
|
#define OTA_LED_ESPNOW_RX_G 0
|
||||||
|
#define OTA_LED_ESPNOW_RX_B 255
|
||||||
|
|
||||||
#define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX
|
#define OTA_MAX_TARGETS CLIENT_REGISTRY_MAX
|
||||||
|
|
||||||
static EventGroupHandle_t s_eg;
|
static EventGroupHandle_t s_eg;
|
||||||
@ -170,6 +176,8 @@ static void ota_slave_prepare_task(void *param) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
send_slave_status(master_mac, OTA_ST_READY, 0, 0);
|
send_slave_status(master_mac, OTA_ST_READY, 0, 0);
|
||||||
|
led_ring_show_ota_progress(0, total_size, OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
||||||
|
OTA_LED_ESPNOW_RX_B);
|
||||||
vTaskDelete(NULL);
|
vTaskDelete(NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,13 +222,27 @@ void ota_espnow_slave_on_payload(const uint8_t master_mac[6],
|
|||||||
ota_feed_result_t r =
|
ota_feed_result_t r =
|
||||||
ota_uart_feed(payload->data.bytes, payload->data.size);
|
ota_uart_feed(payload->data.bytes, payload->data.size);
|
||||||
if (r == OTA_FEED_ERROR) {
|
if (r == OTA_FEED_ERROR) {
|
||||||
|
led_ring_ota_failed();
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, ota_uart_bytes_written(), 13);
|
send_slave_status(master_mac, OTA_ST_FAILED, ota_uart_bytes_written(), 13);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (r == OTA_FEED_BLOCK_WRITTEN) {
|
if (r == OTA_FEED_BLOCK_WRITTEN) {
|
||||||
uint32_t written = ota_uart_bytes_written();
|
uint32_t written = ota_uart_bytes_written();
|
||||||
|
uint32_t total = ota_uart_total_size();
|
||||||
ESP_LOGI(TAG, "block written %lu bytes -> ack master", (unsigned long)written);
|
ESP_LOGI(TAG, "block written %lu bytes -> ack master", (unsigned long)written);
|
||||||
|
led_ring_show_ota_progress(written, total, OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
||||||
|
OTA_LED_ESPNOW_RX_B);
|
||||||
send_slave_status(master_mac, OTA_ST_BLOCK_ACK, written, 0);
|
send_slave_status(master_mac, OTA_ST_BLOCK_ACK, written, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (r == OTA_FEED_OK) {
|
||||||
|
uint32_t total = ota_uart_total_size();
|
||||||
|
if (total > 0) {
|
||||||
|
led_ring_show_ota_progress(ota_uart_bytes_received(), total,
|
||||||
|
OTA_LED_ESPNOW_RX_R, OTA_LED_ESPNOW_RX_G,
|
||||||
|
OTA_LED_ESPNOW_RX_B);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,11 +257,13 @@ void ota_espnow_slave_on_end(const uint8_t master_mac[6]) {
|
|||||||
bool success = false;
|
bool success = false;
|
||||||
esp_err_t err = ota_uart_finish(true, &success);
|
esp_err_t err = ota_uart_finish(true, &success);
|
||||||
if (err != ESP_OK || !success) {
|
if (err != ESP_OK || !success) {
|
||||||
|
led_ring_ota_failed();
|
||||||
send_slave_status(master_mac, OTA_ST_FAILED, written, (uint32_t)err);
|
send_slave_status(master_mac, OTA_ST_FAILED, written, (uint32_t)err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
send_slave_status(master_mac, OTA_ST_SUCCESS, written, 0);
|
send_slave_status(master_mac, OTA_ST_SUCCESS, written, 0);
|
||||||
|
led_ring_ota_success();
|
||||||
ESP_LOGI(TAG, "slave OTA success (%lu bytes), reboot to run",
|
ESP_LOGI(TAG, "slave OTA success (%lu bytes), reboot to run",
|
||||||
(unsigned long)written);
|
(unsigned long)written);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -151,6 +151,10 @@ ota_feed_result_t ota_uart_feed(const uint8_t *data, size_t len) {
|
|||||||
|
|
||||||
uint32_t ota_uart_bytes_written(void) { return s_ota.written; }
|
uint32_t ota_uart_bytes_written(void) { return s_ota.written; }
|
||||||
|
|
||||||
|
uint32_t ota_uart_bytes_received(void) { return s_ota.received; }
|
||||||
|
|
||||||
|
uint32_t ota_uart_total_size(void) { return s_ota.active ? s_ota.total_size : 0; }
|
||||||
|
|
||||||
bool ota_uart_get_staged_image(const esp_partition_t **partition_out,
|
bool ota_uart_get_staged_image(const esp_partition_t **partition_out,
|
||||||
uint32_t *size_out) {
|
uint32_t *size_out) {
|
||||||
if (!s_staged.valid || s_staged.partition == NULL) {
|
if (!s_staged.valid || s_staged.partition == NULL) {
|
||||||
|
|||||||
@ -46,6 +46,12 @@ ota_feed_result_t ota_uart_feed(const uint8_t *data, size_t len);
|
|||||||
|
|
||||||
uint32_t ota_uart_bytes_written(void);
|
uint32_t ota_uart_bytes_written(void);
|
||||||
|
|
||||||
|
/** Bytes accepted in the current session (includes buffered block). */
|
||||||
|
uint32_t ota_uart_bytes_received(void);
|
||||||
|
|
||||||
|
/** Image size from OTA_START / ESP-NOW OTA_START; 0 if inactive. */
|
||||||
|
uint32_t ota_uart_total_size(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush remainder and esp_ota_end. When set_boot is false, the staged image
|
* Flush remainder and esp_ota_end. When set_boot is false, the staged image
|
||||||
* remains readable via ota_uart_get_staged_image() until ota_uart_apply_boot().
|
* remains readable via ota_uart_get_staged_image() until ota_uart_apply_boot().
|
||||||
|
|||||||
@ -96,8 +96,8 @@ typedef struct _alox_EspNowUnicastTestResponse {
|
|||||||
uint32_t seq;
|
uint32_t seq;
|
||||||
} alox_EspNowUnicastTestResponse;
|
} alox_EspNowUnicastTestResponse;
|
||||||
|
|
||||||
/* Host → device: LED ring display (progress bar, digit, or clear).
|
/* Host → device: LED ring display (progress bar, digit, clear, or blink).
|
||||||
mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10, same layout as firmware digit maps). */
|
mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink full ring. */
|
||||||
typedef struct _alox_LedRingProgressRequest {
|
typedef struct _alox_LedRingProgressRequest {
|
||||||
uint32_t mode;
|
uint32_t mode;
|
||||||
/* * 0–100: fraction of ring LEDs to light (mode=progress) */
|
/* * 0–100: fraction of ring LEDs to light (mode=progress) */
|
||||||
@ -107,8 +107,12 @@ typedef struct _alox_LedRingProgressRequest {
|
|||||||
uint32_t r;
|
uint32_t r;
|
||||||
uint32_t g;
|
uint32_t g;
|
||||||
uint32_t b;
|
uint32_t b;
|
||||||
/* * 0–255 brightness scale applied to r/g/b */
|
/* * 0–255 brightness scale; 0 = firmware default (~5 %) */
|
||||||
uint32_t intensity;
|
uint32_t intensity;
|
||||||
|
/* * Pulse length in ms (mode=blink, default 350) */
|
||||||
|
uint32_t blink_ms;
|
||||||
|
/* * Number of pulses (mode=blink, default 1) */
|
||||||
|
uint32_t blink_count;
|
||||||
} alox_LedRingProgressRequest;
|
} alox_LedRingProgressRequest;
|
||||||
|
|
||||||
typedef struct _alox_LedRingProgressResponse {
|
typedef struct _alox_LedRingProgressResponse {
|
||||||
@ -237,7 +241,7 @@ extern "C" {
|
|||||||
#define alox_AccelDeadzoneResponse_init_default {0, 0, 0, 0}
|
#define alox_AccelDeadzoneResponse_init_default {0, 0, 0, 0}
|
||||||
#define alox_EspNowUnicastTestRequest_init_default {0, 0}
|
#define alox_EspNowUnicastTestRequest_init_default {0, 0}
|
||||||
#define alox_EspNowUnicastTestResponse_init_default {0, 0}
|
#define alox_EspNowUnicastTestResponse_init_default {0, 0}
|
||||||
#define alox_LedRingProgressRequest_init_default {0, 0, 0, 0, 0, 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_LedRingProgressResponse_init_default {0, 0, 0, 0}
|
||||||
#define alox_OtaStartPayload_init_default {0}
|
#define alox_OtaStartPayload_init_default {0}
|
||||||
#define alox_OtaPayload_init_default {0, {0, {0}}}
|
#define alox_OtaPayload_init_default {0, {0, {0}}}
|
||||||
@ -258,7 +262,7 @@ extern "C" {
|
|||||||
#define alox_AccelDeadzoneResponse_init_zero {0, 0, 0, 0}
|
#define alox_AccelDeadzoneResponse_init_zero {0, 0, 0, 0}
|
||||||
#define alox_EspNowUnicastTestRequest_init_zero {0, 0}
|
#define alox_EspNowUnicastTestRequest_init_zero {0, 0}
|
||||||
#define alox_EspNowUnicastTestResponse_init_zero {0, 0}
|
#define alox_EspNowUnicastTestResponse_init_zero {0, 0}
|
||||||
#define alox_LedRingProgressRequest_init_zero {0, 0, 0, 0, 0, 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_LedRingProgressResponse_init_zero {0, 0, 0, 0}
|
||||||
#define alox_OtaStartPayload_init_zero {0}
|
#define alox_OtaStartPayload_init_zero {0}
|
||||||
#define alox_OtaPayload_init_zero {0, {0, {0}}}
|
#define alox_OtaPayload_init_zero {0, {0, {0}}}
|
||||||
@ -305,6 +309,8 @@ extern "C" {
|
|||||||
#define alox_LedRingProgressRequest_g_tag 5
|
#define alox_LedRingProgressRequest_g_tag 5
|
||||||
#define alox_LedRingProgressRequest_b_tag 6
|
#define alox_LedRingProgressRequest_b_tag 6
|
||||||
#define alox_LedRingProgressRequest_intensity_tag 7
|
#define alox_LedRingProgressRequest_intensity_tag 7
|
||||||
|
#define alox_LedRingProgressRequest_blink_ms_tag 8
|
||||||
|
#define alox_LedRingProgressRequest_blink_count_tag 9
|
||||||
#define alox_LedRingProgressResponse_success_tag 1
|
#define alox_LedRingProgressResponse_success_tag 1
|
||||||
#define alox_LedRingProgressResponse_mode_tag 2
|
#define alox_LedRingProgressResponse_mode_tag 2
|
||||||
#define alox_LedRingProgressResponse_progress_tag 3
|
#define alox_LedRingProgressResponse_progress_tag 3
|
||||||
@ -469,7 +475,9 @@ X(a, STATIC, SINGULAR, UINT32, digit, 3) \
|
|||||||
X(a, STATIC, SINGULAR, UINT32, r, 4) \
|
X(a, STATIC, SINGULAR, UINT32, r, 4) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, g, 5) \
|
X(a, STATIC, SINGULAR, UINT32, g, 5) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, b, 6) \
|
X(a, STATIC, SINGULAR, UINT32, b, 6) \
|
||||||
X(a, STATIC, SINGULAR, UINT32, intensity, 7)
|
X(a, STATIC, SINGULAR, UINT32, intensity, 7) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, blink_ms, 8) \
|
||||||
|
X(a, STATIC, SINGULAR, UINT32, blink_count, 9)
|
||||||
#define alox_LedRingProgressRequest_CALLBACK NULL
|
#define alox_LedRingProgressRequest_CALLBACK NULL
|
||||||
#define alox_LedRingProgressRequest_DEFAULT NULL
|
#define alox_LedRingProgressRequest_DEFAULT NULL
|
||||||
|
|
||||||
@ -588,7 +596,7 @@ extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
|
|||||||
#define alox_ClientInput_size 22
|
#define alox_ClientInput_size 22
|
||||||
#define alox_EspNowUnicastTestRequest_size 12
|
#define alox_EspNowUnicastTestRequest_size 12
|
||||||
#define alox_EspNowUnicastTestResponse_size 8
|
#define alox_EspNowUnicastTestResponse_size 8
|
||||||
#define alox_LedRingProgressRequest_size 42
|
#define alox_LedRingProgressRequest_size 54
|
||||||
#define alox_LedRingProgressResponse_size 20
|
#define alox_LedRingProgressResponse_size 20
|
||||||
#define alox_OtaEndPayload_size 0
|
#define alox_OtaEndPayload_size 0
|
||||||
#define alox_OtaPayload_size 209
|
#define alox_OtaPayload_size 209
|
||||||
|
|||||||
@ -110,8 +110,8 @@ message EspNowUnicastTestResponse {
|
|||||||
uint32 seq = 2;
|
uint32 seq = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Host → device: LED ring display (progress bar, digit, or clear).
|
// Host → device: LED ring display (progress bar, digit, clear, or blink).
|
||||||
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10, same layout as firmware digit maps).
|
// mode: 0=clear, 1=progress (0–100 %), 2=digit (0–10), 3=blink full ring.
|
||||||
message LedRingProgressRequest {
|
message LedRingProgressRequest {
|
||||||
uint32 mode = 1;
|
uint32 mode = 1;
|
||||||
/** 0–100: fraction of ring LEDs to light (mode=progress) */
|
/** 0–100: fraction of ring LEDs to light (mode=progress) */
|
||||||
@ -121,8 +121,12 @@ message LedRingProgressRequest {
|
|||||||
uint32 r = 4;
|
uint32 r = 4;
|
||||||
uint32 g = 5;
|
uint32 g = 5;
|
||||||
uint32 b = 6;
|
uint32 b = 6;
|
||||||
/** 0–255 brightness scale applied to r/g/b */
|
/** 0–255 brightness scale; 0 = firmware default (~5 %) */
|
||||||
uint32 intensity = 7;
|
uint32 intensity = 7;
|
||||||
|
/** Pulse length in ms (mode=blink, default 350) */
|
||||||
|
uint32 blink_ms = 8;
|
||||||
|
/** Number of pulses (mode=blink, default 1) */
|
||||||
|
uint32 blink_count = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message LedRingProgressResponse {
|
message LedRingProgressResponse {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user