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:
+124
-3
@@ -36,10 +36,90 @@ typedef struct {
|
||||
uint8_t mac[OTA_MAX_TARGETS][6];
|
||||
uint32_t id[OTA_MAX_TARGETS];
|
||||
uint32_t expected_bytes;
|
||||
uint32_t total_bytes;
|
||||
ota_espnow_progress_cbs_t progress;
|
||||
} ota_dist_t;
|
||||
|
||||
static ota_dist_t s_dist;
|
||||
|
||||
typedef struct {
|
||||
uint32_t client_id;
|
||||
uint32_t bytes_written;
|
||||
uint32_t status;
|
||||
uint32_t error;
|
||||
} ota_prog_entry_t;
|
||||
|
||||
static struct {
|
||||
bool active;
|
||||
uint32_t total_bytes;
|
||||
uint32_t aggregate_bytes;
|
||||
uint8_t count;
|
||||
ota_prog_entry_t entries[OTA_MAX_TARGETS];
|
||||
} s_prog;
|
||||
|
||||
static void prog_begin(uint32_t total_bytes) {
|
||||
s_prog.active = true;
|
||||
s_prog.total_bytes = total_bytes;
|
||||
s_prog.aggregate_bytes = 0;
|
||||
s_prog.count = s_dist.count;
|
||||
for (uint8_t i = 0; i < s_dist.count; i++) {
|
||||
s_prog.entries[i].client_id = s_dist.id[i];
|
||||
s_prog.entries[i].bytes_written = 0;
|
||||
s_prog.entries[i].status = OTA_ST_PREPARING;
|
||||
s_prog.entries[i].error = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void prog_end(void) { s_prog.active = false; }
|
||||
|
||||
static void prog_set_aggregate(uint32_t bytes_done) {
|
||||
s_prog.aggregate_bytes = bytes_done;
|
||||
}
|
||||
|
||||
static void prog_update_idx(int idx, uint32_t status, uint32_t bytes,
|
||||
uint32_t error) {
|
||||
if (idx < 0 || idx >= (int)s_prog.count) {
|
||||
return;
|
||||
}
|
||||
ota_prog_entry_t *e = &s_prog.entries[idx];
|
||||
e->status = status;
|
||||
if (bytes > e->bytes_written) {
|
||||
e->bytes_written = bytes;
|
||||
}
|
||||
if (error != 0) {
|
||||
e->error = error;
|
||||
}
|
||||
}
|
||||
|
||||
void ota_espnow_progress_query(uint32_t filter_client_id,
|
||||
alox_OtaSlaveProgressResponse *out) {
|
||||
if (out == NULL) {
|
||||
return;
|
||||
}
|
||||
*out = (alox_OtaSlaveProgressResponse)alox_OtaSlaveProgressResponse_init_zero;
|
||||
out->active = s_prog.active;
|
||||
out->total_bytes = s_prog.total_bytes;
|
||||
out->aggregate_bytes = s_prog.aggregate_bytes;
|
||||
out->slave_count = s_prog.count;
|
||||
|
||||
for (uint8_t i = 0; i < s_prog.count; i++) {
|
||||
const ota_prog_entry_t *e = &s_prog.entries[i];
|
||||
if (filter_client_id != 0 && e->client_id != filter_client_id) {
|
||||
continue;
|
||||
}
|
||||
if (out->slaves_count >=
|
||||
sizeof(out->slaves) / sizeof(out->slaves[0])) {
|
||||
break;
|
||||
}
|
||||
alox_OtaSlaveProgressEntry *dst = &out->slaves[out->slaves_count++];
|
||||
dst->client_id = e->client_id;
|
||||
dst->bytes_written = e->bytes_written;
|
||||
dst->total_bytes = s_prog.total_bytes;
|
||||
dst->status = e->status;
|
||||
dst->error = e->error;
|
||||
}
|
||||
}
|
||||
|
||||
static int find_target_index(const uint8_t mac[6]) {
|
||||
for (uint8_t i = 0; i < s_dist.count; i++) {
|
||||
if (memcmp(s_dist.mac[i], mac, 6) == 0) {
|
||||
@@ -179,9 +259,15 @@ void ota_espnow_master_on_status(const uint8_t slave_mac[6],
|
||||
|
||||
switch (status->status) {
|
||||
case OTA_ST_READY:
|
||||
prog_update_idx(idx, OTA_ST_READY, 0, 0);
|
||||
xEventGroupSetBits(s_eg, bit);
|
||||
break;
|
||||
case OTA_ST_BLOCK_ACK:
|
||||
prog_update_idx(idx, OTA_ST_BLOCK_ACK, status->bytes_written, 0);
|
||||
if (s_dist.progress.per_slave != NULL) {
|
||||
s_dist.progress.per_slave(s_dist.id[idx], status->bytes_written,
|
||||
s_dist.total_bytes);
|
||||
}
|
||||
if (status->bytes_written >= s_dist.expected_bytes) {
|
||||
xEventGroupSetBits(s_eg, bit);
|
||||
} else {
|
||||
@@ -191,9 +277,12 @@ void ota_espnow_master_on_status(const uint8_t slave_mac[6],
|
||||
}
|
||||
break;
|
||||
case OTA_ST_SUCCESS:
|
||||
prog_update_idx(idx, OTA_ST_SUCCESS, status->bytes_written, 0);
|
||||
xEventGroupSetBits(s_eg, bit);
|
||||
break;
|
||||
case OTA_ST_FAILED:
|
||||
prog_update_idx(idx, OTA_ST_FAILED, status->bytes_written,
|
||||
status->error);
|
||||
ESP_LOGW(TAG, "slave %lu OTA failed (err=%lu)",
|
||||
(unsigned long)s_dist.id[idx], (unsigned long)status->error);
|
||||
break;
|
||||
@@ -220,7 +309,8 @@ static size_t collect_targets(void) {
|
||||
}
|
||||
|
||||
static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
uint32_t size) {
|
||||
uint32_t size,
|
||||
const ota_espnow_progress_cbs_t *progress) {
|
||||
if (s_eg == NULL) {
|
||||
s_eg = xEventGroupCreate();
|
||||
if (s_eg == NULL) {
|
||||
@@ -228,6 +318,13 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
}
|
||||
}
|
||||
|
||||
memset(&s_dist.progress, 0, sizeof(s_dist.progress));
|
||||
if (progress != NULL) {
|
||||
s_dist.progress = *progress;
|
||||
}
|
||||
s_dist.total_bytes = size;
|
||||
prog_begin(size);
|
||||
|
||||
ESP_LOGI(TAG, "distributing %lu bytes from %s to %u slave(s)",
|
||||
(unsigned long)size, partition->label, (unsigned)s_dist.count);
|
||||
|
||||
@@ -240,15 +337,22 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "OTA_START to slave %lu failed",
|
||||
(unsigned long)s_dist.id[i]);
|
||||
prog_end();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wait_target_bits(target_mask, OTA_PREPARE_TIMEOUT_MS)) {
|
||||
ESP_LOGE(TAG, "timeout waiting for slave OTA ready");
|
||||
prog_end();
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
prog_set_aggregate(0);
|
||||
if (s_dist.progress.aggregate != NULL) {
|
||||
s_dist.progress.aggregate(0, size, s_dist.count);
|
||||
}
|
||||
|
||||
uint8_t block_buf[OTA_UART_FLASH_BLOCK_SIZE];
|
||||
uint32_t offset = 0;
|
||||
uint32_t seq = 0;
|
||||
@@ -263,6 +367,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "partition read @%lu failed: %s", (unsigned long)offset,
|
||||
esp_err_to_name(err));
|
||||
prog_end();
|
||||
return err;
|
||||
}
|
||||
|
||||
@@ -277,6 +382,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
err = esp_now_comm_send_ota_payload(s_dist.mac[i], seq,
|
||||
block_buf + sent, chunk);
|
||||
if (err != ESP_OK) {
|
||||
prog_end();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
@@ -293,6 +399,7 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
if (!wait_target_bits(target_mask, OTA_BLOCK_TIMEOUT_MS)) {
|
||||
ESP_LOGE(TAG, "timeout block ack @%lu bytes",
|
||||
(unsigned long)s_dist.expected_bytes);
|
||||
prog_end();
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
ESP_LOGI(TAG, "block ack @%lu/%lu (%lu%%)",
|
||||
@@ -303,34 +410,48 @@ static esp_err_t distribute_image(const esp_partition_t *partition,
|
||||
(unsigned long)block_len);
|
||||
}
|
||||
offset += block_len;
|
||||
prog_set_aggregate(offset);
|
||||
if (s_dist.progress.aggregate != NULL) {
|
||||
s_dist.progress.aggregate(offset, size, s_dist.count);
|
||||
}
|
||||
}
|
||||
|
||||
xEventGroupClearBits(s_eg, target_mask);
|
||||
for (uint8_t i = 0; i < s_dist.count; i++) {
|
||||
err = esp_now_comm_send_ota_end(s_dist.mac[i]);
|
||||
if (err != ESP_OK) {
|
||||
prog_end();
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
if (!wait_target_bits(target_mask, OTA_END_TIMEOUT_MS)) {
|
||||
ESP_LOGE(TAG, "timeout waiting for slave OTA success");
|
||||
prog_end();
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
prog_set_aggregate(size);
|
||||
prog_end();
|
||||
ESP_LOGI(TAG, "ESP-NOW OTA complete for %u slave(s)", (unsigned)s_dist.count);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ota_espnow_distribute(const esp_partition_t *partition, uint32_t size) {
|
||||
esp_err_t ota_espnow_distribute(const esp_partition_t *partition, uint32_t size,
|
||||
const ota_espnow_progress_cbs_t *progress) {
|
||||
if (partition == NULL || size == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (collect_targets() == 0) {
|
||||
ESP_LOGI(TAG, "no available slaves — skip ESP-NOW OTA");
|
||||
memset(&s_prog, 0, sizeof(s_prog));
|
||||
s_prog.total_bytes = size;
|
||||
if (progress != NULL && progress->aggregate != NULL) {
|
||||
progress->aggregate(size, size, 0);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
return distribute_image(partition, size);
|
||||
return distribute_image(partition, size, progress);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user