Add per-slave ESP-NOW OTA progress over UART and fix dashboard updates.

Expose OTA_SLAVE_PROGRESS on the master, track per-slave state during
distribution, run ESP-NOW OTA in a background task so the host can poll
while slaves update, and show master/slave progress in the dashboard
with table layout and faster WebSocket refresh during uploads.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-19 21:07:46 +02:00
co-authored by Cursor
parent 5a948a5c8c
commit a0f4a81a55
23 changed files with 1467 additions and 146 deletions
+10 -1
View File
@@ -6,7 +6,7 @@
#error Regenerate this file with the current version of nanopb generator.
#endif
PB_BIND(alox_UartMessage, alox_UartMessage, AUTO)
PB_BIND(alox_UartMessage, alox_UartMessage, 2)
PB_BIND(alox_Ack, alox_Ack, AUTO)
@@ -54,6 +54,15 @@ PB_BIND(alox_OtaEndPayload, alox_OtaEndPayload, AUTO)
PB_BIND(alox_OtaStatusPayload, alox_OtaStatusPayload, AUTO)
PB_BIND(alox_OtaSlaveProgressRequest, alox_OtaSlaveProgressRequest, AUTO)
PB_BIND(alox_OtaSlaveProgressEntry, alox_OtaSlaveProgressEntry, AUTO)
PB_BIND(alox_OtaSlaveProgressResponse, alox_OtaSlaveProgressResponse, 2)
+95 -10
View File
@@ -1,8 +1,8 @@
/* Automatically generated nanopb header */
/* Generated by nanopb-1.0.0-dev */
#ifndef PB_ALOX_MAIN_PROTO_UART_MESSAGES_PB_H_INCLUDED
#define PB_ALOX_MAIN_PROTO_UART_MESSAGES_PB_H_INCLUDED
#ifndef PB_ALOX_UART_MESSAGES_PB_H_INCLUDED
#define PB_ALOX_UART_MESSAGES_PB_H_INCLUDED
#include <pb.h>
#if PB_PROTO_HEADER_VERSION != 40
@@ -23,7 +23,8 @@ typedef enum _alox_MessageType {
alox_MessageType_OTA_PAYLOAD = 17,
alox_MessageType_OTA_END = 18,
alox_MessageType_OTA_STATUS = 19,
alox_MessageType_OTA_START_ESPNOW = 20
alox_MessageType_OTA_START_ESPNOW = 20,
alox_MessageType_OTA_SLAVE_PROGRESS = 21
} alox_MessageType;
/* Struct definitions */
@@ -99,8 +100,8 @@ typedef struct _alox_OtaStartPayload {
uint32_t total_size;
} alox_OtaStartPayload;
/* Host → device: firmware chunk (up to 200 bytes); device buffers 4 KiB before flash write. */
typedef PB_BYTES_ARRAY_T(200) alox_OtaPayload_data_t;
/* Host → device: firmware chunk (up to 200 bytes); device buffers 4 KiB before flash write. */
typedef struct _alox_OtaPayload {
uint32_t seq;
alox_OtaPayload_data_t data;
@@ -112,7 +113,7 @@ typedef struct _alox_OtaEndPayload {
} alox_OtaEndPayload;
/* Device → host status (also used as ACK after each 4 KiB written).
status: 1=preparing, 2=ready, 3=block_ack, 4=success, 5=failed */
status: 1=preparing, 2=ready, 3=block_ack, 4=success, 5=failed, 6=distributing */
typedef struct _alox_OtaStatusPayload {
uint32_t status;
uint32_t bytes_written;
@@ -120,6 +121,29 @@ typedef struct _alox_OtaStatusPayload {
uint32_t error;
} alox_OtaStatusPayload;
/* Host → master: query ESP-NOW slave OTA progress (client_id 0 = all slaves in session). */
typedef struct _alox_OtaSlaveProgressRequest {
uint32_t client_id;
} alox_OtaSlaveProgressRequest;
typedef struct _alox_OtaSlaveProgressEntry {
uint32_t client_id;
uint32_t bytes_written;
uint32_t total_bytes;
/* * 0=idle, 1=preparing, 2=ready, 3=distributing, 4=success, 5=failed */
uint32_t status;
uint32_t error;
} alox_OtaSlaveProgressEntry;
typedef struct _alox_OtaSlaveProgressResponse {
bool active;
uint32_t total_bytes;
uint32_t aggregate_bytes;
uint32_t slave_count;
pb_size_t slaves_count;
alox_OtaSlaveProgressEntry slaves[16];
} alox_OtaSlaveProgressResponse;
typedef struct _alox_UartMessage {
alox_MessageType type;
pb_size_t which_payload;
@@ -137,6 +161,8 @@ typedef struct _alox_UartMessage {
alox_AccelDeadzoneResponse accel_deadzone_response;
alox_EspNowUnicastTestRequest espnow_unicast_test_request;
alox_EspNowUnicastTestResponse espnow_unicast_test_response;
alox_OtaSlaveProgressRequest ota_slave_progress_request;
alox_OtaSlaveProgressResponse ota_slave_progress_response;
} payload;
} alox_UartMessage;
@@ -147,8 +173,8 @@ extern "C" {
/* Helper constants for enums */
#define _alox_MessageType_MIN alox_MessageType_UNKNOWN
#define _alox_MessageType_MAX alox_MessageType_OTA_START_ESPNOW
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_OTA_START_ESPNOW+1))
#define _alox_MessageType_MAX alox_MessageType_OTA_SLAVE_PROGRESS
#define _alox_MessageType_ARRAYSIZE ((alox_MessageType)(alox_MessageType_OTA_SLAVE_PROGRESS+1))
#define alox_UartMessage_type_ENUMTYPE alox_MessageType
@@ -168,6 +194,9 @@ extern "C" {
/* Initializer values for message structs */
#define alox_UartMessage_init_default {_alox_MessageType_MIN, 0, {alox_Ack_init_default}}
#define alox_Ack_init_default {0}
@@ -185,6 +214,9 @@ extern "C" {
#define alox_OtaPayload_init_default {0, {0, {0}}}
#define alox_OtaEndPayload_init_default {0}
#define alox_OtaStatusPayload_init_default {0, 0, 0, 0}
#define alox_OtaSlaveProgressRequest_init_default {0}
#define alox_OtaSlaveProgressEntry_init_default {0, 0, 0, 0, 0}
#define alox_OtaSlaveProgressResponse_init_default {0, 0, 0, 0, 0, {alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default, alox_OtaSlaveProgressEntry_init_default}}
#define alox_UartMessage_init_zero {_alox_MessageType_MIN, 0, {alox_Ack_init_zero}}
#define alox_Ack_init_zero {0}
#define alox_EchoPayload_init_zero {{{NULL}, NULL}}
@@ -201,6 +233,9 @@ extern "C" {
#define alox_OtaPayload_init_zero {0, {0, {0}}}
#define alox_OtaEndPayload_init_zero {0}
#define alox_OtaStatusPayload_init_zero {0, 0, 0, 0}
#define alox_OtaSlaveProgressRequest_init_zero {0}
#define alox_OtaSlaveProgressEntry_init_zero {0, 0, 0, 0, 0}
#define alox_OtaSlaveProgressResponse_init_zero {0, 0, 0, 0, 0, {alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero, alox_OtaSlaveProgressEntry_init_zero}}
/* Field tags (for use in manual encoding/decoding) */
#define alox_EchoPayload_data_tag 1
@@ -239,6 +274,17 @@ extern "C" {
#define alox_OtaStatusPayload_bytes_written_tag 2
#define alox_OtaStatusPayload_target_slot_tag 3
#define alox_OtaStatusPayload_error_tag 4
#define alox_OtaSlaveProgressRequest_client_id_tag 1
#define alox_OtaSlaveProgressEntry_client_id_tag 1
#define alox_OtaSlaveProgressEntry_bytes_written_tag 2
#define alox_OtaSlaveProgressEntry_total_bytes_tag 3
#define alox_OtaSlaveProgressEntry_status_tag 4
#define alox_OtaSlaveProgressEntry_error_tag 5
#define alox_OtaSlaveProgressResponse_active_tag 1
#define alox_OtaSlaveProgressResponse_total_bytes_tag 2
#define alox_OtaSlaveProgressResponse_aggregate_bytes_tag 3
#define alox_OtaSlaveProgressResponse_slave_count_tag 4
#define alox_OtaSlaveProgressResponse_slaves_tag 5
#define alox_UartMessage_type_tag 1
#define alox_UartMessage_ack_payload_tag 2
#define alox_UartMessage_echo_payload_tag 3
@@ -253,6 +299,8 @@ extern "C" {
#define alox_UartMessage_accel_deadzone_response_tag 12
#define alox_UartMessage_espnow_unicast_test_request_tag 13
#define alox_UartMessage_espnow_unicast_test_response_tag 14
#define alox_UartMessage_ota_slave_progress_request_tag 15
#define alox_UartMessage_ota_slave_progress_response_tag 16
/* Struct field encoding specification for nanopb */
#define alox_UartMessage_FIELDLIST(X, a) \
@@ -269,7 +317,9 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,ota_status,payload.ota_status), 10)
X(a, STATIC, ONEOF, MESSAGE, (payload,accel_deadzone_request,payload.accel_deadzone_request), 11) \
X(a, STATIC, ONEOF, MESSAGE, (payload,accel_deadzone_response,payload.accel_deadzone_response), 12) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_unicast_test_request,payload.espnow_unicast_test_request), 13) \
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_unicast_test_response,payload.espnow_unicast_test_response), 14)
X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_unicast_test_response,payload.espnow_unicast_test_response), 14) \
X(a, STATIC, ONEOF, MESSAGE, (payload,ota_slave_progress_request,payload.ota_slave_progress_request), 15) \
X(a, STATIC, ONEOF, MESSAGE, (payload,ota_slave_progress_response,payload.ota_slave_progress_response), 16)
#define alox_UartMessage_CALLBACK NULL
#define alox_UartMessage_DEFAULT NULL
#define alox_UartMessage_payload_ack_payload_MSGTYPE alox_Ack
@@ -285,6 +335,8 @@ X(a, STATIC, ONEOF, MESSAGE, (payload,espnow_unicast_test_response,payload
#define alox_UartMessage_payload_accel_deadzone_response_MSGTYPE alox_AccelDeadzoneResponse
#define alox_UartMessage_payload_espnow_unicast_test_request_MSGTYPE alox_EspNowUnicastTestRequest
#define alox_UartMessage_payload_espnow_unicast_test_response_MSGTYPE alox_EspNowUnicastTestResponse
#define alox_UartMessage_payload_ota_slave_progress_request_MSGTYPE alox_OtaSlaveProgressRequest
#define alox_UartMessage_payload_ota_slave_progress_response_MSGTYPE alox_OtaSlaveProgressResponse
#define alox_Ack_FIELDLIST(X, a) \
@@ -386,6 +438,30 @@ X(a, STATIC, SINGULAR, UINT32, error, 4)
#define alox_OtaStatusPayload_CALLBACK NULL
#define alox_OtaStatusPayload_DEFAULT NULL
#define alox_OtaSlaveProgressRequest_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, client_id, 1)
#define alox_OtaSlaveProgressRequest_CALLBACK NULL
#define alox_OtaSlaveProgressRequest_DEFAULT NULL
#define alox_OtaSlaveProgressEntry_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, UINT32, client_id, 1) \
X(a, STATIC, SINGULAR, UINT32, bytes_written, 2) \
X(a, STATIC, SINGULAR, UINT32, total_bytes, 3) \
X(a, STATIC, SINGULAR, UINT32, status, 4) \
X(a, STATIC, SINGULAR, UINT32, error, 5)
#define alox_OtaSlaveProgressEntry_CALLBACK NULL
#define alox_OtaSlaveProgressEntry_DEFAULT NULL
#define alox_OtaSlaveProgressResponse_FIELDLIST(X, a) \
X(a, STATIC, SINGULAR, BOOL, active, 1) \
X(a, STATIC, SINGULAR, UINT32, total_bytes, 2) \
X(a, STATIC, SINGULAR, UINT32, aggregate_bytes, 3) \
X(a, STATIC, SINGULAR, UINT32, slave_count, 4) \
X(a, STATIC, REPEATED, MESSAGE, slaves, 5)
#define alox_OtaSlaveProgressResponse_CALLBACK NULL
#define alox_OtaSlaveProgressResponse_DEFAULT NULL
#define alox_OtaSlaveProgressResponse_slaves_MSGTYPE alox_OtaSlaveProgressEntry
extern const pb_msgdesc_t alox_UartMessage_msg;
extern const pb_msgdesc_t alox_Ack_msg;
extern const pb_msgdesc_t alox_EchoPayload_msg;
@@ -402,6 +478,9 @@ extern const pb_msgdesc_t alox_OtaStartPayload_msg;
extern const pb_msgdesc_t alox_OtaPayload_msg;
extern const pb_msgdesc_t alox_OtaEndPayload_msg;
extern const pb_msgdesc_t alox_OtaStatusPayload_msg;
extern const pb_msgdesc_t alox_OtaSlaveProgressRequest_msg;
extern const pb_msgdesc_t alox_OtaSlaveProgressEntry_msg;
extern const pb_msgdesc_t alox_OtaSlaveProgressResponse_msg;
/* Defines for backwards compatibility with code written before nanopb-0.4.0 */
#define alox_UartMessage_fields &alox_UartMessage_msg
@@ -420,6 +499,9 @@ extern const pb_msgdesc_t alox_OtaStatusPayload_msg;
#define alox_OtaPayload_fields &alox_OtaPayload_msg
#define alox_OtaEndPayload_fields &alox_OtaEndPayload_msg
#define alox_OtaStatusPayload_fields &alox_OtaStatusPayload_msg
#define alox_OtaSlaveProgressRequest_fields &alox_OtaSlaveProgressRequest_msg
#define alox_OtaSlaveProgressEntry_fields &alox_OtaSlaveProgressEntry_msg
#define alox_OtaSlaveProgressResponse_fields &alox_OtaSlaveProgressResponse_msg
/* Maximum encoded size of messages (where known) */
/* alox_UartMessage_size depends on runtime parameters */
@@ -428,8 +510,7 @@ extern const pb_msgdesc_t alox_OtaStatusPayload_msg;
/* alox_ClientInfo_size depends on runtime parameters */
/* alox_ClientInfoResponse_size depends on runtime parameters */
/* alox_ClientInputResponse_size depends on runtime parameters */
/* alox_OtaPayload_size depends on runtime parameters */
#define ALOX_MAIN_PROTO_UART_MESSAGES_PB_H_MAX_SIZE alox_OtaStatusPayload_size
#define ALOX_UART_MESSAGES_PB_H_MAX_SIZE alox_OtaSlaveProgressResponse_size
#define alox_AccelDeadzoneRequest_size 16
#define alox_AccelDeadzoneResponse_size 20
#define alox_Ack_size 0
@@ -437,6 +518,10 @@ extern const pb_msgdesc_t alox_OtaStatusPayload_msg;
#define alox_EspNowUnicastTestRequest_size 12
#define alox_EspNowUnicastTestResponse_size 8
#define alox_OtaEndPayload_size 0
#define alox_OtaPayload_size 209
#define alox_OtaSlaveProgressEntry_size 30
#define alox_OtaSlaveProgressRequest_size 6
#define alox_OtaSlaveProgressResponse_size 532
#define alox_OtaStartPayload_size 6
#define alox_OtaStatusPayload_size 24
+26 -1
View File
@@ -18,6 +18,7 @@ enum MessageType {
OTA_END = 18;
OTA_STATUS = 19;
OTA_START_ESPNOW = 20;
OTA_SLAVE_PROGRESS = 21;
}
message UartMessage {
@@ -36,6 +37,8 @@ message UartMessage {
AccelDeadzoneResponse accel_deadzone_response = 12;
EspNowUnicastTestRequest espnow_unicast_test_request = 13;
EspNowUnicastTestResponse espnow_unicast_test_response = 14;
OtaSlaveProgressRequest ota_slave_progress_request = 15;
OtaSlaveProgressResponse ota_slave_progress_response = 16;
}
}
@@ -119,10 +122,32 @@ message OtaPayload {
message OtaEndPayload {}
// Device → host status (also used as ACK after each 4 KiB written).
// status: 1=preparing, 2=ready, 3=block_ack, 4=success, 5=failed
// status: 1=preparing, 2=ready, 3=block_ack, 4=success, 5=failed, 6=distributing
message OtaStatusPayload {
uint32 status = 1;
uint32 bytes_written = 2;
uint32 target_slot = 3;
uint32 error = 4;
}
// Host → master: query ESP-NOW slave OTA progress (client_id 0 = all slaves in session).
message OtaSlaveProgressRequest {
uint32 client_id = 1;
}
message OtaSlaveProgressEntry {
uint32 client_id = 1;
uint32 bytes_written = 2;
uint32 total_bytes = 3;
/** 0=idle, 1=preparing, 2=ready, 3=distributing, 4=success, 5=failed */
uint32 status = 4;
uint32 error = 5;
}
message OtaSlaveProgressResponse {
bool active = 1;
uint32 total_bytes = 2;
uint32 aggregate_bytes = 3;
uint32 slave_count = 4;
repeated OtaSlaveProgressEntry slaves = 5 [(nanopb).max_count = 16];
}