Distribute master OTA images to slaves over ESP-NOW.
After UART OTA the master reads the staged partition in 4 KiB blocks (200 B ESP-NOW chunks), waits for per-slave block ACKs, and fixes the final partial block. Slaves reuse ota_uart; send pacing and logging improve reliability. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#include "bosch456.h"
|
||||
#include "client_registry.h"
|
||||
#include "esp_now_comm.h"
|
||||
#include "ota_espnow.h"
|
||||
#include "esp_now_proto.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_event.h"
|
||||
@@ -12,6 +13,7 @@
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/idf_additions.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "ota_uart.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -38,6 +40,9 @@ static bool s_slave_joined;
|
||||
static uint8_t s_master_mac[ESP_NOW_ETH_ALEN];
|
||||
static uint32_t s_last_discover_ms;
|
||||
|
||||
static SemaphoreHandle_t s_send_done;
|
||||
static bool s_send_cb_ready;
|
||||
|
||||
static uint32_t now_ms(void) {
|
||||
return (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS);
|
||||
}
|
||||
@@ -78,6 +83,9 @@ static esp_err_t ensure_peer(const uint8_t *mac) {
|
||||
|
||||
static esp_err_t ensure_broadcast_peer(void) { return ensure_peer(ESPNOW_BCAST); }
|
||||
|
||||
static esp_err_t send_message_ex(const uint8_t *dest_mac,
|
||||
const alox_EspNowMessage *msg, bool wait_done);
|
||||
|
||||
static void fill_presence(alox_EspNowSlavePresence *presence) {
|
||||
presence->network = s_config.network;
|
||||
presence->version = POWERPOD_FW_VERSION;
|
||||
@@ -87,8 +95,22 @@ static void fill_presence(alox_EspNowSlavePresence *presence) {
|
||||
esp_now_proto_setup_presence_encode(presence, s_own_mac);
|
||||
}
|
||||
|
||||
static void espnow_send_done_cb(const esp_now_send_info_t *tx_info,
|
||||
esp_now_send_status_t status) {
|
||||
(void)tx_info;
|
||||
(void)status;
|
||||
if (s_send_done != NULL) {
|
||||
xSemaphoreGive(s_send_done);
|
||||
}
|
||||
}
|
||||
|
||||
static esp_err_t send_message(const uint8_t *dest_mac,
|
||||
const alox_EspNowMessage *msg) {
|
||||
return send_message_ex(dest_mac, msg, false);
|
||||
}
|
||||
|
||||
static esp_err_t send_message_ex(const uint8_t *dest_mac,
|
||||
const alox_EspNowMessage *msg, bool wait_done) {
|
||||
uint8_t buf[ESPNOW_PB_MAX_SIZE];
|
||||
size_t len = 0;
|
||||
|
||||
@@ -98,14 +120,31 @@ static esp_err_t send_message(const uint8_t *dest_mac,
|
||||
return err;
|
||||
}
|
||||
|
||||
if (len > ESP_NOW_MAX_DATA_LEN) {
|
||||
ESP_LOGW(TAG, "encoded len %u > ESP-NOW max %u", (unsigned)len,
|
||||
(unsigned)ESP_NOW_MAX_DATA_LEN);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
if (ensure_peer(dest_mac) != ESP_OK) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (wait_done && s_send_cb_ready && s_send_done != NULL) {
|
||||
xSemaphoreTake(s_send_done, 0);
|
||||
}
|
||||
|
||||
err = esp_now_send(dest_mac, buf, len);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "send type=%u failed: %s", (unsigned)msg->type,
|
||||
esp_err_to_name(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
if (wait_done && s_send_cb_ready && s_send_done != NULL) {
|
||||
if (xSemaphoreTake(s_send_done, pdMS_TO_TICKS(50)) != pdTRUE) {
|
||||
ESP_LOGW(TAG, "send type=%u done timeout", (unsigned)msg->type);
|
||||
}
|
||||
}
|
||||
return err;
|
||||
}
|
||||
@@ -132,6 +171,96 @@ static esp_err_t send_unicast_test(const uint8_t *dest_mac, uint32_t seq) {
|
||||
return send_message(dest_mac, &msg);
|
||||
}
|
||||
|
||||
static esp_err_t send_ota_start(const uint8_t *dest_mac, uint32_t total_size) {
|
||||
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||
|
||||
msg.type = alox_EspNowMessageType_ESPNOW_OTA_START;
|
||||
msg.which_payload = alox_EspNowMessage_ota_start_tag;
|
||||
msg.payload.ota_start.total_size = total_size;
|
||||
|
||||
return send_message_ex(dest_mac, &msg, true);
|
||||
}
|
||||
|
||||
static esp_err_t send_ota_payload(const uint8_t *dest_mac, uint32_t seq,
|
||||
const uint8_t *data, size_t len) {
|
||||
if (data == NULL || len == 0 || len > OTA_UART_HOST_CHUNK_SIZE) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||
|
||||
msg.type = alox_EspNowMessageType_ESPNOW_OTA_PAYLOAD;
|
||||
msg.which_payload = alox_EspNowMessage_ota_payload_tag;
|
||||
msg.payload.ota_payload.seq = seq;
|
||||
msg.payload.ota_payload.data.size = len;
|
||||
memcpy(msg.payload.ota_payload.data.bytes, data, len);
|
||||
|
||||
return send_message_ex(dest_mac, &msg, true);
|
||||
}
|
||||
|
||||
static esp_err_t send_ota_end(const uint8_t *dest_mac) {
|
||||
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||
|
||||
msg.type = alox_EspNowMessageType_ESPNOW_OTA_END;
|
||||
msg.which_payload = alox_EspNowMessage_ota_end_tag;
|
||||
|
||||
return send_message_ex(dest_mac, &msg, true);
|
||||
}
|
||||
|
||||
static esp_err_t send_ota_status(const uint8_t *dest_mac, uint32_t status,
|
||||
uint32_t bytes_written, uint32_t error) {
|
||||
alox_EspNowMessage msg = alox_EspNowMessage_init_zero;
|
||||
|
||||
msg.type = alox_EspNowMessageType_ESPNOW_OTA_STATUS;
|
||||
msg.which_payload = alox_EspNowMessage_ota_status_tag;
|
||||
msg.payload.ota_status.status = status;
|
||||
msg.payload.ota_status.bytes_written = bytes_written;
|
||||
msg.payload.ota_status.error = error;
|
||||
|
||||
return send_message_ex(dest_mac, &msg, true);
|
||||
}
|
||||
|
||||
esp_err_t esp_now_comm_send_ota_start(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
uint32_t total_size) {
|
||||
if (mac == NULL || !s_config.master) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return send_ota_start(mac, total_size);
|
||||
}
|
||||
|
||||
esp_err_t esp_now_comm_send_ota_payload(const uint8_t mac[CLIENT_MAC_LEN],
|
||||
uint32_t seq, const uint8_t *data,
|
||||
size_t len) {
|
||||
if (mac == NULL || !s_config.master) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return send_ota_payload(mac, seq, data, len);
|
||||
}
|
||||
|
||||
esp_err_t esp_now_comm_send_ota_end(const uint8_t mac[CLIENT_MAC_LEN]) {
|
||||
if (mac == NULL || !s_config.master) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return send_ota_end(mac);
|
||||
}
|
||||
|
||||
esp_err_t esp_now_comm_send_ota_status(const uint8_t master_mac[CLIENT_MAC_LEN],
|
||||
uint32_t status, uint32_t bytes_written,
|
||||
uint32_t error) {
|
||||
if (master_mac == NULL || s_config.master) {
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
return send_ota_status(master_mac, status, bytes_written, error);
|
||||
}
|
||||
|
||||
bool esp_now_comm_get_master_mac(uint8_t mac_out[CLIENT_MAC_LEN]) {
|
||||
if (mac_out == NULL || s_config.master || !s_slave_joined) {
|
||||
return false;
|
||||
}
|
||||
memcpy(mac_out, s_master_mac, CLIENT_MAC_LEN);
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
@@ -363,6 +492,20 @@ static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||
case alox_EspNowMessage_accel_deadzone_tag:
|
||||
handle_slave_accel_deadzone(info->src_addr, &msg.payload.accel_deadzone);
|
||||
break;
|
||||
case alox_EspNowMessage_ota_start_tag:
|
||||
case alox_EspNowMessage_ota_payload_tag:
|
||||
case alox_EspNowMessage_ota_end_tag:
|
||||
if (!s_slave_joined || !mac_equal(info->src_addr, s_master_mac)) {
|
||||
break;
|
||||
}
|
||||
if (msg.which_payload == alox_EspNowMessage_ota_start_tag) {
|
||||
ota_espnow_slave_on_start(info->src_addr, &msg.payload.ota_start);
|
||||
} else if (msg.which_payload == alox_EspNowMessage_ota_payload_tag) {
|
||||
ota_espnow_slave_on_payload(info->src_addr, &msg.payload.ota_payload);
|
||||
} else {
|
||||
ota_espnow_slave_on_end(info->src_addr);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "slave: unhandled ESP-NOW which=%u type=%u", msg.which_payload,
|
||||
(unsigned)msg.type);
|
||||
@@ -378,6 +521,12 @@ static void espnow_recv_cb(const esp_now_recv_info_t *info, const uint8_t *data,
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.which_payload == alox_EspNowMessage_ota_status_tag) {
|
||||
ensure_peer(info->src_addr);
|
||||
ota_espnow_master_on_status(info->src_addr, &msg.payload.ota_status);
|
||||
return;
|
||||
}
|
||||
|
||||
const alox_EspNowSlavePresence *presence = esp_now_proto_get_presence(&msg);
|
||||
if (presence != NULL) {
|
||||
/* Registry key is the ESP-NOW sender MAC, not the optional protobuf mac field. */
|
||||
@@ -463,6 +612,14 @@ esp_err_t esp_now_comm_init(const app_config_t *config) {
|
||||
ESP_ERROR_CHECK(esp_now_init());
|
||||
ESP_ERROR_CHECK(esp_now_register_recv_cb(espnow_recv_cb));
|
||||
|
||||
s_send_done = xSemaphoreCreateBinary();
|
||||
if (s_send_done != NULL &&
|
||||
esp_now_register_send_cb(espnow_send_done_cb) == ESP_OK) {
|
||||
s_send_cb_ready = true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "ESP-NOW send-done callback unavailable (OTA may drop packets)");
|
||||
}
|
||||
|
||||
if (config->master) {
|
||||
ESP_ERROR_CHECK(ensure_broadcast_peer());
|
||||
if (xTaskCreate(master_discover_task, "espnow_disc", 4096, NULL, 4,
|
||||
|
||||
Reference in New Issue
Block a user