migrate OTA to be connection manager based

This commit is contained in:
Morgan 'ARR\!' Allen 2026-05-28 22:35:34 -07:00
parent d840e52609
commit 093ce60a9f
4 changed files with 319 additions and 251 deletions

View file

@ -1,5 +1,14 @@
set(REQUIRES bt)
set(SRCS src/baros_ble.c)
set(REQUIRES
app_update
bootloader_support
bt
esp_ringbuf
)
set(SRCS
src/baros_ble.c
src/baros_ble_ota.c
)
idf_component_register(
SRCS ${SRCS}

7
include/baros_ble_ota.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef __BAROS_BLE_OTA_H__
#define __BAROS_BLE_OTA_H__
#endif
#include <stdint.h>
uint8_t baros_ble_ota_init();

View file

@ -1,266 +1,31 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "esp_system.h"
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/bas/ble_svc_bas.h"
#include "freertos/semphr.h"
#include "esp_bt.h"
#include "esp_log.h"
#include "esp_event_base.h"
#include "esp_event.h"
#include "baros.h"
#include "baros_ble_ota.h"
#define TAG "BOS_BLE"
ESP_EVENT_DEFINE_BASE(EVENT_BAROS_BLE);
static uint8_t blehr_addr_type;
static uint16_t conn_handle;
static const char *device_name;
static const struct ble_gatt_svc_def service_defs[] = {
{ NULL }
};
static void ble_advertise(void);
static int ble_gap_event(struct ble_gap_event *event, void *arg) {
switch (event->type) {
case BLE_GAP_EVENT_DISC: {
struct ble_hs_adv_fields fields;
ESP_ERROR_CHECK(ble_hs_adv_parse_fields(&fields, event->disc.data, event->disc.length_data));
ESP_LOGV(TAG, "DISCOVERY: %d", event->disc.length_data);
uint8_t addr[6];
memcpy(&addr, (uint8_t*)&event->disc.addr + 1, 6);
ESP_LOGV(TAG, "addr: %02X:%02X:%02X:%02X:%02X:%02X", addr[5], addr[4], addr[3], addr[2], addr[1], addr[0]);
ESP_ERROR_CHECK(esp_event_post(EVENT_BAROS_BLE, BAROS_BLE_DISC, (void*)&event->disc, sizeof(struct ble_gap_disc_desc), 1000));
/*
uint8_t *buf = (uint8_t*)malloc(event->disc.length_data);
memcpy(buf, event->disc.data, event->disc.length_data);
printf("\ndata: ");
for(uint8_t i = 0; i < event->disc.length_data; i++) {
printf("0x%02X ", buf[i]);
}
printf("\n");
*/
if(fields.name != NULL) {
char *name = (char*)malloc(fields.name_len);
memcpy(name, (uint8_t*)fields.name, fields.name_len);
name[fields.name_len] = '\0';
ESP_LOGV(TAG, "Name: %s", name);
}
ESP_LOGV(TAG, "uuids: %d", fields.num_uuids16);
for (uint8_t i = 0; i < fields.num_uuids16; i++) {
ESP_LOGV(TAG, "uuid: 0x%04X", fields.uuids16[i].value);
if (ble_uuid_u16(&fields.uuids16[i].u) == BLE_SVC_GAP_CHR_UUID16_DEVICE_NAME) {
ESP_LOGV(TAG, "device has name");
}
}
return 0;
}
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed */
conn_handle = event->connect.conn_handle;
ESP_LOGI(TAG, "connection %s; conn_handle: %d status=%d\n",
event->connect.status == 0 ? "established" : "failed",
conn_handle,
event->connect.status
);
// always keep advertising
ble_advertise();
break;
case BLE_GAP_EVENT_DISCONNECT:
ESP_LOGI(TAG, "disconnect; reason=%d\n", event->disconnect.reason);
/* Connection terminated; resume advertising */
ble_advertise();
break;
case BLE_GAP_EVENT_ADV_COMPLETE:
ESP_LOGI(TAG, "adv complete\n");
ble_advertise();
break;
case BLE_GAP_EVENT_SUBSCRIBE:
ESP_LOGI(TAG, "subscribe event; cur_notify=%d\n value handle; "
"val_handle=%d\n",
event->subscribe.cur_notify, event->subscribe.attr_handle);
break;
case BLE_GAP_EVENT_CONN_UPDATE:
ESP_LOGI(TAG, "BLE_GAP_EVENT_CONN_UPDATE, conn_handle: %d status: %d",
event->mtu.conn_handle,
event->enc_change.status
);
break;
case BLE_GAP_EVENT_ENC_CHANGE:
ESP_LOGI(TAG, "BLE_GAP_EVENT_ENC_CHANGE");
break;
case BLE_GAP_EVENT_MTU:
ESP_LOGI(TAG, "mtu update event; conn_handle=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.value);
break;
case BLE_GAP_EVENT_NOTIFY_RX:
ESP_LOGI(TAG, "BLE_GAP_EVENT_NOTIFY_RX; conn_handle=%d mtu=%d attr_handle=%d\n",
event->mtu.conn_handle,
event->mtu.value,
event->notify_rx.attr_handle);
break;
case BLE_GAP_EVENT_NOTIFY_TX:
ESP_LOGI(TAG, "BLE_GAP_EVENT_NOTIFY_TX; conn_handle=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.value);
break;
default:
ESP_LOGW(TAG, "unhandled ble_gap_event; conn_handle=%d type=%d\n",
event->mtu.conn_handle,
event->type
);
}
return 0;
}
static void ble_advertise(void) {
// check if already adverting
if(ble_gap_adv_active()) return;
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
int rc;
memset(&fields, 0, sizeof(fields));
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
ESP_LOGE(TAG, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising */
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(blehr_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, ble_gap_event, NULL);
if (rc != 0) {
ESP_LOGE(TAG, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
int baros_ble_scan_start() {
uint8_t addr_type;
struct ble_gap_disc_params disc_params = {
.filter_duplicates = 1,
.passive = 1,
.itvl = 0,
.window = 0,
.filter_policy = 0,
.limited = 0,
};
ESP_ERROR_CHECK(ble_hs_id_infer_auto(0, &addr_type));
ble_gap_disc_cancel();
ESP_ERROR_CHECK(ble_gap_disc(addr_type, 3000, &disc_params, ble_gap_event, &addr_type));
return 0;
}
int baros_ble_scan_stop() {
return ble_gap_disc_cancel();
}
void nimble_host_task(void *param) {
nimble_port_run();
}
static void on_sync() {
ESP_LOGI(TAG, "on_sync");
int err;
err = ble_hs_id_infer_auto(0, &blehr_addr_type);
ESP_ERROR_CHECK(err);
uint8_t addr_val[6] = {0};
err = ble_hs_id_copy_addr(blehr_addr_type, addr_val, NULL);
ESP_ERROR_CHECK(err);
ble_advertise();
}
static void on_reset(int reason) {
ESP_LOGE(TAG, "on_reset, reason: %d", reason);
}
uint8_t baros_ble_init(baros_ble_cfg_t *cfg) {
esp_err_t err;
ESP_LOGV(TAG, "init");
device_name = cfg->name;
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
//ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
esp_err_t ret = esp_bt_controller_init(&bt_cfg);
ESP_ERROR_CHECK(ret);
nimble_port_init();
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
ESP_ERROR_CHECK(ret);
ble_hs_cfg.sync_cb = on_sync;
ble_hs_cfg.reset_cb = on_reset;
// initialize services
err = ble_gatts_count_cfg(service_defs);
ESP_ERROR_CHECK(err);
err = ble_gatts_add_svcs(service_defs);
ESP_ERROR_CHECK(err);
ESP_LOGI(TAG, "Setting device name: %s", device_name);
err = ble_svc_gap_device_name_set(device_name);
ESP_ERROR_CHECK(err);
vTaskDelay(500 / portTICK_PERIOD_MS);
nimble_port_freertos_init(nimble_host_task);
baros_ble_ota_init();
return 0;
}

287
src/baros_ble_ota.c Normal file
View file

@ -0,0 +1,287 @@
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/ringbuf.h"
#include "esp_app_format.h"
#include "esp_log.h"
#include "esp_ota_ops.h"
#include "ble_ota.h"
#include "esp_delta_ota.h"
#include "esp_partition.h"
#include "esp_app_format.h"
#include "baros.h"
#include "baros_ble_ota.h"
#define TAG "BOS_BLE_OTA"
#define DIGEST_SIZE 32
#define IMG_HEADER_LEN sizeof(esp_image_header_t)
#define OTA_RINGBUF_SIZE 8192
#define OTA_TASK_SIZE 8192
#define PATCH_HEADER_SIZE 64
const esp_partition_t *current_partition;
static RingbufHandle_t s_ringbuf = NULL;
static esp_ota_handle_t out_handle;
SemaphoreHandle_t notify_sem;
static bool verify_patch_header(void *img_hdr_data)
{
if (!img_hdr_data) {
return false;
}
uint32_t esp_delta_ota_magic = 0xfccdde10;
uint32_t recv_magic = *(uint32_t *)img_hdr_data;
uint8_t *digest = (uint8_t *)(img_hdr_data + 4);
uint8_t sha_256[DIGEST_SIZE] = { 0 };
if (recv_magic != esp_delta_ota_magic) {
ESP_LOGE(TAG, "Invalid magic word in patch");
return false;
}
esp_partition_get_sha256(esp_ota_get_running_partition(), sha_256);
if (memcmp(sha_256, digest, DIGEST_SIZE) != 0) {
ESP_LOGE(TAG, "SHA256 of current firmware differs from than in patch header. Invalid patch for current firmware");
return false;
}
return true;
}
static bool verify_chip_id(void *bin_header_data)
{
esp_image_header_t *header = (esp_image_header_t *)bin_header_data;
if (header->chip_id != CONFIG_IDF_FIRMWARE_CHIP_ID) {
ESP_LOGE(TAG, "Mismatch chip id, expected %d, found %d", CONFIG_IDF_FIRMWARE_CHIP_ID, header->chip_id);
return false;
}
return true;
}
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0))
static esp_err_t write_cb(const uint8_t *buf_p, size_t size, void *user_data)
#else
static esp_err_t write_cb(const uint8_t *buf_p, size_t size)
#endif
{
if (size <= 0) {
return ESP_ERR_INVALID_ARG;
}
static char header_data[IMG_HEADER_LEN];
static bool chip_id_verified = false;
static int header_data_read = 0;
int index = 0;
if (!chip_id_verified) {
if (header_data_read + size <= IMG_HEADER_LEN) {
memcpy(header_data + header_data_read, buf_p, size);
header_data_read += size;
return ESP_OK;
} else {
index = IMG_HEADER_LEN - header_data_read;
memcpy(header_data + header_data_read, buf_p, index);
if (!verify_chip_id(header_data)) {
return ESP_ERR_INVALID_VERSION;
}
chip_id_verified = true;
// Write data in header_data buffer.
esp_err_t err = esp_ota_write(out_handle, header_data, IMG_HEADER_LEN);
if (err != ESP_OK) {
return err;
}
}
}
return esp_ota_write(out_handle, buf_p + index, size - index);
}
static esp_err_t read_cb(uint8_t *buf_p, size_t size, int src_offset)
{
esp_err_t temp;
if (size <= 0) {
return ESP_ERR_INVALID_ARG;
}
temp = esp_partition_read(current_partition, src_offset, buf_p, size);
return temp;
}
bool ble_ota_ringbuf_init(uint32_t ringbuf_size) {
s_ringbuf = xRingbufferCreate(ringbuf_size, RINGBUF_TYPE_BYTEBUF);
if (s_ringbuf == NULL) {
return false;
}
return true;
}
size_t write_to_ringbuf(const uint8_t *data, size_t size) {
BaseType_t done = xRingbufferSend(s_ringbuf, (void *)data, size, (TickType_t)portMAX_DELAY);
if (done) {
return size;
} else {
return 0;
}
}
void ota_recv_fw_cb(uint8_t *buf, uint32_t length) {
write_to_ringbuf(buf, length);
}
void ota_task(void *arg) {
esp_partition_t *partition_ptr = NULL;
esp_partition_t partition;
const esp_partition_t *next_partition = NULL;
uint32_t recv_len = 0;
uint8_t *data = NULL;
size_t item_size = 0;
ESP_LOGI(TAG, "ota_task start");
notify_sem = xSemaphoreCreateCounting(100, 0);
xSemaphoreGive(notify_sem);
esp_err_t err;
esp_delta_ota_cfg_t cfg = {
.read_cb = &read_cb,
};
#if (ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 2, 0))
char *user_data = "ble_delta_ota";
cfg.write_cb_with_user_data = &write_cb;
cfg.user_data = user_data;
#else
cfg.write_cb = &write_cb;
#endif
const esp_partition_t *destination_partition;
esp_delta_ota_handle_t handle = esp_delta_ota_init(&cfg);
if (handle == NULL) {
ESP_LOGE(TAG, "delta_ota_set_cfg failed!");
goto OTA_ERROR;
}
/* search ota partition */
current_partition = esp_ota_get_running_partition();
destination_partition = esp_ota_get_next_update_partition(NULL);
if (current_partition == NULL || destination_partition == NULL) {
ESP_LOGE(TAG, "Error getting partition information");
goto OTA_ERROR;
}
if (current_partition->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MAX ||
destination_partition->subtype >= ESP_PARTITION_SUBTYPE_APP_OTA_MAX) {
goto OTA_ERROR;
}
err = esp_ota_begin(destination_partition, OTA_SIZE_UNKNOWN, &(out_handle));
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_begin failed: %s", esp_err_to_name(err));
goto OTA_ERROR;
}
ESP_LOGI(TAG, "wait for data from ringbuf! fw_len = %u", esp_ble_ota_get_fw_length());
/*deal with all receive packet*/
for (;;) {
data = (uint8_t *)xRingbufferReceive(s_ringbuf, &item_size, (TickType_t)portMAX_DELAY);
xSemaphoreTake(notify_sem, portMAX_DELAY);
static int flag = 0;
static char ota_write_data[65] = { 0 };
/*deal with receive patch header*/
if (flag == 0) {
flag = 1;
/* Read size equal to patch header to verify the header*/
memcpy(ota_write_data, data, PATCH_HEADER_SIZE);
if (!verify_patch_header(ota_write_data)) {
ESP_LOGE(TAG, "Patch Header verification failed!");
goto OTA_ERROR;
}
data += 64;
item_size -= 64;
recv_len += 64;
}
ESP_LOGI(TAG, "recv: %u, recv_total:%"PRIu32"\n", item_size, recv_len + item_size);
if (item_size != 0) {
if (esp_delta_ota_feed_patch(handle, (const uint8_t *)data, item_size) < 0) {
ESP_LOGE(TAG, "Error while applying patch");
goto OTA_ERROR;
}
recv_len += item_size;
vRingbufferReturnItem(s_ringbuf, (void *)data);
if (recv_len >= esp_ble_ota_get_fw_length()) {
xSemaphoreGive(notify_sem);
break;
}
}
xSemaphoreGive(notify_sem);
}
err = esp_delta_ota_finalize(handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_delta_ota_finalize() failed : %s", esp_err_to_name(err));
goto OTA_ERROR;
}
err = esp_delta_ota_deinit(handle);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_delta_ota_deinit() failed : %s", esp_err_to_name(err));
goto OTA_ERROR;
}
if (esp_ota_end(out_handle) != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_end failed!\r\n");
goto OTA_ERROR;
}
if (esp_ota_set_boot_partition(destination_partition) != ESP_OK) {
ESP_LOGE(TAG, "esp_ota_set_boot_partition failed!\r\n");
goto OTA_ERROR;
}
vSemaphoreDelete(notify_sem);
esp_restart();
OTA_ERROR:
ESP_LOGE(TAG, "OTA failed");
vTaskDelete(NULL);
}
static void ota_task_init(void) {
xTaskCreate(&ota_task, "ota_task", OTA_TASK_SIZE, NULL, 5, NULL);
return;
}
uint8_t baros_ble_ota_init() {
if (!ble_ota_ringbuf_init(OTA_RINGBUF_SIZE)) {
ESP_LOGE(TAG, "%s init ring buffer failed", __func__);
}
esp_err_t ret = esp_ble_ota_host_init();
ESP_ERROR_CHECK(ret);
esp_ble_ota_recv_fw_data_callback(ota_recv_fw_cb);
ota_task_init();
return 0;
}