From 093ce60a9f70e98bafbadad568c145de4f3c323e Mon Sep 17 00:00:00 2001 From: "Morgan 'ARR\\!' Allen" Date: Thu, 28 May 2026 22:35:34 -0700 Subject: [PATCH] migrate OTA to be connection manager based --- CMakeLists.txt | 13 +- include/baros_ble_ota.h | 7 + src/baros_ble.c | 263 ++---------------------------------- src/baros_ble_ota.c | 287 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 319 insertions(+), 251 deletions(-) create mode 100644 include/baros_ble_ota.h create mode 100644 src/baros_ble_ota.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5fbcd61..197316d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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} diff --git a/include/baros_ble_ota.h b/include/baros_ble_ota.h new file mode 100644 index 0000000..33c6791 --- /dev/null +++ b/include/baros_ble_ota.h @@ -0,0 +1,7 @@ +#ifndef __BAROS_BLE_OTA_H__ +#define __BAROS_BLE_OTA_H__ +#endif + +#include + +uint8_t baros_ble_ota_init(); diff --git a/src/baros_ble.c b/src/baros_ble.c index d735ac3..573c014 100644 --- a/src/baros_ble.c +++ b/src/baros_ble.c @@ -1,266 +1,31 @@ +#include + #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; } diff --git a/src/baros_ble_ota.c b/src/baros_ble_ota.c new file mode 100644 index 0000000..f979880 --- /dev/null +++ b/src/baros_ble_ota.c @@ -0,0 +1,287 @@ +#include + +#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; +}