mirror of
https://github.com/garagetinkering/Video_Game_Mini_Maps.git
synced 2026-06-08 01:27:44 +02:00
initial commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ble_compatibility_test)
|
||||
@@ -0,0 +1,62 @@
|
||||
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
|
||||
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
|
||||
|
||||
| Supported Co-Processors | ESP32 | ESP32-C Series | ESP32-S Series |
|
||||
| ----------------------- | ----- | -------------- | -------------- |
|
||||
|
||||
# ESP-IDF BLE Compatibility Test Example
|
||||
|
||||
This example is to test the Bluetooth compatibility and mobile phones.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
This example has been modified to work with ESP-Hosted. The original ESP-IDF example is at [ https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test ].
|
||||
|
||||
This example is able to run on the ESP32-P4 Dev Board, acting as the BT Host, connected to a ESP32 co-processor via the GPIO header, using SPI FD (full duplex) as Hosted HCI transport. The ESP32-P4 acts as the BT Controller. The following GPIO settings were used:
|
||||
|
||||
| SPI Function | ESP32 GPIO | ESP32-P4 GPIO |
|
||||
| :--- | ---: | ---: |
|
||||
| MOSI | 13 | 4 |
|
||||
| MISO | 12 | 5 |
|
||||
| CLK | 14 | 26 |
|
||||
| CS | 15 | 6 |
|
||||
| Handshake | 26 | 20 |
|
||||
| Data Ready | 4 | 36 |
|
||||
| Reset | -1 | 2 |
|
||||
|
||||
> [!NOTE]
|
||||
> SPI Mode 2 was used on both the ESP32-P4 and ESP32.
|
||||
|
||||
Users are free to choose which supported ESP-Hosted transport to use. See the [main ESP-Hosted README](https://github.com/espressif/esp-hosted-mcu/blob/main/README.md#6-decide-the-communication-bus-in-between-host-and-slave) for a list of supported transports.
|
||||
|
||||
Before project configuration and build, be sure to set the correct chip target using:
|
||||
|
||||
```bash
|
||||
idf.py set-target <chip_name>
|
||||
```
|
||||
|
||||
### Test Scenario
|
||||
|
||||
* ESP32-P4-Function-EV-Board connected to a ESP32 via the GPIO header
|
||||
* [Test case](ble_compatibility_test_case.md)
|
||||
* Test APK: LightBlue V2.0.5
|
||||
|
||||
### Configure the project
|
||||
|
||||
On the ESP32-P4 Dev Board, run `idf.py menuconfig`.
|
||||
|
||||
* Check and enable Classic Bluetooth and Classic BT HID Device under `Component config --> Bluetooth --> Bluedroid Options`
|
||||
* Ensure that `Component config --> Bluetooth --> Controller` is `Disabled`.
|
||||
* Under `Component config --> ESP-Hosted config`:
|
||||
* Configure ESP-Hosted to use `SPI Full-duplex` as the transport
|
||||
* Set the Slave chipset used as `ESP32`
|
||||
* Check and enable `Bluetooth Support`
|
||||
* Configure the GPIOs used for SPI FD on both the ESP32-P4 and ESP32, following the table above
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
|
||||
@@ -0,0 +1,180 @@
|
||||
# Test Case for BLE Smartphone Compatibility
|
||||
|
||||
This document provides a test case for BLE smartphone compatibility and includes detailed procedures for various test items.
|
||||
|
||||
## Preparation
|
||||
|
||||
### What You Need
|
||||
|
||||
* ESP device which needs to flash [this test program](main/ble_compatibility_test.c)
|
||||
* Smartphone with LightBlue® Explorer app
|
||||
|
||||
### Initialization
|
||||
|
||||
Prior to conducting tests, please initialize the smartphone and the ESP device as follows:
|
||||
|
||||
* Set the device name as `BLE_COMP_TEST`.
|
||||
* Set the maximum transmission unit (MTU) of the device to 33 bytes, to test the assembly and division of data packets.
|
||||
* If the smartphone has been paired with the ESP device before, please delete the pairing in the Bluetooth setting as follows: `Bluetooth` -> `My Devices` -> `Find this device with "i" in a circle on the right` -> `Forget this device`. Then restart the Bluetooth service.
|
||||
* Before flashing the test program onto the ESP device, make sure to erase the contents of the flash by executing the command `make erase_flash flash` in the Terminal.
|
||||
* When the ESP device restarts, the pairing information will be erased automatically. After that, make sure that the pairing information in the Bluetooth setting of the smartphone is deleted.
|
||||
|
||||
**Note:**
|
||||
|
||||
* For tests marked with (*) further in the document, please bear in mind the following:
|
||||
* Your phone performance may affect the results of these tests. If such a test fails, it does not mean the phone fails to meet the test requirements, but that you need to arrange targeted tests.
|
||||
* Taking "Test for Connection Success Rate" as an example: if the test cannot be passed for 10 consecutive times, you need to record how many times the test was passed and then arrange targeted tests.
|
||||
* For extended testing, please use the [examples] (https://github.com/espressif/esp-idf/tree/master/examples/bluetooth) provided by Espressif.
|
||||
|
||||
## Test for ADV Performance (*)
|
||||
|
||||
### Search Device
|
||||
|
||||
Refresh the scanning in LightBlue® Explorer to check if the device to be tested can be found quickly. Please repeat this action 10 times.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* The device starts advertizing and outputs the log `(0) ***** advertising start successfully *****`.
|
||||
* LightBlue® Explorer scans and successfully discovers ` BLE_COMP_TEST` each time.
|
||||
|
||||
**Note:**
|
||||
|
||||
* The device broadcasts on 3 channels, with an ADV interval of 40 ms.
|
||||
* Check if the ADV packet can be received.
|
||||
* Check if the Scan Response packet can be received.
|
||||
* The device name is included in Scan Response packets only and cannot be found in ADV packets.
|
||||
|
||||
## Test for Pairing Performance
|
||||
|
||||
### Connect Device
|
||||
|
||||
* Open the LightBlue® Explorer scan list and tap on the device name ` BLE_COMP_TEST` to establish connection.
|
||||
* ESP device prints a passkey: `The passkey notify number: 123456`.
|
||||
* A prompt on the smartphone appears asking if you want to pair. Tap on *Pair*, and then enter the passkey "123456".
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* If the connection is successful:
|
||||
* Smartphone shows DATA beginning with `ADVERTISEMENT DATA`
|
||||
* ESP device outputs the log: `ESP_GATTS_CONNECT_EVT`
|
||||
* When the pairing is established, the device shows the following log in green: `(1) ***** pair status = success *****`
|
||||
|
||||
## Test for Service Discovery Performance
|
||||
|
||||
### Test Procedures
|
||||
|
||||
In LightBlue® Explorer, check the contents of `GATT SERVICES & CHARACTERISTICS`.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* Service that starts with ``000000ff`` appears at the bottom of your smartphone.
|
||||
* This service contains 3 characteristics
|
||||
* `Char_1_Short_WR`
|
||||
* `Char_2_Long_WR`
|
||||
* `Char_3_Short_Notify`
|
||||
|
||||
## Test for Read and Encrypt
|
||||
|
||||
### Test Procedures
|
||||
|
||||
Read the value of `Char_1` in LightBlue, and tap on `READ AGAIN`.
|
||||
|
||||
### Test Results
|
||||
|
||||
* Encryption is successful, if your smartphone shows the value "11 22 33 44", and the ESP device prints the log: `(2) ***** read char_1 *****`.
|
||||
* Encryption fails, if your smartphone shows a blank screen, and the ESP device outputs the error log in red: `GATT_INSUF_AUTHENTICATION: MITM Required`.
|
||||
|
||||
## Test for Short Read and Write
|
||||
|
||||
### Test Procedures
|
||||
|
||||
* Navigate to the WRITE interface in LightBlue® Explorer, and write the value "88 99" to `Char_1`.
|
||||
* Read `Char_1` and check if its value is consistent with the data you have written to it.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* ESP device prints the log: `(3)***** short write success *****`.
|
||||
* LightBlue® Explorer shows "88 99" below `READ AGAIN`.
|
||||
|
||||
## Test for Long Read and Write
|
||||
|
||||
### Test Procedures
|
||||
|
||||
* Navigate to the WRITE interface in LightBlue® Explorer, and write the string `0x001122…FF001122…FF` of 256 bytes to `Char_2`. The data takes up 16 lines and looks as follows:
|
||||
|
||||
```
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
…
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
00112233445566778899AABBCCDDEEFF
|
||||
```
|
||||
|
||||
* Read `Char_2` and check if its value is consistent with the data you have written to it.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* The device prints the log: ``ESP_GATTS_EXEC_WRITE_EVT, Length=256`` and ``(4) ***** long write success *****``.
|
||||
* LightBlue® Explorer shows `(5) ***** read char_2 *****` below `READ AGAIN`.
|
||||
|
||||
**Note:**
|
||||
|
||||
The data to be written can be copied from a text file and pasted into LightBlue® Explorer.
|
||||
|
||||
## Test for Short Notify
|
||||
|
||||
### Test Procedures
|
||||
|
||||
* Enter `Char_3` and tap on `SUBSCRIBE` to enable its Notify function.
|
||||
* Your phone automatically receives Notify data from the device.
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* ESP device prints the log: `(6) ***** send notify AA BB *****`.
|
||||
* "AA BB" appears on your smartphone.
|
||||
|
||||
## Test for Connection Success Rate (*)
|
||||
|
||||
### Test procedures
|
||||
|
||||
* Break the connection
|
||||
* Re-establish the connection
|
||||
* Repeat 10 times
|
||||
|
||||
### Test Results
|
||||
|
||||
The test is passed, if you get the following results:
|
||||
|
||||
* Your phone establishes the connection successfully, and the ESP device outputs the log: `(1) ***** pair status = success *****`.
|
||||
* Your phone breaks the connection, and the device outputs the log: `ESP_GATTS_DISCONNECT_EVT`.
|
||||
* Connection can be set up each time with no issues.
|
||||
|
||||
## Test for Long Connection Stability
|
||||
|
||||
The connection must be stable throughout the tests.
|
||||
|
||||
**Note:**
|
||||
|
||||
If the existing connection breaks:
|
||||
|
||||
* LightBlue® Explorer prints `Disconnected`.
|
||||
* ESP device outputs the log: ``ESP_GATTS_DISCONNECT_EVT, reason = (0) ***** advertising start successfully *****``.
|
||||
|
||||
## Further Information
|
||||
|
||||
* If you see any log entry in red, please record it for future reference or feedback it to our engineer.
|
||||
* Tests to be added in the future:
|
||||
* Multi-connection Test
|
||||
* Automatic Re-connection Test
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "ble_compatibility_test.c"
|
||||
PRIV_REQUIRES bt nvs_flash esp_hosted
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,752 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
/********************************************************************************
|
||||
*
|
||||
* This file is for gatt server. It can send adv data, and get connected by client.
|
||||
*
|
||||
*********************************************************************************/
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
// #include "esp_bt.h"
|
||||
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gatts_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "ble_compatibility_test.h"
|
||||
#include "esp_gatt_common_api.h"
|
||||
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_bluedroid.h"
|
||||
|
||||
#define DEBUG_ON 0
|
||||
|
||||
#if DEBUG_ON
|
||||
#define EXAMPLE_DEBUG ESP_LOGI
|
||||
#else
|
||||
#define EXAMPLE_DEBUG( tag, format, ... )
|
||||
#endif
|
||||
|
||||
#define EXAMPLE_TAG "BLE_COMP"
|
||||
|
||||
#define PROFILE_NUM 1
|
||||
#define PROFILE_APP_IDX 0
|
||||
#define ESP_APP_ID 0x55
|
||||
#define SAMPLE_DEVICE_NAME "BLE_COMP_TEST"
|
||||
#define SVC_INST_ID 0
|
||||
|
||||
/* The max length of characteristic value. When the gatt client write or prepare write,
|
||||
* the data length must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
|
||||
*/
|
||||
#define GATTS_EXAMPLE_CHAR_VAL_LEN_MAX 500
|
||||
#define LONG_CHAR_VAL_LEN 500
|
||||
#define SHORT_CHAR_VAL_LEN 10
|
||||
#define GATTS_NOTIFY_FIRST_PACKET_LEN_MAX 20
|
||||
|
||||
#define PREPARE_BUF_MAX_SIZE 1024
|
||||
#define CHAR_DECLARATION_SIZE (sizeof(uint8_t))
|
||||
|
||||
#define ADV_CONFIG_FLAG (1 << 0)
|
||||
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
|
||||
|
||||
static uint8_t adv_config_done = 0;
|
||||
|
||||
uint16_t gatt_db_handle_table[HRS_IDX_NB];
|
||||
|
||||
typedef struct {
|
||||
uint8_t *prepare_buf;
|
||||
int prepare_len;
|
||||
} prepare_type_env_t;
|
||||
|
||||
static prepare_type_env_t prepare_write_env;
|
||||
|
||||
//#define CONFIG_SET_RAW_ADV_DATA
|
||||
#ifdef CONFIG_SET_RAW_ADV_DATA
|
||||
static uint8_t raw_adv_data[] = {
|
||||
/* Flags */
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
/* TX Power */
|
||||
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xeb,
|
||||
/* Service UUID */
|
||||
0x03, ESP_BLE_AD_TYPE_16SRV_CMPL, 0xFF, 0x00,
|
||||
/* Device Name */
|
||||
0x0E, ESP_BLE_AD_TYPE_NAME_CMPL, 'B', 'L', 'E', '_', 'C', 'O', 'M', 'P', '_', 'T', 'E', 'S', 'T'
|
||||
};
|
||||
|
||||
static uint8_t raw_scan_rsp_data[] = {
|
||||
/* Flags */
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
/* TX Power */
|
||||
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0xeb,
|
||||
/* Service UUID */
|
||||
0x03, ESP_BLE_AD_TYPE_16SRV_CMPL, 0xFF, 0x00
|
||||
};
|
||||
|
||||
#else
|
||||
static uint8_t service_uuid[16] = {
|
||||
/* LSB <--------------------------------------------------------------------------------> MSB */
|
||||
//first uuid, 16bit, [12],[13] is the value
|
||||
0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
/* The length of adv data must be less than 31 bytes */
|
||||
static esp_ble_adv_data_t adv_data = {
|
||||
.set_scan_rsp = false,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x20,
|
||||
.max_interval = 0x40,
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
|
||||
.p_manufacturer_data = NULL, //test_manufacturer,
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = sizeof(service_uuid),
|
||||
.p_service_uuid = service_uuid,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
|
||||
// scan response data
|
||||
static esp_ble_adv_data_t scan_rsp_data = {
|
||||
.set_scan_rsp = true,
|
||||
.include_name = true,
|
||||
.include_txpower = true,
|
||||
.min_interval = 0x20,
|
||||
.max_interval = 0x40,
|
||||
.appearance = 0x00,
|
||||
.manufacturer_len = 0, //TEST_MANUFACTURER_DATA_LEN,
|
||||
.p_manufacturer_data = NULL, //&test_manufacturer[0],
|
||||
.service_data_len = 0,
|
||||
.p_service_data = NULL,
|
||||
.service_uuid_len = 16,
|
||||
.p_service_uuid = service_uuid,
|
||||
.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT),
|
||||
};
|
||||
#endif /* CONFIG_SET_RAW_ADV_DATA */
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = 0x40,
|
||||
.adv_int_max = 0x40,
|
||||
.adv_type = ADV_TYPE_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
struct gatts_profile_inst {
|
||||
esp_gatts_cb_t gatts_cb;
|
||||
uint16_t gatts_if;
|
||||
uint16_t app_id;
|
||||
uint16_t conn_id;
|
||||
uint16_t service_handle;
|
||||
esp_gatt_srvc_id_t service_id;
|
||||
uint16_t char_handle;
|
||||
esp_bt_uuid_t char_uuid;
|
||||
esp_gatt_perm_t perm;
|
||||
esp_gatt_char_prop_t property;
|
||||
uint16_t descr_handle;
|
||||
esp_bt_uuid_t descr_uuid;
|
||||
};
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event,
|
||||
esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param);
|
||||
|
||||
/* One gatt-based profile one app_id and one gatts_if, this array will store the gatts_if returned by ESP_GATTS_REG_EVT */
|
||||
static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = {
|
||||
[PROFILE_APP_IDX] = {
|
||||
.gatts_cb = gatts_profile_event_handler,
|
||||
.gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */
|
||||
},
|
||||
};
|
||||
|
||||
/* Service */
|
||||
static const uint16_t GATTS_SERVICE_UUID_TEST = 0x00FF;
|
||||
static const uint16_t CHAR_1_SHORT_WR = 0xFF01;
|
||||
static const uint16_t CHAR_2_LONG_WR = 0xFF02;
|
||||
static const uint16_t CHAR_3_SHORT_NOTIFY = 0xFF03;
|
||||
|
||||
static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE;
|
||||
static const uint16_t character_declaration_uuid = ESP_GATT_UUID_CHAR_DECLARE;
|
||||
static const uint16_t character_client_config_uuid = ESP_GATT_UUID_CHAR_CLIENT_CONFIG;
|
||||
static const uint16_t character_user_description = ESP_GATT_UUID_CHAR_DESCRIPTION;
|
||||
static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY;
|
||||
static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ;
|
||||
static const uint8_t char1_name[] = "Char_1_Short_WR";
|
||||
static const uint8_t char2_name[] = "Char_2_Long_WR";
|
||||
static const uint8_t char3_name[] = "Char_3_Short_Notify";
|
||||
static const uint8_t char_ccc[2] = {0x00, 0x00};
|
||||
static const uint8_t char_value[4] = {0x11, 0x22, 0x33, 0x44};
|
||||
|
||||
|
||||
/* Full Database Description - Used to add attributes into the database */
|
||||
static const esp_gatts_attr_db_t gatt_db[HRS_IDX_NB] =
|
||||
{
|
||||
// Service Declaration
|
||||
[IDX_SVC] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ,
|
||||
sizeof(uint16_t), sizeof(GATTS_SERVICE_UUID_TEST), (uint8_t *)&GATTS_SERVICE_UUID_TEST}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_1_SHORT_WR, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE | ESP_GATT_PERM_READ_ENC_MITM,
|
||||
SHORT_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic User Descriptor */
|
||||
[IDX_CHAR_CFG_A] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
|
||||
sizeof(char1_name), sizeof(char1_name), (uint8_t *)char1_name}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_read_write}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_2_LONG_WR, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
LONG_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic User Descriptor */
|
||||
[IDX_CHAR_CFG_B] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
|
||||
sizeof(char2_name), sizeof(char2_name), (uint8_t *)char2_name}},
|
||||
|
||||
/* Characteristic Declaration */
|
||||
[IDX_CHAR_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_declaration_uuid, ESP_GATT_PERM_READ,
|
||||
CHAR_DECLARATION_SIZE, CHAR_DECLARATION_SIZE, (uint8_t *)&char_prop_notify}},
|
||||
|
||||
/* Characteristic Value */
|
||||
[IDX_CHAR_VAL_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&CHAR_3_SHORT_NOTIFY, 0,
|
||||
LONG_CHAR_VAL_LEN, sizeof(char_value), (uint8_t *)char_value}},
|
||||
|
||||
/* Characteristic User Descriptor */
|
||||
[IDX_CHAR_CFG_C] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_user_description, ESP_GATT_PERM_READ,
|
||||
sizeof(char3_name), sizeof(char3_name), (uint8_t *)char3_name}},
|
||||
|
||||
/* Characteristic Client Configuration Descriptor */
|
||||
[IDX_CHAR_CFG_C_2] =
|
||||
{{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE,
|
||||
sizeof(uint16_t), sizeof(char_ccc), (uint8_t *)char_ccc}},
|
||||
|
||||
};
|
||||
|
||||
static void show_bonded_devices(void)
|
||||
{
|
||||
int dev_num = esp_ble_get_bond_device_num();
|
||||
if (dev_num == 0) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Bonded devices number zero\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
|
||||
if (!dev_list) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "malloc failed, return\n");
|
||||
return;
|
||||
}
|
||||
esp_ble_get_bond_device_list(&dev_num, dev_list);
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "Bonded devices number : %d\n", dev_num);
|
||||
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "Bonded devices list : %d\n", dev_num);
|
||||
for (int i = 0; i < dev_num; i++) {
|
||||
#if DEBUG_ON
|
||||
ESP_LOG_BUFFER_HEX(EXAMPLE_TAG, (void *)dev_list[i].bd_addr, sizeof(esp_bd_addr_t));
|
||||
#endif
|
||||
}
|
||||
|
||||
free(dev_list);
|
||||
}
|
||||
|
||||
static void __attribute__((unused)) remove_all_bonded_devices(void)
|
||||
{
|
||||
int dev_num = esp_ble_get_bond_device_num();
|
||||
if (dev_num == 0) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Bonded devices number zero\n");
|
||||
return;
|
||||
}
|
||||
|
||||
esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num);
|
||||
if (!dev_list) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "malloc failed, return\n");
|
||||
return;
|
||||
}
|
||||
esp_ble_get_bond_device_list(&dev_num, dev_list);
|
||||
for (int i = 0; i < dev_num; i++) {
|
||||
esp_ble_remove_bond_device(dev_list[i].bd_addr);
|
||||
}
|
||||
|
||||
free(dev_list);
|
||||
}
|
||||
|
||||
static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
#ifdef CONFIG_SET_RAW_ADV_DATA
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~ADV_CONFIG_FLAG);
|
||||
if (adv_config_done == 0){
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
|
||||
if (adv_config_done == 0){
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
#else
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~ADV_CONFIG_FLAG);
|
||||
if (adv_config_done == 0){
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
|
||||
if (adv_config_done == 0){
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
/* advertising start complete event to indicate advertising start successfully or failed */
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "advertising start failed");
|
||||
}else{
|
||||
ESP_LOGI(EXAMPLE_TAG, "(0) ***** advertising start successfully ***** ");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "Advertising stop failed");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(EXAMPLE_TAG, "Stop adv successfully");
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "update connection params status = %d, conn_int = %d, latency = %d, timeout = %d",
|
||||
param->update_conn_params.status,
|
||||
param->update_conn_params.conn_int,
|
||||
param->update_conn_params.latency,
|
||||
param->update_conn_params.timeout);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT");
|
||||
//esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00);
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_NC_REQ_EVT:
|
||||
/* The app will receive this event when the IO has DisplayYesNO capability and the peer device IO also has DisplayYesNo capability.
|
||||
show the passkey number to the user to confirm it with the number displayed by peer device. */
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
/* send the positive(true) security response to the peer device to accept the security request.
|
||||
If not accept the security request, should send the security response with negative(false) accept value*/
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
case ESP_GAP_BLE_PASSKEY_NOTIF_EVT: ///the app will receive this evt when the IO has Output capability and the peer device IO has Input capability.
|
||||
///show the passkey number to the user to input it in the peer device.
|
||||
ESP_LOGI(EXAMPLE_TAG, "The passkey notify number:%06" PRIu32, param->ble_security.key_notif.passkey);
|
||||
break;
|
||||
case ESP_GAP_BLE_KEY_EVT:
|
||||
//shows the ble key info share with peer device to the user.
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type));
|
||||
break;
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT: {
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "remote BD_ADDR: %08x%04x",\
|
||||
(bd_addr[0] << 24) + (bd_addr[1] << 16) + (bd_addr[2] << 8) + bd_addr[3],
|
||||
(bd_addr[4] << 8) + bd_addr[5]);
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type);
|
||||
if (param->ble_security.auth_cmpl.success){
|
||||
ESP_LOGI(EXAMPLE_TAG, "(1) ***** pair status = success ***** ");
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(EXAMPLE_TAG, "***** pair status = fail, reason = 0x%x *****", param->ble_security.auth_cmpl.fail_reason);
|
||||
}
|
||||
show_bonded_devices();
|
||||
break;
|
||||
}
|
||||
case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: {
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT status = %d", param->remove_bond_dev_cmpl.status);
|
||||
#if DEBUG_ON
|
||||
ESP_LOG_BUFFER_HEX(EXAMPLE_TAG, (void *)param->remove_bond_dev_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
#endif
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "------------------------------------");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void example_prepare_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "prepare write, handle = %d, value len = %d", param->write.handle, param->write.len);
|
||||
esp_gatt_status_t status = ESP_GATT_OK;
|
||||
if (param->write.offset > PREPARE_BUF_MAX_SIZE) {
|
||||
status = ESP_GATT_INVALID_OFFSET;
|
||||
} else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) {
|
||||
status = ESP_GATT_INVALID_ATTR_LEN;
|
||||
}
|
||||
|
||||
if (status == ESP_GATT_OK && prepare_write_env->prepare_buf == NULL) {
|
||||
prepare_write_env->prepare_buf = (uint8_t *)malloc(PREPARE_BUF_MAX_SIZE * sizeof(uint8_t));
|
||||
prepare_write_env->prepare_len = 0;
|
||||
if (prepare_write_env->prepare_buf == NULL) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s, Gatt_server prep no mem", __func__);
|
||||
status = ESP_GATT_NO_RESOURCES;
|
||||
}
|
||||
}
|
||||
|
||||
/*send response when param->write.need_rsp is true */
|
||||
if (param->write.need_rsp){
|
||||
esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t));
|
||||
if (gatt_rsp != NULL){
|
||||
gatt_rsp->attr_value.len = param->write.len;
|
||||
gatt_rsp->attr_value.handle = param->write.handle;
|
||||
gatt_rsp->attr_value.offset = param->write.offset;
|
||||
gatt_rsp->attr_value.auth_req = ESP_GATT_AUTH_REQ_NONE;
|
||||
memcpy(gatt_rsp->attr_value.value, param->write.value, param->write.len);
|
||||
esp_err_t response_err = esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, gatt_rsp);
|
||||
if (response_err != ESP_OK){
|
||||
ESP_LOGE(EXAMPLE_TAG, "Send response error");
|
||||
}
|
||||
free(gatt_rsp);
|
||||
}else{
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s, malloc failed, and no resource to send response", __func__);
|
||||
status = ESP_GATT_NO_RESOURCES;
|
||||
}
|
||||
}
|
||||
if (status != ESP_GATT_OK){
|
||||
return;
|
||||
}
|
||||
memcpy(prepare_write_env->prepare_buf + param->write.offset,
|
||||
param->write.value,
|
||||
param->write.len);
|
||||
prepare_write_env->prepare_len += param->write.len;
|
||||
|
||||
}
|
||||
uint8_t long_write[16] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF};
|
||||
void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){
|
||||
if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC && prepare_write_env->prepare_buf){
|
||||
if(prepare_write_env->prepare_len == 256) {
|
||||
bool long_write_success = true;
|
||||
for(uint16_t i = 0; i < prepare_write_env->prepare_len; i ++) {
|
||||
if(prepare_write_env->prepare_buf[i] != long_write[i%16]) {
|
||||
long_write_success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(long_write_success) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "(4) ***** long write success ***** ");
|
||||
}
|
||||
}
|
||||
}else{
|
||||
ESP_LOGI(EXAMPLE_TAG,"ESP_GATT_PREP_WRITE_CANCEL");
|
||||
}
|
||||
if (prepare_write_env->prepare_buf) {
|
||||
free(prepare_write_env->prepare_buf);
|
||||
prepare_write_env->prepare_buf = NULL;
|
||||
}
|
||||
prepare_write_env->prepare_len = 0;
|
||||
}
|
||||
|
||||
static void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GATTS_REG_EVT:{
|
||||
esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME);
|
||||
if (set_dev_name_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "set device name failed, error code = %x", set_dev_name_ret);
|
||||
}
|
||||
#ifdef CONFIG_SET_RAW_ADV_DATA
|
||||
esp_err_t raw_adv_ret = esp_ble_gap_config_adv_data_raw(raw_adv_data, sizeof(raw_adv_data));
|
||||
if (raw_adv_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config raw adv data failed, error code = %x ", raw_adv_ret);
|
||||
}
|
||||
adv_config_done |= ADV_CONFIG_FLAG;
|
||||
esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_scan_rsp_data, sizeof(raw_scan_rsp_data));
|
||||
if (raw_scan_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config raw scan rsp data failed, error code = %x", raw_scan_ret);
|
||||
}
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
#else
|
||||
//config adv data
|
||||
esp_err_t ret = esp_ble_gap_config_adv_data(&adv_data);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config adv data failed, error code = %x", ret);
|
||||
}
|
||||
adv_config_done |= ADV_CONFIG_FLAG;
|
||||
//config scan response data
|
||||
ret = esp_ble_gap_config_adv_data(&scan_rsp_data);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "config scan response data failed, error code = %x", ret);
|
||||
}
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
#endif
|
||||
esp_err_t create_attr_ret = esp_ble_gatts_create_attr_tab(gatt_db, gatts_if, HRS_IDX_NB, SVC_INST_ID);
|
||||
if (create_attr_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "create attr table failed, error code = %x", create_attr_ret);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_READ_EVT:
|
||||
//ESP_LOGE(EXAMPLE_TAG, "ESP_GATTS_READ_EVT, handle=0x%d, offset=%d", param->read.handle, param->read.offset);
|
||||
if(gatt_db_handle_table[IDX_CHAR_VAL_A] == param->read.handle) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "(2) ***** read char1 ***** ");
|
||||
}
|
||||
if(gatt_db_handle_table[IDX_CHAR_VAL_B] == param->read.handle) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "(5) ***** read char2 ***** ");
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_WRITE_EVT:
|
||||
if (!param->write.is_prep){
|
||||
// the data length of gattc write must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
|
||||
if (gatt_db_handle_table[IDX_CHAR_CFG_C_2] == param->write.handle && param->write.len == 2){
|
||||
uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0];
|
||||
uint8_t notify_data[2];
|
||||
notify_data[0] = 0xAA;
|
||||
notify_data[1] = 0xBB;
|
||||
|
||||
if (descr_value == 0x0001){
|
||||
//the size of notify_data[] need less than MTU size
|
||||
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatt_db_handle_table[IDX_CHAR_VAL_C],
|
||||
sizeof(notify_data), notify_data, false);
|
||||
ESP_LOGI(EXAMPLE_TAG, "(6) ***** send notify AA BB ***** ");
|
||||
}else if (descr_value == 0x0002){
|
||||
//the size of indicate_data[] need less than MTU size
|
||||
esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatt_db_handle_table[IDX_CHAR_VAL_C],
|
||||
sizeof(notify_data), notify_data, true);
|
||||
}
|
||||
else if (descr_value == 0x0000){
|
||||
ESP_LOGI(EXAMPLE_TAG, "notify/indicate disable ");
|
||||
}else{
|
||||
ESP_LOGE(EXAMPLE_TAG, "unknown descr value");
|
||||
ESP_LOG_BUFFER_HEX(EXAMPLE_TAG, param->write.value, param->write.len);
|
||||
}
|
||||
|
||||
}
|
||||
if(gatt_db_handle_table[IDX_CHAR_VAL_A] == param->write.handle && param->write.len == 2) {
|
||||
uint8_t write_data[2] = {0x88, 0x99};
|
||||
if(memcmp(write_data, param->write.value, param->write.len) == 0) {
|
||||
ESP_LOGI(EXAMPLE_TAG, "(3)***** short write success ***** ");
|
||||
}
|
||||
}
|
||||
|
||||
/* send response when param->write.need_rsp is true*/
|
||||
if (param->write.need_rsp){
|
||||
esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL);
|
||||
}
|
||||
}else{
|
||||
/* handle prepare write */
|
||||
example_prepare_write_event_env(gatts_if, &prepare_write_env, param);
|
||||
}
|
||||
break;
|
||||
case ESP_GATTS_EXEC_WRITE_EVT:
|
||||
// the length of gattc prepare write data must be less than GATTS_EXAMPLE_CHAR_VAL_LEN_MAX.
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT, Length=%d", prepare_write_env.prepare_len);
|
||||
example_exec_write_event_env(&prepare_write_env, param);
|
||||
break;
|
||||
case ESP_GATTS_MTU_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu);
|
||||
break;
|
||||
case ESP_GATTS_CONF_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "ESP_GATTS_CONF_EVT, status = %d", param->conf.status);
|
||||
break;
|
||||
case ESP_GATTS_START_EVT:
|
||||
EXAMPLE_DEBUG(EXAMPLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle);
|
||||
break;
|
||||
case ESP_GATTS_CONNECT_EVT:
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id);
|
||||
/* start security connect with peer device when receive the connect event sent by the master */
|
||||
esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM);
|
||||
break;
|
||||
case ESP_GATTS_DISCONNECT_EVT:
|
||||
ESP_LOGI(EXAMPLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = %d", param->disconnect.reason);
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
break;
|
||||
case ESP_GATTS_CREAT_ATTR_TAB_EVT:{
|
||||
if (param->add_attr_tab.status != ESP_GATT_OK){
|
||||
ESP_LOGE(EXAMPLE_TAG, "create attribute table failed, error code=0x%x", param->add_attr_tab.status);
|
||||
}
|
||||
else if (param->add_attr_tab.num_handle != HRS_IDX_NB){
|
||||
ESP_LOGE(EXAMPLE_TAG, "create attribute table abnormally, num_handle (%d) \
|
||||
doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, HRS_IDX_NB);
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(EXAMPLE_TAG, "create attribute table successfully, the number handle = %d",param->add_attr_tab.num_handle);
|
||||
memcpy(gatt_db_handle_table, param->add_attr_tab.handles, sizeof(gatt_db_handle_table));
|
||||
esp_ble_gatts_start_service(gatt_db_handle_table[IDX_SVC]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param)
|
||||
{
|
||||
|
||||
/* If event is register event, store the gatts_if for each profile */
|
||||
if (event == ESP_GATTS_REG_EVT) {
|
||||
if (param->reg.status == ESP_GATT_OK) {
|
||||
heart_rate_profile_tab[PROFILE_APP_IDX].gatts_if = gatts_if;
|
||||
} else {
|
||||
ESP_LOGE(EXAMPLE_TAG, "reg app failed, app_id %04x, status %d",
|
||||
param->reg.app_id,
|
||||
param->reg.status);
|
||||
return;
|
||||
}
|
||||
}
|
||||
do {
|
||||
int idx;
|
||||
for (idx = 0; idx < PROFILE_NUM; idx++) {
|
||||
/* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */
|
||||
if (gatts_if == ESP_GATT_IF_NONE || gatts_if == heart_rate_profile_tab[idx].gatts_if) {
|
||||
if (heart_rate_profile_tab[idx].gatts_cb) {
|
||||
heart_rate_profile_tab[idx].gatts_cb(event, gatts_if, param);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
/* Initialize NVS. */
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
// initialise connection to co-processor
|
||||
esp_hosted_connect_to_slave();
|
||||
|
||||
// get fw version
|
||||
ESP_LOGI("INFO", "getting fw version");
|
||||
esp_hosted_coprocessor_fwver_t fwver;
|
||||
if (ESP_OK == esp_hosted_get_coprocessor_fwversion(&fwver)) {
|
||||
ESP_LOGI("INFO", "FW Version: %" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
fwver.major1, fwver.minor1, fwver.patch1);
|
||||
} else {
|
||||
ESP_LOGW("INFO", "failed to get fw version");
|
||||
}
|
||||
|
||||
// init bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_init()) {
|
||||
ESP_LOGW("INFO", "failed to init bt controller");
|
||||
}
|
||||
|
||||
// enable bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_enable()) {
|
||||
ESP_LOGW("INFO", "failed to enable bt controller");
|
||||
}
|
||||
|
||||
hosted_hci_bluedroid_open();
|
||||
|
||||
/* get HCI driver operations */
|
||||
esp_bluedroid_hci_driver_operations_t operations = {
|
||||
.send = hosted_hci_bluedroid_send,
|
||||
.check_send_available = hosted_hci_bluedroid_check_send_available,
|
||||
.register_host_callback = hosted_hci_bluedroid_register_host_callback,
|
||||
};
|
||||
esp_bluedroid_attach_hci_driver(&operations);
|
||||
|
||||
#if 0
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
ret = esp_bt_controller_init(&bt_cfg);
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(EXAMPLE_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_register_callback(gatts_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "gatts register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_register_callback(gap_event_handler);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gatts_app_register(ESP_APP_ID);
|
||||
if (ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "gatts app register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(33);
|
||||
if (local_mtu_ret){
|
||||
ESP_LOGE(EXAMPLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret);
|
||||
}
|
||||
|
||||
/* set the security iocap & auth_req & key size & init key response key parameters to the stack*/
|
||||
esp_ble_auth_req_t auth_req = ESP_LE_AUTH_REQ_SC_MITM_BOND; //bonding with peer device after authentication
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_OUT; //set the IO capability to No output No input
|
||||
uint8_t key_size = 16; //the key size should be 7~16 bytes
|
||||
uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK;
|
||||
uint32_t passkey = 123456;
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_STATIC_PASSKEY, &passkey, sizeof(uint32_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_AUTHEN_REQ_MODE, &auth_req, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_MAX_KEY_SIZE, &key_size, sizeof(uint8_t));
|
||||
/* If your BLE device act as a Slave, the init_key means you hope which types of key of the master should distribute to you,
|
||||
and the response key means which key you can distribute to the Master;
|
||||
If your BLE device act as a master, the response key means you hope which types of key of the slave should distribute to you,
|
||||
and the init key means which key you can distribute to the slave. */
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_INIT_KEY, &init_key, sizeof(uint8_t));
|
||||
esp_ble_gap_set_security_param(ESP_BLE_SM_SET_RSP_KEY, &rsp_key, sizeof(uint8_t));
|
||||
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
||||
/* Attributes State Machine */
|
||||
enum
|
||||
{
|
||||
IDX_SVC,
|
||||
IDX_CHAR_A,
|
||||
IDX_CHAR_VAL_A,
|
||||
IDX_CHAR_CFG_A,
|
||||
|
||||
IDX_CHAR_B,
|
||||
IDX_CHAR_VAL_B,
|
||||
IDX_CHAR_CFG_B,
|
||||
|
||||
IDX_CHAR_C,
|
||||
IDX_CHAR_VAL_C,
|
||||
IDX_CHAR_CFG_C,
|
||||
IDX_CHAR_CFG_C_2,
|
||||
|
||||
HRS_IDX_NB,
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10'
|
||||
@@ -0,0 +1,25 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# BT config
|
||||
# - ESP32 co-processor only supports BLE 4.2
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_CONTROLLER_DISABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3.
|
||||
CONFIG_BT_LE_50_FEATURE_SUPPORT=n
|
||||
|
||||
#
|
||||
# Wi-Fi Remote
|
||||
#
|
||||
CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y
|
||||
|
||||
#
|
||||
# Enable ESP Hosted BT
|
||||
# Used as VHCI transport between BT Host and Controller
|
||||
#
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID=y
|
||||
CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI=y
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(bt_hid_mouse_device)
|
||||
@@ -0,0 +1,301 @@
|
||||
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
|
||||
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
|
||||
|
||||
| Supported Co-Processors | ESP32 |
|
||||
| ----------------------- | ----- |
|
||||
|
||||
# Bluetooth HID Device example
|
||||
|
||||
This example aims to show how to implement a Bluetooth HID device using the APIs provided by Classic Bluetooth HID profile.
|
||||
|
||||
This example simulates a Bluetooth HID mouse device that periodically sends report to remote Bluetooth HID host after connection. The report indicates a horizontally moving pointer and can be observed on the display on the HID host side. If you want to build an HID device, this can be your first example to look at.
|
||||
|
||||
## How to use example
|
||||
|
||||
This example has been modified to work with ESP-Hosted. The original ESP-IDF example is at [ https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/classic_bt/bt_hid_mouse_device/ ].
|
||||
|
||||
### Hardware Required
|
||||
|
||||
* This example is able to run on the ESP32-P4 Dev Board, acting as the BT Host, connected to a ESP32 co-processor via the GPIO header, using SPI FD (full duplex) as Hosted HCI transport. The ESP32 acts as the BT Controller. The following GPIO settings were used:
|
||||
|
||||
| SPI Function | ESP32 GPIO | ESP32-P4 GPIO |
|
||||
| :--- | ---: | ---: |
|
||||
| MOSI | 13 | 4 |
|
||||
| MISO | 12 | 5 |
|
||||
| CLK | 14 | 26 |
|
||||
| CS | 15 | 6 |
|
||||
| Handshake | 26 | 20 |
|
||||
| Data Ready | 4 | 36 |
|
||||
| Reset | -1 | 2 |
|
||||
|
||||
> [!NOTE]
|
||||
> SPI Mode 2 was used on both the ESP32-P4 and ESP32.
|
||||
|
||||
Users are free to choose which supported ESP-Hosted transport to use. See the [main ESP-Hosted README](https://github.com/espressif/esp-hosted-mcu/blob/main/README.md#6-decide-the-communication-bus-in-between-host-and-slave) for a list of supported transports.
|
||||
|
||||
* This example is supposed to connect to a Classic Bluetooth HID Host device, e.g. laptop or tablet.
|
||||
|
||||
### Configure the project
|
||||
|
||||
On the ESP32-P4 Dev Board, run `idf.py menuconfig`.
|
||||
|
||||
* Check and enable Classic Bluetooth and Classic BT HID Device under `Component config --> Bluetooth --> Bluedroid Options`
|
||||
* Ensure that `Component config --> Bluetooth --> Controller` is `Disabled`.
|
||||
* Under `Component config --> ESP-Hosted config`:
|
||||
* Configure ESP-Hosted to use `SPI Full-duplex` as the transport
|
||||
* Set the Slave chipset used as `ESP32`
|
||||
* Check and enable `Bluetooth Support`
|
||||
* Configure the GPIOs used for SPI FD on both the ESP32-P4 and ESP32, following the table above
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
The following log will be shown on the IDF monitor console:
|
||||
|
||||
```
|
||||
I (499) main_task: Calling app_main()
|
||||
I (509) transport: Attempt connection with slave: retry[0]
|
||||
I (509) transport: Reset slave using GPIO[2]
|
||||
I (509) os_wrapper_esp: GPIO [2] configured
|
||||
I (509) gpio: GPIO[2]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (1699) transport: Received INIT event from ESP32 peripheral
|
||||
I (1699) transport: EVENT: 12
|
||||
I (1699) transport: EVENT: 11
|
||||
I (1699) transport: capabilities: 0xf8
|
||||
I (1709) transport: Features supported are:
|
||||
I (1709) transport: - HCI over SPI
|
||||
I (1709) transport: - BT/BLE dual mode
|
||||
I (1719) transport: EVENT: 13
|
||||
I (1719) transport: ESP board type is : 0
|
||||
|
||||
I (1719) transport: Base transport is set-up
|
||||
|
||||
I (1729) transport: Slave chip Id[12]
|
||||
I (1729) vhci_drv: Host BT Support: Enabled
|
||||
I (1729) vhci_drv: BT Transport Type: VHCI
|
||||
I (1739) spi: Received INIT event
|
||||
I (2799) app_main: setting device name
|
||||
I (2799) esp_bt_gap_cb: event: 10
|
||||
I (2799) app_main: setting cod major, peripheral
|
||||
I (4799) app_main: register hid device callback
|
||||
I (4799) app_main: starting hid device
|
||||
I (4799) esp_bt_hidd_cb: setting hid parameters
|
||||
I (4799) esp_bt_gap_cb: event: 10
|
||||
I (4799) esp_bt_hidd_cb: setting hid parameters success!
|
||||
I (4799) esp_bt_hidd_cb: setting to connectable, discoverable
|
||||
I (4809) app_main: Own address:[10:97:bd:d5:8a:62]
|
||||
I (4809) app_main: exiting
|
||||
```
|
||||
|
||||
The messages show the successful initialization of Bluetooth stack and HID application. ESP32-P4 will become discoverable with the Bluetooth device name as "HID Mouse Example", by nearby Bluetooth HID Host device.
|
||||
|
||||
Connect to ESP32-P4 on the HID Host side, then finish bonding. After that the HID connection will be established. IDF monitor console will continue to print messages like:
|
||||
|
||||
```
|
||||
W (21229) BT_HCI: hcif conn complete: hdl 0x80, st 0x0
|
||||
I (21229) esp_bt_gap_cb: event: 16
|
||||
I (21859) esp_bt_gap_cb: authentication success: XXXXXXXX
|
||||
I (21859) esp_bt_gap_cb: 64 49 7d d0 fd 99
|
||||
I (21889) esp_bt_gap_cb: event: 21
|
||||
W (22299) BT_HIDD: hidd_l2cif_config_cfm: config failed, retry
|
||||
W (22319) BT_APPL: new conn_srvc id:20, app_id:1
|
||||
I (22319) esp_bt_hidd_cb: connected to 64:49:7d:d0:fd:99
|
||||
I (22319) mouse_move_task: starting
|
||||
I (22319) esp_bt_hidd_cb: making self non-discoverable and non-connectable.
|
||||
W (22329) BT_HCI: hci cmd send: sniff: hdl 0x80, intv(10 18)
|
||||
I (22339) esp_bt_hidd_cb: ESP_HIDD_SEND_REPORT_EVT id:0x00, type:1
|
||||
W (22349) BT_HCI: hcif mode change: hdl 0x80, mode 2, intv 18, status 0x0
|
||||
I (22349) esp_bt_gap_cb: ESP_BT_GAP_MODE_CHG_EVT mode:2
|
||||
I (22369) esp_bt_hidd_cb: ESP_HIDD_SEND_REPORT_EVT id:0x00, type:1
|
||||
I (22419) esp_bt_hidd_cb: ESP_HIDD_SEND_REPORT_EVT id:0x00, type:1
|
||||
I (22469) esp_bt_hidd_cb: ESP_HIDD_SEND_REPORT_EVT id:0x00, type:1
|
||||
I (22519) esp_bt_hidd_cb: ESP_HIDD_SEND_REPORT_EVT id:0x00, type:1
|
||||
I (22569) esp_bt_hidd_cb: ESP_HIDD_SEND_REPORT_EVT id:0x00, type:1
|
||||
```
|
||||
|
||||
ESP32-P4 will generate and send HID mouse reports periodically. On the screen of HID Host, the cursor will move horizontally from left to right and then right to left, and so on so forth.
|
||||
|
||||
## Example Breakdown
|
||||
|
||||
### Initial settings for Bluetooth HID device profile
|
||||
|
||||
Bluetooth HID device requires the specific major and minor device type in the Class of Device (CoD), the following lines of source code performs the configuration of CoD:
|
||||
|
||||
```
|
||||
void app_main(void) {
|
||||
...
|
||||
ESP_LOGI(TAG, "setting cod major, peripheral");
|
||||
esp_bt_cod_t cod;
|
||||
cod.major = ESP_BT_COD_MAJOR_DEV_PERIPHERAL;
|
||||
esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_MAJOR_MINOR);
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Bluetooth HID device profile requires the information of service name, provide, device subclass, report descriptor for SDP server, as well as L2CAP QoS configurations from the application. Following lines in function `app_main` initialize these information fields:
|
||||
|
||||
```
|
||||
void app_main(void) {
|
||||
...
|
||||
// Initialize HID SDP information and L2CAP parameters.
|
||||
// to be used in the call of `esp_bt_hid_device_register_app` after profile initialization finishes
|
||||
do {
|
||||
s_local_param.app_param.name = "Mouse";
|
||||
s_local_param.app_param.description = "Mouse Example";
|
||||
s_local_param.app_param.provider = "ESP32";
|
||||
s_local_param.app_param.subclass = ESP_HID_CLASS_MIC;
|
||||
s_local_param.app_param.desc_list = hid_mouse_descriptor;
|
||||
s_local_param.app_param.desc_list_len = hid_mouse_descriptor_len;
|
||||
|
||||
memset(&s_local_param.both_qos, 0, sizeof(esp_hidd_qos_param_t)); // don't set the qos parameters
|
||||
} while (0);
|
||||
|
||||
// Report Protocol Mode is the default mode, according to Bluetooth HID specification
|
||||
s_local_param.protocol_mode = ESP_HIDD_REPORT_MODE;
|
||||
|
||||
ESP_LOGI(TAG, "register hid device callback");
|
||||
esp_bt_hid_device_register_callback(esp_bt_hidd_cb);
|
||||
|
||||
ESP_LOGI(TAG, "starting hid device");
|
||||
esp_bt_hid_device_init();
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
The information is set to global struct `s_local_param` and will be used upon successful profile initialization, i.e. reception of `ESP_HIDD_INIT_EVT` which is generated after the call of `esp_bt_hid_device_init()`:
|
||||
|
||||
```
|
||||
void esp_bt_hidd_cb(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param)
|
||||
{
|
||||
...
|
||||
switch (event) {
|
||||
case ESP_HIDD_INIT_EVT:
|
||||
if (param->init.status == ESP_HIDD_SUCCESS) {
|
||||
ESP_LOGI(TAG, "setting hid parameters");
|
||||
esp_bt_hid_device_register_app(&s_local_param.app_param, &s_local_param.both_qos, &s_local_param.both_qos);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "init hidd failed!");
|
||||
}
|
||||
break;
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
### Determination of HID Report Mode
|
||||
|
||||
There are two HID report modes: Report Protocol Mode and Boot Protocol Mode. The former is the default mode. The two report modes differ in the report contents and format. The example supports both of the two modes.
|
||||
|
||||
Report Mode requires report descriptor to describe the usage and format of the reports. For Bluetooth HID device, the report descriptor shall be provided in the SDP server, which can be discovered and used by remote HID Host.
|
||||
|
||||
Boot Mode only supports keyboards and mice, with pre-defined report formats. Therefore it does not require a report descriptor parser on the remote HID Host. It is originally used to simplify the design of PC BIOSs.
|
||||
|
||||
The following code lines set Report Protocol Mode as the default Report Mode:
|
||||
|
||||
```
|
||||
void app_main(void) {
|
||||
...
|
||||
// Report Protocol Mode is the default mode, according to Bluetooth HID specification
|
||||
s_local_param.protocol_mode = ESP_HIDD_REPORT_MODE;
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Report Mode can be choosen by remote HID Host through the SET_PROTOCOL request:
|
||||
|
||||
```
|
||||
void esp_bt_hidd_cb(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param)
|
||||
{
|
||||
...
|
||||
switch (event) {
|
||||
...
|
||||
case ESP_HIDD_SET_PROTOCOL_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_SET_PROTOCOL_EVT");
|
||||
if (param->set_protocol.protocol_mode == ESP_HIDD_BOOT_MODE) {
|
||||
ESP_LOGI(TAG, " - boot protocol");
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
s_local_param.x_dir = -1;
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
} else if (param->set_protocol.protocol_mode == ESP_HIDD_REPORT_MODE) {
|
||||
ESP_LOGI(TAG, " - report protocol");
|
||||
}
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
s_local_param.protocol_mode = param->set_protocol.protocol_mode;
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
break;
|
||||
....
|
||||
}
|
||||
....
|
||||
}
|
||||
```
|
||||
|
||||
### Report generation
|
||||
|
||||
The example simulates a mouse by creating a FreeRTOS task that periodically generates and sends the HID mouse report:
|
||||
|
||||
```
|
||||
// move the mouse left and right
|
||||
void mouse_move_task(void* pvParameters)
|
||||
{
|
||||
const char* TAG = "mouse_move_task";
|
||||
|
||||
ESP_LOGI(TAG, "starting");
|
||||
for(;;) {
|
||||
s_local_param.x_dir = 1;
|
||||
int8_t step = 10;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
s_local_param.x_dir *= -1;
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
for (int j = 0; j < 100; j++) {
|
||||
send_mouse_report(0, s_local_param.x_dir * step, 0, 0);
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Function `send_mouse_report` is used to pack the information into a mouse HID report and sends it to HID Host, according to the Report Mode applied:
|
||||
|
||||
```
|
||||
// send the buttons, change in x, and change in y
|
||||
void send_mouse_report(uint8_t buttons, char dx, char dy, char wheel)
|
||||
{
|
||||
uint8_t report_id;
|
||||
uint16_t report_size;
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
if (s_local_param.protocol_mode == ESP_HIDD_REPORT_MODE) {
|
||||
report_id = 0;
|
||||
report_size = REPORT_PROTOCOL_MOUSE_REPORT_SIZE;
|
||||
s_local_param.buffer[0] = buttons;
|
||||
s_local_param.buffer[1] = dx;
|
||||
s_local_param.buffer[2] = dy;
|
||||
s_local_param.buffer[3] = wheel;
|
||||
} else {
|
||||
// Boot Mode
|
||||
report_id = ESP_HIDD_BOOT_REPORT_ID_MOUSE;
|
||||
report_size = ESP_HIDD_BOOT_REPORT_SIZE_MOUSE - 1;
|
||||
s_local_param.buffer[0] = buttons;
|
||||
s_local_param.buffer[1] = dx;
|
||||
s_local_param.buffer[2] = dy;
|
||||
}
|
||||
esp_bt_hid_device_send_report(ESP_HIDD_REPORT_TYPE_INTRDATA, report_id, report_size, s_local_param.buffer);
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,8 @@
|
||||
#set(COMPONENT_SRCS "main.c")
|
||||
#set(COMPONENT_ADD_INCLUDEDIRS "")
|
||||
|
||||
#register_component()
|
||||
|
||||
idf_component_register(SRCS "main.c"
|
||||
PRIV_REQUIRES bt nvs_flash esp_hosted
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,15 @@
|
||||
menu "HID Example Configuration"
|
||||
config EXAMPLE_SSP_ENABLED
|
||||
bool "Secure Simple Pairing"
|
||||
depends on BT_CLASSIC_ENABLED
|
||||
default y
|
||||
help
|
||||
This enables the Secure Simple Pairing. If disable this option,
|
||||
Bluedroid will only support Legacy Pairing
|
||||
|
||||
config EXAMPLE_LOCAL_DEVICE_NAME
|
||||
string "Local Device Name"
|
||||
default "HID Mouse Example"
|
||||
help
|
||||
Use this option to set local device name.
|
||||
endmenu
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10'
|
||||
@@ -0,0 +1,500 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "esp_hidd_api.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_err.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_bluedroid.h"
|
||||
|
||||
#define REPORT_PROTOCOL_MOUSE_REPORT_SIZE (4)
|
||||
#define REPORT_BUFFER_SIZE REPORT_PROTOCOL_MOUSE_REPORT_SIZE
|
||||
|
||||
static const char local_device_name[] = CONFIG_EXAMPLE_LOCAL_DEVICE_NAME;
|
||||
|
||||
typedef struct {
|
||||
esp_hidd_app_param_t app_param;
|
||||
esp_hidd_qos_param_t both_qos;
|
||||
uint8_t protocol_mode;
|
||||
SemaphoreHandle_t mouse_mutex;
|
||||
TaskHandle_t mouse_task_hdl;
|
||||
uint8_t buffer[REPORT_BUFFER_SIZE];
|
||||
int8_t x_dir;
|
||||
} local_param_t;
|
||||
|
||||
static local_param_t s_local_param = {0};
|
||||
|
||||
// HID report descriptor for a generic mouse. The contents of the report are:
|
||||
// 3 buttons, moving information for X and Y cursors, information for a wheel.
|
||||
uint8_t hid_mouse_descriptor[] = {
|
||||
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
|
||||
0x09, 0x02, // USAGE (Mouse)
|
||||
0xa1, 0x01, // COLLECTION (Application)
|
||||
|
||||
0x09, 0x01, // USAGE (Pointer)
|
||||
0xa1, 0x00, // COLLECTION (Physical)
|
||||
|
||||
0x05, 0x09, // USAGE_PAGE (Button)
|
||||
0x19, 0x01, // USAGE_MINIMUM (Button 1)
|
||||
0x29, 0x03, // USAGE_MAXIMUM (Button 3)
|
||||
0x15, 0x00, // LOGICAL_MINIMUM (0)
|
||||
0x25, 0x01, // LOGICAL_MAXIMUM (1)
|
||||
0x95, 0x03, // REPORT_COUNT (3)
|
||||
0x75, 0x01, // REPORT_SIZE (1)
|
||||
0x81, 0x02, // INPUT (Data,Var,Abs)
|
||||
0x95, 0x01, // REPORT_COUNT (1)
|
||||
0x75, 0x05, // REPORT_SIZE (5)
|
||||
0x81, 0x03, // INPUT (Cnst,Var,Abs)
|
||||
|
||||
0x05, 0x01, // USAGE_PAGE (Generic Desktop)
|
||||
0x09, 0x30, // USAGE (X)
|
||||
0x09, 0x31, // USAGE (Y)
|
||||
0x09, 0x38, // USAGE (Wheel)
|
||||
0x15, 0x81, // LOGICAL_MINIMUM (-127)
|
||||
0x25, 0x7f, // LOGICAL_MAXIMUM (127)
|
||||
0x75, 0x08, // REPORT_SIZE (8)
|
||||
0x95, 0x03, // REPORT_COUNT (3)
|
||||
0x81, 0x06, // INPUT (Data,Var,Rel)
|
||||
|
||||
0xc0, // END_COLLECTION
|
||||
0xc0 // END_COLLECTION
|
||||
};
|
||||
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *p = bda;
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
p[0], p[1], p[2], p[3], p[4], p[5]);
|
||||
return str;
|
||||
}
|
||||
|
||||
const int hid_mouse_descriptor_len = sizeof(hid_mouse_descriptor);
|
||||
|
||||
/**
|
||||
* @brief Integrity check of the report ID and report type for GET_REPORT request from HID host.
|
||||
* Boot Protocol Mode requires report ID. For Report Protocol Mode, when the report descriptor
|
||||
* does not declare report ID Global ITEMS, the report ID does not exist in the GET_REPORT request,
|
||||
* and a value of 0 for report_id will occur in ESP_HIDD_GET_REPORT_EVT callback parameter.
|
||||
*/
|
||||
bool check_report_id_type(uint8_t report_id, uint8_t report_type)
|
||||
{
|
||||
bool ret = false;
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
do {
|
||||
if (report_type != ESP_HIDD_REPORT_TYPE_INPUT) {
|
||||
break;
|
||||
}
|
||||
if (s_local_param.protocol_mode == ESP_HIDD_BOOT_MODE) {
|
||||
if (report_id == ESP_HIDD_BOOT_REPORT_ID_MOUSE) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
if (report_id == 0) {
|
||||
ret = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (0);
|
||||
|
||||
if (!ret) {
|
||||
if (s_local_param.protocol_mode == ESP_HIDD_BOOT_MODE) {
|
||||
esp_bt_hid_device_report_error(ESP_HID_PAR_HANDSHAKE_RSP_ERR_INVALID_REP_ID);
|
||||
} else {
|
||||
esp_bt_hid_device_report_error(ESP_HID_PAR_HANDSHAKE_RSP_ERR_INVALID_REP_ID);
|
||||
}
|
||||
}
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// send the buttons, change in x, and change in y
|
||||
void send_mouse_report(uint8_t buttons, char dx, char dy, char wheel)
|
||||
{
|
||||
uint8_t report_id;
|
||||
uint16_t report_size;
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
if (s_local_param.protocol_mode == ESP_HIDD_REPORT_MODE) {
|
||||
report_id = 0;
|
||||
report_size = REPORT_PROTOCOL_MOUSE_REPORT_SIZE;
|
||||
s_local_param.buffer[0] = buttons;
|
||||
s_local_param.buffer[1] = dx;
|
||||
s_local_param.buffer[2] = dy;
|
||||
s_local_param.buffer[3] = wheel;
|
||||
} else {
|
||||
// Boot Mode
|
||||
report_id = ESP_HIDD_BOOT_REPORT_ID_MOUSE;
|
||||
report_size = ESP_HIDD_BOOT_REPORT_SIZE_MOUSE - 1;
|
||||
s_local_param.buffer[0] = buttons;
|
||||
s_local_param.buffer[1] = dx;
|
||||
s_local_param.buffer[2] = dy;
|
||||
}
|
||||
esp_bt_hid_device_send_report(ESP_HIDD_REPORT_TYPE_INTRDATA, report_id, report_size, s_local_param.buffer);
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
}
|
||||
|
||||
// move the mouse left and right
|
||||
void mouse_move_task(void *pvParameters)
|
||||
{
|
||||
const char *TAG = "mouse_move_task";
|
||||
|
||||
ESP_LOGI(TAG, "starting");
|
||||
for (;;) {
|
||||
s_local_param.x_dir = 1;
|
||||
int8_t step = 10;
|
||||
for (int i = 0; i < 2; i++) {
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
s_local_param.x_dir *= -1;
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
for (int j = 0; j < 100; j++) {
|
||||
send_mouse_report(0, s_local_param.x_dir * step, 0, 0);
|
||||
vTaskDelay(50 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
vTaskDelay(1000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
|
||||
void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
const char *TAG = "esp_bt_gap_cb";
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_AUTH_CMPL_EVT: {
|
||||
if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(TAG, "authentication success: %s", param->auth_cmpl.device_name);
|
||||
ESP_LOG_BUFFER_HEX(TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "authentication failed, status:%d", param->auth_cmpl.stat);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_PIN_REQ_EVT: {
|
||||
ESP_LOGI(TAG, "ESP_BT_GAP_PIN_REQ_EVT min_16_digit:%d", param->pin_req.min_16_digit);
|
||||
if (param->pin_req.min_16_digit) {
|
||||
ESP_LOGI(TAG, "Input pin code: 0000 0000 0000 0000");
|
||||
esp_bt_pin_code_t pin_code = {0};
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 16, pin_code);
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Input pin code: 1234");
|
||||
esp_bt_pin_code_t pin_code;
|
||||
pin_code[0] = '1';
|
||||
pin_code[1] = '2';
|
||||
pin_code[2] = '3';
|
||||
pin_code[3] = '4';
|
||||
esp_bt_gap_pin_reply(param->pin_req.bda, true, 4, pin_code);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
#if (CONFIG_EXAMPLE_SSP_ENABLED == true)
|
||||
case ESP_BT_GAP_CFM_REQ_EVT:
|
||||
ESP_LOGI(TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %"PRIu32, param->cfm_req.num_val);
|
||||
esp_bt_gap_ssp_confirm_reply(param->cfm_req.bda, true);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_NOTIF_EVT:
|
||||
ESP_LOGI(TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%"PRIu32, param->key_notif.passkey);
|
||||
break;
|
||||
case ESP_BT_GAP_KEY_REQ_EVT:
|
||||
ESP_LOGI(TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!");
|
||||
break;
|
||||
#endif
|
||||
case ESP_BT_GAP_MODE_CHG_EVT:
|
||||
ESP_LOGI(TAG, "ESP_BT_GAP_MODE_CHG_EVT mode:%d", param->mode_chg.mode);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGI(TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_start_up(void)
|
||||
{
|
||||
s_local_param.mouse_mutex = xSemaphoreCreateMutex();
|
||||
memset(s_local_param.buffer, 0, REPORT_BUFFER_SIZE);
|
||||
xTaskCreate(mouse_move_task, "mouse_move_task", 2 * 1024, NULL, configMAX_PRIORITIES - 3, &s_local_param.mouse_task_hdl);
|
||||
return;
|
||||
}
|
||||
|
||||
void bt_app_task_shut_down(void)
|
||||
{
|
||||
if (s_local_param.mouse_task_hdl) {
|
||||
vTaskDelete(s_local_param.mouse_task_hdl);
|
||||
s_local_param.mouse_task_hdl = NULL;
|
||||
}
|
||||
|
||||
if (s_local_param.mouse_mutex) {
|
||||
vSemaphoreDelete(s_local_param.mouse_mutex);
|
||||
s_local_param.mouse_mutex = NULL;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
void esp_bt_hidd_cb(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param)
|
||||
{
|
||||
static const char *TAG = "esp_bt_hidd_cb";
|
||||
switch (event) {
|
||||
case ESP_HIDD_INIT_EVT:
|
||||
if (param->init.status == ESP_HIDD_SUCCESS) {
|
||||
ESP_LOGI(TAG, "setting hid parameters");
|
||||
esp_bt_hid_device_register_app(&s_local_param.app_param, &s_local_param.both_qos, &s_local_param.both_qos);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "init hidd failed!");
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_DEINIT_EVT:
|
||||
break;
|
||||
case ESP_HIDD_REGISTER_APP_EVT:
|
||||
if (param->register_app.status == ESP_HIDD_SUCCESS) {
|
||||
ESP_LOGI(TAG, "setting hid parameters success!");
|
||||
ESP_LOGI(TAG, "setting to connectable, discoverable");
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
if (param->register_app.in_use) {
|
||||
ESP_LOGI(TAG, "start virtual cable plug!");
|
||||
esp_bt_hid_device_connect(param->register_app.bd_addr);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "setting hid parameters failed!");
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_UNREGISTER_APP_EVT:
|
||||
if (param->unregister_app.status == ESP_HIDD_SUCCESS) {
|
||||
ESP_LOGI(TAG, "unregister app success!");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "unregister app failed!");
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_OPEN_EVT:
|
||||
if (param->open.status == ESP_HIDD_SUCCESS) {
|
||||
if (param->open.conn_status == ESP_HIDD_CONN_STATE_CONNECTING) {
|
||||
ESP_LOGI(TAG, "connecting...");
|
||||
} else if (param->open.conn_status == ESP_HIDD_CONN_STATE_CONNECTED) {
|
||||
ESP_LOGI(TAG, "connected to %02x:%02x:%02x:%02x:%02x:%02x", param->open.bd_addr[0],
|
||||
param->open.bd_addr[1], param->open.bd_addr[2], param->open.bd_addr[3], param->open.bd_addr[4],
|
||||
param->open.bd_addr[5]);
|
||||
bt_app_task_start_up();
|
||||
ESP_LOGI(TAG, "making self non-discoverable and non-connectable.");
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "unknown connection status");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "open failed!");
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_CLOSE_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_CLOSE_EVT");
|
||||
if (param->close.status == ESP_HIDD_SUCCESS) {
|
||||
if (param->close.conn_status == ESP_HIDD_CONN_STATE_DISCONNECTING) {
|
||||
ESP_LOGI(TAG, "disconnecting...");
|
||||
} else if (param->close.conn_status == ESP_HIDD_CONN_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(TAG, "disconnected!");
|
||||
bt_app_task_shut_down();
|
||||
ESP_LOGI(TAG, "making self discoverable and connectable again.");
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "unknown connection status");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "close failed!");
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_SEND_REPORT_EVT:
|
||||
if (param->send_report.status == ESP_HIDD_SUCCESS) {
|
||||
ESP_LOGI(TAG, "ESP_HIDD_SEND_REPORT_EVT id:0x%02x, type:%d", param->send_report.report_id,
|
||||
param->send_report.report_type);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "ESP_HIDD_SEND_REPORT_EVT id:0x%02x, type:%d, status:%d, reason:%d",
|
||||
param->send_report.report_id, param->send_report.report_type, param->send_report.status,
|
||||
param->send_report.reason);
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_REPORT_ERR_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_REPORT_ERR_EVT");
|
||||
break;
|
||||
case ESP_HIDD_GET_REPORT_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_GET_REPORT_EVT id:0x%02x, type:%d, size:%d", param->get_report.report_id,
|
||||
param->get_report.report_type, param->get_report.buffer_size);
|
||||
if (check_report_id_type(param->get_report.report_id, param->get_report.report_type)) {
|
||||
uint8_t report_id;
|
||||
uint16_t report_len;
|
||||
if (s_local_param.protocol_mode == ESP_HIDD_REPORT_MODE) {
|
||||
report_id = 0;
|
||||
report_len = REPORT_PROTOCOL_MOUSE_REPORT_SIZE;
|
||||
} else {
|
||||
// Boot Mode
|
||||
report_id = ESP_HIDD_BOOT_REPORT_ID_MOUSE;
|
||||
report_len = ESP_HIDD_BOOT_REPORT_SIZE_MOUSE - 1;
|
||||
}
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
esp_bt_hid_device_send_report(param->get_report.report_type, report_id, report_len, s_local_param.buffer);
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "check_report_id failed!");
|
||||
}
|
||||
break;
|
||||
case ESP_HIDD_SET_REPORT_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_SET_REPORT_EVT");
|
||||
break;
|
||||
case ESP_HIDD_SET_PROTOCOL_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_SET_PROTOCOL_EVT");
|
||||
if (param->set_protocol.protocol_mode == ESP_HIDD_BOOT_MODE) {
|
||||
ESP_LOGI(TAG, " - boot protocol");
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
s_local_param.x_dir = -1;
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
} else if (param->set_protocol.protocol_mode == ESP_HIDD_REPORT_MODE) {
|
||||
ESP_LOGI(TAG, " - report protocol");
|
||||
}
|
||||
xSemaphoreTake(s_local_param.mouse_mutex, portMAX_DELAY);
|
||||
s_local_param.protocol_mode = param->set_protocol.protocol_mode;
|
||||
xSemaphoreGive(s_local_param.mouse_mutex);
|
||||
break;
|
||||
case ESP_HIDD_INTR_DATA_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_INTR_DATA_EVT");
|
||||
break;
|
||||
case ESP_HIDD_VC_UNPLUG_EVT:
|
||||
ESP_LOGI(TAG, "ESP_HIDD_VC_UNPLUG_EVT");
|
||||
if (param->vc_unplug.status == ESP_HIDD_SUCCESS) {
|
||||
if (param->close.conn_status == ESP_HIDD_CONN_STATE_DISCONNECTED) {
|
||||
ESP_LOGI(TAG, "disconnected!");
|
||||
bt_app_task_shut_down();
|
||||
ESP_LOGI(TAG, "making self discoverable and connectable again.");
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "unknown connection status");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "close failed!");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
const char *TAG = "app_main";
|
||||
esp_err_t ret;
|
||||
char bda_str[18] = {0};
|
||||
|
||||
ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
// initialise connection to co-processor
|
||||
esp_hosted_connect_to_slave();
|
||||
|
||||
// init bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_init()) {
|
||||
ESP_LOGE("INFO", "failed to init bt controller");
|
||||
}
|
||||
|
||||
// enable bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_enable()) {
|
||||
ESP_LOGE("INFO", "failed to enable bt controller");
|
||||
}
|
||||
|
||||
hosted_hci_bluedroid_open();
|
||||
|
||||
/* get HCI driver operations */
|
||||
esp_bluedroid_hci_driver_operations_t operations = {
|
||||
.send = hosted_hci_bluedroid_send,
|
||||
.check_send_available = hosted_hci_bluedroid_check_send_available,
|
||||
.register_host_callback = hosted_hci_bluedroid_register_host_callback,
|
||||
};
|
||||
esp_bluedroid_attach_hci_driver(&operations);
|
||||
|
||||
if ((ret = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "enable bluedroid failed: %s", esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "gap register failed: %s", esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "setting device name");
|
||||
esp_bt_gap_set_device_name(local_device_name);
|
||||
|
||||
ESP_LOGI(TAG, "setting cod major, peripheral");
|
||||
esp_bt_cod_t cod = {0};
|
||||
cod.major = ESP_BT_COD_MAJOR_DEV_PERIPHERAL;
|
||||
#if ESP_IDF_VERSION > ESP_IDF_VERSION_VAL(5, 3, 1)
|
||||
cod.minor = ESP_BT_COD_MINOR_PERIPHERAL_POINTING;
|
||||
#endif
|
||||
esp_bt_gap_set_cod(cod, ESP_BT_SET_COD_MAJOR_MINOR);
|
||||
|
||||
vTaskDelay(2000 / portTICK_PERIOD_MS);
|
||||
|
||||
// Initialize HID SDP information and L2CAP parameters.
|
||||
// to be used in the call of `esp_bt_hid_device_register_app` after profile initialization finishes
|
||||
do {
|
||||
s_local_param.app_param.name = "Mouse";
|
||||
s_local_param.app_param.description = "Mouse Example";
|
||||
s_local_param.app_param.provider = "ESP32";
|
||||
s_local_param.app_param.subclass = ESP_HID_CLASS_MIC; // keep same with minor class of COD
|
||||
s_local_param.app_param.desc_list = hid_mouse_descriptor;
|
||||
s_local_param.app_param.desc_list_len = hid_mouse_descriptor_len;
|
||||
|
||||
memset(&s_local_param.both_qos, 0, sizeof(esp_hidd_qos_param_t)); // don't set the qos parameters
|
||||
} while (0);
|
||||
|
||||
// Report Protocol Mode is the default mode, according to Bluetooth HID specification
|
||||
s_local_param.protocol_mode = ESP_HIDD_REPORT_MODE;
|
||||
|
||||
ESP_LOGI(TAG, "register hid device callback");
|
||||
esp_bt_hid_device_register_callback(esp_bt_hidd_cb);
|
||||
|
||||
ESP_LOGI(TAG, "starting hid device");
|
||||
esp_bt_hid_device_init();
|
||||
|
||||
#if (CONFIG_EXAMPLE_SSP_ENABLED == true)
|
||||
/* Set default parameters for Secure Simple Pairing */
|
||||
esp_bt_sp_param_t param_type = ESP_BT_SP_IOCAP_MODE;
|
||||
esp_bt_io_cap_t iocap = ESP_BT_IO_CAP_NONE;
|
||||
esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t));
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Set default parameters for Legacy Pairing
|
||||
* Use variable pin, input pin code when pairing
|
||||
*/
|
||||
esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_VARIABLE;
|
||||
esp_bt_pin_code_t pin_code;
|
||||
esp_bt_gap_set_pin(pin_type, 0, pin_code);
|
||||
|
||||
ESP_LOGI(TAG, "Own address:[%s]", bda2str((uint8_t *)esp_bt_dev_get_address(), bda_str, sizeof(bda_str)));
|
||||
ESP_LOGI(TAG, "exiting");
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# BT config
|
||||
# - ESP32 co-processor only supports BLE 4.2
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_CONTROLLER_DISABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_HID_ENABLED=y
|
||||
CONFIG_BT_HID_DEVICE_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n
|
||||
CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y
|
||||
CONFIG_BTDM_CTRL_MODE_BTDM=n
|
||||
|
||||
#
|
||||
# Wi-Fi Remote
|
||||
# - set ESP32 as default co-processor
|
||||
#
|
||||
CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32=y
|
||||
|
||||
#
|
||||
# Enable ESP Hosted BT
|
||||
# Used as VHCI transport between BT Host and Controller
|
||||
#
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID=y
|
||||
CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(host_hci_uart)
|
||||
@@ -0,0 +1,109 @@
|
||||
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
|
||||
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
|
||||
|
||||
| Supported Co-Processors | ESP32 |
|
||||
| ----------------------- | ----- |
|
||||
|
||||
ESP-IDF ESP-Hosted Hosted HCI Host
|
||||
==================================
|
||||
|
||||
This is a Bluetooth Host using ESP-Hosted as HCI IO to the BT Controller.
|
||||
|
||||
## Example Layout
|
||||
|
||||
This example is modified based on [bt_discovery](https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/classic_bt/bt_discovery), and all modifications are listed below:
|
||||
|
||||
- Removed all dependencies on controller from `main.c`.
|
||||
|
||||
```
|
||||
#include "esp_bt.h"
|
||||
|
||||
...
|
||||
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE));
|
||||
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
```
|
||||
|
||||
- Add support for ESP-Hosted HCI interface: `esp_hosted_bt.h`.
|
||||
|
||||
- Open HCI interface in `main.c`.
|
||||
|
||||
```
|
||||
#include "esp_hosted_bt.h"
|
||||
|
||||
...
|
||||
|
||||
/* initialize TRANSPORT first */
|
||||
hosted_hci_bluedroid_open();
|
||||
|
||||
/* get HCI driver operations */
|
||||
esp_bluedroid_hci_driver_operations_t operations = {
|
||||
.send = hosted_hci_bluedroid_uart_send,
|
||||
.check_send_available = hosted_hci_bluedroid_check_send_available,
|
||||
.register_host_callback = hosted_hci_bluedroid_register_host_callback,
|
||||
};
|
||||
esp_bluedroid_attach_hci_driver(&operations);
|
||||
```
|
||||
|
||||
## How to use example
|
||||
|
||||
### Hardware Required
|
||||
|
||||
This example runs on the ESP32-P4 Dev Board connected to a ESP32 via the GPIO header, using SPI FD (full duplex) as Hosted HCI transport. The following GPIO settings were used:
|
||||
|
||||
| SPI Function | ESP32 GPIO | ESP32-P4 GPIO |
|
||||
| :--- | ---: | ---: |
|
||||
| MOSI | 13 | 4 |
|
||||
| MISO | 12 | 5 |
|
||||
| CLK | 14 | 26 |
|
||||
| CS | 15 | 6 |
|
||||
| Handshake | 26 | 20 |
|
||||
| Data Ready | 4 | 36 |
|
||||
| Reset | -1 | 2 |
|
||||
|
||||
> [!NOTE]
|
||||
> SPI Mode 2 was used on both the ESP32-P4 and ESP32.
|
||||
|
||||
Users are free to choose which supported ESP-Hosted transport to use. See the [main ESP-Hosted README](https://github.com/espressif/esp-hosted-mcu/blob/main/README.md#6-decide-the-communication-bus-in-between-host-and-slave) for a list of supported transports.
|
||||
|
||||
For standard HCI, configure the co-processor Bluetooth Controller to use UART as the HCI transport, then select appropriate GPIOs on the ESP32-P4 to configure as a UART. In this mode, ESP-Hosted is not involved in transporting the HCI data.
|
||||
|
||||
See the ESP-IDF [UART HCI Host example](https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/bluedroid_host_only/bluedroid_host_only_uart) on how to set-up UART for the Bluetooth Host.
|
||||
|
||||
### Configure the project
|
||||
|
||||
First, set the host target to ESP32-P4:
|
||||
|
||||
```
|
||||
idf.py set-target esp32p4
|
||||
```
|
||||
|
||||
For the ESP32 co-processor, run `idf.py menuconfig` and configure `Example Configuration` for SPI Full-duplex with the correct SPI mode and GPIOs.
|
||||
|
||||
For the ESP32-P4 host, run `idf.py menuconfig` and under `Component config ---> ESP-Hosted config`:
|
||||
|
||||
* set the transport to be `SPI Full-duplex` with the correct SPI modem GPIOs (see above table) and SPI Clock frequency (10 MHz max).
|
||||
* set the Slave chipset used as `ESP32`.
|
||||
* set `Bluetooth Support ---> Enable Hosted Bluedroid Bluetooth support` to enable Bluedroid support. Leave the HCI type as `VHCI`.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
After setting the host target and configuring the project, build and flash the co-processor and host projects, then run monitor tool to view serial output on both the ESP32 and ESP32-P4:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "main.c"
|
||||
PRIV_REQUIRES bt nvs_flash esp_hosted
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10'
|
||||
@@ -0,0 +1,320 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
|
||||
|
||||
/****************************************************************************
|
||||
*
|
||||
* This file is for Classic Bluetooth device and service discovery Demo.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "nvs.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_device.h"
|
||||
#include "esp_gap_bt_api.h"
|
||||
#include "esp_bluedroid_hci.h"
|
||||
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_bluedroid.h"
|
||||
|
||||
#define GAP_TAG "GAP"
|
||||
|
||||
typedef enum {
|
||||
APP_GAP_STATE_IDLE = 0,
|
||||
APP_GAP_STATE_DEVICE_DISCOVERING,
|
||||
APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE,
|
||||
APP_GAP_STATE_SERVICE_DISCOVERING,
|
||||
APP_GAP_STATE_SERVICE_DISCOVER_COMPLETE,
|
||||
} app_gap_state_t;
|
||||
|
||||
typedef struct {
|
||||
bool dev_found;
|
||||
uint8_t bdname_len;
|
||||
uint8_t eir_len;
|
||||
uint8_t rssi;
|
||||
uint32_t cod;
|
||||
uint8_t eir[ESP_BT_GAP_EIR_DATA_LEN];
|
||||
uint8_t bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1];
|
||||
esp_bd_addr_t bda;
|
||||
app_gap_state_t state;
|
||||
} app_gap_cb_t;
|
||||
|
||||
static app_gap_cb_t m_dev_info;
|
||||
|
||||
static char *bda2str(esp_bd_addr_t bda, char *str, size_t size)
|
||||
{
|
||||
if (bda == NULL || str == NULL || size < 18) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
uint8_t *p = bda;
|
||||
sprintf(str, "%02x:%02x:%02x:%02x:%02x:%02x",
|
||||
p[0], p[1], p[2], p[3], p[4], p[5]);
|
||||
return str;
|
||||
}
|
||||
|
||||
static char *uuid2str(esp_bt_uuid_t *uuid, char *str, size_t size)
|
||||
{
|
||||
if (uuid == NULL || str == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (uuid->len == 2 && size >= 5) {
|
||||
sprintf(str, "%04x", uuid->uuid.uuid16);
|
||||
} else if (uuid->len == 4 && size >= 9) {
|
||||
sprintf(str, "%08"PRIx32, uuid->uuid.uuid32);
|
||||
} else if (uuid->len == 16 && size >= 37) {
|
||||
uint8_t *p = uuid->uuid.uuid128;
|
||||
sprintf(str, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
|
||||
p[15], p[14], p[13], p[12], p[11], p[10], p[9], p[8],
|
||||
p[7], p[6], p[5], p[4], p[3], p[2], p[1], p[0]);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static bool get_name_from_eir(uint8_t *eir, uint8_t *bdname, uint8_t *bdname_len)
|
||||
{
|
||||
uint8_t *rmt_bdname = NULL;
|
||||
uint8_t rmt_bdname_len = 0;
|
||||
|
||||
if (!eir) {
|
||||
return false;
|
||||
}
|
||||
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_CMPL_LOCAL_NAME, &rmt_bdname_len);
|
||||
if (!rmt_bdname) {
|
||||
rmt_bdname = esp_bt_gap_resolve_eir_data(eir, ESP_BT_EIR_TYPE_SHORT_LOCAL_NAME, &rmt_bdname_len);
|
||||
}
|
||||
|
||||
if (rmt_bdname) {
|
||||
if (rmt_bdname_len > ESP_BT_GAP_MAX_BDNAME_LEN) {
|
||||
rmt_bdname_len = ESP_BT_GAP_MAX_BDNAME_LEN;
|
||||
}
|
||||
|
||||
if (bdname) {
|
||||
memcpy(bdname, rmt_bdname, rmt_bdname_len);
|
||||
bdname[rmt_bdname_len] = '\0';
|
||||
}
|
||||
if (bdname_len) {
|
||||
*bdname_len = rmt_bdname_len;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void update_device_info(esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
char bda_str[18];
|
||||
uint32_t cod = 0;
|
||||
int32_t rssi = -129; /* invalid value */
|
||||
uint8_t *bdname = NULL;
|
||||
uint8_t bdname_len = 0;
|
||||
uint8_t *eir = NULL;
|
||||
uint8_t eir_len = 0;
|
||||
esp_bt_gap_dev_prop_t *p;
|
||||
|
||||
ESP_LOGI(GAP_TAG, "Device found: %s", bda2str(param->disc_res.bda, bda_str, 18));
|
||||
for (int i = 0; i < param->disc_res.num_prop; i++) {
|
||||
p = param->disc_res.prop + i;
|
||||
switch (p->type) {
|
||||
case ESP_BT_GAP_DEV_PROP_COD:
|
||||
cod = *(uint32_t *)(p->val);
|
||||
ESP_LOGI(GAP_TAG, "--Class of Device: 0x%"PRIx32, cod);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_RSSI:
|
||||
rssi = *(int8_t *)(p->val);
|
||||
ESP_LOGI(GAP_TAG, "--RSSI: %"PRId32, rssi);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_BDNAME:
|
||||
bdname_len = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN :
|
||||
(uint8_t)p->len;
|
||||
bdname = (uint8_t *)(p->val);
|
||||
break;
|
||||
case ESP_BT_GAP_DEV_PROP_EIR: {
|
||||
eir_len = p->len;
|
||||
eir = (uint8_t *)(p->val);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* search for device with Major device type "PHONE" or "Audio/Video" in COD */
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
if (p_dev->dev_found) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!esp_bt_gap_is_valid_cod(cod) ||
|
||||
(!(esp_bt_gap_get_cod_major_dev(cod) == ESP_BT_COD_MAJOR_DEV_PHONE) &&
|
||||
!(esp_bt_gap_get_cod_major_dev(cod) == ESP_BT_COD_MAJOR_DEV_AV))) {
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(p_dev->bda, param->disc_res.bda, ESP_BD_ADDR_LEN);
|
||||
p_dev->dev_found = true;
|
||||
|
||||
p_dev->cod = cod;
|
||||
p_dev->rssi = rssi;
|
||||
if (bdname_len > 0) {
|
||||
memcpy(p_dev->bdname, bdname, bdname_len);
|
||||
p_dev->bdname[bdname_len] = '\0';
|
||||
p_dev->bdname_len = bdname_len;
|
||||
}
|
||||
if (eir_len > 0) {
|
||||
memcpy(p_dev->eir, eir, eir_len);
|
||||
p_dev->eir_len = eir_len;
|
||||
}
|
||||
|
||||
if (p_dev->bdname_len == 0) {
|
||||
get_name_from_eir(p_dev->eir, p_dev->bdname, &p_dev->bdname_len);
|
||||
}
|
||||
|
||||
ESP_LOGI(GAP_TAG, "Found a target device, address %s, name %s", bda_str, p_dev->bdname);
|
||||
p_dev->state = APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE;
|
||||
ESP_LOGI(GAP_TAG, "Cancel device discovery ...");
|
||||
esp_bt_gap_cancel_discovery();
|
||||
}
|
||||
|
||||
static void bt_app_gap_init(void)
|
||||
{
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
memset(p_dev, 0, sizeof(app_gap_cb_t));
|
||||
|
||||
p_dev->state = APP_GAP_STATE_IDLE;
|
||||
}
|
||||
|
||||
static void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param)
|
||||
{
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
char bda_str[18];
|
||||
char uuid_str[37];
|
||||
|
||||
switch (event) {
|
||||
case ESP_BT_GAP_DISC_RES_EVT: {
|
||||
update_device_info(param);
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: {
|
||||
if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) {
|
||||
ESP_LOGI(GAP_TAG, "Device discovery stopped.");
|
||||
if ( (p_dev->state == APP_GAP_STATE_DEVICE_DISCOVER_COMPLETE ||
|
||||
p_dev->state == APP_GAP_STATE_DEVICE_DISCOVERING)
|
||||
&& p_dev->dev_found) {
|
||||
p_dev->state = APP_GAP_STATE_SERVICE_DISCOVERING;
|
||||
ESP_LOGI(GAP_TAG, "Discover services ...");
|
||||
esp_bt_gap_get_remote_services(p_dev->bda);
|
||||
}
|
||||
} else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) {
|
||||
ESP_LOGI(GAP_TAG, "Discovery started.");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_RMT_SRVCS_EVT: {
|
||||
if (memcmp(param->rmt_srvcs.bda, p_dev->bda, ESP_BD_ADDR_LEN) == 0 &&
|
||||
p_dev->state == APP_GAP_STATE_SERVICE_DISCOVERING) {
|
||||
p_dev->state = APP_GAP_STATE_SERVICE_DISCOVER_COMPLETE;
|
||||
if (param->rmt_srvcs.stat == ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGI(GAP_TAG, "Services for device %s found", bda2str(p_dev->bda, bda_str, 18));
|
||||
for (int i = 0; i < param->rmt_srvcs.num_uuids; i++) {
|
||||
esp_bt_uuid_t *u = param->rmt_srvcs.uuid_list + i;
|
||||
ESP_LOGI(GAP_TAG, "--%s", uuid2str(u, uuid_str, 37));
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(GAP_TAG, "Services for device %s not found", bda2str(p_dev->bda, bda_str, 18));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_BT_GAP_RMT_SRVC_REC_EVT:
|
||||
default: {
|
||||
ESP_LOGI(GAP_TAG, "event: %d", event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
static void bt_app_gap_start_up(void)
|
||||
{
|
||||
/* register GAP callback function */
|
||||
esp_bt_gap_register_callback(bt_app_gap_cb);
|
||||
|
||||
char *dev_name = "ESP_GAP_INQRUIY";
|
||||
esp_bt_gap_set_device_name(dev_name);
|
||||
|
||||
/* set discoverable and connectable mode, wait to be connected */
|
||||
esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE);
|
||||
|
||||
/* initialize device information and status */
|
||||
bt_app_gap_init();
|
||||
|
||||
/* start to discover nearby Bluetooth devices */
|
||||
app_gap_cb_t *p_dev = &m_dev_info;
|
||||
p_dev->state = APP_GAP_STATE_DEVICE_DISCOVERING;
|
||||
esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0);
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS — it is used to store PHY calibration data and save key-value pairs in flash memory*/
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
// initialise connection to co-processor
|
||||
esp_hosted_connect_to_slave();
|
||||
|
||||
// init bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_init()) {
|
||||
ESP_LOGW("INFO", "failed to init bt controller");
|
||||
}
|
||||
|
||||
// enable bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_enable()) {
|
||||
ESP_LOGW("INFO", "failed to enable bt controller");
|
||||
}
|
||||
|
||||
hosted_hci_bluedroid_open();
|
||||
|
||||
/* get HCI driver operations */
|
||||
esp_bluedroid_hci_driver_operations_t operations = {
|
||||
.send = hosted_hci_bluedroid_send,
|
||||
.check_send_available = hosted_hci_bluedroid_check_send_available,
|
||||
.register_host_callback = hosted_hci_bluedroid_register_host_callback,
|
||||
};
|
||||
esp_bluedroid_attach_hci_driver(&operations);
|
||||
|
||||
if ((ret = esp_bluedroid_init()) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s initialize bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ret = esp_bluedroid_enable()) != ESP_OK) {
|
||||
ESP_LOGE(GAP_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
bt_app_gap_start_up();
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# BT config
|
||||
# - ESP32 co-processor only supports BLE 4.2
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_CONTROLLER_DISABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_CLASSIC_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
|
||||
#
|
||||
# Wi-Fi Remote
|
||||
# - set ESP32 as default co-processor
|
||||
#
|
||||
CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32=y
|
||||
|
||||
#
|
||||
# Enable ESP Hosted BT
|
||||
# Used as VHCI transport between BT Host and Controller
|
||||
#
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID=y
|
||||
CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI=
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bt_mac_addr_init)
|
||||
@@ -0,0 +1,178 @@
|
||||
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
|
||||
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
|
||||
|
||||
| Supported Co-Processors | ESP32 | ESP32-C Series | ESP32-S Series |
|
||||
| ----------------------- | ----- | -------------- | -------------- |
|
||||
|
||||
# BT Controller MAC Address and Initialisation Example
|
||||
|
||||
This example shows how to change the MAC Address. When run, a BLE
|
||||
Scanner should show a `Bluedroid_Beacon` device with the assigned MAC
|
||||
address.
|
||||
|
||||
For more information on the Bluedroid Beacon, see the ESP-IDF
|
||||
[Bluedroid Beacon example](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/ble_get_started/bluedroid/Bluedroid_Beacon).
|
||||
|
||||
This example can be modified to use NimBLE instead of Bluedroid.
|
||||
|
||||
To run the example without modifying the BT Mac address, run `idf.py
|
||||
menuconfig` --> `Example Configuration` and turn off `Update MAC
|
||||
Address of BT Controller` option.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the
|
||||
project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
## Example Output
|
||||
|
||||
```
|
||||
I (25) boot: ESP-IDF v6.0-dev-2343-g92b90afa77 2nd stage bootloader
|
||||
I (26) boot: compile time Sep 19 2025 13:47:15
|
||||
I (26) boot: Multicore bootloader
|
||||
I (29) boot: chip revision: v1.0
|
||||
I (30) boot: efuse block revision: v0.1
|
||||
I (34) boot.esp32p4: SPI Speed : 80MHz
|
||||
I (38) boot.esp32p4: SPI Mode : DIO
|
||||
I (41) boot.esp32p4: SPI Flash Size : 2MB
|
||||
I (45) boot: Enabling RNG early entropy source...
|
||||
I (50) boot: Partition Table:
|
||||
I (52) boot: ## Label Usage Type ST Offset Length
|
||||
I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000
|
||||
I (65) boot: 1 phy_init RF data 01 01 0000f000 00001000
|
||||
I (72) boot: 2 factory factory app 00 00 00010000 00100000
|
||||
I (79) boot: End of partition table
|
||||
I (82) esp_image: segment 0: paddr=00010020 vaddr=40070020 size=3166ch (202348) map
|
||||
I (125) esp_image: segment 1: paddr=00041694 vaddr=30100000 size=00044h ( 68) load
|
||||
I (127) esp_image: segment 2: paddr=000416e0 vaddr=4ff00000 size=0e6ech ( 59116) load
|
||||
I (141) esp_image: segment 3: paddr=0004fdd4 vaddr=4ff0e700 size=00244h ( 580) load
|
||||
I (143) esp_image: segment 4: paddr=00050020 vaddr=40000020 size=6b1f4h (438772) map
|
||||
I (221) esp_image: segment 5: paddr=000bb21c vaddr=4ff0e944 size=02398h ( 9112) load
|
||||
I (228) boot: Loaded app from partition at offset 0x10000
|
||||
I (228) boot: Disabling RNG early entropy source...
|
||||
W (239) pmu_pvt: blk_version is less than 2, pvt auto dbias init not supported in efuse.
|
||||
I (241) cpu_start: Multicore app
|
||||
I (251) cpu_start: GPIO 38 and 37 are used as console UART I/O pins
|
||||
I (252) cpu_start: Pro cpu start user code
|
||||
I (252) cpu_start: cpu freq: 360000000 Hz
|
||||
I (254) app_init: Application information:
|
||||
I (257) app_init: Project name: bt_mac_addr_init
|
||||
I (262) app_init: App version: bb05bb9c
|
||||
I (266) app_init: Compile time: Sep 19 2025 14:40:33
|
||||
I (271) app_init: ELF file SHA256: ab0fe993f...
|
||||
I (275) app_init: ESP-IDF: v6.0-dev-2343-g92b90afa77
|
||||
I (281) efuse_init: Min chip rev: v0.1
|
||||
I (285) efuse_init: Max chip rev: v1.99
|
||||
I (289) efuse_init: Chip rev: v1.0
|
||||
I (293) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (299) heap_init: At 4FF130B0 len 00027F10 (159 KiB): RAM
|
||||
I (304) heap_init: At 4FF3AFC0 len 00004BF0 (18 KiB): RAM
|
||||
I (309) heap_init: At 4FF40000 len 00060000 (384 KiB): RAM
|
||||
I (315) heap_init: At 50108080 len 00007F80 (31 KiB): RTCRAM
|
||||
I (320) heap_init: At 30100044 len 00001FBC (7 KiB): TCM
|
||||
I (326) spi_flash: detected chip: gd
|
||||
I (328) spi_flash: flash io: dio
|
||||
W (331) spi_flash: Detected size(16384k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
|
||||
I (343) host_init: ESP Hosted : Host chip_ip[18]
|
||||
I (375) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
|
||||
I (375) H_API: ** add_esp_wifi_remote_channels **
|
||||
I (376) transport: Add ESP-Hosted channel IF[1]: S[0] Tx[0x4000c480] Rx[0x40020c3c]
|
||||
--- 0x4000c480: transport_drv_sta_tx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_bt_controller_mac_addr/components/esp_hosted/host/drivers/transport/transport_drv.c:245
|
||||
--- 0x40020c3c: esp_wifi_remote_channel_rx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_bt_controller_mac_addr/managed_components/espressif__esp_wifi_remote/esp_wifi_remote_net.c:19
|
||||
I (384) transport: Add ESP-Hosted channel IF[2]: S[0] Tx[0x4000c3c0] Rx[0x40020c3c]
|
||||
--- 0x4000c3c0: transport_drv_ap_tx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_bt_controller_mac_addr/components/esp_hosted/host/drivers/transport/transport_drv.c:275
|
||||
--- 0x40020c3c: esp_wifi_remote_channel_rx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_bt_controller_mac_addr/managed_components/espressif__esp_wifi_remote/esp_wifi_remote_net.c:19
|
||||
sdio_mempool_create free:196416 min-free:196416 lfb-def:155648 lfb-8bit:155648
|
||||
|
||||
I (400) H_SDIO_DRV: sdio_data_to_rx_buf_task started
|
||||
I (403) main_task: Started on CPU0
|
||||
I (406) main_task: Calling app_main()
|
||||
I (416) H_API: ESP-Hosted Try to communicate with ESP-Hosted slave
|
||||
|
||||
I (416) transport: Attempt connection with slave: retry[0]
|
||||
W (421) H_SDIO_DRV: Reset slave using GPIO[54]
|
||||
I (425) os_wrapper_esp: GPIO [54] configured
|
||||
I (1631) sdio_wrapper: SDIO master: Slot 1, Data-Lines: 4-bit Freq(KHz)[40000 KHz]
|
||||
I (1631) sdio_wrapper: GPIOs: CLK[18] CMD[19] D0[14] D1[15] D2[16] D3[17] Slave_Reset[54]
|
||||
I (1635) sdio_wrapper: Queues: Tx[20] Rx[20] SDIO-Rx-Mode[1]
|
||||
Name:
|
||||
Type: SDIO
|
||||
Speed: 40.00 MHz (limit: 40.00 MHz)
|
||||
Size: 0MB
|
||||
CSD: ver=1, sector_size=0, capacity=0 read_bl_len=0
|
||||
SCR: sd_spec=0, bus_width=0
|
||||
TUPLE: DEVICE, size: 3: D9 01 FF
|
||||
TUPLE: MANFID, size: 4
|
||||
MANF: 0092, CARD: 6666
|
||||
TUPLE: FUNCID, size: 2: 0C 00
|
||||
TUPLE: FUNCE, size: 4: 00 00 02 32
|
||||
TUPLE: CONFIG, size: 5: 01 01 00 02 07
|
||||
TUPLE: CFTABLE_ENTRY, size: 8
|
||||
INDX: C1, Intface: 1, Default: 1, Conf-Entry-Num: 1
|
||||
IF: 41
|
||||
FS: 30, misc: 0, mem_space: 1, irq: 1, io_space: 0, timing: 0, power: 0
|
||||
IR: 30, mask: 1, IRQ: FF FF
|
||||
LEN: FFFF
|
||||
TUPLE: END
|
||||
I (1714) sdio_wrapper: Function 0 Blocksize: 512
|
||||
I (1718) sdio_wrapper: Function 1 Blocksize: 512
|
||||
I (1723) H_SDIO_DRV: Card init success, TRANSPORT_RX_ACTIVE
|
||||
I (1728) transport: set_transport_state: 1
|
||||
I (1732) transport: Waiting for esp_hosted slave to be ready
|
||||
I (1800) H_SDIO_DRV: SDIO Host operating in STREAMING MODE
|
||||
I (1800) H_SDIO_DRV: Open data path at slave
|
||||
I (1800) H_SDIO_DRV: Starting SDIO process rx task
|
||||
I (1828) H_SDIO_DRV: Received ESP_PRIV_IF type message
|
||||
I (1828) transport: Received INIT event from ESP32 peripheral
|
||||
I (1828) transport: EVENT: 12
|
||||
I (1830) transport: Identified slave [esp32c6]
|
||||
I (1834) transport: EVENT: 11
|
||||
I (1836) transport: capabilities: 0xd
|
||||
I (1840) transport: Features supported are:
|
||||
I (1844) transport: * WLAN
|
||||
I (1846) transport: - HCI over SDIO
|
||||
I (1850) transport: - BLE only
|
||||
I (1853) transport: EVENT: 13
|
||||
I (1856) transport: ESP board type is : 13
|
||||
|
||||
I (1860) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
|
||||
I (1866) H_API: Transport active
|
||||
I (1869) transport: Slave chip Id[12]
|
||||
I (1872) transport: raw_tp_dir[-], flow_ctrl: low[60] high[80]
|
||||
I (1878) transport: transport_delayed_init
|
||||
I (1881) esp_cli: Remove any existing deep_sleep cmd in cli
|
||||
I (1887) esp_cli: Registering command: crash
|
||||
I (1891) esp_cli: Registering command: reboot
|
||||
I (1895) esp_cli: Registering command: mem-dump
|
||||
I (1899) esp_cli: Registering command: task-dump
|
||||
I (1903) esp_cli: Registering command: cpu-dump
|
||||
I (1908) esp_cli: Registering command: heap-trace
|
||||
I (1912) esp_cli: Registering command: sock-dump
|
||||
I (1916) esp_cli: Registering command: host-power-save
|
||||
I (1921) vhci_drv: Host BT Support: Enabled
|
||||
I (1925) vhci_drv: BT Transport Type: VHCI
|
||||
I (1929) H_SDIO_DRV: Received INIT event
|
||||
I (1933) H_SDIO_DRV: Event type: 0x22
|
||||
I (1936) H_SDIO_DRV: Write thread started
|
||||
I (1940) BT Example: getting fw version
|
||||
I (2403) BT Example: FW Version: 2.4.4
|
||||
I (2410) BT Example: Current BT controller mac address is 9c:9e:6e:5a:b1:66
|
||||
I (2410) BT Example: Updating MAC address
|
||||
I (2472) BT Example: New BT controller mac address is 08:3a:8d:01:01:01
|
||||
I (2582) transport: Attempt connection with slave: retry[0]
|
||||
I (2582) transport: Transport is already up
|
||||
I (2607) main_task: Returned from app_main()
|
||||
I (2608) BT Example: Advertising data raw set, status 0
|
||||
I (2609) BT Example: Scan response data raw set, status 0
|
||||
I (2616) BT Example: Advertising start successfully
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-hosted-mcu/issues) on ESP-Hosted on GitHub. We will get back to you soon.
|
||||
|
||||
## References
|
||||
|
||||
* Bluetooth Implementation in ESP-Hosted: https://github.com/espressif/esp-hosted-mcu/blob/main/docs/bluetooth_design.md
|
||||
@@ -0,0 +1,4 @@
|
||||
set(srcs "main.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,8 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_UPDATE_MAC_ADDRESS
|
||||
bool "Update MAC Address of BT Controller"
|
||||
default y
|
||||
help
|
||||
Update the MAC Address on the BT Controller
|
||||
endmenu
|
||||
@@ -0,0 +1,9 @@
|
||||
dependencies:
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10'
|
||||
@@ -0,0 +1,252 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_bluedroid.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
/* BLE */
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
#include "esp_log.h"
|
||||
|
||||
#define UPDATE_MAC_ADDRESS CONFIG_EXAMPLE_UPDATE_MAC_ADDRESS
|
||||
|
||||
#define ADV_CONFIG_FLAG (1 << 0)
|
||||
#define SCAN_RSP_CONFIG_FLAG (1 << 1)
|
||||
#define URI_PREFIX_HTTPS (0x17)
|
||||
|
||||
static const char *TAG = "BT Example";
|
||||
|
||||
#if UPDATE_MAC_ADDRESS
|
||||
// Note 1: This is an example MAC address for demo purposes. In production,
|
||||
// use a unique MAC address from your allocated range or locally
|
||||
// administered address space
|
||||
// Note 2: This MAC address setting is temporary and will revert after device
|
||||
// reset. For permanent MAC address change, modify during hardware
|
||||
// provisioning or burn directly into eFuse using appropriate commands
|
||||
static uint8_t new_mac_addr[] = { 0x08, 0x3A, 0x8D, 0x01, 0x01, 0x01 };
|
||||
#endif
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
static const char device_name[] = "Bluedroid_Beacon";
|
||||
|
||||
static uint8_t adv_config_done = 0;
|
||||
static esp_bd_addr_t local_addr;
|
||||
static uint8_t local_addr_type;
|
||||
|
||||
static esp_ble_adv_params_t adv_params = {
|
||||
.adv_int_min = 0x20, // 20ms
|
||||
.adv_int_max = 0x20, // 20ms
|
||||
.adv_type = ADV_TYPE_SCAN_IND,
|
||||
.own_addr_type = BLE_ADDR_TYPE_PUBLIC,
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
//configure raw data for advertising packet
|
||||
static uint8_t adv_raw_data[] = {
|
||||
0x02, ESP_BLE_AD_TYPE_FLAG, 0x06,
|
||||
0x11, ESP_BLE_AD_TYPE_NAME_CMPL, 'B', 'l', 'u', 'e', 'd', 'r', 'o', 'i', 'd', '_', 'B', 'e', 'a', 'c', 'o', 'n',
|
||||
0x02, ESP_BLE_AD_TYPE_TX_PWR, 0x09,
|
||||
0x03, ESP_BLE_AD_TYPE_APPEARANCE, 0x00,0x02,
|
||||
0x02, ESP_BLE_AD_TYPE_LE_ROLE, 0x00,
|
||||
};
|
||||
|
||||
static uint8_t scan_rsp_raw_data[] = {
|
||||
0x08, ESP_BLE_AD_TYPE_LE_DEV_ADDR, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x11, ESP_BLE_AD_TYPE_URI, URI_PREFIX_HTTPS, '/', '/', 'e', 's', 'p', 'r', 'e', 's', 's', 'i', 'f', '.', 'c', 'o', 'm',
|
||||
};
|
||||
|
||||
// function to get and (optionally) set BT Controller MAC address
|
||||
static void bt_mac_actions(void)
|
||||
{
|
||||
uint8_t mac[10] = { 0 };
|
||||
size_t mac_len = 0;
|
||||
|
||||
// get length of bt mac address
|
||||
mac_len = esp_hosted_iface_mac_addr_len_get(ESP_MAC_BT);
|
||||
if (mac_len) {
|
||||
ESP_LOGI(TAG, "length of mac address for BT is %" PRIu16, mac_len);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to get length of mac address for BT");
|
||||
}
|
||||
|
||||
// get current bt mac address
|
||||
if (ESP_OK == esp_hosted_iface_mac_addr_get(mac, mac_len, ESP_MAC_BT)) {
|
||||
ESP_LOGI(TAG, "Current BT controller mac address is "MACSTR, MAC2STR(mac));
|
||||
#if UPDATE_MAC_ADDRESS
|
||||
// update BT mac address - must be done before initialising bt controller
|
||||
ESP_LOGI(TAG, "Updating MAC address");
|
||||
// switch OUI
|
||||
memcpy(mac, new_mac_addr, mac_len);
|
||||
if (ESP_OK == esp_hosted_iface_mac_addr_set(mac, mac_len, ESP_MAC_BT)) {
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to set mac address for BT");
|
||||
}
|
||||
if (ESP_OK == esp_hosted_iface_mac_addr_get(mac, mac_len, ESP_MAC_BT)) {
|
||||
ESP_LOGI(TAG, "New BT controller mac address is "MACSTR, MAC2STR(mac));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to get mac address for BT after updating");
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to get mac address for BT");
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// initialise connection to co-processor
|
||||
esp_hosted_connect_to_slave();
|
||||
|
||||
// get fw version
|
||||
ESP_LOGI(TAG, "getting fw version");
|
||||
esp_hosted_coprocessor_fwver_t fwver;
|
||||
if (ESP_OK == esp_hosted_get_coprocessor_fwversion(&fwver)) {
|
||||
ESP_LOGI(TAG, "FW Version: %" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
fwver.major1, fwver.minor1, fwver.patch1);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "failed to get fw version");
|
||||
}
|
||||
|
||||
// changes to the MAC must be done before the BT Controller is initalised
|
||||
bt_mac_actions();
|
||||
|
||||
// init bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_init()) {
|
||||
ESP_LOGW(TAG, "Failed to init bt controller");
|
||||
return;
|
||||
}
|
||||
|
||||
// enable bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_enable()) {
|
||||
ESP_LOGW(TAG, "Failed to enable bt controller");
|
||||
return;
|
||||
}
|
||||
|
||||
// initialise ESP-Hosted connection for Bluedroid
|
||||
hosted_hci_bluedroid_open();
|
||||
|
||||
/* get HCI driver operations */
|
||||
esp_bluedroid_hci_driver_operations_t operations = {
|
||||
.send = hosted_hci_bluedroid_send,
|
||||
.check_send_available = hosted_hci_bluedroid_check_send_available,
|
||||
.register_host_callback = hosted_hci_bluedroid_register_host_callback,
|
||||
};
|
||||
esp_bluedroid_attach_hci_driver(&operations);
|
||||
|
||||
ret = esp_bluedroid_init();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_bluedroid_enable();
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret));
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_register_callback(esp_gap_cb);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "gap register error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_set_device_name(device_name);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "set device name error, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
//config adv data
|
||||
adv_config_done |= ADV_CONFIG_FLAG;
|
||||
adv_config_done |= SCAN_RSP_CONFIG_FLAG;
|
||||
ret = esp_ble_gap_config_adv_data_raw(adv_raw_data, sizeof(adv_raw_data));
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "config adv data failed, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = esp_ble_gap_get_local_used_addr(local_addr, &local_addr_type);
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "get local used address failed, error code = %x", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
scan_rsp_raw_data[2] = local_addr[5];
|
||||
scan_rsp_raw_data[3] = local_addr[4];
|
||||
scan_rsp_raw_data[4] = local_addr[3];
|
||||
scan_rsp_raw_data[5] = local_addr[2];
|
||||
scan_rsp_raw_data[6] = local_addr[1];
|
||||
scan_rsp_raw_data[7] = local_addr[0];
|
||||
ret = esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_raw_data, sizeof(scan_rsp_raw_data));
|
||||
if (ret) {
|
||||
ESP_LOGE(TAG, "config scan rsp data failed, error code = %x", ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Advertising data set, status %d", param->adv_data_cmpl.status);
|
||||
adv_config_done &= (~ADV_CONFIG_FLAG);
|
||||
if (adv_config_done == 0) {
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Advertising data raw set, status %d", param->adv_data_raw_cmpl.status);
|
||||
adv_config_done &= (~ADV_CONFIG_FLAG);
|
||||
if (adv_config_done == 0) {
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Scan response data set, status %d", param->scan_rsp_data_cmpl.status);
|
||||
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
|
||||
if (adv_config_done == 0) {
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT:
|
||||
ESP_LOGI(TAG, "Scan response data raw set, status %d", param->scan_rsp_data_raw_cmpl.status);
|
||||
adv_config_done &= (~SCAN_RSP_CONFIG_FLAG);
|
||||
if (adv_config_done == 0) {
|
||||
esp_ble_gap_start_advertising(&adv_params);
|
||||
}
|
||||
break;
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
if (param->adv_start_cmpl.status != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(TAG, "Advertising start failed, status %d", param->adv_start_cmpl.status);
|
||||
break;
|
||||
}
|
||||
ESP_LOGI(TAG, "Advertising start successfully");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_CONTROLLER_DISABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=y
|
||||
CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n
|
||||
CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y
|
||||
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not used on ESP32, ESP32-C3 and ESP32-S3.
|
||||
# CONFIG_BT_LE_50_FEATURE_SUPPORT is not set
|
||||
|
||||
#
|
||||
# Wi-Fi Remote
|
||||
#
|
||||
CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y
|
||||
|
||||
#
|
||||
# Enable ESP Hosted BT
|
||||
# Used as VHCI transport between BT Host and Controller
|
||||
#
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_BLUEDROID=y
|
||||
CONFIG_ESP_HOSTED_BLUEDROID_HCI_VHCI=y
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(iperf)
|
||||
@@ -0,0 +1,331 @@
|
||||
# Network Split with Host Deep Sleep Example
|
||||
|
||||
This example shows how to use two powerful ESP-Hosted features together: **Network Split** and **Host Deep Sleep**. These features allow a host MCU to save power by sleeping while the slave device keeps the network connection alive and only wakes the host when necessary.
|
||||
|
||||
## What This Example Does
|
||||
|
||||
**Network Split** - Smart traffic routing:
|
||||
- Host handles important traffic (ports 49152-61439)
|
||||
- Slave handles background traffic (ports 61440-65535)
|
||||
- No unnecessary host wake-ups
|
||||
|
||||
|
||||
**Host Deep Sleep** - Ultra-low power mode:
|
||||
- Host can sleep while slave stays connected to WiFi
|
||||
- Slave wakes host only for important traffic
|
||||
- Wake-up can also be triggered by commands or MQTT messages
|
||||
|
||||
## Supported Platforms and Transports
|
||||
|
||||
### Supported Co-processors
|
||||
|
||||
| Co-Processors Supported | ESP32-C5 | ESP32-C6/C61 | ESP32 | ESP32-S2 | ESP32-S3 |
|
||||
| :---------------------- | :------- | :----------- | :---- | :------- | :------- |
|
||||
|
||||
### Supported Host MCUs
|
||||
|
||||
| Hosts Supported | ESP32-P4 | ESP32-H2 | Any other MCU hosts |
|
||||
| :-------------- | :------- | :------- | :------------------ |
|
||||
|
||||
### Supported Transport Interfaces
|
||||
|
||||
| Transport Interface | SDIO | SPI Full-Duplex | SPI Half-Duplex | UART |
|
||||
| :------------------ | :--- | :-------------- | :-------------- | :--- |
|
||||
|
||||
## Example Hardware Used
|
||||
|
||||
This example is designed for the **ESP32-P4-Function-EV-Board** with its built-in ESP32-C6.
|
||||
|
||||
**Default setup:**
|
||||
- **Host**: ESP32-P4 (handles important network traffic)
|
||||
- **Slave**: ESP32-C6 (maintains WiFi and handles background traffic)
|
||||
- **Connection**: SDIO (pre-wired on the board)
|
||||
- **Wake-up wire**: Already connected between GPIO2 (slave) and GPIO6 (host)
|
||||
|
||||
All listed items in default setup are customizable to suit specific use cases and requirements.
|
||||
|
||||
|
||||
## Flash the Slave (ESP32-C6)
|
||||
|
||||
The slave needs ESP-Hosted firmware with Network Split and Host Power Save enabled.
|
||||
|
||||
With below additional configuration, Flash the slave using setup instructions, see: [Slave Example Guide](../../slave/README.md)
|
||||
|
||||
|
||||
#### Slave Side Configuration:
|
||||
|
||||
Using `idf.py menuconfig`, Enable:
|
||||
|
||||
```
|
||||
# Minimal slave config
|
||||
Example Configuration
|
||||
├── [*] Enable Network Split
|
||||
└── [*] Allow host to power save
|
||||
```
|
||||
|
||||
**Advanced slave settings** (optional):
|
||||
```
|
||||
Example Configuration
|
||||
└── Network Split Configuration
|
||||
├── Host Static Port Forwarding
|
||||
│ ├── TCP dst: 22,80,443,8080,8554
|
||||
│ └── UDP dst: 53,123
|
||||
├── Port Ranges
|
||||
│ ├── Host: 49152–61439
|
||||
│ └── Slave: 61440–65535
|
||||
└── Host power save config
|
||||
├── Allow host to enter deep sleep
|
||||
├── Slave out: Host wakeup GPIO (2)
|
||||
└── Host Wakeup GPIO Level (High)
|
||||
```
|
||||
|
||||
Continue to build and flash slave using [Slave Example Guide](../../slave/README.md)
|
||||
|
||||
|
||||
## Flash the Host (ESP32-P4)
|
||||
|
||||
The host needs ESP-Hosted firmware with Network Split and Host Power Save configured.
|
||||
|
||||
**Essential host configuration:**
|
||||
1. Set target: `idf.py set-target esp32p4`
|
||||
|
||||
2. Network split and Host power save is pre-configured in this example.
|
||||
(Optionally) customise the configuration using `idf.py menuconfig`
|
||||
```
|
||||
Component config
|
||||
└── ESP-Hosted config
|
||||
├── [*] Enable Network Split
|
||||
│ └── Network Split Configuration
|
||||
│ └── Port Ranges
|
||||
│ ├── Host: 49152–61439
|
||||
│ └── Slave: 61440–65535
|
||||
└── [*] Enable Host Power Save
|
||||
└── Host Power Save Configuration
|
||||
[*] Allow host to enter deep sleep.
|
||||
└── Deep Sleep Configuration
|
||||
├── Host Wakeup GPIO (6)
|
||||
└── Host wakeup GPIO active low
|
||||
```
|
||||
|
||||
3. Build and flash: `idf.py build && idf.py -p <HOST_PORT> flash monitor`
|
||||
|
||||
## Testing the Example
|
||||
|
||||
### Basic Power Save Test
|
||||
1. Wait for both devices to connect to WiFi
|
||||
2. Connect to AP using command, `sta_connect <SSID> <Password>`
|
||||
3. On host console: type `host-power-save`
|
||||
4. Host goes to sleep, slave stays connected
|
||||
5. On slave console: type `wake-up-host` to wake the host
|
||||
|
||||
### Smart Wake-up Test
|
||||
1. Put host to sleep using `host-power-save`
|
||||
2. Send dummy TCP/UDP packet to the device IP in host ports (49152–61439) → host wakes automatically
|
||||
3. Send dummy TCP/UDP packet to the device IP in slave ports (61440-65535) → slave handles without waking host
|
||||
|
||||
##### Sample program to test Network based `Smart Wake-up Test`
|
||||
|
||||
<summary>C program to send UDP packet:</br></br>send_udp_pkt.c</summary>
|
||||
<details>
|
||||
|
||||
```c
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright 2015-2025 Espressif Systems (Shanghai) PTE LTD
|
||||
|
||||
/* This is just sample program to send sample udp packet */
|
||||
|
||||
/*
|
||||
====================================================================
|
||||
send_udp_pkt.c
|
||||
====================================================================
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/socket.h>
|
||||
#include <errno.h>
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 3) {
|
||||
fprintf(stderr, "Usage: %s <IP> <PORT>\n", argv[0]);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
const char *ip = argv[1];
|
||||
int port = atoi(argv[2]);
|
||||
const char *msg = "Hello, UDP!";
|
||||
|
||||
int sock = socket(AF_INET, SOCK_DGRAM, 0);
|
||||
if (sock < 0) {
|
||||
perror("socket");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// Non-blocking mode
|
||||
int flags = fcntl(sock, F_GETFL, 0);
|
||||
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
|
||||
|
||||
struct sockaddr_in addr = {
|
||||
.sin_family = AF_INET,
|
||||
.sin_port = htons(port),
|
||||
};
|
||||
inet_pton(AF_INET, ip, &addr.sin_addr);
|
||||
|
||||
ssize_t sent = sendto(sock, msg, strlen(msg), 0,
|
||||
(struct sockaddr *)&addr, sizeof(addr));
|
||||
|
||||
if (sent < 0) {
|
||||
if (errno == EWOULDBLOCK || errno == EAGAIN) {
|
||||
fprintf(stderr, "sendto would block, try again later\n");
|
||||
} else {
|
||||
perror("sendto");
|
||||
}
|
||||
} else {
|
||||
printf("Sent %zd bytes to %s:%d\n", sent, ip, port);
|
||||
}
|
||||
|
||||
close(sock);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
|
||||
<summary>Shell script to trigger wake-up: </br></br>send_udp_pkt.sh</summary>
|
||||
<details>
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Simple script to build and run send_udp_pkt.c
|
||||
|
||||
#====================================================================
|
||||
# send_udp_pkt.sh
|
||||
#====================================================================
|
||||
# Check if IP address is provided
|
||||
if [ $# -eq 0 ]; then
|
||||
echo "❌ Error: IP address is required!"
|
||||
echo "Usage: $0 <ip_address> [port]"
|
||||
echo "Example: $0 192.168.1.100 60000"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IP_ADDRESS="$1"
|
||||
DEFAULT_PORT="60000"
|
||||
|
||||
# Check if port is provided as second argument
|
||||
if [ $# -ge 2 ]; then
|
||||
PORT="$2"
|
||||
else
|
||||
PORT="$DEFAULT_PORT"
|
||||
echo "No port specified, using default port: $DEFAULT_PORT"
|
||||
fi
|
||||
|
||||
echo "===== Network Split Host Wakeup with UDP Packet Tool ====="
|
||||
echo "Target: $IP_ADDRESS:$PORT"
|
||||
echo "Building and running network wakeup utility..."
|
||||
|
||||
# Compile the program
|
||||
gcc -o send_udp_pkt send_udp_pkt.c -pthread
|
||||
|
||||
# Check if compilation succeeded
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "❌ Compilation failed!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "✅ Build successful!"
|
||||
echo "Starting network wakeup utility..."
|
||||
echo "-------------------------------------------"
|
||||
|
||||
# Run the program with IP address and port
|
||||
./send_udp_pkt $IP_ADDRESS $PORT
|
||||
|
||||
# Capture result
|
||||
RESULT=$?
|
||||
if [ $RESULT -eq 0 ]; then
|
||||
echo "-------------------------------------------"
|
||||
echo "✅ Network wakeup packet sent successfully to $IP_ADDRESS:$PORT!"
|
||||
else
|
||||
echo "-------------------------------------------"
|
||||
echo "❌ Network wakeup failed with code: $RESULT"
|
||||
fi
|
||||
|
||||
exit $RESULT
|
||||
```
|
||||
</details>
|
||||
|
||||
Create both files in some temporary directory and run:
|
||||
```bash
|
||||
bash send_udp_pkt.sh <device_ip_address> 62000 # Send packet to slave port --> ❌ No host wake up
|
||||
|
||||
bash send_udp_pkt.sh <device_ip_address> 123 # Send packet to host reserved udp port ==> ✅ host wake up
|
||||
```
|
||||
|
||||
> [!TIP]
|
||||
>
|
||||
> You can design your packet routing & filtering as per application application use-case and deploy in slave firmware.
|
||||
> Refer to the https://github.com/espressif/esp-hosted-mcu/blob/663d6631af6e7a6735755e2247ab60363fda87c8/slave/main/lwip_filter.c#L349
|
||||
|
||||
### Performance Test
|
||||
1. Run iPerf server: `iperf -s -p 5001`
|
||||
2. From another device: `iperf -c <device_ip> -p 5001`
|
||||
3. Watch logs to see which device handles the traffic
|
||||
For more details on iperf tests, refer [iperf test](README_iperf.md)
|
||||
|
||||
## Customizing for Other Hardware
|
||||
|
||||
#### Different Transport (e.g., SPI instead of SDIO)
|
||||
|
||||
**Slave side:**
|
||||
```
|
||||
Example Configuration
|
||||
└── Bus Config in between Host and Co-processor
|
||||
└── Transport layer
|
||||
└── (X) SPI Full-duplex
|
||||
```
|
||||
|
||||
**Host side:**
|
||||
```
|
||||
Component config
|
||||
└── ESP-Hosted config
|
||||
└── Transport layer
|
||||
└── (X) SPI Full-duplex
|
||||
```
|
||||
|
||||
Configure the specific GPIO pins for the chosen transport bus and Host wake up GPIO pin
|
||||
|
||||
#### Different MCU Combination
|
||||
1. Change `idf.py set-target` to your MCU
|
||||
2. Update GPIO pins in menuconfig to match your wiring
|
||||
3. Make sure wake-up GPIO on host supports deep sleep wake-up
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
**Host won't wake up:**
|
||||
- Check GPIO wire connection between slave GPIO2 and host GPIO6
|
||||
- Verify host GPIO6 supports RTC wake-up
|
||||
- Check GPIO level settings match on both sides
|
||||
|
||||
**WiFi connection fails:**
|
||||
- Double-check WiFi credentials in both devices
|
||||
- Make sure both devices use the same WiFi settings
|
||||
|
||||
**Traffic not splitting correctly:**
|
||||
- Check port range settings in slave menuconfig
|
||||
- Monitor logs to see packet routing decisions
|
||||
- Verify Network Split is enabled on slave
|
||||
|
||||
## References
|
||||
|
||||
- [Network Split Documentation](../../docs/feature_network_split.md)
|
||||
- [Host Power Save Documentation](../../docs/feature_host_power_save.md)
|
||||
- [ESP32-P4-Function-EV-Board Guide](../../docs/esp32_p4_function_ev_board.md)
|
||||
- [Slave Example Guide](../../slave/README.md)
|
||||
- [Main ESP-Hosted Documentation](../../README.md)
|
||||
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
| Supported Targets | ESP32 | ESP32-C2 | ESP32-C3 | ESP32-C5 | ESP32-C6 | ESP32-C61 | ESP32-P4 | ESP32-S2 | ESP32-S3 |
|
||||
| ----------------- | ----- | -------- | -------- | -------- | -------- | --------- | -------- | -------- | -------- |
|
||||
|
||||
# Iperf Example
|
||||
|
||||
## Note about iperf version
|
||||
The iperf example doesn't support all features in standard iperf. It's compatible with iperf version 2.x.
|
||||
|
||||
- Refer to the ESP Component Registry iperf-cmd page for more information: https://components.espressif.com/components/espressif/iperf-cmd
|
||||
|
||||
## Note about 80MHz flash frequency (ESP32)
|
||||
The iperf can get better throughput if the SPI flash frequency is set to 80MHz, but the system may crash in 80MHz mode for ESP-WROVER-KIT.
|
||||
Removing R140\~R145 from the board can fix this issue. Currently the default SPI frequency is set to 40MHz, if you want to change the SPI flash
|
||||
frequency to 80MHz, please make sure R140\~R145 are removed from ESP-WROVER-KIT or use ESP32 DevKitC.
|
||||
|
||||
## Introduction
|
||||
This example implements the protocol used by the common performance measurement tool [iPerf](https://iperf.fr/).
|
||||
Performance can be measured between two ESP targets running this example, or between a single ESP target and a computer running the iPerf tool
|
||||
|
||||
Demo steps to test station TCP Tx performance:
|
||||
|
||||
- Configure in `menuconfig` which serial output you are using. Execute `idf.py menuconfig` and go to `Component config/ESP System Settings/Channel for console output`, then select the appropriate interface. By default the UART0 interface is used, this means that for example in the ESP32-S3-DevKitC-1 or ESP32-C6-DevKitC-1 you should connect to the micro-usb connector labeled as UART and not to the one labeled as USB. To use the one labeled as USB you should change the aforementioned setting to `USB Serial/JTAG Controller`.
|
||||
|
||||
- Build and flash the iperf example with `sdkconfig.defaults`, which contains performance test specific configurations
|
||||
- Use `help` for detailed command usage information.
|
||||
|
||||
- Run the demo as station mode and join the target AP
|
||||
- `sta_connect <ssid> <password>`
|
||||
- NOTE: the dut is started in station mode by default. If you want to use the dut as softap, please set wifi mode first:
|
||||
- `wifi_mode ap`
|
||||
- `ap_set <dut_ap_ssid> <dut_ap_password>`
|
||||
|
||||
- Run iperf as server on AP side
|
||||
- `iperf -s -i 3`
|
||||
|
||||
- Run iperf as client on ESP side
|
||||
- `iperf -c 192.168.10.42 -i 3 -t 60`
|
||||
|
||||
The console output, which is printed by station TCP RX throughput test, looks like:
|
||||
|
||||
```
|
||||
iperf> sta_connect testap-11 ********
|
||||
I (36836) WIFI: Connecting to testap-11...
|
||||
I (36839) WIFI: DONE.WIFI_CONNECT_START,OK.
|
||||
iperf> I (39248) WIFI: WIFI_EVENT_STA_DISCONNECTED! reason: 201
|
||||
I (39249) WIFI: trying to reconnect...
|
||||
I (41811) wifi:new:<11,2>, old:<1,0>, ap:<255,255>, sta:<11,2>, prof:1, snd_ch_cfg:0x0
|
||||
I (41813) wifi:state: init -> auth (0xb0)
|
||||
I (41816) wifi:state: auth -> assoc (0x0)
|
||||
I (41840) wifi:state: assoc -> run (0x10)
|
||||
I (41847) wifi:<ba-add>idx:0 (ifx:0, 30:5a:3a:74:90:f0), tid:0, ssn:0, winSize:64
|
||||
I (41914) wifi:connected with testap-11, aid = 1, channel 11, 40D, bssid = 30:5a:3a:74:90:f0
|
||||
I (41915) wifi:security: WPA2-PSK, phy: bgn, rssi: -34
|
||||
I (41926) wifi:pm start, type: 0
|
||||
|
||||
I (41927) wifi:dp: 1, bi: 102400, li: 3, scale listen interval from 307200 us to 307200 us
|
||||
I (41929) WIFI: WIFI_EVENT_STA_CONNECTED!
|
||||
I (41983) wifi:AP's beacon interval = 102400 us, DTIM period = 3
|
||||
I (42929) esp_netif_handlers: sta ip: 192.168.1.79, mask: 255.255.255.0, gw: 192.168.1.1
|
||||
I (42930) WIFI: IP_EVENT_STA_GOT_IP: Interface "sta" address: 192.168.1.79
|
||||
I (42942) WIFI: - IPv4 address: 192.168.1.79,
|
||||
iperf>
|
||||
iperf> iperf -s -i 2
|
||||
I (84810) IPERF: mode=tcp-server sip=0.0.0.0:5001, dip=0.0.0.0:5001, interval=2, time=30
|
||||
I (84812) iperf: Socket created
|
||||
iperf> I (87967) iperf: accept: 192.168.1.2,43726
|
||||
|
||||
Interval Bandwidth
|
||||
0.0- 2.0 sec 24.36 Mbits/sec
|
||||
2.0- 4.0 sec 23.38 Mbits/sec
|
||||
4.0- 6.0 sec 24.02 Mbits/sec
|
||||
6.0- 8.0 sec 25.27 Mbits/sec
|
||||
8.0-10.0 sec 23.84 Mbits/sec
|
||||
```
|
||||
|
||||
Steps to test station/soft-AP TCP/UDP RX/TX throughput are similar as test steps in station TCP TX.
|
||||
|
||||
#### Host Flashing Steps
|
||||
|
||||
* Set the ESP32-P4 as the target
|
||||
|
||||
```sh
|
||||
idf.py set-target esp32p4
|
||||
```
|
||||
|
||||
You may see lines similar to this:
|
||||
|
||||
```
|
||||
NOTICE: Processing 11 dependencies:
|
||||
NOTICE: [1/11] cmd_system (*) (/home/esp/esp-idf/examples/system/console/advanced/components/cmd_system)
|
||||
NOTICE: [2/11] esp-qa/ping-cmd (1.0.0)
|
||||
NOTICE: [3/11] esp-qa/wifi-cmd (0.1.10)
|
||||
NOTICE: [4/11] espressif/eppp_link (0.2.0)
|
||||
NOTICE: [5/11] espressif/esp-extconn (0.1.0)
|
||||
NOTICE: [6/11] espressif/esp_hosted (1.1.1)
|
||||
NOTICE: [7/11] espressif/esp_serial_slave_link (1.1.0)
|
||||
NOTICE: [8/11] espressif/esp_wifi_remote (0.5.3)
|
||||
NOTICE: [9/11] espressif/iperf (0.1.3)
|
||||
NOTICE: [10/11] espressif/iperf-cmd (0.1.3)
|
||||
NOTICE: [11/11] idf (5.5.0)
|
||||
```
|
||||
|
||||
The Wi-Fi Remote and ESP-Hosted components are automatically fetched during the configuration.
|
||||
|
||||
* Build
|
||||
|
||||
```sh
|
||||
idf.py build
|
||||
```
|
||||
* Flash
|
||||
|
||||
```sh
|
||||
idf.py -p <host_serial_port> flash
|
||||
```
|
||||
* Monitor
|
||||
|
||||
```sh
|
||||
idf.py -p <host_serial_port> monitor
|
||||
```
|
||||
@@ -0,0 +1,2 @@
|
||||
idf_component_register(SRCS "iperf_example_main.c"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,29 @@
|
||||
dependencies:
|
||||
cmd_system:
|
||||
path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_system
|
||||
esp-qa/ping-cmd:
|
||||
version: ~1.0.0
|
||||
esp-qa/wifi-cmd:
|
||||
matches:
|
||||
- if: idf_version >=6.0
|
||||
version: ~0.2.0
|
||||
- if: idf_version >=5.5,<6.0
|
||||
version: ~0.2.2
|
||||
- if: idf_version >=5.4,<5.5
|
||||
version: ~0.1.8
|
||||
- if: idf_version >=5.3,<5.4
|
||||
version: ~0.1.0
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '*'
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '*'
|
||||
espressif/iperf-cmd:
|
||||
matches:
|
||||
- if: idf_version >=6.0
|
||||
version: ~0.1.3
|
||||
- if: idf_version <6.0
|
||||
version: ~0.1.1
|
||||
@@ -0,0 +1,140 @@
|
||||
/* Wi-Fi iperf Example
|
||||
|
||||
This example code is in the Public Domain (or CC0 licensed, at your option.)
|
||||
|
||||
Unless required by applicable law or agreed to in writing, this
|
||||
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied.
|
||||
*/
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_console.h"
|
||||
#include "cmd_system.h"
|
||||
|
||||
/* component manager */
|
||||
#include "iperf.h"
|
||||
#include "wifi_cmd.h"
|
||||
#include "iperf_cmd.h"
|
||||
#include "ping_cmd.h"
|
||||
|
||||
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS || CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
|
||||
#include "esp_wifi_he.h"
|
||||
#endif
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
|
||||
extern int wifi_cmd_get_tx_statistics(int argc, char **argv);
|
||||
extern int wifi_cmd_clr_tx_statistics(int argc, char **argv);
|
||||
#endif
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
|
||||
extern int wifi_cmd_get_rx_statistics(int argc, char **argv);
|
||||
extern int wifi_cmd_clr_rx_statistics(int argc, char **argv);
|
||||
#endif
|
||||
|
||||
#if defined(CONFIG_ESP_EXT_CONN_ENABLE) && defined(CONFIG_ESP_HOST_WIFI_ENABLED)
|
||||
#include "esp_extconn.h"
|
||||
#endif
|
||||
|
||||
void iperf_hook_show_wifi_stats(iperf_traffic_type_t type, iperf_status_t status)
|
||||
{
|
||||
if (status == IPERF_STARTED) {
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
|
||||
if (type != IPERF_UDP_SERVER) {
|
||||
wifi_cmd_clr_tx_statistics(0, NULL);
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
|
||||
if (type != IPERF_UDP_CLIENT) {
|
||||
wifi_cmd_clr_rx_statistics(0, NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (status == IPERF_STOPPED) {
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
|
||||
if (type != IPERF_UDP_SERVER) {
|
||||
wifi_cmd_get_tx_statistics(0, NULL);
|
||||
}
|
||||
#endif
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
|
||||
if (type != IPERF_UDP_CLIENT) {
|
||||
wifi_cmd_get_rx_statistics(0, NULL);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
#if defined(CONFIG_ESP_EXT_CONN_ENABLE) && defined(CONFIG_ESP_HOST_WIFI_ENABLED)
|
||||
esp_extconn_config_t ext_config = ESP_EXTCONN_CONFIG_DEFAULT();
|
||||
esp_extconn_init(&ext_config);
|
||||
#endif
|
||||
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK( ret );
|
||||
|
||||
/* initialise wifi */
|
||||
app_wifi_initialise_config_t config = APP_WIFI_CONFIG_DEFAULT();
|
||||
config.storage = WIFI_STORAGE_RAM;
|
||||
config.ps_type = WIFI_PS_NONE;
|
||||
app_initialise_wifi(&config);
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_RX_MU_STATS
|
||||
esp_wifi_enable_rx_statistics(true, true);
|
||||
#else
|
||||
esp_wifi_enable_rx_statistics(true, false);
|
||||
#endif
|
||||
#endif
|
||||
#if CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS
|
||||
esp_wifi_enable_tx_statistics(ESP_WIFI_ACI_BE, true);
|
||||
#endif
|
||||
|
||||
|
||||
esp_console_repl_t *repl = NULL;
|
||||
esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
|
||||
repl_config.prompt = "iperf>";
|
||||
|
||||
// init console REPL environment
|
||||
#if CONFIG_ESP_CONSOLE_UART
|
||||
esp_console_dev_uart_config_t uart_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_uart(&uart_config, &repl_config, &repl));
|
||||
#elif CONFIG_ESP_CONSOLE_USB_CDC
|
||||
esp_console_dev_usb_cdc_config_t cdc_config = ESP_CONSOLE_DEV_CDC_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_usb_cdc(&cdc_config, &repl_config, &repl));
|
||||
#elif CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG
|
||||
esp_console_dev_usb_serial_jtag_config_t usbjtag_config = ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(&usbjtag_config, &repl_config, &repl));
|
||||
#endif
|
||||
|
||||
/* Register commands */
|
||||
register_system();
|
||||
app_register_all_wifi_commands();
|
||||
app_register_iperf_commands();
|
||||
ping_cmd_register_ping();
|
||||
app_register_iperf_hook_func(iperf_hook_show_wifi_stats);
|
||||
|
||||
|
||||
printf("\n ==================================================\n");
|
||||
printf(" | Steps to test WiFi throughput |\n");
|
||||
printf(" | |\n");
|
||||
printf(" | 1. Print 'help' to gain overview of commands |\n");
|
||||
printf(" | 2. Configure device to station or soft-AP |\n");
|
||||
printf(" | 3. Setup WiFi connection |\n");
|
||||
printf(" | 4. Run iperf to test UDP/TCP RX/TX throughput |\n");
|
||||
printf(" | |\n");
|
||||
printf(" =================================================\n\n");
|
||||
|
||||
// start console REPL
|
||||
ESP_ERROR_CHECK(esp_console_start_repl(repl));
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_HOSTED_SPI_HD_HOST_INTERFACE=y
|
||||
@@ -0,0 +1 @@
|
||||
CONFIG_ESP_HOSTED_UART_HOST_INTERFACE=y
|
||||
@@ -0,0 +1,24 @@
|
||||
CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
|
||||
|
||||
CONFIG_FREERTOS_UNICORE=n
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
CONFIG_ESP_INT_WDT=n
|
||||
CONFIG_ESP_TASK_WDT_EN=n
|
||||
|
||||
CONFIG_LWIP_IRAM_OPTIMIZATION=y
|
||||
CONFIG_LWIP_TCPIP_TASK_PRIO=23
|
||||
|
||||
CONFIG_IPERF_TRAFFIC_TASK_PRIORITY=23
|
||||
CONFIG_IPERF_REPORT_TASK_PRIORITY=24
|
||||
CONFIG_COMPILER_OPTIMIZATION_PERF=y
|
||||
|
||||
CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE=y
|
||||
|
||||
# Enable Network Split
|
||||
CONFIG_ESP_HOSTED_NETWORK_SPLIT_ENABLED=y
|
||||
|
||||
#Enable Host power save
|
||||
CONFIG_ESP_HOSTED_HOST_POWER_SAVE_ENABLED=y
|
||||
CONFIG_ESP_HOSTED_HOST_DEEP_SLEEP_ALLOWED=y
|
||||
#CONFIG_ESP_HOSTED_HOST_WAKEUP_GPIO=6
|
||||
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# ESP32-specific
|
||||
#
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=32
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65534
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=65534
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_40M=y
|
||||
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
|
||||
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# ESP32-C2
|
||||
#
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=14
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=14
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=18
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=16
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=14
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=7200
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=11520
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
||||
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_120=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=120
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_60M=y
|
||||
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
|
||||
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# ESP32C3
|
||||
#
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=20
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=40
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=40
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=32
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=40960
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=40960
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64
|
||||
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=160
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
|
||||
@@ -0,0 +1,43 @@
|
||||
#
|
||||
# ESP32C5-Specific
|
||||
#
|
||||
CONFIG_IDF_ENV_FPGA=n
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=20
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=48
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=48
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=22
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=41760
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=65535
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=48
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=48
|
||||
CONFIG_LWIP_IP_REASS_MAX_PBUFS=15
|
||||
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
|
||||
#
|
||||
# Wi-Fi
|
||||
#
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS=y
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS=y
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_RX_MU_STATS=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_HESIGB=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_MU_CFO=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_CTRL_NDPA=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_CTRL_BFRP=n
|
||||
CONFIG_ESP_WIFI_SLP_IRAM_OPT=n
|
||||
CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION=n
|
||||
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
|
||||
|
||||
CONFIG_IPERF_DEF_TCP_TX_BUFFER_LEN=2880
|
||||
@@ -0,0 +1,28 @@
|
||||
#
|
||||
# ESP32C6-Specific
|
||||
#
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=40
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=60
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=40
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=32
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=51200
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=65535
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64
|
||||
|
||||
CONFIG_LWIP_IP_REASS_MAX_PBUFS=15
|
||||
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS=n
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS=n
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION=y
|
||||
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
|
||||
@@ -0,0 +1,43 @@
|
||||
#
|
||||
# ESP32C61-Specific
|
||||
#
|
||||
CONFIG_IDF_ENV_FPGA=n
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=20
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=40
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=36
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=20
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=25920
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=43200
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=48
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=48
|
||||
CONFIG_LWIP_IP_REASS_MAX_PBUFS=15
|
||||
|
||||
#
|
||||
# Serial flasher config
|
||||
#
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
|
||||
#
|
||||
# Wi-Fi
|
||||
#
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_TX_STATS=y
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_RX_STATS=y
|
||||
CONFIG_ESP_WIFI_ENABLE_WIFI_RX_MU_STATS=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_HESIGB=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_MU_CFO=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_CTRL_NDPA=n
|
||||
CONFIG_ESP_WIFI_ENABLE_DUMP_CTRL_BFRP=n
|
||||
CONFIG_ESP_WIFI_SLP_IRAM_OPT=n
|
||||
CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION=n
|
||||
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
|
||||
|
||||
CONFIG_IPERF_DEF_TCP_TX_BUFFER_LEN=2880
|
||||
@@ -0,0 +1,23 @@
|
||||
#
|
||||
# ESP32-P4 specific
|
||||
#
|
||||
|
||||
CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
|
||||
CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y
|
||||
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=32
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65534
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=65534
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64
|
||||
|
||||
CONFIG_LWIP_TCP_SACK_OUT=y
|
||||
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# ESP32S2-specific
|
||||
#
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=8
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=24
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=24
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=16
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=16
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=17280
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=28000
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=32
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=32
|
||||
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
|
||||
CONFIG_ESP32S2_INSTRUCTION_CACHE_16KB=y
|
||||
CONFIG_ESP32S2_INSTRUCTION_CACHE_LINE_16B=y
|
||||
CONFIG_ESP32S2_INSTRUCTION_CACHE_WRAP=y
|
||||
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
|
||||
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# ESP32S3-specific
|
||||
#
|
||||
|
||||
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=16
|
||||
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=64
|
||||
CONFIG_WIFI_RMT_AMPDU_TX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_TX_BA_WIN=32
|
||||
CONFIG_WIFI_RMT_AMPDU_RX_ENABLED=y
|
||||
CONFIG_WIFI_RMT_RX_BA_WIN=32
|
||||
|
||||
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=65535
|
||||
CONFIG_LWIP_TCP_WND_DEFAULT=65535
|
||||
CONFIG_LWIP_TCP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_UDP_RECVMBOX_SIZE=64
|
||||
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64
|
||||
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
|
||||
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
|
||||
|
||||
CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
|
||||
CONFIG_ESPTOOLPY_FLASHFREQ_80M=y
|
||||
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_WRAP=y
|
||||
@@ -0,0 +1,8 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(bleprph_host_only)
|
||||
@@ -0,0 +1,238 @@
|
||||
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
|
||||
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
|
||||
|
||||
| Supported Co-Processors | ESP32 | ESP32-C Series | ESP32-S Series |
|
||||
| ----------------------- | ----- | -------------- | -------------- |
|
||||
|
||||
# BLE Peripheral Example
|
||||
|
||||
This example uses the UART transport written by application itself. Refer the file [main/uart_driver.c](main/uart_driver.c).
|
||||
|
||||
To write the custom transport in the application, the controller should be disabled and the default uart-transport should be disabled (when the controller is disabled, by default the uart-transport is selected). In order to compile the custom transport in the application, the default uart-transport should be disabled. Refer to the sdkconfig.defaults.
|
||||
|
||||
As an example, the [BLE Peripheral Example Walkthrough](https://github.com/espressif/esp-hosted-mcu/blob/main/examples/host_nimble_bleprph_host_only_uart_hci/tutorial/bleprph_host_only_walkthrough.md) shows an ESP32-P4 connected to a ESP32 over UART. See the Walkthrough for information on hardware setup.
|
||||
|
||||
For more information about the application, refer to the bleprph [README file](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/nimble/bleprph/README.md).
|
||||
To test this demo, any BLE scanner app can be used.
|
||||
|
||||
## How to Use Example
|
||||
|
||||
### Project Configuration for Host
|
||||
Before project configuration and build, be sure to set the correct chip target on both the host and co-processor using:
|
||||
|
||||
```bash
|
||||
idf.py set-target <chip_name>
|
||||
```
|
||||
|
||||
### Project Configuration for Host
|
||||
|
||||
Open the project configuration menu:
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In the `Component config-> Bluetooth` menu:
|
||||
|
||||
* Select `controller` to Disabled.
|
||||
* Disable `Nimble Options-> Host-controller Transport -> Enable Uart Transport`.
|
||||
|
||||
>[!Important]
|
||||
> Co-processor selection is done by wifi-remote. Ensure the correct co-processor chip is selected in `Component config` -> `Wi-Fi Remote` -> `choose slave target`. The target selected will affect the ESP-Hosted transport options and default GPIOs used.
|
||||
|
||||
### Setup and Configuration for Co-processor
|
||||
|
||||
On the co-processor, UART HCI setup is done through the Bluetooth Component kconfig settings. In menuconfig, select `Component config` -> `Bluetooth` -> `Controller Options` -> `HCI mode` or `HCI Config` and set it to `UART(H4)`.
|
||||
|
||||
Depending on the selected co-processor, you can configure various UART parameters (Tx, Rx pins, hardware flow control, RTS, CTS pins, baudrate) through the Bluetooth Component. Other UART parameters not handled by the Bluetooth Component are configured by ESP-Hosted through `Example Configuration` -> `HCI UART Settings`.
|
||||
|
||||
> [!NOTE]
|
||||
> Make sure the UART GPIO pins selected do not conflict with the GPIO
|
||||
> pins used for the selected ESP-Hosted transport.
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the project on both the host and co-processor.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
Console output when `host_nimble_bleprph_host_only_uart_hci` is running on ESP32-P4 and using ESP32 as the BT controller:
|
||||
|
||||
```
|
||||
I (25) boot: ESP-IDF v5.5-dev-1868-g39f34a65a9-dirty 2nd stage bootloader
|
||||
I (26) boot: compile time Feb 13 2025 17:15:22
|
||||
I (26) boot: Multicore bootloader
|
||||
I (29) boot: chip revision: v1.0
|
||||
I (31) boot: efuse block revision: v0.1
|
||||
I (34) boot.esp32p4: SPI Speed : 80MHz
|
||||
I (38) boot.esp32p4: SPI Mode : DIO
|
||||
I (42) boot.esp32p4: SPI Flash Size : 2MB
|
||||
I (46) boot: Enabling RNG early entropy source...
|
||||
I (50) boot: Partition Table:
|
||||
I (53) boot: ## Label Usage Type ST Offset Length
|
||||
I (59) boot: 0 nvs WiFi data 01 02 00009000 00006000
|
||||
I (66) boot: 1 phy_init RF data 01 01 0000f000 00001000
|
||||
I (72) boot: 2 factory factory app 00 00 00010000 00100000
|
||||
I (80) boot: End of partition table
|
||||
I (82) esp_image: segment 0: paddr=00010020 vaddr=40070020 size=252a0h (152224) map
|
||||
I (116) esp_image: segment 1: paddr=000352c8 vaddr=30100000 size=00044h ( 68) load
|
||||
I (118) esp_image: segment 2: paddr=00035314 vaddr=4ff00000 size=0ad04h ( 44292) load
|
||||
I (130) esp_image: segment 3: paddr=00040020 vaddr=40000020 size=68930h (428336) map
|
||||
I (202) esp_image: segment 4: paddr=000a8958 vaddr=4ff0ad04 size=069e0h ( 27104) load
|
||||
I (209) esp_image: segment 5: paddr=000af340 vaddr=4ff11700 size=026d8h ( 9944) load
|
||||
I (217) boot: Loaded app from partition at offset 0x10000
|
||||
I (217) boot: Disabling RNG early entropy source...
|
||||
I (228) cpu_start: Multicore app
|
||||
I (238) cpu_start: Pro cpu start user code
|
||||
I (239) cpu_start: cpu freq: 360000000 Hz
|
||||
I (239) app_init: Application information:
|
||||
I (239) app_init: Project name: bleprph_host_only
|
||||
I (243) app_init: App version: 6eaa9b1
|
||||
I (247) app_init: Compile time: Feb 13 2025 17:15:16
|
||||
I (252) app_init: ELF file SHA256: 8861453bb...
|
||||
I (257) app_init: ESP-IDF: v5.5-dev-1868-g39f34a65a9-dirty
|
||||
I (263) efuse_init: Min chip rev: v0.1
|
||||
I (266) efuse_init: Max chip rev: v1.99
|
||||
I (270) efuse_init: Chip rev: v1.0
|
||||
I (274) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (281) heap_init: At 4FF17130 len 00023E90 (143 KiB): RAM
|
||||
I (286) heap_init: At 4FF3AFC0 len 00004BF0 (18 KiB): RAM
|
||||
I (291) heap_init: At 4FF40000 len 00060000 (384 KiB): RAM
|
||||
I (296) heap_init: At 50108080 len 00007F80 (31 KiB): RTCRAM
|
||||
I (301) heap_init: At 30100044 len 00001FBC (7 KiB): TCM
|
||||
I (307) spi_flash: detected chip: generic
|
||||
I (310) spi_flash: flash io: dio
|
||||
W (313) spi_flash: Detected size(16384k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
|
||||
I (326) host_init: ESP Hosted : Host chip_ip[18]
|
||||
I (357) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
|
||||
sdio_mempool_create free:177280 min-free:177280 lfb-def:131072 lfb-8bit:131072
|
||||
|
||||
I (361) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (369) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (378) gpio: GPIO[14]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (386) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (394) gpio: GPIO[16]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (403) gpio: GPIO[17]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (411) H_API: ** add_esp_wifi_remote_channels **
|
||||
I (415) transport: Add ESP-Hosted channel IF[1]: S[0] Tx[0x4000f988] Rx[0x4001ddee]
|
||||
--- 0x4000f988: transport_drv_sta_tx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_nimble_bleprph_host_only_uart_hci/components/esp_hosted/host/drivers/transport/transport_drv.c:208
|
||||
0x4001ddee: esp_wifi_remote_channel_rx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_nimble_bleprph_host_only_uart_hci/managed_components/espressif__esp_wifi_remote/esp_wifi_remote_net.c:19
|
||||
|
||||
I (423) transport: Add ESP-Hosted channel IF[2]: S[0] Tx[0x4000f8d0] Rx[0x4001ddee]
|
||||
--- 0x4000f8d0: transport_drv_ap_tx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_nimble_bleprph_host_only_uart_hci/components/esp_hosted/host/drivers/transport/transport_drv.c:238
|
||||
0x4001ddee: esp_wifi_remote_channel_rx at /home/kysoh/projects/gitlab_esp_hosted_mcu/examples/host_nimble_bleprph_host_only_uart_hci/managed_components/espressif__esp_wifi_remote/esp_wifi_remote_net.c:19
|
||||
|
||||
I (431) main_task: Started on CPU0
|
||||
I (441) main_task: Calling app_main()
|
||||
I (451) NimBLE_BLE_PRPH: BLE Host Task Started
|
||||
I (451) uart: queue free spaces: 8
|
||||
I (471) NimBLE: GAP procedure initiated: stop advertising.
|
||||
|
||||
<EFBFBD>I (501) main_task: Returned from app_main()
|
||||
I (501) NimBLE: ogf=0x08, ocf=0x004e, hci_err=0x201 : BLE_ERR_UNKNOWN_HCI_CMD (Unknown HCI Command)
|
||||
|
||||
I (501) NimBLE: Device Address:
|
||||
I (511) NimBLE: 10:97:bd:d5:8a:62
|
||||
I (511) NimBLE:
|
||||
|
||||
I (521) NimBLE: GAP procedure initiated: advertise;
|
||||
I (521) NimBLE: disc_mode=2
|
||||
I (521) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
|
||||
I (531) NimBLE:
|
||||
```
|
||||
|
||||
Console output on ESP32 as the BT controller:
|
||||
|
||||
```
|
||||
I (29) boot: ESP-IDF v5.5-dev-1868-g39f34a65a9-dirty 2nd stage bootloader
|
||||
I (29) boot: compile time Feb 13 2025 17:15:30
|
||||
I (29) boot: Multicore bootloader
|
||||
I (33) boot: chip revision: v3.0
|
||||
I (36) boot.esp32: SPI Speed : 40MHz
|
||||
I (39) boot.esp32: SPI Mode : DIO
|
||||
I (43) boot.esp32: SPI Flash Size : 4MB
|
||||
I (46) boot: Enabling RNG early entropy source...
|
||||
I (51) boot: Partition Table:
|
||||
I (53) boot: ## Label Usage Type ST Offset Length
|
||||
I (60) boot: 0 nvs WiFi data 01 02 00009000 00004000
|
||||
I (66) boot: 1 otadata OTA data 01 00 0000d000 00002000
|
||||
I (73) boot: 2 phy_init RF data 01 01 0000f000 00001000
|
||||
I (79) boot: 3 ota_0 OTA app 00 10 00010000 001c0000
|
||||
I (86) boot: 4 ota_1 OTA app 00 11 001d0000 001c0000
|
||||
I (92) boot: End of partition table
|
||||
I (96) boot: No factory image, trying OTA 0
|
||||
I (100) esp_image: segment 0: paddr=00010020 vaddr=3f400020 size=285e0h (165344) map
|
||||
I (164) esp_image: segment 1: paddr=00038608 vaddr=3ff80000 size=0001ch ( 28) load
|
||||
I (164) esp_image: segment 2: paddr=0003862c vaddr=3ffbdb60 size=05da4h ( 23972) load
|
||||
I (176) esp_image: segment 3: paddr=0003e3d8 vaddr=40080000 size=01c40h ( 7232) load
|
||||
I (180) esp_image: segment 4: paddr=00040020 vaddr=400d0020 size=9c69ch (640668) map
|
||||
I (401) esp_image: segment 5: paddr=000dc6c4 vaddr=40081c40 size=1dc1ch (121884) load
|
||||
I (465) boot: Loaded app from partition at offset 0x10000
|
||||
I (503) boot: Set actual ota_seq=1 in otadata[0]
|
||||
I (503) boot: Disabling RNG early entropy source...
|
||||
I (513) cpu_start: Multicore app
|
||||
I (521) cpu_start: Pro cpu start user code
|
||||
I (521) cpu_start: cpu freq: 240000000 Hz
|
||||
I (521) app_init: Application information:
|
||||
I (521) app_init: Project name: network_adapter
|
||||
I (526) app_init: App version: 6eaa9b1
|
||||
I (530) app_init: Compile time: Feb 13 2025 17:15:24
|
||||
I (535) app_init: ELF file SHA256: b3b6ed47b...
|
||||
I (539) app_init: ESP-IDF: v5.5-dev-1868-g39f34a65a9-dirty
|
||||
I (545) efuse_init: Min chip rev: v0.0
|
||||
I (549) efuse_init: Max chip rev: v3.99
|
||||
I (553) efuse_init: Chip rev: v3.0
|
||||
I (557) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (563) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM
|
||||
I (568) heap_init: At 3FFB6388 len 00001C78 (7 KiB): DRAM
|
||||
I (573) heap_init: At 3FFB9A20 len 00004108 (16 KiB): DRAM
|
||||
I (578) heap_init: At 3FFC8F18 len 000170E8 (92 KiB): DRAM
|
||||
I (584) heap_init: At 3FFE0440 len 00003AE0 (14 KiB): D/IRAM
|
||||
I (589) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM
|
||||
I (594) heap_init: At 4009F85C len 000007A4 (1 KiB): IRAM
|
||||
I (601) spi_flash: detected chip: generic
|
||||
I (603) spi_flash: flash io: dio
|
||||
W (606) spi_flash: Detected size(8192k) larger than the size in the binary image header(4096k). Using the size in the binary image header.
|
||||
I (620) coexist: coex firmware version: e727207
|
||||
I (624) main_task: Started on CPU0
|
||||
I (626) main_task: Calling app_main()
|
||||
I (628) fg_mcu_slave: *********************************************************************
|
||||
I (634) fg_mcu_slave: ESP-Hosted-MCU Slave FW version :: 1.1.3
|
||||
|
||||
I (642) fg_mcu_slave: Transport used :: SPI + UART
|
||||
I (648) fg_mcu_slave: *********************************************************************
|
||||
I (654) fg_mcu_slave: Supported features are:
|
||||
I (656) fg_mcu_slave: - WLAN over SPI
|
||||
I (660) h_bt: - BT/BLE
|
||||
I (662) h_bt: - HCI Over UART
|
||||
I (664) h_bt: - BT/BLE dual mode
|
||||
I (666) fg_mcu_slave: capabilities: 0xba
|
||||
I (670) fg_mcu_slave: Supported extended features are:
|
||||
I (674) h_bt: - BT/BLE (extended)
|
||||
I (676) fg_mcu_slave: extended capabilities: 0x0
|
||||
I (686) h_bt: ESP Bluetooth MAC addr: 10:97:bd:d5:8a:62
|
||||
I (686) bt_uart: UART1 Pins: TX 5, RX 18, RTS 19, CTS 23 Baudrate:921600
|
||||
I (688) BTDM_INIT: BT controller compile version [194dd63]
|
||||
I (694) BTDM_INIT: Bluetooth MAC: 10:97:bd:d5:8a:62
|
||||
I (698) phy_init: phy_version 4840,02e0d70,Sep 2 2024,19:39:07
|
||||
I (1006) SPI_DRIVER: Using SPI interface
|
||||
I (1008) gpio: GPIO[26]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (1008) gpio: GPIO[4]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (1014) SPI_DRIVER: SPI Ctrl:1 mode: 2, Freq:ConfigAtHost
|
||||
GPIOs: MOSI: 13, MISO: 12, CS: 15, CLK: 14 HS: 26 DR: 4
|
||||
|
||||
I (1022) SPI_DRIVER: Hosted SPI queue size: Tx:10 Rx:10
|
||||
I (1026) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (1032) gpio: GPIO[15]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (1042) SPI_DRIVER: Slave chip Id[12]
|
||||
I (1042) fg_mcu_slave: Initial set up done
|
||||
I (1046) slave_ctrl: event ESPInit
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-hosted-mcu/issues) on GitHub. We will get back to you soon.
|
||||
@@ -0,0 +1,6 @@
|
||||
set(srcs "main.c"
|
||||
"gatt_svr.c"
|
||||
"uart_driver.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,144 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
menu "Uart Configuration"
|
||||
|
||||
config EXAMPLE_HCI_UART_BAUDRATE
|
||||
int "UART Baudrate for HCI"
|
||||
range 115200 921600
|
||||
default 921600
|
||||
help
|
||||
UART Baudrate for HCI. Please use standard baudrate.
|
||||
|
||||
config EXAMPLE_HCI_UART_TX_PIN
|
||||
int "UART Tx Pin"
|
||||
default 24 if IDF_TARGET_ESP32P4
|
||||
default 4
|
||||
help
|
||||
UART HCI Tx pin
|
||||
|
||||
config EXAMPLE_HCI_UART_RX_PIN
|
||||
int "UART Rx Pin"
|
||||
default 33 if IDF_TARGET_ESP32P4
|
||||
default 5
|
||||
help
|
||||
UART HCI Rx pin
|
||||
|
||||
choice EXAMPLE_USE_HCI_UART_FLOW_CTRL
|
||||
prompt "Uart Flow Control"
|
||||
default EXAMPLE_UART_HW_FLOWCTRL_DISABLE
|
||||
help
|
||||
Uart Flow Control
|
||||
|
||||
config EXAMPLE_UART_HW_FLOWCTRL_DISABLE
|
||||
bool "Disable"
|
||||
config EXAMPLE_UART_HW_FLOWCTRL_CTS_RTS
|
||||
bool "Enable hardware flow control"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_HCI_UART_FLOW_CTRL
|
||||
int
|
||||
default 0 if EXAMPLE_UART_HW_FLOWCTRL_DISABLE
|
||||
default 3 if EXAMPLE_UART_HW_FLOWCTRL_CTS_RTS
|
||||
|
||||
config EXAMPLE_HCI_UART_RTS_PIN
|
||||
int "UART Rts Pin"
|
||||
default 20 if IDF_TARGET_ESP32P4
|
||||
default 19
|
||||
help
|
||||
UART HCI RTS pin
|
||||
|
||||
config EXAMPLE_HCI_UART_CTS_PIN
|
||||
int "UART Cts Pin"
|
||||
default 21 if IDF_TARGET_ESP32P4
|
||||
default 23
|
||||
help
|
||||
UART HCI CTS pin
|
||||
|
||||
config EXAMPLE_HCI_UART_INVERT_RTS
|
||||
depends on EXAMPLE_UART_HW_FLOWCTRL_CTS_RTS
|
||||
bool "Invert RTS Signal"
|
||||
default y if SLAVE_IDF_TARGET_ESP32 || SLAVE_IDF_TARGET_ESP32S3 || SLAVE_IDF_TARGET_ESP32C3
|
||||
default n
|
||||
help
|
||||
RTS signal may need to be inverted to work with some ESP chips (ESP32, ESP32-C3, ESP32-S3)
|
||||
|
||||
endmenu
|
||||
|
||||
choice EXAMPLE_USE_IO_TYPE
|
||||
prompt "I/O Capability"
|
||||
default BLE_SM_IO_CAP_NO_IO
|
||||
help
|
||||
I/O capability of device.
|
||||
|
||||
config BLE_SM_IO_CAP_DISP_ONLY
|
||||
bool "DISPLAY ONLY"
|
||||
config BLE_SM_IO_CAP_DISP_YES_NO
|
||||
bool "DISPLAY YESNO"
|
||||
config BLE_SM_IO_CAP_KEYBOARD_ONLY
|
||||
bool "KEYBOARD ONLY"
|
||||
config BLE_SM_IO_CAP_NO_IO
|
||||
bool "Just works"
|
||||
config BLE_SM_IO_CAP_KEYBOARD_DISP
|
||||
bool "Both KEYBOARD & DISPLAY"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_IO_TYPE
|
||||
int
|
||||
default 0 if BLE_SM_IO_CAP_DISP_ONLY
|
||||
default 1 if BLE_SM_IO_CAP_DISP_YES_NO
|
||||
default 2 if BLE_SM_IO_CAP_KEYBOARD_ONLY
|
||||
default 3 if BLE_SM_IO_CAP_NO_IO
|
||||
default 4 if BLE_SM_IO_CAP_KEYBOARD_DISP
|
||||
|
||||
config EXAMPLE_BONDING
|
||||
bool
|
||||
default n
|
||||
prompt "Use Bonding"
|
||||
help
|
||||
Use this option to enable/disable bonding.
|
||||
|
||||
config EXAMPLE_MITM
|
||||
bool
|
||||
default n
|
||||
prompt "MITM security"
|
||||
help
|
||||
Use this option to enable/disable MITM security.
|
||||
|
||||
config EXAMPLE_USE_SC
|
||||
bool
|
||||
depends on BT_NIMBLE_SM_SC
|
||||
default n
|
||||
prompt "Use Secure Connection feature"
|
||||
help
|
||||
Use this option to enable/disable Security Manager Secure Connection 4.2 feature.
|
||||
|
||||
config EXAMPLE_EXTENDED_ADV
|
||||
bool
|
||||
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
|
||||
default y if SOC_ESP_NIMBLE_CONTROLLER
|
||||
select BT_NIMBLE_EXT_ADV
|
||||
prompt "Enable Extended Adv"
|
||||
help
|
||||
Use this option to enable extended advertising in the example.
|
||||
If this option is disabled, ensure config BT_NIMBLE_EXT_ADV is
|
||||
also disabled from Nimble stack menuconfig
|
||||
|
||||
config EXAMPLE_RANDOM_ADDR
|
||||
bool
|
||||
prompt "Advertise RANDOM Address"
|
||||
help
|
||||
Use this option to advertise a random address instead of public address
|
||||
|
||||
config EXAMPLE_ENCRYPTION
|
||||
bool
|
||||
prompt "Enable Link Encryption"
|
||||
help
|
||||
This adds Encrypted Read and Write permissions in the custom GATT server.
|
||||
|
||||
config EXAMPLE_RESOLVE_PEER_ADDR
|
||||
bool
|
||||
prompt "Enable resolving peer address"
|
||||
help
|
||||
Use this option to enable resolving peer's address.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#ifndef H_BLEPRPH_
|
||||
#define H_BLEPRPH_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "nimble/ble.h"
|
||||
#include "modlog/modlog.h"
|
||||
#include "esp_peripheral.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ble_hs_cfg;
|
||||
struct ble_gatt_register_ctxt;
|
||||
|
||||
/** GATT server. */
|
||||
#define GATT_SVR_SVC_ALERT_UUID 0x1811
|
||||
#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
|
||||
#define GATT_SVR_CHR_NEW_ALERT 0x2A46
|
||||
#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
|
||||
#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45
|
||||
#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44
|
||||
|
||||
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
|
||||
int gatt_svr_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_uuid.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include "bleprph.h"
|
||||
#include "services/ans/ble_svc_ans.h"
|
||||
|
||||
/*** Maximum number of characteristics with the notify flag ***/
|
||||
#define MAX_NOTIFY 5
|
||||
|
||||
static const ble_uuid128_t gatt_svr_svc_uuid =
|
||||
BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
|
||||
0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);
|
||||
|
||||
/* A characteristic that can be subscribed to */
|
||||
static uint8_t gatt_svr_chr_val;
|
||||
static uint16_t gatt_svr_chr_val_handle;
|
||||
static const ble_uuid128_t gatt_svr_chr_uuid =
|
||||
BLE_UUID128_INIT(0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11,
|
||||
0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33);
|
||||
|
||||
/* A custom descriptor */
|
||||
static uint8_t gatt_svr_dsc_val;
|
||||
static const ble_uuid128_t gatt_svr_dsc_uuid =
|
||||
BLE_UUID128_INIT(0x01, 0x01, 0x01, 0x01, 0x12, 0x12, 0x12, 0x12,
|
||||
0x23, 0x23, 0x23, 0x23, 0x34, 0x34, 0x34, 0x34);
|
||||
|
||||
static int
|
||||
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt,
|
||||
void *arg);
|
||||
|
||||
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
|
||||
{
|
||||
/*** Service ***/
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = &gatt_svr_svc_uuid.u,
|
||||
.characteristics = (struct ble_gatt_chr_def[])
|
||||
{ {
|
||||
/*** This characteristic can be subscribed to by writing 0x00 and 0x01 to the CCCD ***/
|
||||
.uuid = &gatt_svr_chr_uuid.u,
|
||||
.access_cb = gatt_svc_access,
|
||||
#if CONFIG_EXAMPLE_ENCRYPTION
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE |
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC |
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
|
||||
#else
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
|
||||
#endif
|
||||
.val_handle = &gatt_svr_chr_val_handle,
|
||||
.descriptors = (struct ble_gatt_dsc_def[])
|
||||
{ {
|
||||
.uuid = &gatt_svr_dsc_uuid.u,
|
||||
#if CONFIG_EXAMPLE_ENCRYPTION
|
||||
.att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_ENC,
|
||||
#else
|
||||
.att_flags = BLE_ATT_F_READ,
|
||||
#endif
|
||||
.access_cb = gatt_svc_access,
|
||||
}, {
|
||||
0, /* No more descriptors in this characteristic */
|
||||
}
|
||||
},
|
||||
}, {
|
||||
0, /* No more characteristics in this service. */
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
0, /* No more services. */
|
||||
},
|
||||
};
|
||||
|
||||
static int
|
||||
gatt_svr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
|
||||
void *dst, uint16_t *len)
|
||||
{
|
||||
uint16_t om_len;
|
||||
int rc;
|
||||
|
||||
om_len = OS_MBUF_PKTLEN(om);
|
||||
if (om_len < min_len || om_len > max_len) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
|
||||
if (rc != 0) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback whenever a characteristic/descriptor is read or written to.
|
||||
* Here reads and writes need to be handled.
|
||||
* ctxt->op tells weather the operation is read or write and
|
||||
* weather it is on a characteristic or descriptor,
|
||||
* ctxt->dsc->uuid tells which characteristic/descriptor is accessed.
|
||||
* attr_handle give the value handle of the attribute being accessed.
|
||||
* Accordingly do:
|
||||
* Append the value to ctxt->om if the operation is READ
|
||||
* Write ctxt->om to the value if the operation is WRITE
|
||||
**/
|
||||
static int
|
||||
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
const ble_uuid_t *uuid;
|
||||
int rc;
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_ACCESS_OP_READ_CHR:
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
MODLOG_DFLT(INFO, "Characteristic read; conn_handle=%d attr_handle=%d\n",
|
||||
conn_handle, attr_handle);
|
||||
} else {
|
||||
MODLOG_DFLT(INFO, "Characteristic read by NimBLE stack; attr_handle=%d\n",
|
||||
attr_handle);
|
||||
}
|
||||
uuid = ctxt->chr->uuid;
|
||||
if (attr_handle == gatt_svr_chr_val_handle) {
|
||||
rc = os_mbuf_append(ctxt->om,
|
||||
&gatt_svr_chr_val,
|
||||
sizeof(gatt_svr_chr_val));
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
goto unknown;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_WRITE_CHR:
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
MODLOG_DFLT(INFO, "Characteristic write; conn_handle=%d attr_handle=%d",
|
||||
conn_handle, attr_handle);
|
||||
} else {
|
||||
MODLOG_DFLT(INFO, "Characteristic write by NimBLE stack; attr_handle=%d",
|
||||
attr_handle);
|
||||
}
|
||||
uuid = ctxt->chr->uuid;
|
||||
if (attr_handle == gatt_svr_chr_val_handle) {
|
||||
rc = gatt_svr_write(ctxt->om,
|
||||
sizeof(gatt_svr_chr_val),
|
||||
sizeof(gatt_svr_chr_val),
|
||||
&gatt_svr_chr_val, NULL);
|
||||
ble_gatts_chr_updated(attr_handle);
|
||||
MODLOG_DFLT(INFO, "Notification/Indication scheduled for "
|
||||
"all subscribed peers.\n");
|
||||
return rc;
|
||||
}
|
||||
goto unknown;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_READ_DSC:
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
MODLOG_DFLT(INFO, "Descriptor read; conn_handle=%d attr_handle=%d\n",
|
||||
conn_handle, attr_handle);
|
||||
} else {
|
||||
MODLOG_DFLT(INFO, "Descriptor read by NimBLE stack; attr_handle=%d\n",
|
||||
attr_handle);
|
||||
}
|
||||
uuid = ctxt->dsc->uuid;
|
||||
if (ble_uuid_cmp(uuid, &gatt_svr_dsc_uuid.u) == 0) {
|
||||
rc = os_mbuf_append(ctxt->om,
|
||||
&gatt_svr_dsc_val,
|
||||
sizeof(gatt_svr_chr_val));
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
goto unknown;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_WRITE_DSC:
|
||||
goto unknown;
|
||||
|
||||
default:
|
||||
goto unknown;
|
||||
}
|
||||
|
||||
unknown:
|
||||
/* Unknown characteristic/descriptor;
|
||||
* The NimBLE host should not have called this function;
|
||||
*/
|
||||
assert(0);
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
void
|
||||
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
|
||||
{
|
||||
char buf[BLE_UUID_STR_LEN];
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_REGISTER_OP_SVC:
|
||||
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
|
||||
ctxt->svc.handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_CHR:
|
||||
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
|
||||
"def_handle=%d val_handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
|
||||
ctxt->chr.def_handle,
|
||||
ctxt->chr.val_handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_DSC:
|
||||
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
|
||||
ctxt->dsc.handle);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
gatt_svr_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
ble_svc_ans_init();
|
||||
|
||||
rc = ble_gatts_count_cfg(gatt_svr_svcs);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ble_gatts_add_svcs(gatt_svr_svcs);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Setting a value for the read-only descriptor */
|
||||
gatt_svr_dsc_val = 0x99;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
dependencies:
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10'
|
||||
nimble_peripheral_utils:
|
||||
path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils
|
||||
@@ -0,0 +1,569 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/util/util.h"
|
||||
#include "console/console.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "bleprph.h"
|
||||
#include "uart_driver.h"
|
||||
|
||||
#include "esp_hosted.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
static uint8_t ext_adv_pattern_1[] = {
|
||||
0x02, 0x01, 0x06,
|
||||
0x03, 0x03, 0xab, 0xcd,
|
||||
0x03, 0x03, 0x18, 0x11,
|
||||
0x11, 0X09, 'n', 'i', 'm', 'b', 'l', 'e', '-', 'b', 'l', 'e', 'p', 'r', 'p', 'h', '-', 'e',
|
||||
};
|
||||
#endif
|
||||
|
||||
static const char *tag = "NimBLE_BLE_PRPH";
|
||||
static int bleprph_gap_event(struct ble_gap_event *event, void *arg);
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM;
|
||||
#else
|
||||
static uint8_t own_addr_type;
|
||||
#endif
|
||||
|
||||
void ble_store_config_init(void);
|
||||
|
||||
/**
|
||||
* Logs information about a connection to the console.
|
||||
*/
|
||||
static void
|
||||
bleprph_print_conn_desc(struct ble_gap_conn_desc *desc)
|
||||
{
|
||||
MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=",
|
||||
desc->conn_handle, desc->our_ota_addr.type);
|
||||
print_addr(desc->our_ota_addr.val);
|
||||
MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=",
|
||||
desc->our_id_addr.type);
|
||||
print_addr(desc->our_id_addr.val);
|
||||
MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=",
|
||||
desc->peer_ota_addr.type);
|
||||
print_addr(desc->peer_ota_addr.val);
|
||||
MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=",
|
||||
desc->peer_id_addr.type);
|
||||
print_addr(desc->peer_id_addr.val);
|
||||
MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d "
|
||||
"encrypted=%d authenticated=%d bonded=%d\n",
|
||||
desc->conn_itvl, desc->conn_latency,
|
||||
desc->supervision_timeout,
|
||||
desc->sec_state.encrypted,
|
||||
desc->sec_state.authenticated,
|
||||
desc->sec_state.bonded);
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
/**
|
||||
* Enables advertising with the following parameters:
|
||||
* o General discoverable mode.
|
||||
* o Undirected connectable mode.
|
||||
*/
|
||||
static void
|
||||
ext_bleprph_advertise(void)
|
||||
{
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 0;
|
||||
int rc;
|
||||
|
||||
/* First check if any instance is already active */
|
||||
if(ble_gap_ext_adv_active(instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* use defaults for non-set params */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
/* enable connectable advertising */
|
||||
params.connectable = 1;
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
|
||||
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M;
|
||||
//params.tx_power = 127;
|
||||
params.sid = 1;
|
||||
|
||||
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
|
||||
/* configure instance 0 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL,
|
||||
bleprph_gap_event, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
/* in this case only scan response is allowed */
|
||||
|
||||
/* get mbuf for scan rsp data */
|
||||
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
|
||||
assert(data);
|
||||
|
||||
/* fill mbuf with scan rsp data */
|
||||
rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
|
||||
assert(rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* Enables advertising with the following parameters:
|
||||
* o General discoverable mode.
|
||||
* o Undirected connectable mode.
|
||||
*/
|
||||
static void
|
||||
bleprph_advertise(void)
|
||||
{
|
||||
struct ble_gap_adv_params adv_params;
|
||||
struct ble_hs_adv_fields fields;
|
||||
const char *name;
|
||||
int rc;
|
||||
|
||||
/**
|
||||
* Set the advertisement data included in our advertisements:
|
||||
* o Flags (indicates advertisement type and other general info).
|
||||
* o Advertising tx power.
|
||||
* o Device name.
|
||||
* o 16-bit service UUIDs (alert notifications).
|
||||
*/
|
||||
|
||||
memset(&fields, 0, sizeof fields);
|
||||
|
||||
/* Advertise two flags:
|
||||
* o Discoverability in forthcoming advertisement (general)
|
||||
* o BLE-only (BR/EDR unsupported).
|
||||
*/
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN |
|
||||
BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
|
||||
/* Indicate that the TX power level field should be included; have the
|
||||
* stack fill this value automatically. This is done by assigning the
|
||||
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
|
||||
*/
|
||||
fields.tx_pwr_lvl_is_present = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
|
||||
name = ble_svc_gap_device_name();
|
||||
fields.name = (uint8_t *)name;
|
||||
fields.name_len = strlen(name);
|
||||
fields.name_is_complete = 1;
|
||||
|
||||
fields.uuids16 = (ble_uuid16_t[]) {
|
||||
BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID)
|
||||
};
|
||||
fields.num_uuids16 = 1;
|
||||
fields.uuids16_is_complete = 1;
|
||||
|
||||
rc = ble_gap_adv_set_fields(&fields);
|
||||
if (rc != 0) {
|
||||
MODLOG_DFLT(ERROR, "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(own_addr_type, NULL, BLE_HS_FOREVER,
|
||||
&adv_params, bleprph_gap_event, NULL);
|
||||
if (rc != 0) {
|
||||
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
static void bleprph_power_control(uint16_t conn_handle)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ble_gap_read_remote_transmit_power_level(conn_handle, 0x01 ); // Attempting on LE 1M phy
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_set_transmit_power_reporting_enable(conn_handle, 0x1, 0x1);
|
||||
assert (rc == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The nimble host executes this callback when a GAP event occurs. The
|
||||
* application associates a GAP event callback with each connection that forms.
|
||||
* bleprph uses the same callback for all connections.
|
||||
*
|
||||
* @param event The type of event being signalled.
|
||||
* @param ctxt Various information pertaining to the event.
|
||||
* @param arg Application-specified argument; unused by
|
||||
* bleprph.
|
||||
*
|
||||
* @return 0 if the application successfully handled the
|
||||
* event; nonzero on failure. The semantics
|
||||
* of the return code is specific to the
|
||||
* particular GAP event being signalled.
|
||||
*/
|
||||
static int
|
||||
bleprph_gap_event(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
struct ble_gap_conn_desc desc;
|
||||
int rc;
|
||||
|
||||
switch (event->type) {
|
||||
#if defined(BLE_GAP_EVENT_LINK_ESTAB)
|
||||
case BLE_GAP_EVENT_LINK_ESTAB:
|
||||
#else
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
#endif
|
||||
/* A new connection was established or a connection attempt failed. */
|
||||
MODLOG_DFLT(INFO, "connection %s; status=%d ",
|
||||
event->connect.status == 0 ? "established" : "failed",
|
||||
event->connect.status);
|
||||
if (event->connect.status == 0) {
|
||||
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
}
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
if (event->connect.status != 0) {
|
||||
/* Connection failed; resume advertising. */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
bleprph_power_control(event->link_estab.conn_handle);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
|
||||
bleprph_print_conn_desc(&event->disconnect.conn);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
/* Connection terminated; resume advertising. */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
/* The central has updated the connection parameters. */
|
||||
MODLOG_DFLT(INFO, "connection updated; status=%d ",
|
||||
event->conn_update.status);
|
||||
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
MODLOG_DFLT(INFO, "advertise complete; reason=%d",
|
||||
event->adv_complete.reason);
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ENC_CHANGE:
|
||||
/* Encryption has been enabled or disabled for this connection. */
|
||||
MODLOG_DFLT(INFO, "encryption change event; status=%d ",
|
||||
event->enc_change.status);
|
||||
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_TX:
|
||||
MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d "
|
||||
"status=%d is_indication=%d",
|
||||
event->notify_tx.conn_handle,
|
||||
event->notify_tx.attr_handle,
|
||||
event->notify_tx.status,
|
||||
event->notify_tx.indication);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d "
|
||||
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
|
||||
event->subscribe.conn_handle,
|
||||
event->subscribe.attr_handle,
|
||||
event->subscribe.reason,
|
||||
event->subscribe.prev_notify,
|
||||
event->subscribe.cur_notify,
|
||||
event->subscribe.prev_indicate,
|
||||
event->subscribe.cur_indicate);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
|
||||
event->mtu.conn_handle,
|
||||
event->mtu.channel_id,
|
||||
event->mtu.value);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_REPEAT_PAIRING:
|
||||
/* We already have a bond with the peer, but it is attempting to
|
||||
* establish a new secure link. This app sacrifices security for
|
||||
* convenience: just throw away the old bond and accept the new link.
|
||||
*/
|
||||
|
||||
/* Delete the old bond. */
|
||||
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
ble_store_util_delete_peer(&desc.peer_id_addr);
|
||||
|
||||
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
|
||||
* continue with the pairing operation.
|
||||
*/
|
||||
return BLE_GAP_REPEAT_PAIRING_RETRY;
|
||||
|
||||
case BLE_GAP_EVENT_PASSKEY_ACTION:
|
||||
ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started");
|
||||
struct ble_sm_io pkey = {0};
|
||||
int key = 0;
|
||||
|
||||
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
|
||||
pkey.action = event->passkey.params.action;
|
||||
pkey.passkey = 123456; // This is the passkey to be entered on peer
|
||||
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
|
||||
ESP_LOGI(tag, "Passkey on device's display: %" PRIu32 , event->passkey.params.numcmp);
|
||||
ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N");
|
||||
pkey.action = event->passkey.params.action;
|
||||
if (scli_receive_key(&key)) {
|
||||
pkey.numcmp_accept = key;
|
||||
} else {
|
||||
pkey.numcmp_accept = 0;
|
||||
ESP_LOGE(tag, "Timeout! Rejecting the key");
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
|
||||
static uint8_t tem_oob[16] = {0};
|
||||
pkey.action = event->passkey.params.action;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
pkey.oob[i] = tem_oob[i];
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
|
||||
ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456");
|
||||
pkey.action = event->passkey.params.action;
|
||||
if (scli_receive_key(&key)) {
|
||||
pkey.passkey = key;
|
||||
} else {
|
||||
pkey.passkey = 0;
|
||||
ESP_LOGE(tag, "Timeout! Passing 0 as the key");
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
}
|
||||
return 0;
|
||||
|
||||
#if MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
case BLE_GAP_EVENT_TRANSMIT_POWER:
|
||||
MODLOG_DFLT(INFO, "Transmit power event : status=%d conn_handle=%d reason=%d "
|
||||
"phy=%d power_level=%x power_level_flag=%d delta=%d",
|
||||
event->transmit_power.status,
|
||||
event->transmit_power.conn_handle,
|
||||
event->transmit_power.reason,
|
||||
event->transmit_power.phy,
|
||||
event->transmit_power.transmit_power_level,
|
||||
event->transmit_power.transmit_power_level_flag,
|
||||
event->transmit_power.delta);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_PATHLOSS_THRESHOLD:
|
||||
MODLOG_DFLT(INFO, "Pathloss threshold event : conn_handle=%d current path loss=%d "
|
||||
"zone_entered =%d",
|
||||
event->pathloss_threshold.conn_handle,
|
||||
event->pathloss_threshold.current_path_loss,
|
||||
event->pathloss_threshold.zone_entered);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
bleprph_on_reset(int reason)
|
||||
{
|
||||
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static void
|
||||
ble_app_set_addr(void)
|
||||
{
|
||||
ble_addr_t addr;
|
||||
int rc;
|
||||
|
||||
/* generate new non-resolvable private address */
|
||||
rc = ble_hs_id_gen_rnd(0, &addr);
|
||||
assert(rc == 0);
|
||||
|
||||
/* set generated address */
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
|
||||
assert(rc == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
bleprph_on_sync(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
/* Generate a non-resolvable private address. */
|
||||
ble_app_set_addr();
|
||||
#endif
|
||||
|
||||
/* Make sure we have proper identity address set (public preferred) */
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
rc = ble_hs_util_ensure_addr(1);
|
||||
#else
|
||||
rc = ble_hs_util_ensure_addr(0);
|
||||
#endif
|
||||
assert(rc == 0);
|
||||
|
||||
/* Figure out address to use while advertising (no privacy for now) */
|
||||
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
||||
if (rc != 0) {
|
||||
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Printing ADDR */
|
||||
uint8_t addr_val[6] = {0};
|
||||
rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
|
||||
|
||||
MODLOG_DFLT(INFO, "Device Address: ");
|
||||
print_addr(addr_val);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
/* Begin advertising. */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
}
|
||||
|
||||
void bleprph_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(tag, "BLE Host Task Started");
|
||||
/* This function will return only when nimble_port_stop() is executed */
|
||||
nimble_port_run();
|
||||
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
void
|
||||
app_main(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// initialise connection to co-processor
|
||||
esp_hosted_connect_to_slave();
|
||||
|
||||
// init bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_init()) {
|
||||
ESP_LOGW("INFO", "failed to init bt controller");
|
||||
}
|
||||
|
||||
// enable bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_enable()) {
|
||||
ESP_LOGW("INFO", "failed to enable bt controller");
|
||||
}
|
||||
|
||||
hci_uart_open();
|
||||
|
||||
ret = nimble_port_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = bleprph_on_reset;
|
||||
ble_hs_cfg.sync_cb = bleprph_on_sync;
|
||||
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE;
|
||||
#ifdef CONFIG_EXAMPLE_BONDING
|
||||
ble_hs_cfg.sm_bonding = 1;
|
||||
/* Enable the appropriate bit masks to make sure the keys
|
||||
* that are needed are exchanged
|
||||
*/
|
||||
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
|
||||
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_MITM
|
||||
ble_hs_cfg.sm_mitm = 1;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_USE_SC
|
||||
ble_hs_cfg.sm_sc = 1;
|
||||
#else
|
||||
ble_hs_cfg.sm_sc = 0;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_RESOLVE_PEER_ADDR
|
||||
/* Stores the IRK */
|
||||
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
|
||||
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
|
||||
#endif
|
||||
|
||||
rc = gatt_svr_init();
|
||||
assert(rc == 0);
|
||||
|
||||
/* Set the default device name. */
|
||||
rc = ble_svc_gap_device_name_set("nimble-bleprph");
|
||||
assert(rc == 0);
|
||||
|
||||
/* XXX Need to have template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(bleprph_host_task);
|
||||
|
||||
/* Initialize command line interface to accept input from user */
|
||||
rc = scli_init();
|
||||
if (rc != ESP_OK) {
|
||||
ESP_LOGE(tag, "scli_init() failed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "driver/uart.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_attr.h"
|
||||
#include "uart_driver.h"
|
||||
#include "nimble/hci_common.h"
|
||||
#include "host/ble_hs_mbuf.h"
|
||||
|
||||
#define TAG "UART_HCI"
|
||||
|
||||
#define UART_NO (1)
|
||||
#define UART_BUF_SZ (1024)
|
||||
|
||||
#define UART_TX_PIN (CONFIG_EXAMPLE_HCI_UART_TX_PIN)
|
||||
#define UART_RX_PIN (CONFIG_EXAMPLE_HCI_UART_RX_PIN)
|
||||
#define UART_RTS_PIN (CONFIG_EXAMPLE_HCI_UART_RTS_PIN)
|
||||
#define UART_CTS_PIN (CONFIG_EXAMPLE_HCI_UART_CTS_PIN)
|
||||
#define HCI_H4_ACL (0x02)
|
||||
#define HCI_H4_CMD (0x01)
|
||||
#define HCI_H4_EVT (0x04)
|
||||
#define BLE_HCI_EVENT_HDR_LEN (2)
|
||||
#define BLE_HCI_CMD_HDR_LEN (3)
|
||||
|
||||
enum {
|
||||
UART_RX_TYPE = 0,
|
||||
UART_RX_LEN,
|
||||
UART_RX_DATA,
|
||||
};
|
||||
|
||||
enum {
|
||||
DATA_TYPE_COMMAND = 1,
|
||||
DATA_TYPE_ACL = 2,
|
||||
DATA_TYPE_EVENT = 4
|
||||
};
|
||||
|
||||
TaskHandle_t s_rx_task_hdl;
|
||||
|
||||
static void IRAM_ATTR hci_uart_rx_task(void *arg)
|
||||
{
|
||||
uint8_t buf[1026];
|
||||
int len_now_read = -1;
|
||||
uint32_t len_to_read = 1;
|
||||
uint32_t len_total_read = 0;
|
||||
uint8_t rx_st = UART_RX_TYPE;
|
||||
|
||||
while (1) {
|
||||
len_now_read = uart_read_bytes(UART_NO, &buf[len_total_read], len_to_read, portMAX_DELAY);
|
||||
assert(len_now_read == len_to_read);
|
||||
len_total_read += len_now_read;
|
||||
|
||||
switch (rx_st) {
|
||||
case UART_RX_TYPE: {
|
||||
assert(buf[0] >= DATA_TYPE_ACL && buf[0] <= DATA_TYPE_EVENT);
|
||||
if (buf[0] == DATA_TYPE_ACL) {
|
||||
len_to_read = 4;
|
||||
} else if (buf[0] == DATA_TYPE_EVENT) {
|
||||
len_to_read = 2;
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
rx_st = UART_RX_LEN;
|
||||
}
|
||||
break;
|
||||
|
||||
case UART_RX_LEN: {
|
||||
if (buf[0] == DATA_TYPE_ACL) {
|
||||
len_to_read = buf[3] | (buf[4] << 8);
|
||||
} else if (buf[0] == DATA_TYPE_EVENT) {
|
||||
len_to_read = buf[2];
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
rx_st = UART_RX_DATA;
|
||||
}
|
||||
break;
|
||||
|
||||
case UART_RX_DATA: {
|
||||
uint8_t *data = buf;
|
||||
int rc;
|
||||
|
||||
if (data[0] == HCI_H4_EVT) {
|
||||
uint8_t *evbuf;
|
||||
int totlen;
|
||||
|
||||
totlen = BLE_HCI_EVENT_HDR_LEN + data[2];
|
||||
assert(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN);
|
||||
|
||||
if (totlen > MYNEWT_VAL(BLE_TRANSPORT_EVT_SIZE)) {
|
||||
ESP_LOGE(TAG, "Received HCI data length at host (%d)"
|
||||
"exceeds maximum configured HCI event buffer size (%d).",
|
||||
totlen, MYNEWT_VAL(BLE_TRANSPORT_EVT_SIZE));
|
||||
break;
|
||||
}
|
||||
|
||||
if (data[1] == BLE_HCI_EVCODE_HW_ERROR) {
|
||||
rx_st = UART_RX_TYPE;
|
||||
len_to_read = 1;
|
||||
len_total_read = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Allocate LE Advertising Report Event from lo pool only */
|
||||
if ((data[1] == BLE_HCI_EVCODE_LE_META) &&
|
||||
(data[3] == BLE_HCI_LE_SUBEV_ADV_RPT || data[3] == BLE_HCI_LE_SUBEV_EXT_ADV_RPT)) {
|
||||
evbuf = ble_transport_alloc_evt(1);
|
||||
/* Skip advertising report if we're out of memory */
|
||||
if (!evbuf) {
|
||||
ESP_LOGE(TAG, "No buffers");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
evbuf = ble_transport_alloc_evt(0);
|
||||
assert(evbuf != NULL);
|
||||
}
|
||||
|
||||
memset(evbuf, 0, sizeof * evbuf);
|
||||
memcpy(evbuf, &data[1], totlen);
|
||||
|
||||
rc = ble_transport_to_hs_evt(evbuf);
|
||||
assert(rc == 0);
|
||||
} else if (data[0] == HCI_H4_ACL) {
|
||||
struct os_mbuf *m = NULL;
|
||||
m = ble_transport_alloc_acl_from_ll();
|
||||
if (!m) {
|
||||
ESP_LOGE(TAG, "No buffers");
|
||||
}
|
||||
|
||||
if ((rc = os_mbuf_append(m, &data[1], len_total_read - 1)) != 0) {
|
||||
ESP_LOGE(TAG, "%s failed to os_mbuf_append; rc = %d", __func__, rc);
|
||||
os_mbuf_free_chain(m);
|
||||
return;
|
||||
}
|
||||
|
||||
ble_transport_to_hs_acl(m);
|
||||
}
|
||||
|
||||
rx_st = UART_RX_TYPE;
|
||||
len_to_read = 1;
|
||||
len_total_read = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
vTaskDelete(NULL);
|
||||
}
|
||||
|
||||
void hci_uart_send(uint8_t *buf, uint16_t len)
|
||||
{
|
||||
uint8_t *p = buf;
|
||||
int len_write = 0;
|
||||
|
||||
while (len) {
|
||||
len_write = uart_write_bytes(UART_NO, p, len);
|
||||
assert(len_write > 0);
|
||||
len -= len_write;
|
||||
p += len_write;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
ble_transport_ll_init(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
ble_transport_ll_deinit(void)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int
|
||||
ble_transport_to_ll_acl_impl(struct os_mbuf *om)
|
||||
{
|
||||
uint8_t buf[OS_MBUF_PKTLEN(om) + 1];
|
||||
int rc;
|
||||
buf[0] = HCI_H4_ACL;
|
||||
rc = ble_hs_mbuf_to_flat(om, buf + 1, OS_MBUF_PKTLEN(om), NULL);
|
||||
if(rc) {
|
||||
ESP_LOGE(TAG, "Error copying data %d", rc);
|
||||
os_mbuf_free_chain(om);
|
||||
return rc;
|
||||
}
|
||||
hci_uart_send(buf, OS_MBUF_PKTLEN(om) + 1);
|
||||
os_mbuf_free_chain(om);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
ble_transport_to_ll_cmd_impl(void *buf)
|
||||
{
|
||||
int len = 3 + ((uint8_t *)buf)[2] + 1;
|
||||
uint8_t data[258];
|
||||
data[0] = HCI_H4_CMD;
|
||||
memcpy(data + 1, buf, len - 1);
|
||||
hci_uart_send(data, len);
|
||||
ble_transport_free(buf);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void hci_uart_open(void)
|
||||
{
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = CONFIG_EXAMPLE_HCI_UART_BAUDRATE,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = CONFIG_EXAMPLE_HCI_UART_FLOW_CTRL,
|
||||
.source_clk = UART_SCLK_DEFAULT,
|
||||
};
|
||||
|
||||
int intr_alloc_flags = 0;
|
||||
#if CONFIG_UART_ISR_IN_IRAM
|
||||
intr_alloc_flags = ESP_INTR_FLAG_IRAM;
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(uart_driver_install(UART_NO, UART_BUF_SZ * 2, UART_BUF_SZ * 2, 0, NULL, intr_alloc_flags));
|
||||
ESP_ERROR_CHECK(uart_param_config(UART_NO, &uart_config));
|
||||
|
||||
#if CONFIG_EXAMPLE_HCI_UART_INVERT_RTS
|
||||
/* to communicate with co-processor with UART hw
|
||||
* flowcontrol, logic of RTS signal must be inverted
|
||||
*/
|
||||
uint32_t invert_mask = 0;
|
||||
invert_mask |= UART_SIGNAL_RTS_INV;
|
||||
|
||||
ESP_ERROR_CHECK(uart_set_line_inverse(UART_NO, invert_mask));
|
||||
#endif
|
||||
|
||||
ESP_ERROR_CHECK(uart_set_pin(UART_NO, UART_TX_PIN, UART_RX_PIN, UART_RTS_PIN, UART_CTS_PIN));
|
||||
|
||||
xTaskCreate(hci_uart_rx_task, "hci_uart_rx_task", 2048, NULL, 12, &s_rx_task_hdl);
|
||||
}
|
||||
|
||||
void hci_uart_close(void)
|
||||
{
|
||||
if (s_rx_task_hdl) {
|
||||
vTaskDelete(s_rx_task_hdl);
|
||||
}
|
||||
uart_driver_delete(UART_NO);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
|
||||
#ifndef __UART_DRIVER_H__
|
||||
#define __UART_DRIVER_H__
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* @brief open HCI transport of uart
|
||||
*/
|
||||
void hci_uart_open(void);
|
||||
|
||||
/**
|
||||
* @brief close HCI transport of uart
|
||||
*/
|
||||
void hci_uart_close(void);
|
||||
|
||||
/**
|
||||
* @brief send data from host to HCI transport
|
||||
*
|
||||
* @param[in] data pointer to data buffer
|
||||
* @param[in] len length of data
|
||||
*/
|
||||
void hci_uart_send(uint8_t *data, uint16_t len);
|
||||
|
||||
#endif /* __UART_DRIVER_H__ */
|
||||
@@ -0,0 +1,13 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_CONTROLLER_DISABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
@@ -0,0 +1,83 @@
|
||||
# BLE Peripheral Example Walkthrough
|
||||
|
||||
## Introduction
|
||||
|
||||
The tutorial is focused on how to implement the custom transport in the application and register it with nimble-host. This example has the exact same working as bleprph except the transport used is custom UART transport written in `"main/uart_driver.c"` file. The controller is disabled for this example, the nimble-host tries to communicate with the external connected controller using UART transport.
|
||||
|
||||
Note: This walkthrough only covers the transport used by the application, for detailed explanation see the [bleprph_walkthrough](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/nimble/bleprph/tutorial/bleprph_walkthrough.md).
|
||||
|
||||
## Hardware Setup for Host
|
||||
|
||||
This example uses the ESP32-P4 chip to run the `host_nimble_bleprph_host_only_uart_hci` example. The example runs the nimble-host and sends and receives the commands and events using uart. The ESP32 co-procesor runs ESP-Hosted slave, with the BT controller configured to use the UART.
|
||||
|
||||
Below is the setup of ESP32-P4 and ESP32.
|
||||
|
||||
<img src="hardware_setup.jpg" alt="ESP32-P4-Function-EV-Board with ESP32, connected via UART" width="600" />
|
||||
|
||||
The ESP32-P4 gpio pins are configurable using `idf.py menuconfig` -> `Example Configuration` -> `Uart Configuration`. The tx pin of one chip connects to the rx pin of the other and vice versa. If you enable `Uart Flow Control`, connect the `CTS` pin on one chip to the `RTS` pin on the other and vice versa.
|
||||
|
||||
## Setup for Co-processor
|
||||
|
||||
UART HCI setup is done through the Bluetooth Component kconfig
|
||||
settings. In menuconfig, select `Component config` -> `Bluetooth` ->
|
||||
`Controller Options` -> `HCI mode` or `HCI Config` and set it to
|
||||
`UART(H4)`.
|
||||
|
||||
Depending on the selected co-processor, you can configure various UART
|
||||
parameters (Tx, Rx pins, hardware flow control, RTS, CTS pins,
|
||||
baudrate) through the Bluetooth Component. Other UART parameters not
|
||||
handled by the Bluetooth Component are configured by ESP-Hosted
|
||||
through `Example Configuration` -> `HCI UART Settings`.
|
||||
|
||||
> [!NOTE]
|
||||
> Make sure the UART GPIO pins selected do not conflict with the GPIO
|
||||
> pins used for the selected ESP-Hosted transport.
|
||||
|
||||
Co-processor setup for HCI over UART can also be found in the ESP-Hosted Bluetooth Design document on how to [configure the co-processor to use UART for HCI](https://github.com/espressif/esp-hosted-mcu/blob/main/docs/bluetooth_design.md#6-configuring-the-co-processor-for-uart-hci).
|
||||
|
||||
|
||||
## GPIO Setup for Host and Co-processor
|
||||
|
||||
Below is the pin connection chart for the ESP32-P4 (as host) and ESP32 (as BT controller).
|
||||
|
||||
| ESP32-P4 Signal | ESP32-P4 GPIO Pin | ESP32 Signal | ESP32 GPIO Pin |
|
||||
|:---------------:|------------------:|:------------:|---------------:|
|
||||
| Rx | 33 | Tx | 5 |
|
||||
| Tx | 24 | Rx | 18 |
|
||||
| RTS | 20 | CTS | 23 |
|
||||
| CTS | 21 | RTS | 19 |
|
||||
|
||||
## Main Entry Point
|
||||
|
||||
The program’s entry point is the app_main() function:
|
||||
|
||||
```c
|
||||
void
|
||||
app_main(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
hci_uart_open();
|
||||
ret = nimble_port_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(tag, "Failed to init nimble %d ", ret);
|
||||
return;
|
||||
}
|
||||
....
|
||||
....
|
||||
}
|
||||
```
|
||||
In the main function `hci_uart_open()` configures the uart with necessary parameters like baud_rate, parity, etc. The file [uart_driver.c](../main/uart_driver.c) has all the uart transport code along with the necessary apis mandated by nimble-host. `ble_transport_to_ll_acl_impl` is the api used to send the data to the controller. and `ble_transport_to_ll_cmd_impl` is used to send the command to the controller. These two apis are implemented as they are necessary. Along with these two apis, any data that is received in the rx direction is redirected to the host using `ble_transport_to_hs_evt` and `ble_transport_to_hs_acl` apis.
|
||||
|
||||
## Conclusion
|
||||
1. This example covered how to write the custom transport layer and use it with nimble-host.
|
||||
2. The example used the esp-hosted network-adapter example flashed on esp32c6 connected through UART to test this demo.
|
||||
3. In the similar fashion the external controller could be connected.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 483 KiB |
@@ -0,0 +1,6 @@
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(bleprph_host_only_vhci)
|
||||
@@ -0,0 +1,205 @@
|
||||
| Supported Hosts | ESP32 | ESP32-P Series | ESP32-H Series | ESP32-C Series | ESP32-S Series | Any other MCU hosts |
|
||||
| --------------- | ----- | -------------- | -------------- | -------------- | -------------- | ------------------- |
|
||||
|
||||
| Supported Co-Processors | ESP32 | ESP32-C Series | ESP32-S Series |
|
||||
| ----------------------- | ----- | -------------- | -------------- |
|
||||
|
||||
# BLE Host-Only Peripheral Hosted HCI Example
|
||||
|
||||
This example uses the Bluetooth Hosted HCI transport provided by ESP-Hosted.
|
||||
|
||||
To use the Hosted HCI transport in the application, the Bluetooth
|
||||
controller should be disabled and the default uart-transport should
|
||||
also be disabled (when the controller is disabled, by default the
|
||||
uart-transport is selected). The Bluetooth Hosted HCI transport in
|
||||
ESP-Hosted should also be enabled.
|
||||
|
||||
Refer to the `sdkconfig.defaults` file which has the required
|
||||
configuration.
|
||||
|
||||
To test this demo, any BLE scanner app can be used.
|
||||
|
||||
## How to Use This Example
|
||||
|
||||
Before project configuration and build, be sure to set the correct
|
||||
chip target using:
|
||||
|
||||
```bash
|
||||
idf.py set-target <chip_name>
|
||||
```
|
||||
|
||||
### Configure the project
|
||||
|
||||
Open the project configuration menu:
|
||||
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
In the `Component config-> Bluetooth` menu:
|
||||
|
||||
* Select `controller` to Disabled.
|
||||
* Disable `Nimble Options-> Host-controller Transport -> Enable Uart Transport`.
|
||||
|
||||
In the `Component config-> ESP-Hosted config` menu:
|
||||
|
||||
* Enable `Bluetooth Support-> Enable Hosted Bluetooth support`
|
||||
|
||||
### Build and Flash
|
||||
|
||||
Run `idf.py -p PORT flash monitor` to build, flash and monitor the
|
||||
project.
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the [Getting Started Guide](https://idf.espressif.com/) for full
|
||||
steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
## Example Output
|
||||
|
||||
This is the console output when `bleprph_host_only_vhci` is running on
|
||||
the ESP32-P4 and using the Bluetooth Controller of the ESP32C6 on the
|
||||
ESP32-P4-Function-EV-Board. The data is transferred through SDIO
|
||||
between the ESP32-P4 and ESP32-C6:
|
||||
|
||||
```
|
||||
I (25) boot: ESP-IDF v5.4-dev-2793-g24047f9a04-dirty 2nd stage bootloader
|
||||
I (26) boot: compile time Sep 9 2024 16:23:48
|
||||
I (27) boot: Multicore bootloader
|
||||
I (32) boot: chip revision: v0.1
|
||||
I (34) boot: efuse block revision: v0.0
|
||||
I (39) boot.esp32p4: SPI Speed : 80MHz
|
||||
I (44) boot.esp32p4: SPI Mode : DIO
|
||||
I (49) boot.esp32p4: SPI Flash Size : 2MB
|
||||
I (53) boot: Enabling RNG early entropy source...
|
||||
I (59) boot: Partition Table:
|
||||
I (62) boot: ## Label Usage Type ST Offset Length
|
||||
I (70) boot: 0 nvs WiFi data 01 02 00009000 00006000
|
||||
I (77) boot: 1 phy_init RF data 01 01 0000f000 00001000
|
||||
I (84) boot: 2 factory factory app 00 00 00010000 00100000
|
||||
I (93) boot: End of partition table
|
||||
I (96) esp_image: segment 0: paddr=00010020 vaddr=40070020 size=2ae08h (175624) map
|
||||
I (135) esp_image: segment 1: paddr=0003ae30 vaddr=30100000 size=0000ch ( 12) load
|
||||
I (137) esp_image: segment 2: paddr=0003ae44 vaddr=3010000c size=00038h ( 56) load
|
||||
I (142) esp_image: segment 3: paddr=0003ae84 vaddr=4ff00000 size=05194h ( 20884) load
|
||||
I (154) esp_image: segment 4: paddr=00040020 vaddr=40000020 size=638b0h (407728) map
|
||||
I (226) esp_image: segment 5: paddr=000a38d8 vaddr=4ff05194 size=0b9a0h ( 47520) load
|
||||
I (237) esp_image: segment 6: paddr=000af280 vaddr=4ff10b80 size=02328h ( 9000) load
|
||||
I (244) boot: Loaded app from partition at offset 0x10000
|
||||
I (244) boot: Disabling RNG early entropy source...
|
||||
I (258) cpu_start: Multicore app
|
||||
W (267) clk: esp_perip_clk_init() has not been implemented yet
|
||||
I (274) cpu_start: Pro cpu start user code
|
||||
I (274) cpu_start: cpu freq: 360000000 Hz
|
||||
I (274) app_init: Application information:
|
||||
I (277) app_init: Project name: bleprph_host_only_vhci
|
||||
I (283) app_init: App version: 1c568c9-dirty
|
||||
I (288) app_init: Compile time: Sep 9 2024 16:23:43
|
||||
I (294) app_init: ELF file SHA256: ee5a16527...
|
||||
I (299) app_init: ESP-IDF: v5.4-dev-2793-g24047f9a04-dirty
|
||||
I (306) efuse_init: Min chip rev: v0.1
|
||||
I (311) efuse_init: Max chip rev: v0.99
|
||||
I (316) efuse_init: Chip rev: v0.1
|
||||
I (321) heap_init: Initializing. RAM available for dynamic allocation:
|
||||
I (328) heap_init: At 4FF160B0 len 00024F10 (147 KiB): RAM
|
||||
I (334) heap_init: At 4FF3AFC0 len 00004BF0 (18 KiB): RAM
|
||||
I (340) heap_init: At 4FF40000 len 00060000 (384 KiB): RAM
|
||||
I (347) heap_init: At 50108080 len 00007F80 (31 KiB): RTCRAM
|
||||
I (353) heap_init: At 30100044 len 00001FBC (7 KiB): TCM
|
||||
I (360) spi_flash: detected chip: generic
|
||||
I (363) spi_flash: flash io: dio
|
||||
W (367) spi_flash: Detected size(16384k) larger than the size in the binary image header(2048k). Using the size in the binary image header.
|
||||
I (381) host_init: ESP Hosted : Host chip_ip[18]
|
||||
I (409) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
|
||||
sdio_mempool_create free:181028 min-free:181028 lfb-def:139264 lfb-8bit:139264
|
||||
|
||||
I (414) gpio: GPIO[18]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (423) gpio: GPIO[19]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (433) gpio: GPIO[14]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (442) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (451) gpio: GPIO[16]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (461) gpio: GPIO[17]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (470) H_API: ** add_esp_wifi_remote_channels **
|
||||
I (475) transport: Add ESP-Hosted channel IF[1]: S[0] Tx[0x4000d110] Rx[0x4001b4aa]
|
||||
0x4000d110: transport_drv_sta_tx at /home/kysoh/projects/esp_as_mcu_host/examples/bleprph_host_only_vhci/components/esp_hosted/host/drivers/transport/transport_drv.c:208
|
||||
0x4001b4aa: esp_wifi_remote_channel_rx at /home/kysoh/projects/esp_as_mcu_host/examples/bleprph_host_only_vhci/managed_components/espressif__esp_wifi_remote/esp_wifi_remote_net.c:19
|
||||
|
||||
I (484) transport: Add ESP-Hosted channel IF[2]: S[0] Tx[0x4000d058] Rx[0x4001b4aa]
|
||||
0x4000d058: transport_drv_ap_tx at /home/kysoh/projects/esp_as_mcu_host/examples/bleprph_host_only_vhci/components/esp_hosted/host/drivers/transport/transport_drv.c:238
|
||||
0x4001b4aa: esp_wifi_remote_channel_rx at /home/kysoh/projects/esp_as_mcu_host/examples/bleprph_host_only_vhci/managed_components/espressif__esp_wifi_remote/esp_wifi_remote_net.c:19
|
||||
|
||||
I (493) main_task: Started on CPU0
|
||||
I (503) main_task: Calling app_main()
|
||||
I (513) transport: Attempt connection with slave: retry[0]
|
||||
I (513) transport: Reset slave using GPIO[54]
|
||||
I (513) os_wrapper_esp: GPIO [54] configured
|
||||
I (513) gpio: GPIO[54]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0
|
||||
I (1693) sdio_wrapper: SDIO master: Data-Lines: 4-bit Freq(KHz)[40000 KHz]
|
||||
I (1693) sdio_wrapper: GPIOs: CLK[18] CMD[19] D0[14] D1[15] D2[16] D3[17] Slave_Reset[54]
|
||||
I (1693) H_SDIO_DRV: Starting SDIO process rx task
|
||||
I (1693) sdio_wrapper: Queues: Tx[20] Rx[20] SDIO-Rx-Mode[1]
|
||||
I (1733) gpio: GPIO[15]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
I (1733) gpio: GPIO[17]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
|
||||
Name:
|
||||
Type: SDIO
|
||||
Speed: 40.00 MHz (limit: 40.00 MHz)
|
||||
Size: 0MB
|
||||
CSD: ver=1, sector_size=0, capacity=0 read_bl_len=0
|
||||
SCR: sd_spec=0, bus_width=0
|
||||
TUPLE: DEVICE, size: 3: D9 01 FF
|
||||
TUPLE: MANFID, size: 4
|
||||
MANF: 0092, CARD: 6666
|
||||
TUPLE: FUNCID, size: 2: 0C 00
|
||||
TUPLE: FUNCE, size: 4: 00 00 02 32
|
||||
TUPLE: CONFIG, size: 5: 01 01 00 02 07
|
||||
TUPLE: CFTABLE_ENTRY, size: 8
|
||||
INDX: C1, Intface: 1, Default: 1, Conf-Entry-Num: 1
|
||||
IF: 41
|
||||
FS: 30, misc: 0, mem_space: 1, irq: 1, io_space: 0, timing: 0, power: 0
|
||||
IR: 30, mask: 1, IRQ: FF FF
|
||||
LEN: FFFF
|
||||
TUPLE: END
|
||||
I (1783) sdio_wrapper: Function 0 Blocksize: 512
|
||||
I (1793) sdio_wrapper: Function 1 Blocksize: 512
|
||||
I (1793) H_SDIO_DRV: SDIO Host operating in STREAMING MODE
|
||||
I (1803) H_SDIO_DRV: generate slave intr
|
||||
I (1813) transport: Received INIT event from ESP32 peripheral
|
||||
I (1813) transport: EVENT: 12
|
||||
I (1813) transport: EVENT: 11
|
||||
I (1823) transport: capabilities: 0xd
|
||||
I (1823) transport: Features supported are:
|
||||
I (1833) transport: * WLAN
|
||||
I (1833) transport: - HCI over SDIO
|
||||
I (1843) transport: - BLE only
|
||||
I (1843) transport: EVENT: 13
|
||||
I (1843) transport: ESP board type is : 13
|
||||
|
||||
I (1853) transport: Base transport is set-up
|
||||
|
||||
I (1853) transport: Slave chip Id[12]
|
||||
I (1863) vhci_drv: Host BT Support: Enabled
|
||||
I (1863) vhci_drv: BT Transport Type: VHCI
|
||||
I (1873) H_SDIO_DRV: Received INIT event
|
||||
I (1883) rpc_wrap: Received Slave ESP Init
|
||||
I (2623) NimBLE_BLE_PRPH: BLE Host Task Started
|
||||
I (2623) uart: queue free spaces: 8
|
||||
I (2623) main_task: Returned from app_main()
|
||||
I (2623) NimBLE: GAP procedure initiated: stop advertising.
|
||||
|
||||
I (2633) NimBLE: Device Address:
|
||||
I (2633) NimBLE: 40:4c:ca:5b:9a:e2
|
||||
I (2633) NimBLE:
|
||||
|
||||
I (2643) NimBLE: GAP procedure initiated: advertise;
|
||||
I (2643) NimBLE: disc_mode=2
|
||||
I (2653) NimBLE: adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
|
||||
I (2663) NimBLE:
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
For any technical queries, please open an [issue](https://github.com/espressif/esp-hosted-mcu/issues) on ESP-Hosted on GitHub. We will get back to you soon.
|
||||
|
||||
## References
|
||||
|
||||
* Bluetooth Implementation in ESP-Hosted: https://github.com/espressif/esp-hosted-mcu/blob/main/docs/bluetooth_design.md
|
||||
@@ -0,0 +1,5 @@
|
||||
set(srcs "main.c"
|
||||
"gatt_svr.c")
|
||||
|
||||
idf_component_register(SRCS "${srcs}"
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,80 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
choice EXAMPLE_USE_IO_TYPE
|
||||
prompt "I/O Capability"
|
||||
default BLE_SM_IO_CAP_NO_IO
|
||||
help
|
||||
I/O capability of device.
|
||||
|
||||
config BLE_SM_IO_CAP_DISP_ONLY
|
||||
bool "DISPLAY ONLY"
|
||||
config BLE_SM_IO_CAP_DISP_YES_NO
|
||||
bool "DISPLAY YESNO"
|
||||
config BLE_SM_IO_CAP_KEYBOARD_ONLY
|
||||
bool "KEYBOARD ONLY"
|
||||
config BLE_SM_IO_CAP_NO_IO
|
||||
bool "Just works"
|
||||
config BLE_SM_IO_CAP_KEYBOARD_DISP
|
||||
bool "Both KEYBOARD & DISPLAY"
|
||||
endchoice
|
||||
|
||||
config EXAMPLE_IO_TYPE
|
||||
int
|
||||
default 0 if BLE_SM_IO_CAP_DISP_ONLY
|
||||
default 1 if BLE_SM_IO_CAP_DISP_YES_NO
|
||||
default 2 if BLE_SM_IO_CAP_KEYBOARD_ONLY
|
||||
default 3 if BLE_SM_IO_CAP_NO_IO
|
||||
default 4 if BLE_SM_IO_CAP_KEYBOARD_DISP
|
||||
|
||||
config EXAMPLE_BONDING
|
||||
bool
|
||||
default n
|
||||
prompt "Use Bonding"
|
||||
help
|
||||
Use this option to enable/disable bonding.
|
||||
|
||||
config EXAMPLE_MITM
|
||||
bool
|
||||
default n
|
||||
prompt "MITM security"
|
||||
help
|
||||
Use this option to enable/disable MITM security.
|
||||
|
||||
config EXAMPLE_USE_SC
|
||||
bool
|
||||
depends on BT_NIMBLE_SM_SC
|
||||
default n
|
||||
prompt "Use Secure Connection feature"
|
||||
help
|
||||
Use this option to enable/disable Security Manager Secure Connection 4.2 feature.
|
||||
|
||||
config EXAMPLE_EXTENDED_ADV
|
||||
bool
|
||||
depends on SOC_BLE_50_SUPPORTED && BT_NIMBLE_50_FEATURE_SUPPORT
|
||||
default y if SOC_ESP_NIMBLE_CONTROLLER
|
||||
select BT_NIMBLE_EXT_ADV
|
||||
prompt "Enable Extended Adv"
|
||||
help
|
||||
Use this option to enable extended advertising in the example.
|
||||
If this option is disabled, ensure config BT_NIMBLE_EXT_ADV is
|
||||
also disabled from Nimble stack menuconfig
|
||||
|
||||
config EXAMPLE_RANDOM_ADDR
|
||||
bool
|
||||
prompt "Advertise RANDOM Address"
|
||||
help
|
||||
Use this option to advertise a random address instead of public address
|
||||
|
||||
config EXAMPLE_ENCRYPTION
|
||||
bool
|
||||
prompt "Enable Link Encryption"
|
||||
help
|
||||
This adds Encrypted Read and Write permissions in the custom GATT server.
|
||||
|
||||
config EXAMPLE_RESOLVE_PEER_ADDR
|
||||
bool
|
||||
prompt "Enable resolving peer address"
|
||||
help
|
||||
Use this option to enable resolving peer's address.
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#ifndef H_BLEPRPH_
|
||||
#define H_BLEPRPH_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include "nimble/ble.h"
|
||||
#include "modlog/modlog.h"
|
||||
#include "esp_peripheral.h"
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct ble_hs_cfg;
|
||||
struct ble_gatt_register_ctxt;
|
||||
|
||||
/** GATT server. */
|
||||
#define GATT_SVR_SVC_ALERT_UUID 0x1811
|
||||
#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
|
||||
#define GATT_SVR_CHR_NEW_ALERT 0x2A46
|
||||
#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
|
||||
#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45
|
||||
#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44
|
||||
|
||||
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
|
||||
int gatt_svr_init(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2024 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_uuid.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include "bleprph.h"
|
||||
#include "services/ans/ble_svc_ans.h"
|
||||
|
||||
/*** Maximum number of characteristics with the notify flag ***/
|
||||
#define MAX_NOTIFY 5
|
||||
|
||||
static const ble_uuid128_t gatt_svr_svc_uuid =
|
||||
BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
|
||||
0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);
|
||||
|
||||
/* A characteristic that can be subscribed to */
|
||||
static uint8_t gatt_svr_chr_val;
|
||||
static uint16_t gatt_svr_chr_val_handle;
|
||||
static const ble_uuid128_t gatt_svr_chr_uuid =
|
||||
BLE_UUID128_INIT(0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11,
|
||||
0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33);
|
||||
|
||||
/* A custom descriptor */
|
||||
static uint8_t gatt_svr_dsc_val;
|
||||
static const ble_uuid128_t gatt_svr_dsc_uuid =
|
||||
BLE_UUID128_INIT(0x01, 0x01, 0x01, 0x01, 0x12, 0x12, 0x12, 0x12,
|
||||
0x23, 0x23, 0x23, 0x23, 0x34, 0x34, 0x34, 0x34);
|
||||
|
||||
static int
|
||||
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt,
|
||||
void *arg);
|
||||
|
||||
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
|
||||
{
|
||||
/*** Service ***/
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = &gatt_svr_svc_uuid.u,
|
||||
.characteristics = (struct ble_gatt_chr_def[])
|
||||
{ {
|
||||
/*** This characteristic can be subscribed to by writing 0x00 and 0x01 to the CCCD ***/
|
||||
.uuid = &gatt_svr_chr_uuid.u,
|
||||
.access_cb = gatt_svc_access,
|
||||
#if CONFIG_EXAMPLE_ENCRYPTION
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE |
|
||||
BLE_GATT_CHR_F_READ_ENC | BLE_GATT_CHR_F_WRITE_ENC |
|
||||
BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
|
||||
#else
|
||||
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY | BLE_GATT_CHR_F_INDICATE,
|
||||
#endif
|
||||
.val_handle = &gatt_svr_chr_val_handle,
|
||||
.descriptors = (struct ble_gatt_dsc_def[])
|
||||
{ {
|
||||
.uuid = &gatt_svr_dsc_uuid.u,
|
||||
#if CONFIG_EXAMPLE_ENCRYPTION
|
||||
.att_flags = BLE_ATT_F_READ | BLE_ATT_F_READ_ENC,
|
||||
#else
|
||||
.att_flags = BLE_ATT_F_READ,
|
||||
#endif
|
||||
.access_cb = gatt_svc_access,
|
||||
}, {
|
||||
0, /* No more descriptors in this characteristic */
|
||||
}
|
||||
},
|
||||
}, {
|
||||
0, /* No more characteristics in this service. */
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
0, /* No more services. */
|
||||
},
|
||||
};
|
||||
|
||||
static int
|
||||
gatt_svr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
|
||||
void *dst, uint16_t *len)
|
||||
{
|
||||
uint16_t om_len;
|
||||
int rc;
|
||||
|
||||
om_len = OS_MBUF_PKTLEN(om);
|
||||
if (om_len < min_len || om_len > max_len) {
|
||||
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
|
||||
}
|
||||
|
||||
rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
|
||||
if (rc != 0) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access callback whenever a characteristic/descriptor is read or written to.
|
||||
* Here reads and writes need to be handled.
|
||||
* ctxt->op tells weather the operation is read or write and
|
||||
* weather it is on a characteristic or descriptor,
|
||||
* ctxt->dsc->uuid tells which characteristic/descriptor is accessed.
|
||||
* attr_handle give the value handle of the attribute being accessed.
|
||||
* Accordingly do:
|
||||
* Append the value to ctxt->om if the operation is READ
|
||||
* Write ctxt->om to the value if the operation is WRITE
|
||||
**/
|
||||
static int
|
||||
gatt_svc_access(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
const ble_uuid_t *uuid;
|
||||
int rc;
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_ACCESS_OP_READ_CHR:
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
MODLOG_DFLT(INFO, "Characteristic read; conn_handle=%d attr_handle=%d\n",
|
||||
conn_handle, attr_handle);
|
||||
} else {
|
||||
MODLOG_DFLT(INFO, "Characteristic read by NimBLE stack; attr_handle=%d\n",
|
||||
attr_handle);
|
||||
}
|
||||
uuid = ctxt->chr->uuid;
|
||||
if (attr_handle == gatt_svr_chr_val_handle) {
|
||||
rc = os_mbuf_append(ctxt->om,
|
||||
&gatt_svr_chr_val,
|
||||
sizeof(gatt_svr_chr_val));
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
goto unknown;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_WRITE_CHR:
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
MODLOG_DFLT(INFO, "Characteristic write; conn_handle=%d attr_handle=%d",
|
||||
conn_handle, attr_handle);
|
||||
} else {
|
||||
MODLOG_DFLT(INFO, "Characteristic write by NimBLE stack; attr_handle=%d",
|
||||
attr_handle);
|
||||
}
|
||||
uuid = ctxt->chr->uuid;
|
||||
if (attr_handle == gatt_svr_chr_val_handle) {
|
||||
rc = gatt_svr_write(ctxt->om,
|
||||
sizeof(gatt_svr_chr_val),
|
||||
sizeof(gatt_svr_chr_val),
|
||||
&gatt_svr_chr_val, NULL);
|
||||
ble_gatts_chr_updated(attr_handle);
|
||||
MODLOG_DFLT(INFO, "Notification/Indication scheduled for "
|
||||
"all subscribed peers.\n");
|
||||
return rc;
|
||||
}
|
||||
goto unknown;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_READ_DSC:
|
||||
if (conn_handle != BLE_HS_CONN_HANDLE_NONE) {
|
||||
MODLOG_DFLT(INFO, "Descriptor read; conn_handle=%d attr_handle=%d\n",
|
||||
conn_handle, attr_handle);
|
||||
} else {
|
||||
MODLOG_DFLT(INFO, "Descriptor read by NimBLE stack; attr_handle=%d\n",
|
||||
attr_handle);
|
||||
}
|
||||
uuid = ctxt->dsc->uuid;
|
||||
if (ble_uuid_cmp(uuid, &gatt_svr_dsc_uuid.u) == 0) {
|
||||
rc = os_mbuf_append(ctxt->om,
|
||||
&gatt_svr_dsc_val,
|
||||
sizeof(gatt_svr_chr_val));
|
||||
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
goto unknown;
|
||||
|
||||
case BLE_GATT_ACCESS_OP_WRITE_DSC:
|
||||
goto unknown;
|
||||
|
||||
default:
|
||||
goto unknown;
|
||||
}
|
||||
|
||||
unknown:
|
||||
/* Unknown characteristic/descriptor;
|
||||
* The NimBLE host should not have called this function;
|
||||
*/
|
||||
assert(0);
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
void
|
||||
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
|
||||
{
|
||||
char buf[BLE_UUID_STR_LEN];
|
||||
|
||||
switch (ctxt->op) {
|
||||
case BLE_GATT_REGISTER_OP_SVC:
|
||||
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
|
||||
ctxt->svc.handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_CHR:
|
||||
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
|
||||
"def_handle=%d val_handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
|
||||
ctxt->chr.def_handle,
|
||||
ctxt->chr.val_handle);
|
||||
break;
|
||||
|
||||
case BLE_GATT_REGISTER_OP_DSC:
|
||||
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
|
||||
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
|
||||
ctxt->dsc.handle);
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
gatt_svr_init(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
ble_svc_gap_init();
|
||||
ble_svc_gatt_init();
|
||||
ble_svc_ans_init();
|
||||
|
||||
rc = ble_gatts_count_cfg(gatt_svr_svcs);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
rc = ble_gatts_add_svcs(gatt_svr_svcs);
|
||||
if (rc != 0) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* Setting a value for the read-only descriptor */
|
||||
gatt_svr_dsc_val = 0x99;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
dependencies:
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: ~2
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10'
|
||||
nimble_peripheral_utils:
|
||||
path: ${IDF_PATH}/examples/bluetooth/nimble/common/nimble_peripheral_utils
|
||||
@@ -0,0 +1,576 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "esp_log.h"
|
||||
#include "nvs_flash.h"
|
||||
/* BLE */
|
||||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/util/util.h"
|
||||
#include "console/console.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "bleprph.h"
|
||||
|
||||
#include "esp_hosted.h"
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
static uint8_t ext_adv_pattern_1[] = {
|
||||
0x02, 0x01, 0x06,
|
||||
0x03, 0x03, 0xab, 0xcd,
|
||||
0x03, 0x03, 0x18, 0x11,
|
||||
0x11, 0X09, 'n', 'i', 'm', 'b', 'l', 'e', '-', 'b', 'l', 'e', 'p', 'r', 'p', 'h', '-', 'e',
|
||||
};
|
||||
#endif
|
||||
|
||||
static const char *tag = "NimBLE_BLE_PRPH";
|
||||
static int bleprph_gap_event(struct ble_gap_event *event, void *arg);
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static uint8_t own_addr_type = BLE_OWN_ADDR_RANDOM;
|
||||
#else
|
||||
static uint8_t own_addr_type;
|
||||
#endif
|
||||
|
||||
void ble_store_config_init(void);
|
||||
|
||||
/**
|
||||
* Logs information about a connection to the console.
|
||||
*/
|
||||
static void
|
||||
bleprph_print_conn_desc(struct ble_gap_conn_desc *desc)
|
||||
{
|
||||
MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=",
|
||||
desc->conn_handle, desc->our_ota_addr.type);
|
||||
print_addr(desc->our_ota_addr.val);
|
||||
MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=",
|
||||
desc->our_id_addr.type);
|
||||
print_addr(desc->our_id_addr.val);
|
||||
MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=",
|
||||
desc->peer_ota_addr.type);
|
||||
print_addr(desc->peer_ota_addr.val);
|
||||
MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=",
|
||||
desc->peer_id_addr.type);
|
||||
print_addr(desc->peer_id_addr.val);
|
||||
MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d "
|
||||
"encrypted=%d authenticated=%d bonded=%d\n",
|
||||
desc->conn_itvl, desc->conn_latency,
|
||||
desc->supervision_timeout,
|
||||
desc->sec_state.encrypted,
|
||||
desc->sec_state.authenticated,
|
||||
desc->sec_state.bonded);
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
/**
|
||||
* Enables advertising with the following parameters:
|
||||
* o General discoverable mode.
|
||||
* o Undirected connectable mode.
|
||||
*/
|
||||
static void
|
||||
ext_bleprph_advertise(void)
|
||||
{
|
||||
struct ble_gap_ext_adv_params params;
|
||||
struct os_mbuf *data;
|
||||
uint8_t instance = 0;
|
||||
int rc;
|
||||
|
||||
/* First check if any instance is already active */
|
||||
if(ble_gap_ext_adv_active(instance)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* use defaults for non-set params */
|
||||
memset (¶ms, 0, sizeof(params));
|
||||
|
||||
/* enable connectable advertising */
|
||||
params.connectable = 1;
|
||||
|
||||
/* advertise using random addr */
|
||||
params.own_addr_type = BLE_OWN_ADDR_PUBLIC;
|
||||
|
||||
params.primary_phy = BLE_HCI_LE_PHY_1M;
|
||||
params.secondary_phy = BLE_HCI_LE_PHY_2M;
|
||||
//params.tx_power = 127;
|
||||
params.sid = 1;
|
||||
|
||||
params.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
params.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
|
||||
/* configure instance 0 */
|
||||
rc = ble_gap_ext_adv_configure(instance, ¶ms, NULL,
|
||||
bleprph_gap_event, NULL);
|
||||
assert (rc == 0);
|
||||
|
||||
/* in this case only scan response is allowed */
|
||||
|
||||
/* get mbuf for scan rsp data */
|
||||
data = os_msys_get_pkthdr(sizeof(ext_adv_pattern_1), 0);
|
||||
assert(data);
|
||||
|
||||
/* fill mbuf with scan rsp data */
|
||||
rc = os_mbuf_append(data, ext_adv_pattern_1, sizeof(ext_adv_pattern_1));
|
||||
assert(rc == 0);
|
||||
|
||||
rc = ble_gap_ext_adv_set_data(instance, data);
|
||||
assert (rc == 0);
|
||||
|
||||
/* start advertising */
|
||||
rc = ble_gap_ext_adv_start(instance, 0, 0);
|
||||
assert (rc == 0);
|
||||
}
|
||||
#else
|
||||
/**
|
||||
* Enables advertising with the following parameters:
|
||||
* o General discoverable mode.
|
||||
* o Undirected connectable mode.
|
||||
*/
|
||||
static void
|
||||
bleprph_advertise(void)
|
||||
{
|
||||
struct ble_gap_adv_params adv_params;
|
||||
struct ble_hs_adv_fields fields;
|
||||
const char *name;
|
||||
int rc;
|
||||
|
||||
/**
|
||||
* Set the advertisement data included in our advertisements:
|
||||
* o Flags (indicates advertisement type and other general info).
|
||||
* o Advertising tx power.
|
||||
* o Device name.
|
||||
* o 16-bit service UUIDs (alert notifications).
|
||||
*/
|
||||
|
||||
memset(&fields, 0, sizeof fields);
|
||||
|
||||
/* Advertise two flags:
|
||||
* o Discoverability in forthcoming advertisement (general)
|
||||
* o BLE-only (BR/EDR unsupported).
|
||||
*/
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN |
|
||||
BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
|
||||
/* Indicate that the TX power level field should be included; have the
|
||||
* stack fill this value automatically. This is done by assigning the
|
||||
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
|
||||
*/
|
||||
fields.tx_pwr_lvl_is_present = 1;
|
||||
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
|
||||
|
||||
name = ble_svc_gap_device_name();
|
||||
fields.name = (uint8_t *)name;
|
||||
fields.name_len = strlen(name);
|
||||
fields.name_is_complete = 1;
|
||||
|
||||
fields.uuids16 = (ble_uuid16_t[]) {
|
||||
BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID)
|
||||
};
|
||||
fields.num_uuids16 = 1;
|
||||
fields.uuids16_is_complete = 1;
|
||||
|
||||
rc = ble_gap_adv_set_fields(&fields);
|
||||
if (rc != 0) {
|
||||
MODLOG_DFLT(ERROR, "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(own_addr_type, NULL, BLE_HS_FOREVER,
|
||||
&adv_params, bleprph_gap_event, NULL);
|
||||
if (rc != 0) {
|
||||
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
static void bleprph_power_control(uint16_t conn_handle)
|
||||
{
|
||||
int rc;
|
||||
|
||||
rc = ble_gap_read_remote_transmit_power_level(conn_handle, 0x01 ); // Attempting on LE 1M phy
|
||||
assert (rc == 0);
|
||||
|
||||
rc = ble_gap_set_transmit_power_reporting_enable(conn_handle, 0x1, 0x1);
|
||||
assert (rc == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* The nimble host executes this callback when a GAP event occurs. The
|
||||
* application associates a GAP event callback with each connection that forms.
|
||||
* bleprph uses the same callback for all connections.
|
||||
*
|
||||
* @param event The type of event being signalled.
|
||||
* @param ctxt Various information pertaining to the event.
|
||||
* @param arg Application-specified argument; unused by
|
||||
* bleprph.
|
||||
*
|
||||
* @return 0 if the application successfully handled the
|
||||
* event; nonzero on failure. The semantics
|
||||
* of the return code is specific to the
|
||||
* particular GAP event being signalled.
|
||||
*/
|
||||
static int
|
||||
bleprph_gap_event(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
struct ble_gap_conn_desc desc;
|
||||
int rc;
|
||||
|
||||
switch (event->type) {
|
||||
#if defined(BLE_GAP_EVENT_LINK_ESTAB)
|
||||
case BLE_GAP_EVENT_LINK_ESTAB:
|
||||
#else
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
#endif
|
||||
/* A new connection was established or a connection attempt failed. */
|
||||
MODLOG_DFLT(INFO, "connection %s; status=%d ",
|
||||
event->connect.status == 0 ? "established" : "failed",
|
||||
event->connect.status);
|
||||
if (event->connect.status == 0) {
|
||||
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
}
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
if (event->connect.status != 0) {
|
||||
/* Connection failed; resume advertising. */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
bleprph_power_control(event->connect.conn_handle);
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
|
||||
bleprph_print_conn_desc(&event->disconnect.conn);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
|
||||
/* Connection terminated; resume advertising. */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_CONN_UPDATE:
|
||||
/* The central has updated the connection parameters. */
|
||||
MODLOG_DFLT(INFO, "connection updated; status=%d ",
|
||||
event->conn_update.status);
|
||||
rc = ble_gap_conn_find(event->conn_update.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ADV_COMPLETE:
|
||||
MODLOG_DFLT(INFO, "advertise complete; reason=%d",
|
||||
event->adv_complete.reason);
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_ENC_CHANGE:
|
||||
/* Encryption has been enabled or disabled for this connection. */
|
||||
MODLOG_DFLT(INFO, "encryption change event; status=%d ",
|
||||
event->enc_change.status);
|
||||
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
bleprph_print_conn_desc(&desc);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_NOTIFY_TX:
|
||||
MODLOG_DFLT(INFO, "notify_tx event; conn_handle=%d attr_handle=%d "
|
||||
"status=%d is_indication=%d",
|
||||
event->notify_tx.conn_handle,
|
||||
event->notify_tx.attr_handle,
|
||||
event->notify_tx.status,
|
||||
event->notify_tx.indication);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d "
|
||||
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
|
||||
event->subscribe.conn_handle,
|
||||
event->subscribe.attr_handle,
|
||||
event->subscribe.reason,
|
||||
event->subscribe.prev_notify,
|
||||
event->subscribe.cur_notify,
|
||||
event->subscribe.prev_indicate,
|
||||
event->subscribe.cur_indicate);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_MTU:
|
||||
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
|
||||
event->mtu.conn_handle,
|
||||
event->mtu.channel_id,
|
||||
event->mtu.value);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_REPEAT_PAIRING:
|
||||
/* We already have a bond with the peer, but it is attempting to
|
||||
* establish a new secure link. This app sacrifices security for
|
||||
* convenience: just throw away the old bond and accept the new link.
|
||||
*/
|
||||
|
||||
/* Delete the old bond. */
|
||||
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
|
||||
assert(rc == 0);
|
||||
ble_store_util_delete_peer(&desc.peer_id_addr);
|
||||
|
||||
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
|
||||
* continue with the pairing operation.
|
||||
*/
|
||||
return BLE_GAP_REPEAT_PAIRING_RETRY;
|
||||
|
||||
case BLE_GAP_EVENT_PASSKEY_ACTION:
|
||||
ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started");
|
||||
struct ble_sm_io pkey = {0};
|
||||
int key = 0;
|
||||
|
||||
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
|
||||
pkey.action = event->passkey.params.action;
|
||||
pkey.passkey = 123456; // This is the passkey to be entered on peer
|
||||
ESP_LOGI(tag, "Enter passkey %" PRIu32 "on the peer side", pkey.passkey);
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
|
||||
ESP_LOGI(tag, "Passkey on device's display: %" PRIu32 , event->passkey.params.numcmp);
|
||||
ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N");
|
||||
pkey.action = event->passkey.params.action;
|
||||
if (scli_receive_key(&key)) {
|
||||
pkey.numcmp_accept = key;
|
||||
} else {
|
||||
pkey.numcmp_accept = 0;
|
||||
ESP_LOGE(tag, "Timeout! Rejecting the key");
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
|
||||
static uint8_t tem_oob[16] = {0};
|
||||
pkey.action = event->passkey.params.action;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
pkey.oob[i] = tem_oob[i];
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
|
||||
ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456");
|
||||
pkey.action = event->passkey.params.action;
|
||||
if (scli_receive_key(&key)) {
|
||||
pkey.passkey = key;
|
||||
} else {
|
||||
pkey.passkey = 0;
|
||||
ESP_LOGE(tag, "Timeout! Passing 0 as the key");
|
||||
}
|
||||
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
|
||||
ESP_LOGI(tag, "ble_sm_inject_io result: %d", rc);
|
||||
}
|
||||
return 0;
|
||||
|
||||
#if MYNEWT_VAL(BLE_POWER_CONTROL)
|
||||
case BLE_GAP_EVENT_TRANSMIT_POWER:
|
||||
MODLOG_DFLT(INFO, "Transmit power event : status=%d conn_handle=%d reason=%d "
|
||||
"phy=%d power_level=%x power_level_flag=%d delta=%d",
|
||||
event->transmit_power.status,
|
||||
event->transmit_power.conn_handle,
|
||||
event->transmit_power.reason,
|
||||
event->transmit_power.phy,
|
||||
event->transmit_power.transmit_power_level,
|
||||
event->transmit_power.transmit_power_level_flag,
|
||||
event->transmit_power.delta);
|
||||
return 0;
|
||||
|
||||
case BLE_GAP_EVENT_PATHLOSS_THRESHOLD:
|
||||
MODLOG_DFLT(INFO, "Pathloss threshold event : conn_handle=%d current path loss=%d "
|
||||
"zone_entered =%d",
|
||||
event->pathloss_threshold.conn_handle,
|
||||
event->pathloss_threshold.current_path_loss,
|
||||
event->pathloss_threshold.zone_entered);
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
bleprph_on_reset(int reason)
|
||||
{
|
||||
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
|
||||
}
|
||||
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
static void
|
||||
ble_app_set_addr(void)
|
||||
{
|
||||
ble_addr_t addr;
|
||||
int rc;
|
||||
|
||||
/* generate new non-resolvable private address */
|
||||
rc = ble_hs_id_gen_rnd(0, &addr);
|
||||
assert(rc == 0);
|
||||
|
||||
/* set generated address */
|
||||
rc = ble_hs_id_set_rnd(addr.val);
|
||||
|
||||
assert(rc == 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
bleprph_on_sync(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
/* Generate a non-resolvable private address. */
|
||||
ble_app_set_addr();
|
||||
#endif
|
||||
|
||||
/* Make sure we have proper identity address set (public preferred) */
|
||||
#if CONFIG_EXAMPLE_RANDOM_ADDR
|
||||
rc = ble_hs_util_ensure_addr(1);
|
||||
#else
|
||||
rc = ble_hs_util_ensure_addr(0);
|
||||
#endif
|
||||
assert(rc == 0);
|
||||
|
||||
/* Figure out address to use while advertising (no privacy for now) */
|
||||
rc = ble_hs_id_infer_auto(0, &own_addr_type);
|
||||
if (rc != 0) {
|
||||
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Printing ADDR */
|
||||
uint8_t addr_val[6] = {0};
|
||||
rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
|
||||
|
||||
MODLOG_DFLT(INFO, "Device Address: ");
|
||||
print_addr(addr_val);
|
||||
MODLOG_DFLT(INFO, "\n");
|
||||
/* Begin advertising. */
|
||||
#if CONFIG_EXAMPLE_EXTENDED_ADV
|
||||
ext_bleprph_advertise();
|
||||
#else
|
||||
bleprph_advertise();
|
||||
#endif
|
||||
}
|
||||
|
||||
void bleprph_host_task(void *param)
|
||||
{
|
||||
ESP_LOGI(tag, "BLE Host Task Started");
|
||||
/* This function will return only when nimble_port_stop() is executed */
|
||||
nimble_port_run();
|
||||
|
||||
nimble_port_freertos_deinit();
|
||||
}
|
||||
|
||||
void
|
||||
app_main(void)
|
||||
{
|
||||
int rc;
|
||||
|
||||
/* Initialize NVS — it is used to store PHY calibration data */
|
||||
esp_err_t ret = nvs_flash_init();
|
||||
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
|
||||
ESP_ERROR_CHECK(nvs_flash_erase());
|
||||
ret = nvs_flash_init();
|
||||
}
|
||||
ESP_ERROR_CHECK(ret);
|
||||
|
||||
// initialise connection to co-processor
|
||||
esp_hosted_connect_to_slave();
|
||||
|
||||
// get fw version
|
||||
ESP_LOGI("INFO", "getting fw version");
|
||||
esp_hosted_coprocessor_fwver_t fwver;
|
||||
if (ESP_OK == esp_hosted_get_coprocessor_fwversion(&fwver)) {
|
||||
ESP_LOGI("INFO", "FW Version: %" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
fwver.major1, fwver.minor1, fwver.patch1);
|
||||
} else {
|
||||
ESP_LOGW("INFO", "failed to get fw version");
|
||||
}
|
||||
|
||||
// init bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_init()) {
|
||||
ESP_LOGW("INFO", "failed to init bt controller");
|
||||
}
|
||||
|
||||
// enable bt controller
|
||||
if (ESP_OK != esp_hosted_bt_controller_enable()) {
|
||||
ESP_LOGW("INFO", "failed to enable bt controller");
|
||||
}
|
||||
|
||||
ret = nimble_port_init();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW("INFO", "Failed to init nimble %d ", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Initialize the NimBLE host configuration. */
|
||||
ble_hs_cfg.reset_cb = bleprph_on_reset;
|
||||
ble_hs_cfg.sync_cb = bleprph_on_sync;
|
||||
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
|
||||
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
|
||||
|
||||
ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE;
|
||||
#ifdef CONFIG_EXAMPLE_BONDING
|
||||
ble_hs_cfg.sm_bonding = 1;
|
||||
/* Enable the appropriate bit masks to make sure the keys
|
||||
* that are needed are exchanged
|
||||
*/
|
||||
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
|
||||
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ENC;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_MITM
|
||||
ble_hs_cfg.sm_mitm = 1;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_USE_SC
|
||||
ble_hs_cfg.sm_sc = 1;
|
||||
#else
|
||||
ble_hs_cfg.sm_sc = 0;
|
||||
#endif
|
||||
#ifdef CONFIG_EXAMPLE_RESOLVE_PEER_ADDR
|
||||
/* Stores the IRK */
|
||||
ble_hs_cfg.sm_our_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
|
||||
ble_hs_cfg.sm_their_key_dist |= BLE_SM_PAIR_KEY_DIST_ID;
|
||||
#endif
|
||||
|
||||
rc = gatt_svr_init();
|
||||
assert(rc == 0);
|
||||
|
||||
/* Set the default device name. */
|
||||
rc = ble_svc_gap_device_name_set("nimble-bleprph");
|
||||
assert(rc == 0);
|
||||
|
||||
/* XXX Need to have template for store */
|
||||
ble_store_config_init();
|
||||
|
||||
nimble_port_freertos_init(bleprph_host_task);
|
||||
|
||||
/* Initialize command line interface to accept input from user */
|
||||
rc = scli_init();
|
||||
if (rc != ESP_OK) {
|
||||
ESP_LOGE(tag, "scli_init() failed");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
# Override some defaults so BT stack is enabled
|
||||
# in this example
|
||||
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
#
|
||||
# BT config
|
||||
#
|
||||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_CONTROLLER_DISABLED=y
|
||||
CONFIG_BT_BLUEDROID_ENABLED=n
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_TRANSPORT_UART=n
|
||||
|
||||
#
|
||||
# Wi-Fi Remote
|
||||
#
|
||||
CONFIG_ESP_WIFI_REMOTE_LIBRARY_HOSTED=y
|
||||
|
||||
#
|
||||
# Enable ESP Hosted BT
|
||||
# Used as VHCI transport between BT Host and Controller
|
||||
#
|
||||
CONFIG_ESP_HOSTED_ENABLE_BT_NIMBLE=y
|
||||
CONFIG_ESP_HOSTED_NIMBLE_HCI_VHCI=y
|
||||
@@ -0,0 +1,7 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
|
||||
set(PARTITION_TABLE_FILE "${CMAKE_CURRENT_SOURCE_DIR}/partitions.csv")
|
||||
|
||||
project(host_performs_slave_ota)
|
||||
@@ -0,0 +1,512 @@
|
||||
# ESP-Hosted Slave OTA Example
|
||||
|
||||
| Supported Targets | ESP32-P4 | ESP32-H2 |
|
||||
| ----------------- | -------- | -------- |
|
||||
|
||||
## What This Example Does
|
||||
|
||||
This example demonstrates **ESP-Hosted Transport OTA** - updating slave firmware **through the existing ESP-Hosted connection** (SDIO/SPI/UART) without any additional hardware or physical access.
|
||||
|
||||
**Key Features:**
|
||||
- **No extra hardware needed** - uses your existing ESP-Hosted transport
|
||||
- **Three OTA methods** - choose what works best for your deployment
|
||||
- **Version checking** - prevents unnecessary updates
|
||||
|
||||
## ESP-Hosted Slave Firmware Update Approaches
|
||||
|
||||
There are a lot of ways that you can flash the slave firmware.
|
||||
To limit possibilities, **two main approaches** could be understood.
|
||||
|
||||
##### 1. ESP-Hosted Slave OTA (Current Example focus this)
|
||||
- **Hardware**: None (reuses existing SDIO/SPI/UART serial channel from ESP-Hosted)
|
||||
- **Use case**: Production updates, remote deployment
|
||||
- **Access**: Over-the-air OR through Host partition (Uses RPC calls to slave)
|
||||
|
||||
##### 2. Direct UART Flashing
|
||||
- **Hardware**: ESP-Prog or UART connection
|
||||
- **Use case**: Initial slave FW setup, firmware recovery, development
|
||||
- **Access**: Requires physical access to device
|
||||
|
||||
|
||||
**This example focuses entirely on Method 1**
|
||||
|
||||
---
|
||||
|
||||
## ESP-Hosted Slave OTA APIs
|
||||
|
||||
The ESP-Hosted slave OTA functionality is built around **4 core APIs** that handle the complete OTA process. These APIs are transport-agnostic and can work with any firmware source (LittleFS, Partition, HTTPS, SPIFFS, etc.).
|
||||
|
||||
### ESP-Hosted-MCU Slave OTA APIs Used
|
||||
|
||||
#### 1. `esp_hosted_slave_ota_begin()`
|
||||
```c
|
||||
esp_err_t esp_hosted_slave_ota_begin(void);
|
||||
```
|
||||
- **Purpose**: Initializes the OTA process on the slave
|
||||
- **Arguments**: None
|
||||
- **Returns**: `ESP_OK` on success, error code on failure
|
||||
- **What it does**:
|
||||
- Prepares slave for firmware reception
|
||||
- Allocates OTA buffers
|
||||
- Sets up the OTA partition on slave
|
||||
|
||||
#### 2. `esp_hosted_slave_ota_write()`
|
||||
```c
|
||||
esp_err_t esp_hosted_slave_ota_write(const void *data, size_t size);
|
||||
```
|
||||
- **Purpose**: Sends firmware data chunks to the slave
|
||||
- **Arguments**:
|
||||
- `data`: Pointer to firmware data chunk
|
||||
- `size`: Size of the data chunk (typically 1400-1500 bytes)
|
||||
- **Returns**: `ESP_OK` on success, error code on failure
|
||||
- **What it does**:
|
||||
- Transmits firmware data over ESP-Hosted transport (SDIO/SPI/UART)
|
||||
- Slave writes data to its OTA partition
|
||||
- Can be called multiple times for large firmware
|
||||
|
||||
#### 3. `esp_hosted_slave_ota_end()`
|
||||
```c
|
||||
esp_err_t esp_hosted_slave_ota_end(void);
|
||||
```
|
||||
- **Purpose**: Finalizes the OTA process
|
||||
- **Arguments**: None
|
||||
- **Returns**: `ESP_OK` on success, error code on failure
|
||||
- **What it does**:
|
||||
- Validates the complete firmware image on slave
|
||||
- Calculates and verifies checksums
|
||||
- Marks the new firmware as valid but not active
|
||||
|
||||
#### 4. `esp_hosted_slave_ota_activate()`
|
||||
```c
|
||||
esp_err_t esp_hosted_slave_ota_activate(void);
|
||||
```
|
||||
- **Purpose**: Activates the newly flashed firmware
|
||||
- **Arguments**: None
|
||||
- **Returns**: `ESP_OK` on success, error code on failure
|
||||
- **What it does**:
|
||||
- Switches slave's boot partition to the new firmware
|
||||
- Triggers slave reboot with new firmware
|
||||
- **Note**: After this call, slave will restart with new firmware
|
||||
|
||||
### Additional API used
|
||||
|
||||
#### 5. `esp_hosted_get_coprocessor_fwversion()`
|
||||
```c
|
||||
esp_err_t esp_hosted_get_coprocessor_fwversion(esp_hosted_coprocessor_fwver_t *version);
|
||||
```
|
||||
- **Purpose**: Gets the currently running slave firmware version
|
||||
- **Arguments**:
|
||||
- `version`: Pointer to structure to store version information
|
||||
- **Returns**: `ESP_OK` on success, error code on failure
|
||||
- **Structure**:
|
||||
```c
|
||||
typedef struct {
|
||||
uint32_t major1; // Major version
|
||||
uint32_t minor1; // Minor version
|
||||
uint32_t patch1; // Patch version
|
||||
} esp_hosted_coprocessor_fwver_t;
|
||||
```
|
||||
|
||||
### How This Example Works
|
||||
|
||||
This example demonstrates the **complete OTA workflow**:
|
||||
|
||||
1. **Version Check** (optional): Uses `esp_hosted_get_coprocessor_fwversion()` to get current slave version
|
||||
2. **Begin OTA**: Calls `esp_hosted_slave_ota_begin()` to initialize
|
||||
3. **Transfer Firmware**:
|
||||
- Reads firmware from source (LittleFS file/Partition/HTTPS download)
|
||||
- Calls `esp_hosted_slave_ota_write()` repeatedly with chunks
|
||||
4. **Finalize**: Calls `esp_hosted_slave_ota_end()` to validate firmware
|
||||
5. **Activate**: Calls `esp_hosted_slave_ota_activate()` to switch to new firmware
|
||||
|
||||
### Firmware Source Flexibility
|
||||
|
||||
**Important**: These APIs are **not limited to the three methods shown**. You can use:
|
||||
- **LittleFS** (this example)
|
||||
- **Dedicated Partition** (this example)
|
||||
- **HTTPS Download** (this example)
|
||||
- **SPIFFS filesystem**
|
||||
- **SD Card files**
|
||||
- **NVS storage**
|
||||
- **Custom network protocols**
|
||||
- **Any other firmware source**
|
||||
|
||||
The **key requirement** is that you can read the firmware data and feed it to `esp_hosted_slave_ota_write()` in chunks.
|
||||
|
||||
---
|
||||
|
||||
## ESP-Hosted Slave OTA Methods
|
||||
|
||||
Choose the method that best fits your deployment:
|
||||
|
||||
| Method | Remarks |
|
||||
|--------|----------|
|
||||
| Slave OTA using LittleFS | Local deployment. One of Host partition formatted as LittleFS and pushed the slave fw as file |
|
||||
| Slave OTA using Host Partition | Local deployment. Slave Fw flashed in one of Host partition directly |
|
||||
| Slave OTA Using HTTPS | Needs internet access. Can re-use HTTPS server in the deployment |
|
||||
|
||||
`Ease of Use`: LittleFS > HTTPS > Partition
|
||||
|
||||
Pre-requisites:
|
||||
1. Please ensure the slave application firmware binary is located in the designated path specified below to prevent 'File not Found' build errors.
|
||||
2. For HTTPS-based updates, verify that the required certificates are properly installed to avoid connection establishment failures.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
host_performs_slave_ota/
|
||||
├── components/
|
||||
│ ├── ota_partition/ # Slave OTA using Host Partition method
|
||||
│ │ └── slave_fw_bin/ # Put slave .bin files here
|
||||
│ ├── ota_littlefs/ # Slave OTA using LittleFS method
|
||||
│ │ └── slave_fw_bin/ # Put slave .bin files here
|
||||
│ └── ota_https/ # Slave OTA Using HTTPS method
|
||||
│ ├── certs/ # SSL certificates
|
||||
│ └── test_server/ # Local HTTPS server
|
||||
├── partitions.csv # Universal partition table
|
||||
└── main/ # Main application
|
||||
```
|
||||
|
||||
## Host Partition Table
|
||||
```
|
||||
nvs, data, nvs, 0x9000, 16K,
|
||||
otadata, data, ota, 0xd000, 8K,
|
||||
phy_init, data, phy, 0xf000, 4K,
|
||||
ota_0, app, ota_0, 0x10000, 2M,
|
||||
ota_1, app, ota_1, 0x210000, 2M,
|
||||
storage, data, littlefs, 0x410000, 0x1E0000, # Used by Slave OTA using LittleFS
|
||||
slave_fw, data, 0x40, 0x5F0000, 0x200000, # Used by Slave OTA using Host Partition
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Method 1: Slave OTA using LittleFS
|
||||
|
||||
**Best for**: General use, dynamic updates, multiple firmware storage
|
||||
|
||||
### How it works:
|
||||
- Slave firmware stored in LittleFS filesystem
|
||||
- Build system creates filesystem image with firmware
|
||||
- OTA mounts filesystem and reads firmware file
|
||||
|
||||
### Setup Steps:
|
||||
|
||||
1. **Build slave firmware:**
|
||||
Please build the slave firmware referring the [slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave).
|
||||
|
||||
2. **Copy slave firmware:**
|
||||
```bash
|
||||
cp slave/build/network_adapter.bin examples/host_performs_slave_ota/components/ota_littlefs/slave_fw_bin/
|
||||
```
|
||||
|
||||
3. **Configure (optional - already default):**
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
# ESP-Hosted Slave OTA Configuration → OTA Method → LittleFS OTA
|
||||
```
|
||||
|
||||
4. **Build and flash:**
|
||||
```bash
|
||||
idf.py -p <host_serial_port> build flash monitor
|
||||
```
|
||||
|
||||
### What happens:
|
||||
- Build system creates LittleFS image containing slave firmware
|
||||
- LittleFS image flashed to `storage` partition
|
||||
- At runtime, OTA mounts filesystem, finds firmware, and compares versions
|
||||
- Only updates if versions differ
|
||||
|
||||
---
|
||||
|
||||
## Method 2: Slave OTA using Host Partition
|
||||
|
||||
**Best for**: Production, fastest updates, most reliable
|
||||
|
||||
### How it works:
|
||||
- Slave firmware pre-flashed to dedicated partition
|
||||
- OTA reads directly from partition (no filesystem overhead)
|
||||
- Version comparison prevents unnecessary updates
|
||||
|
||||
### Setup Steps:
|
||||
|
||||
1. **Build slave firmware:**
|
||||
Please build the slave firmware referring the [slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave).
|
||||
|
||||
2. **Copy slave firmware:**
|
||||
```bash
|
||||
cp slave/build/network_adapter.bin examples/host_performs_slave_ota/components/ota_partition/slave_fw_bin/
|
||||
```
|
||||
|
||||
3. **Configure:**
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
# ESP-Hosted Slave OTA Configuration → OTA Method → Partition OTA
|
||||
# (Default) Set partition label to: slave_fw
|
||||
```
|
||||
|
||||
4. **Build and flash:**
|
||||
```bash
|
||||
idf.py -p <host_serial_port> build flash monitor
|
||||
```
|
||||
|
||||
### What happens:
|
||||
- Build system detects slave firmware and shows notice
|
||||
- During flash, slave firmware automatically flashed to `slave_fw` partition
|
||||
- At runtime, OTA reads firmware from partition and compares versions
|
||||
- Only updates if versions differ
|
||||
|
||||
---
|
||||
|
||||
## Method 3: Slave OTA Using HTTPS
|
||||
|
||||
**Best for**: Remote updates, internet deployment
|
||||
|
||||
### How it works:
|
||||
- Downloads slave firmware from HTTPS server
|
||||
- Verifies image header while downloading
|
||||
- Supports both self-signed and CA certificates
|
||||
|
||||
### HTTPS Certificate Handling
|
||||
|
||||
The Slave OTA Using HTTPS supports two certificate modes:
|
||||
|
||||
#### Production Mode
|
||||
For real servers with CA-signed certificates, disable self signed testing.
|
||||
```
|
||||
ESP-Hosted Slave OTA Configuration
|
||||
└── OTA Method
|
||||
└── HTTPS OTA Config
|
||||
└── Use self-signed certificate (Testing Only) ---> ❌ DISABLE
|
||||
```
|
||||
|
||||
**How it works:**
|
||||
- Uses ESP-IDF's built-in CA certificate bundle (`esp_crt_bundle_attach`)
|
||||
- Automatically validates certificates from major CAs
|
||||
- No additional certificate files needed
|
||||
- Production-ready security
|
||||
|
||||
#### Testing Mode (Self-Signed Certificates)
|
||||
**For local testing with self-signed certificates**
|
||||
|
||||
1. **Enable self-signed certificates:**
|
||||
```
|
||||
ESP-Hosted Slave OTA Configuration
|
||||
└── OTA Method
|
||||
└── HTTPS OTA Config
|
||||
└── Use self-signed certificate (Testing Only) ---> ENABLE
|
||||
```
|
||||
|
||||
2. **Generate SSL certificates:**
|
||||
```bash
|
||||
cd examples/host_performs_slave_ota/components/ota_https/test_server
|
||||
./create_self_signed_certs.sh
|
||||
```
|
||||
|
||||
3. **Start HTTPS server:**
|
||||
```bash
|
||||
cd examples/host_performs_slave_ota/components/ota_https/test_server
|
||||
python3 create_https_server.py
|
||||
```
|
||||
|
||||
4. **Copy slave firmware to server:**
|
||||
Please build the slave firmware referring the [slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave).
|
||||
```bash
|
||||
cp slave/build/network_adapter.bin examples/host_performs_slave_ota/components/ota_https/test_server/
|
||||
```
|
||||
|
||||
5. **Configure WiFi and URL:**
|
||||
```bash
|
||||
idf.py menuconfig
|
||||
# ESP-Hosted Slave OTA Configuration → OTA Method → HTTPS OTA
|
||||
# Set HTTPS OTA URL to: https://<YOUR_IP>:8443/network_adapter.bin
|
||||
# Set WiFi SSID and password
|
||||
```
|
||||
|
||||
6. **Build and flash:**
|
||||
```bash
|
||||
idf.py -p <host_serial_port> build flash monitor
|
||||
```
|
||||
|
||||
### What happens:
|
||||
- ESP32 connects to WiFi
|
||||
- Downloads firmware over HTTPS with certificate verification
|
||||
- Verifies image header during download
|
||||
- Compares versions and only updates if different
|
||||
|
||||
---
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
### Menuconfig Options:
|
||||
```
|
||||
ESP-Hosted Slave OTA Configuration
|
||||
├── OTA Method
|
||||
│ ├── HTTPS OTA (default)
|
||||
│ │ │
|
||||
│ │ ├── Wi-Fi Config
|
||||
│ │ │ ├── SSID <--change-->
|
||||
│ │ │ └── Password <--change-->
|
||||
│ │ │
|
||||
│ │ └── HTTPS OTA Config
|
||||
│ │ ├── HTTPS OTA URL (https://...) <--change-->
|
||||
│ │ └── Use self-signed certificate (Testing Only) <--ENABLE-to-test-using-self-certs>
|
||||
│ │ └── Skip certificate Common Name check <--ENABLE-to-test-using-self-certs>
|
||||
│ │
|
||||
│ ├── LittleFS OTA
|
||||
│ │ └── Delete OTA file from LittleFS once finished (y)
|
||||
│ │
|
||||
│ └── Partition OTA
|
||||
│ └── Partition Label (for Partition Slave OTA) (slave_fw)
|
||||
│
|
||||
├── Host-Slave version compatibility check (y)
|
||||
│
|
||||
└── Skip OTA if slave firmware versions match (y)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Expected Output
|
||||
|
||||
##### Successful OTA:
|
||||
```
|
||||
I (1234) host_performs_slave_ota: ESP-Hosted initialized successfully
|
||||
I (1235) host_performs_slave_ota: Using XXXX OTA method
|
||||
I (1240) ota_XXXX: Current slave firmware version: 2.5.12
|
||||
I (1242) ota_XXXX: New slave firmware version: 2.6.0
|
||||
I (1245) ota_XXXX: Version differs - proceeding with OTA
|
||||
I (5678) ota_XXXX: XXXX OTA completed successfully
|
||||
I (5680) host_performs_slave_ota: OTA completed successfully
|
||||
```
|
||||
|
||||
##### Same Version (Skip OTA):
|
||||
```
|
||||
I (1240) ota_XXXX: Current slave firmware version: 2.6.0
|
||||
I (1242) ota_XXXX: New slave firmware version: 2.6.0
|
||||
W (1245) ota_XXXX: Versions match. Skipping OTA.
|
||||
I (1246) host_performs_slave_ota: OTA not required
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Issues:
|
||||
- **"No .bin files found"**: Copy slave firmware to appropriate `slave_fw_bin/` directory
|
||||
- **"Partition not found"**: Check `partitions.csv` includes required partitions
|
||||
|
||||
### Runtime Issues:
|
||||
- **"Failed to initialize ESP-Hosted"**: Check hardware connections
|
||||
- **"OTA failed"**: Check version format mismatch, ensure slave firmware uses semantic versioning
|
||||
- **WiFi issues (HTTPS)**: Verify network credentials and connectivity
|
||||
- **SSL errors (HTTPS)**: Ensure certificate CN matches server IP address
|
||||
|
||||
### Version Issues:
|
||||
- Slave firmware now uses semantic versioning (e.g., `2.5.12`) instead of git hashes
|
||||
- Host compares versions and skips OTA if versions match
|
||||
- Check slave firmware version with `esp_hosted_get_coprocessor_fwversion()`
|
||||
|
||||
## Limitation
|
||||
- Older slave firmwares (< `2.15.12`) exposed git commit as `PROJECT_VER`, instead of `X.Y.Z` Hosted slave firmware version.
|
||||
So, Comparison of `Slave firmware image version` Vs `Slave current FW version` will always fail. So OTA will be triggered.
|
||||
|
||||
---
|
||||
|
||||
## Direct UART Flashing
|
||||
|
||||
> [!NOTE]
|
||||
>
|
||||
> This section is for reference only. The main focus of this example is ESP-Hosted Transport OTA above.
|
||||
|
||||
### Slave Flashing Using ESP-Prog
|
||||
|
||||
> [!NOTE]
|
||||
> ESP-Prog is only required if you want to flash firmware to the
|
||||
> slave using the standard ESP Tools.
|
||||
|
||||
In following section, ESP32-P4-Function EV Board is considered as example, to showcase, how ESP-Prog is to be connected. Any ESP can be programmed with ESP-Prog, including Host as well.
|
||||
|
||||
The image below shows the board with an ESP-Prog connected to the header to communicate with the on-board ESP32-C6..
|
||||
|
||||
<img src="../../docs/images/esp32-p4-function-ev-board-esp-prog.jpg" alt="ESP32-P4-Function-EV-Board with ESP-Prog Connected to ESP32-C6" width="800" />
|
||||
|
||||
*ESP32-P4-Function-EV-Board with ESP-Prog Connected to ESP32-C6*
|
||||
|
||||
If you need to update the ESP-Hosted slave firmware on the on-board ESP32-C6 module using ESP-Prog, follow these steps:
|
||||
|
||||
1. Check out the ESP-Hosted slave example project:
|
||||
|
||||
```
|
||||
idf.py create-project-from-example "espressif/esp_hosted:slave"
|
||||
```
|
||||
|
||||
2. Set the target and start `Menuconfig`:
|
||||
|
||||
```sh
|
||||
idf.py set-target esp32c6
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
3. Navigate and ensure SDIO is enabled. By default it should already be enabled.
|
||||
```
|
||||
Example Configuration
|
||||
└── Bus Config in between Host and Co-processor
|
||||
└── Transport layer
|
||||
└── Select "SDIO"
|
||||
```
|
||||
|
||||
4. Build the firmware:
|
||||
|
||||
```sh
|
||||
idf.py build
|
||||
```
|
||||
|
||||
5. Connect the Program Header on the ESP-Prog to the `PROG_C6` header
|
||||
on the board. The connections are as follows:
|
||||
|
||||
| ESP-Prog | PROG_C6 | Notes |
|
||||
| --- | --- | --- |
|
||||
| ESP\_EN | EN | |
|
||||
| ESP\_TXD | TXD | |
|
||||
| ESP\_RXD | RXD | |
|
||||
| VDD | - | Do not connect |
|
||||
| GND | GND | |
|
||||
| ESP\_IO0 | IO0 | |
|
||||
|
||||
|
||||
6. Flashing the firmware
|
||||
|
||||
The on-board ESP32-P4 controls the reset signal for the ESP32-C6. To
|
||||
prevent the P4 interfering with the C6 while flashing (by asserting
|
||||
the C6 Reset signal during the firmware download), set the P4 into
|
||||
Bootloader mode before flashing the firmware to the C6:
|
||||
|
||||
###### Manual Way
|
||||
1. hold down the `BOOT` button on the board
|
||||
2. press and release the `RST` button on the board
|
||||
3. release the `BOOT` button
|
||||
|
||||
###### Script Way
|
||||
|
||||
```sh
|
||||
esptool.py -p <host_serial_port> --before default_reset --after no_reset run
|
||||
```
|
||||
|
||||
You can now flash the firmware to the C6 (and monitor the console
|
||||
output):
|
||||
|
||||
```sh
|
||||
idf.py -p <Serial Port> flash monitor
|
||||
```
|
||||
|
||||
### Flashing Slave FW From Host MCU Using direct UART
|
||||
You can connect above ESP32-C6 (slave) GPIOs directly on ESP32-P4 (Host) GPIOs and use [ESP-Serial-Flasher](https://github.com/espressif/esp-serial-flasher) to flash the Slave firmware from Host.
|
||||
|
||||
**Hardware Required**: Host MCU UART connection (dedicated UART needed, cannot use ESP-Hosted bus)
|
||||
|
||||
**Steps**:
|
||||
|
||||
1. Connect host UART to slave flashing pins
|
||||
2. Put host in bootloader mode (same as explined above)
|
||||
3. Use esp-serial-flasher library to flash over UART
|
||||
@@ -0,0 +1,64 @@
|
||||
# Common script to find the newest firmware file dynamically during build
|
||||
# This runs every time during build, not just during configuration
|
||||
#
|
||||
# Required input variables:
|
||||
# - SOURCE_COMPONENT_DIR: Path to the component source directory
|
||||
# - BINARY_COMPONENT_DIR: Path to the component binary directory
|
||||
# - OTA_ACTION: "prepare_littlefs" or "select_for_partition"
|
||||
#
|
||||
# Optional input variables:
|
||||
# - COMPONENT_NAME: Name for logging (default: "OTA")
|
||||
|
||||
if(NOT DEFINED COMPONENT_NAME)
|
||||
set(COMPONENT_NAME "OTA")
|
||||
endif()
|
||||
|
||||
# Use the passed component directories
|
||||
set(SOURCE_DIR "${SOURCE_COMPONENT_DIR}/slave_fw_bin")
|
||||
set(TEMP_DIR "${BINARY_COMPONENT_DIR}/temp_littlefs")
|
||||
|
||||
message(STATUS "${COMPONENT_NAME}: Dynamically searching for newest firmware...")
|
||||
message(STATUS "${COMPONENT_NAME}: Searching in ${SOURCE_DIR}")
|
||||
|
||||
# Get all .bin files
|
||||
file(GLOB FIRMWARE_FILES "${SOURCE_DIR}/*.bin")
|
||||
|
||||
if(NOT FIRMWARE_FILES)
|
||||
message(FATAL_ERROR "${COMPONENT_NAME}: No .bin files found in ${SOURCE_DIR}")
|
||||
endif()
|
||||
|
||||
# Find the newest file by timestamp
|
||||
set(NEWEST_FILE "")
|
||||
set(NEWEST_TIMESTAMP 0)
|
||||
|
||||
foreach(FIRMWARE_FILE ${FIRMWARE_FILES})
|
||||
file(TIMESTAMP "${FIRMWARE_FILE}" FILE_TIMESTAMP "%s")
|
||||
message(STATUS "${COMPONENT_NAME}: File ${FIRMWARE_FILE} timestamp: ${FILE_TIMESTAMP}")
|
||||
if(FILE_TIMESTAMP GREATER NEWEST_TIMESTAMP)
|
||||
set(NEWEST_FILE "${FIRMWARE_FILE}")
|
||||
set(NEWEST_TIMESTAMP "${FILE_TIMESTAMP}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
get_filename_component(NEWEST_FILENAME "${NEWEST_FILE}" NAME)
|
||||
message(STATUS "${COMPONENT_NAME}: Selected newest firmware: ${NEWEST_FILENAME}")
|
||||
|
||||
# Perform the requested action
|
||||
if(OTA_ACTION STREQUAL "prepare_littlefs")
|
||||
# Clean and recreate temp directory for LittleFS
|
||||
file(REMOVE_RECURSE "${TEMP_DIR}")
|
||||
file(MAKE_DIRECTORY "${TEMP_DIR}")
|
||||
file(COPY "${NEWEST_FILE}" DESTINATION "${TEMP_DIR}")
|
||||
message(STATUS "${COMPONENT_NAME}: Prepared ${NEWEST_FILENAME} in temp directory for LittleFS")
|
||||
|
||||
elseif(OTA_ACTION STREQUAL "select_for_partition")
|
||||
# Write selection to files for partition OTA
|
||||
file(WRITE "${BINARY_COMPONENT_DIR}/selected_firmware.txt" "${NEWEST_FILE}")
|
||||
file(WRITE "${BINARY_COMPONENT_DIR}/selected_firmware_name.txt" "${NEWEST_FILENAME}")
|
||||
message(STATUS "${COMPONENT_NAME}: Saved firmware selection for partition flashing")
|
||||
|
||||
else()
|
||||
message(FATAL_ERROR "${COMPONENT_NAME}: Unknown OTA_ACTION: ${OTA_ACTION}")
|
||||
endif()
|
||||
|
||||
message(STATUS "${COMPONENT_NAME}: Firmware selection completed")
|
||||
@@ -0,0 +1,61 @@
|
||||
# Script to flash the dynamically selected firmware for partition OTA
|
||||
# Reads the firmware path from the selection file and flashes it
|
||||
#
|
||||
# Required input variables:
|
||||
# - BINARY_COMPONENT_DIR: Path to the component binary directory
|
||||
# - SLAVE_FW_OFFSET: Partition offset for flashing
|
||||
# - PYTHON: Python executable path
|
||||
|
||||
if(NOT DEFINED COMPONENT_NAME)
|
||||
set(COMPONENT_NAME "Partition OTA")
|
||||
endif()
|
||||
|
||||
# Read the selected firmware path and name
|
||||
set(SELECTION_FILE "${BINARY_COMPONENT_DIR}/selected_firmware.txt")
|
||||
set(NAME_FILE "${BINARY_COMPONENT_DIR}/selected_firmware_name.txt")
|
||||
|
||||
if(NOT EXISTS "${SELECTION_FILE}")
|
||||
message(FATAL_ERROR "${COMPONENT_NAME}: Selection file not found: ${SELECTION_FILE}")
|
||||
endif()
|
||||
|
||||
file(READ "${SELECTION_FILE}" SELECTED_FIRMWARE_PATH)
|
||||
file(READ "${NAME_FILE}" SELECTED_FIRMWARE_NAME)
|
||||
|
||||
# Remove any trailing newlines
|
||||
string(STRIP "${SELECTED_FIRMWARE_PATH}" SELECTED_FIRMWARE_PATH)
|
||||
string(STRIP "${SELECTED_FIRMWARE_NAME}" SELECTED_FIRMWARE_NAME)
|
||||
|
||||
message(STATUS "-----------------------------------------------------")
|
||||
message(STATUS "${COMPONENT_NAME}: Flashing ${SELECTED_FIRMWARE_NAME}")
|
||||
message(STATUS "Path: ${SELECTED_FIRMWARE_PATH}")
|
||||
message(STATUS "Offset: ${SLAVE_FW_OFFSET}")
|
||||
message(STATUS "-----------------------------------------------------")
|
||||
|
||||
# Check if file exists
|
||||
if(NOT EXISTS "${SELECTED_FIRMWARE_PATH}")
|
||||
message(FATAL_ERROR "❌ ERROR: Selected firmware file not found: ${SELECTED_FIRMWARE_PATH}")
|
||||
endif()
|
||||
|
||||
# Execute esptool (this will inherit the port from the parent build process)
|
||||
execute_process(
|
||||
COMMAND ${PYTHON} -m esptool
|
||||
-b 2000000
|
||||
write-flash
|
||||
--force
|
||||
${SLAVE_FW_OFFSET} "${SELECTED_FIRMWARE_PATH}"
|
||||
RESULT_VARIABLE FLASH_RESULT
|
||||
OUTPUT_VARIABLE FLASH_OUTPUT
|
||||
ERROR_VARIABLE FLASH_ERROR
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
if(FLASH_OUTPUT)
|
||||
message(STATUS "${FLASH_OUTPUT}")
|
||||
endif()
|
||||
|
||||
if(NOT FLASH_RESULT EQUAL 0)
|
||||
message(FATAL_ERROR "❌ ERROR: Failed to flash firmware:\n${FLASH_ERROR}")
|
||||
endif()
|
||||
|
||||
message(STATUS "✅ Successfully flashed ${SELECTED_FIRMWARE_NAME} to partition!")
|
||||
@@ -0,0 +1,28 @@
|
||||
if(CONFIG_OTA_METHOD_HTTPS)
|
||||
set(SRC_LIST "ota_https.c" "ota_https_wifi.c")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${SRC_LIST}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES esp_http_client app_update esp_wifi esp_hosted nvs_flash esp_https_ota mbedtls esp-tls bootloader_support esp_app_format)
|
||||
|
||||
# Conditional certificate embedding based on Kconfig
|
||||
if(CONFIG_OTA_METHOD_HTTPS)
|
||||
if(CONFIG_OTA_USE_SELF_SIGNED_CERT)
|
||||
if(EXISTS "${COMPONENT_DIR}/certs/server_cert.pem")
|
||||
target_add_binary_data(
|
||||
${COMPONENT_LIB}
|
||||
"certs/server_cert.pem"
|
||||
TEXT
|
||||
)
|
||||
message(STATUS "✓ HTTPS OTA: Embedding self-signed certificate for testing")
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"HTTPS OTA: Self-signed cert enabled but certs/server_cert.pem not found! \n"
|
||||
"1. Run ./create_self_signed_certs.sh in components/ota_https/test_server directory to generate certificates.\n"
|
||||
"2. Run ./create_https_server.py in components/ota_https/test_server directory to start the HTTPS server.")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "✓ HTTPS OTA: Using CA certificate bundle (Production mode)")
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,394 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_http_client.h"
|
||||
#include "esp_crt_bundle.h" // For certificate bundle
|
||||
#include "sdkconfig.h" // For configuration macros
|
||||
#include "esp_app_format.h"
|
||||
#include "esp_app_desc.h"
|
||||
#include "esp_hosted_api_types.h"
|
||||
|
||||
#ifndef CHUNK_SIZE
|
||||
#define CHUNK_SIZE 1400
|
||||
#endif
|
||||
|
||||
static const char* TAG = "https_ota";
|
||||
|
||||
extern esp_err_t establish_wifi_connection(void);
|
||||
|
||||
/* Self-signed certificate - embedded at compile time (only if enabled) */
|
||||
#ifdef CONFIG_OTA_USE_SELF_SIGNED_CERT
|
||||
extern const uint8_t server_cert_pem_start[] asm("_binary_server_cert_pem_start");
|
||||
extern const uint8_t server_cert_pem_end[] asm("_binary_server_cert_pem_end");
|
||||
#endif
|
||||
|
||||
uint8_t http_err = 0;
|
||||
|
||||
/* Function to parse ESP32 image header and get firmware info from buffer */
|
||||
static esp_err_t parse_image_header_from_buffer(const uint8_t* buffer, size_t buffer_size, size_t* firmware_size, char* app_version_str, size_t version_str_len)
|
||||
{
|
||||
esp_image_header_t image_header;
|
||||
esp_image_segment_header_t segment_header;
|
||||
esp_app_desc_t app_desc;
|
||||
size_t offset = 0;
|
||||
size_t total_size = 0;
|
||||
|
||||
/* Check if buffer has enough data for image header */
|
||||
if (buffer_size < sizeof(image_header)) {
|
||||
ESP_LOGE(TAG, "Buffer too small for image header verification");
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
/* Read image header from buffer */
|
||||
memcpy(&image_header, buffer + offset, sizeof(image_header));
|
||||
|
||||
/* Validate magic number */
|
||||
if (image_header.magic != ESP_IMAGE_HEADER_MAGIC) {
|
||||
ESP_LOGE(TAG, "Invalid image magic: 0x%" PRIx8, image_header.magic);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Image header: magic=0x%" PRIx8 ", segment_count=%" PRIu8 ", hash_appended=%" PRIu8,
|
||||
image_header.magic, image_header.segment_count, image_header.hash_appended);
|
||||
|
||||
/* Calculate total size by reading all segments */
|
||||
offset = sizeof(image_header);
|
||||
total_size = sizeof(image_header);
|
||||
|
||||
for (int i = 0; i < image_header.segment_count; i++) {
|
||||
/* Check if buffer has enough data for segment header */
|
||||
if (buffer_size < offset + sizeof(segment_header)) {
|
||||
ESP_LOGW(TAG, "Buffer too small to read all segment headers, using partial verification");
|
||||
break;
|
||||
}
|
||||
|
||||
/* Read segment header from buffer */
|
||||
memcpy(&segment_header, buffer + offset, sizeof(segment_header));
|
||||
|
||||
ESP_LOGI(TAG, "Segment %d: data_len=%" PRIu32 ", load_addr=0x%" PRIx32, i, segment_header.data_len, segment_header.load_addr);
|
||||
|
||||
/* Add segment header size + data size */
|
||||
total_size += sizeof(segment_header) + segment_header.data_len;
|
||||
offset += sizeof(segment_header) + segment_header.data_len;
|
||||
|
||||
/* Read app description from the first segment */
|
||||
if (i == 0) {
|
||||
size_t app_desc_offset = sizeof(image_header) + sizeof(segment_header);
|
||||
if (buffer_size >= app_desc_offset + sizeof(app_desc)) {
|
||||
memcpy(&app_desc, buffer + app_desc_offset, sizeof(app_desc));
|
||||
strncpy(app_version_str, app_desc.version, version_str_len - 1);
|
||||
app_version_str[version_str_len - 1] = '\0';
|
||||
ESP_LOGI(TAG, "Found app description: version='%s', project_name='%s'",
|
||||
app_desc.version, app_desc.project_name);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Buffer too small to read app description");
|
||||
strncpy(app_version_str, "unknown", version_str_len - 1);
|
||||
app_version_str[version_str_len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add padding to align to 16 bytes */
|
||||
size_t padding = (16 - (total_size % 16)) % 16;
|
||||
if (padding > 0) {
|
||||
ESP_LOGD(TAG, "Adding %u bytes of padding for alignment", (unsigned int)padding);
|
||||
total_size += padding;
|
||||
}
|
||||
|
||||
/* Add the checksum byte (always present) */
|
||||
total_size += 1;
|
||||
ESP_LOGD(TAG, "Added 1 byte for checksum");
|
||||
|
||||
/* Add SHA256 hash if appended */
|
||||
bool has_hash = (image_header.hash_appended == 1);
|
||||
if (has_hash) {
|
||||
total_size += 32; // SHA256 hash is 32 bytes
|
||||
ESP_LOGD(TAG, "Added 32 bytes for SHA256 hash (hash_appended=1)");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "No SHA256 hash appended (hash_appended=0)");
|
||||
}
|
||||
|
||||
*firmware_size = total_size;
|
||||
ESP_LOGI(TAG, "Total image size: %u bytes", (unsigned int)*firmware_size);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t http_client_event_handler(esp_http_client_event_t *evt)
|
||||
{
|
||||
switch(evt->event_id) {
|
||||
case HTTP_EVENT_ERROR:
|
||||
ESP_LOGE(TAG, "HTTP_EVENT_ERROR");
|
||||
http_err = 1;
|
||||
break;
|
||||
case HTTP_EVENT_ON_CONNECTED:
|
||||
ESP_LOGI(TAG, "HTTPS_EVENT_ON_CONNECTED - SSL handshake successful");
|
||||
break;
|
||||
case HTTP_EVENT_HEADER_SENT:
|
||||
ESP_LOGI(TAG, "HTTPS_EVENT_HEADER_SENT");
|
||||
break;
|
||||
case HTTP_EVENT_ON_HEADER:
|
||||
ESP_LOGD(TAG, "HTTPS_EVENT_ON_HEADER: %s=%s", evt->header_key, evt->header_value);
|
||||
if (strcmp(evt->header_key, "Content-Length") == 0) {
|
||||
ESP_LOGI(TAG, "Content-Length: %s bytes", evt->header_value);
|
||||
}
|
||||
break;
|
||||
case HTTP_EVENT_ON_DATA:
|
||||
/* Data received - logged elsewhere */
|
||||
break;
|
||||
case HTTP_EVENT_ON_FINISH:
|
||||
ESP_LOGI(TAG, "HTTPS_EVENT_ON_FINISH - Transfer complete");
|
||||
break;
|
||||
case HTTP_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "HTTPS_EVENT_DISCONNECTED");
|
||||
break;
|
||||
case HTTP_EVENT_REDIRECT:
|
||||
ESP_LOGW(TAG, "HTTPS_EVENT_REDIRECT");
|
||||
break;
|
||||
default:
|
||||
ESP_LOGD(TAG, "Unhandled HTTPS event id: %d", evt->event_id);
|
||||
break;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ota_https_perform(const char* image_url)
|
||||
{
|
||||
uint8_t *ota_chunk = NULL;
|
||||
esp_err_t err = ESP_OK;
|
||||
int data_read = 0;
|
||||
int ota_failed = 0;
|
||||
|
||||
if ((image_url == NULL) || (image_url[0] == '\0')) {
|
||||
ESP_LOGE(TAG, "Invalid image URL");
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
// Validate HTTPS URL
|
||||
if (strncmp(image_url, "https://", 8) != 0) {
|
||||
ESP_LOGE(TAG, "URL must use HTTPS protocol");
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
err = establish_wifi_connection();
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "WiFi connection failed: %s", esp_err_to_name(err));
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting HTTPS OTA from URL: %s", image_url);
|
||||
|
||||
#ifdef CONFIG_OTA_USE_SELF_SIGNED_CERT
|
||||
// Log certificate information for self-signed mode
|
||||
size_t cert_len = server_cert_pem_end - server_cert_pem_start;
|
||||
ESP_LOGI(TAG, "Security: Self-signed certificate (Testing mode)");
|
||||
ESP_LOGI(TAG, "Certificate size: %u bytes", (unsigned int)cert_len);
|
||||
|
||||
if (cert_len == 0) {
|
||||
ESP_LOGE(TAG, "Certificate not embedded properly! Check CMakeLists.txt");
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "Security: CA Certificate Bundle (Production mode)");
|
||||
ESP_LOGI(TAG, "Supports: Let's Encrypt, DigiCert, and 200+ CAs");
|
||||
#endif
|
||||
|
||||
esp_http_client_config_t config = {
|
||||
.url = image_url,
|
||||
.timeout_ms = CONFIG_OTA_HTTPS_TIMEOUT_MS,
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL, // Force HTTPS
|
||||
.event_handler = http_client_event_handler,
|
||||
.buffer_size = 8192, // Larger buffer for SSL
|
||||
.buffer_size_tx = 4096, // Increased TX buffer
|
||||
|
||||
#ifdef CONFIG_OTA_USE_SELF_SIGNED_CERT
|
||||
/* TESTING MODE - Self-signed certificate */
|
||||
.cert_pem = (const char *)server_cert_pem_start,
|
||||
.cert_len = server_cert_pem_end - server_cert_pem_start,
|
||||
.skip_cert_common_name_check = CONFIG_OTA_SKIP_CERT_CN_CHECK,
|
||||
.use_global_ca_store = false, // Use only our certificate
|
||||
#else
|
||||
/* PRODUCTION MODE - CA certificate bundle */
|
||||
.crt_bundle_attach = esp_crt_bundle_attach,
|
||||
.skip_cert_common_name_check = false, // Always validate CN in production
|
||||
#endif
|
||||
|
||||
.keep_alive_enable = true,
|
||||
.keep_alive_idle = 5,
|
||||
.keep_alive_interval = 5,
|
||||
.keep_alive_count = 3,
|
||||
};
|
||||
|
||||
esp_http_client_handle_t client = esp_http_client_init(&config);
|
||||
if (client == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to initialize HTTPS client");
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
/* Open connection */
|
||||
ESP_LOGI(TAG, "Opening HTTPS connection...");
|
||||
if ((err = esp_http_client_open(client, 0)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open HTTPS connection: %s", esp_err_to_name(err));
|
||||
ESP_LOGE(TAG, "Common causes:");
|
||||
ESP_LOGE(TAG, " - Certificate CN doesn't match server IP");
|
||||
ESP_LOGE(TAG, " - Server not running or unreachable");
|
||||
ESP_LOGE(TAG, " - WiFi connection issues");
|
||||
ESP_LOGE(TAG, " - Firewall blocking port 8443");
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
if (http_err) {
|
||||
ESP_LOGE(TAG, "Exiting OTA, due to http failure");
|
||||
esp_http_client_cleanup(client);
|
||||
http_err = 0;
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
/* Fetch headers */
|
||||
ESP_LOGI(TAG, "Fetching HTTPS headers...");
|
||||
int64_t content_length = esp_http_client_fetch_headers(client);
|
||||
|
||||
int http_status = esp_http_client_get_status_code(client);
|
||||
if (http_status != 200) {
|
||||
ESP_LOGE(TAG, "HTTPS request failed with status: %d", http_status);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
if (content_length <= 0) {
|
||||
ESP_LOGE(TAG, "HTTP client fetch headers failed");
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %"PRId64,
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "HTTP GET Status = %d, content_length = %"PRId64,
|
||||
esp_http_client_get_status_code(client),
|
||||
esp_http_client_get_content_length(client));
|
||||
|
||||
/* Begin OTA */
|
||||
ESP_LOGI(TAG, "Preparing OTA");
|
||||
if ((err = esp_hosted_slave_ota_begin()) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_hosted_slave_ota_begin failed: %s", esp_err_to_name(err));
|
||||
ESP_LOGI(TAG, "esp_ota_begin failed, error=%s", esp_err_to_name(err));
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ota_chunk = (uint8_t*)calloc(1, CHUNK_SIZE);
|
||||
if (!ota_chunk) {
|
||||
ESP_LOGE(TAG, "Failed to allocate OTA chunk memory");
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting OTA data transfer over HTTPS");
|
||||
|
||||
/* Read and write OTA data */
|
||||
bool header_verified = false;
|
||||
int chunk_count = 0;
|
||||
|
||||
while ((data_read = esp_http_client_read(client, (char*)ota_chunk, CHUNK_SIZE)) > 0) {
|
||||
ESP_LOGD(TAG, "Read image length %d", data_read);
|
||||
|
||||
/* Verify image header from the first chunk */
|
||||
if (!header_verified && chunk_count == 0) {
|
||||
size_t firmware_size;
|
||||
char app_version[32];
|
||||
|
||||
ESP_LOGI(TAG, "Verifying image header from first chunk (%d bytes)", data_read);
|
||||
if ((err = parse_image_header_from_buffer(ota_chunk, data_read, &firmware_size, app_version, sizeof(app_version))) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Image header verification failed: %s", esp_err_to_name(err));
|
||||
ota_failed = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Image verified - Size: %u bytes, Version: %s", (unsigned int)firmware_size, app_version);
|
||||
|
||||
#ifdef CONFIG_OTA_VERSION_CHECK_SLAVEFW_SLAVE
|
||||
/* Get current running slave firmware version and compare */
|
||||
esp_hosted_coprocessor_fwver_t current_slave_version = {0};
|
||||
esp_err_t version_ret = esp_hosted_get_coprocessor_fwversion(¤t_slave_version);
|
||||
|
||||
if (version_ret == ESP_OK) {
|
||||
char current_version_str[32];
|
||||
snprintf(current_version_str, sizeof(current_version_str), "%" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
current_slave_version.major1, current_slave_version.minor1, current_slave_version.patch1);
|
||||
|
||||
ESP_LOGI(TAG, "Current slave firmware version: %s", current_version_str);
|
||||
ESP_LOGI(TAG, "New slave firmware version: %s", app_version);
|
||||
|
||||
if (strcmp(app_version, current_version_str) == 0) {
|
||||
ESP_LOGW(TAG, "Current slave firmware version (%s) is the same as new version (%s). Skipping OTA.",
|
||||
current_version_str, app_version);
|
||||
/* Cleanup and return success */
|
||||
free(ota_chunk);
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_NOT_REQUIRED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Version differs - proceeding with OTA from %s to %s", current_version_str, app_version);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Could not get current slave firmware version (error: %s), proceeding with OTA",
|
||||
esp_err_to_name(version_ret));
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "Version check disabled - proceeding with OTA (new firmware version: %s)", app_version);
|
||||
#endif
|
||||
|
||||
header_verified = true;
|
||||
}
|
||||
|
||||
if ((err = esp_hosted_slave_ota_write(ota_chunk, data_read)) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_hosted_slave_ota_write failed: %s", esp_err_to_name(err));
|
||||
ota_failed = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
chunk_count++;
|
||||
}
|
||||
|
||||
/* Cleanup resources */
|
||||
free(ota_chunk);
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
|
||||
/* Check for read errors */
|
||||
if (data_read < 0) {
|
||||
ESP_LOGE(TAG, "Error: HTTPS data read error");
|
||||
ota_failed = 1;
|
||||
}
|
||||
|
||||
/* End OTA */
|
||||
if ((err = esp_hosted_slave_ota_end()) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ota_end failed, error=%s", esp_err_to_name(err));
|
||||
esp_http_client_close(client);
|
||||
esp_http_client_cleanup(client);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
/* Final result */
|
||||
if (ota_failed) {
|
||||
ESP_LOGE(TAG, "********* Slave OTA Failed *******************");
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
} else {
|
||||
ESP_LOGI(TAG, "********* Slave OTA Complete *******************");
|
||||
return ESP_HOSTED_SLAVE_OTA_COMPLETED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Perform HTTP OTA update
|
||||
*
|
||||
* @param url URL to download firmware from
|
||||
* @return esp_err_t ESP_OK on success
|
||||
*/
|
||||
esp_err_t ota_https_perform(const char* url);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
/* Miniature version of esp-idf example of Wi-Fi station,
|
||||
* <esp-idf>/examples/wifi/getting_started/station/main/station_example_main.c
|
||||
* */
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/event_groups.h"
|
||||
#include "esp_system.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
#include "lwip/err.h"
|
||||
#include "lwip/sys.h"
|
||||
|
||||
#ifdef CONFIG_OTA_METHOD_HTTPS
|
||||
#include "nvs_flash.h"
|
||||
|
||||
static const char* TAG = "ota_https_wifi";
|
||||
|
||||
#define EXAMPLE_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
|
||||
#define EXAMPLE_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
|
||||
#define EXAMPLE_ESP_MAXIMUM_RETRY CONFIG_ESP_MAXIMUM_RETRY
|
||||
|
||||
#define WIFI_CONNECTED_BIT BIT0
|
||||
#define WIFI_FAIL_BIT BIT1
|
||||
|
||||
/* FreeRTOS event group to signal when we are connected*/
|
||||
static EventGroupHandle_t s_wifi_event_group;
|
||||
|
||||
static int s_retry_num = 0;
|
||||
|
||||
|
||||
static void event_handler(void* arg, esp_event_base_t event_base,
|
||||
int32_t event_id, void* event_data)
|
||||
{
|
||||
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
|
||||
esp_wifi_connect();
|
||||
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
|
||||
if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) {
|
||||
esp_wifi_connect();
|
||||
s_retry_num++;
|
||||
ESP_LOGI(TAG, "retry to connect to the AP");
|
||||
} else {
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
|
||||
}
|
||||
ESP_LOGI(TAG,"connect to the AP fail");
|
||||
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
|
||||
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
|
||||
ESP_LOGI(TAG, "got ip:" IPSTR, IP2STR(&event->ip_info.ip));
|
||||
s_retry_num = 0;
|
||||
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_init_sta(void)
|
||||
{
|
||||
s_wifi_event_group = xEventGroupCreate();
|
||||
|
||||
esp_netif_init();
|
||||
|
||||
esp_event_loop_create_default();
|
||||
esp_netif_create_default_wifi_sta();
|
||||
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
esp_event_handler_instance_t instance_any_id;
|
||||
esp_event_handler_instance_t instance_got_ip;
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
|
||||
ESP_EVENT_ANY_ID,
|
||||
&event_handler,
|
||||
NULL,
|
||||
&instance_any_id));
|
||||
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
|
||||
IP_EVENT_STA_GOT_IP,
|
||||
&event_handler,
|
||||
NULL,
|
||||
&instance_got_ip));
|
||||
|
||||
wifi_config_t wifi_config = {
|
||||
.sta = {
|
||||
.ssid = EXAMPLE_ESP_WIFI_SSID,
|
||||
.password = EXAMPLE_ESP_WIFI_PASS,
|
||||
},
|
||||
};
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
|
||||
ESP_ERROR_CHECK(esp_wifi_start() );
|
||||
|
||||
ESP_LOGI(TAG, "wifi_init_sta finished.");
|
||||
|
||||
/* Waiting until either the connection is established (WIFI_CONNECTED_BIT) or connection failed for the maximum
|
||||
* number of re-tries (WIFI_FAIL_BIT). The bits are set by event_handler() (see above) */
|
||||
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
|
||||
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
|
||||
pdFALSE,
|
||||
pdFALSE,
|
||||
portMAX_DELAY);
|
||||
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
|
||||
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
|
||||
} else if (bits & WIFI_FAIL_BIT) {
|
||||
ESP_LOGI(TAG, "Failed to connect to SSID:%s, password:%s",
|
||||
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "UNEXPECTED EVENT");
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t establish_wifi_connection(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
|
||||
wifi_init_sta();
|
||||
|
||||
EventBits_t bits = xEventGroupGetBits(s_wifi_event_group);
|
||||
if (bits & WIFI_CONNECTED_BIT) {
|
||||
return ESP_OK;
|
||||
} else if (bits & WIFI_FAIL_BIT) {
|
||||
ESP_LOGE(TAG, "WiFi connection failed after maximum retries");
|
||||
return ESP_FAIL;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "WiFi connection in unexpected state");
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,58 @@
|
||||
# Test Server Tools
|
||||
|
||||
This directory contains tools for testing HTTPS OTA functionality with self-signed certificates.
|
||||
|
||||
## Files
|
||||
|
||||
- `create_self_signed_certs.sh` - Generates self-signed certificates for testing
|
||||
- `create_https_server.py` - Simple HTTPS server for serving OTA files
|
||||
- `README.md` - This file
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Generate Test Certificates
|
||||
```bash
|
||||
cd test_server
|
||||
bash create_self_signed_certs.sh
|
||||
```
|
||||
|
||||
### 2. Start HTTPS Server
|
||||
```bash
|
||||
cd test_server
|
||||
python3 create_https_server.py
|
||||
```
|
||||
|
||||
### 3. Configure ESP32
|
||||
```bash
|
||||
# From project root
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
Enable in menuconfig:
|
||||
- `Component config` → `HTTPS OTA Configuration` → `Use self-signed certificate (Testing Only)` → **YES**
|
||||
- Update `OTA Server URL` to the URL shown by the server
|
||||
|
||||
## Important Notes
|
||||
|
||||
- **Testing Only**: Self-signed certificates should only be used for testing
|
||||
- **Security**: For production, use proper CA-signed certificates
|
||||
- **IP Address**: The certificate is generated for your current IP address
|
||||
- **Regenerate**: If your IP changes, regenerate certificates
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Certificate not found error
|
||||
```bash
|
||||
# Make sure you're in test_server directory
|
||||
cd test_server
|
||||
bash create_self_signed_certs.sh
|
||||
```
|
||||
|
||||
### Server won't start
|
||||
- Check if port 8443 is available
|
||||
- Ensure certificates exist in `../certs/`
|
||||
```
|
||||
ls ../certs/
|
||||
server_cert.pem server_key.pem
|
||||
```
|
||||
- Run from `test_server/` directory
|
||||
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
import http.server
|
||||
import ssl
|
||||
import socket
|
||||
import os
|
||||
import sys
|
||||
|
||||
def get_local_ip():
|
||||
"""Get the local IP address of this machine"""
|
||||
try:
|
||||
# Connect to a remote address (doesn't actually send data)
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
||||
s.connect(("8.8.8.8", 80))
|
||||
return s.getsockname()[0]
|
||||
except Exception:
|
||||
return "127.0.0.1"
|
||||
|
||||
# Check if certificates exist (relative to component certs)
|
||||
cert_path = '../certs/server_cert.pem'
|
||||
key_path = '../certs/server_key.pem'
|
||||
|
||||
if not os.path.exists(cert_path) or not os.path.exists(key_path):
|
||||
print("ERROR: Certificates not found!")
|
||||
print(f"Please run: bash create_self_signed_certs.sh")
|
||||
print(f"Looking for: {cert_path} and {key_path}")
|
||||
print(f"Note: Run this server from components/ota_https/test_server/ directory")
|
||||
sys.exit(1)
|
||||
|
||||
# Get local IP
|
||||
local_ip = get_local_ip()
|
||||
port = 8443
|
||||
|
||||
# Create HTTPS server
|
||||
class CORSHTTPRequestHandler(http.server.SimpleHTTPRequestHandler):
|
||||
def end_headers(self):
|
||||
self.send_header('Access-Control-Allow-Origin', '*')
|
||||
self.send_header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS')
|
||||
self.send_header('Access-Control-Allow-Headers', '*')
|
||||
super().end_headers()
|
||||
|
||||
def log_message(self, format, *args):
|
||||
"""Enhanced logging"""
|
||||
print(f"[{self.date_time_string()}] {format % args}")
|
||||
|
||||
server_address = ('0.0.0.0', port)
|
||||
httpd = http.server.HTTPServer(server_address, CORSHTTPRequestHandler)
|
||||
|
||||
# Configure SSL with proper settings for ESP32
|
||||
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ssl_context.minimum_version = ssl.TLSVersion.TLSv1_2
|
||||
ssl_context.set_ciphers('ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS')
|
||||
ssl_context.load_cert_chain(cert_path, key_path)
|
||||
|
||||
# Wrap socket with SSL
|
||||
httpd.socket = ssl_context.wrap_socket(httpd.socket, server_side=True)
|
||||
|
||||
print("=" * 60)
|
||||
print("HTTPS OTA Server Started Successfully!")
|
||||
print("=" * 60)
|
||||
print(f"Server running on: https://{local_ip}:{port}")
|
||||
print(f"Serving files from: {os.getcwd()}")
|
||||
print(f"Using certificates:")
|
||||
print(f" Cert: {cert_path}")
|
||||
print(f" Key: {key_path}")
|
||||
print("")
|
||||
print("ESP32 Configuration:")
|
||||
print(f" Set OTA_HTTP_URL to: https://{local_ip}:{port}/firmware.bin")
|
||||
print("")
|
||||
print("To test, place your firmware.bin file in this directory")
|
||||
print("Press Ctrl+C to stop the server")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
httpd.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\n🛑 Server stopped by user")
|
||||
httpd.server_close()
|
||||
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Create certificates for HTTPS OTA testing
|
||||
# This script should be run from the component's test_server directory
|
||||
|
||||
# Create a directory for certificates in the component's certs folder
|
||||
mkdir -p ../certs
|
||||
cd ../certs
|
||||
|
||||
# Get your machine's IP address
|
||||
IP_ADDRESS=$(ipconfig getifaddr en0 2>/dev/null || ipconfig getifaddr en1 2>/dev/null || echo "192.168.1.100")
|
||||
echo "Creating certificate for IP: $IP_ADDRESS"
|
||||
echo "If this IP is incorrect, please update the CN field manually"
|
||||
|
||||
# Generate private key
|
||||
openssl genrsa -out server_key.pem 2048
|
||||
|
||||
# Generate self-signed certificate (valid for 365 days)
|
||||
# Use your actual IP address as Common Name
|
||||
openssl req -new -x509 -key server_key.pem -out server_cert.pem -days 365 \
|
||||
-subj "/C=US/ST=State/L=City/O=ESP-OTA/CN=$IP_ADDRESS" \
|
||||
-extensions v3_req \
|
||||
-config <(cat <<EOF
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = v3_req
|
||||
prompt = no
|
||||
|
||||
[req_distinguished_name]
|
||||
C = US
|
||||
ST = State
|
||||
L = City
|
||||
O = ESP-OTA
|
||||
CN = $IP_ADDRESS
|
||||
|
||||
[v3_req]
|
||||
basicConstraints = CA:FALSE
|
||||
keyUsage = nonRepudiation, digitalSignature, keyEncipherment
|
||||
subjectAltName = @alt_names
|
||||
|
||||
[alt_names]
|
||||
IP.1 = $IP_ADDRESS
|
||||
IP.2 = 127.0.0.1
|
||||
DNS.1 = localhost
|
||||
EOF
|
||||
)
|
||||
|
||||
# View certificate details
|
||||
echo "Certificate created with the following details:"
|
||||
openssl x509 -in server_cert.pem -text -noout | grep -A 5 "Subject:"
|
||||
openssl x509 -in server_cert.pem -text -noout | grep -A 10 "Subject Alternative Name"
|
||||
|
||||
echo ""
|
||||
echo "IMPORTANT: Your HTTPS server must run on IP: $IP_ADDRESS"
|
||||
echo "Update your Kconfig OTA_HTTP_URL to: https://$IP_ADDRESS:8443/firmware.bin"
|
||||
@@ -0,0 +1,55 @@
|
||||
# LittleFS OTA Component CMakeLists.txt
|
||||
# This component handles LittleFS-based OTA updates
|
||||
|
||||
# Only process firmware files if LittleFS OTA is actually selected
|
||||
message(STATUS "🔍 LittleFS OTA: CONFIG_OTA_METHOD_LITTLEFS = ${CONFIG_OTA_METHOD_LITTLEFS}")
|
||||
if(CONFIG_OTA_METHOD_LITTLEFS)
|
||||
set(SRC_LIST "ota_littlefs.c")
|
||||
|
||||
# Check if we have any firmware files (just for early validation)
|
||||
file(GLOB SLAVE_FW_BIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/slave_fw_bin/*.bin")
|
||||
if(NOT SLAVE_FW_BIN_FILES)
|
||||
message(WARNING "🔴 LittleFS OTA: No .bin files found in slave_fw_bin directory!")
|
||||
endif()
|
||||
else()
|
||||
# LittleFS OTA not selected, skip firmware processing
|
||||
message(STATUS "LittleFS OTA: Not selected, skipping firmware processing")
|
||||
endif()
|
||||
|
||||
# Register the component
|
||||
idf_component_register(SRCS ${SRC_LIST}
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES "littlefs" "esp_hosted" "bootloader_support" "esp_app_format")
|
||||
|
||||
# Create LittleFS partition image after component registration
|
||||
if(CONFIG_OTA_METHOD_LITTLEFS)
|
||||
set(TEMP_LITTLEFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/temp_littlefs")
|
||||
|
||||
# Create a custom target that runs the common script to find newest firmware dynamically
|
||||
add_custom_target(prepare_littlefs_temp
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_COMPONENT_DIR=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-DBINARY_COMPONENT_DIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
-DCOMPONENT_NAME=LittleFS OTA
|
||||
-DOTA_ACTION=prepare_littlefs
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/../common_ota_scripts/find_newest_firmware.cmake
|
||||
COMMENT "LittleFS OTA: Dynamically finding and preparing newest firmware"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Make the target depend on all firmware files so it rebuilds when they change
|
||||
file(GLOB CURRENT_FIRMWARE_FILES "${CMAKE_CURRENT_SOURCE_DIR}/slave_fw_bin/*.bin")
|
||||
if(CURRENT_FIRMWARE_FILES)
|
||||
add_custom_target(firmware_files_dependency DEPENDS ${CURRENT_FIRMWARE_FILES})
|
||||
add_dependencies(prepare_littlefs_temp firmware_files_dependency)
|
||||
endif()
|
||||
|
||||
# Create the LittleFS partition image with proper dependency
|
||||
littlefs_create_partition_image(storage ${TEMP_LITTLEFS_DIR} FLASH_IN_PROJECT DEPENDS prepare_littlefs_temp)
|
||||
|
||||
# Clean up: Remove the temporary directory after image creation
|
||||
add_custom_command(TARGET ${COMPONENT_LIB} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E remove_directory ${TEMP_LITTLEFS_DIR}
|
||||
COMMENT "LittleFS OTA: Cleaning up temporary directory"
|
||||
)
|
||||
endif()
|
||||
@@ -0,0 +1,369 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_littlefs.h"
|
||||
#include "esp_hosted_ota.h"
|
||||
#include <unistd.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_app_format.h"
|
||||
#include "esp_app_desc.h"
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_api_types.h"
|
||||
|
||||
static const char* TAG = "ota_littlefs";
|
||||
|
||||
#ifndef CHUNK_SIZE
|
||||
#define CHUNK_SIZE 1500
|
||||
#endif
|
||||
|
||||
/* Function to parse ESP32 image header and get firmware info from file */
|
||||
static esp_err_t parse_image_header_from_file(const char* file_path, size_t* firmware_size, char* app_version_str, size_t version_str_len)
|
||||
{
|
||||
FILE* file;
|
||||
esp_image_header_t image_header;
|
||||
esp_image_segment_header_t segment_header;
|
||||
esp_app_desc_t app_desc;
|
||||
size_t offset = 0;
|
||||
size_t total_size = 0;
|
||||
|
||||
file = fopen(file_path, "rb");
|
||||
if (file == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open firmware file for header verification: %s", file_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Read image header */
|
||||
if (fread(&image_header, sizeof(image_header), 1, file) != 1) {
|
||||
ESP_LOGE(TAG, "Failed to read image header from file");
|
||||
fclose(file);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
/* Validate magic number */
|
||||
if (image_header.magic != ESP_IMAGE_HEADER_MAGIC) {
|
||||
ESP_LOGE(TAG, "Invalid image magic: 0x%" PRIx8, image_header.magic);
|
||||
fclose(file);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Image header: magic=0x%" PRIx8 ", segment_count=%" PRIu8 ", hash_appended=%" PRIu8,
|
||||
image_header.magic, image_header.segment_count, image_header.hash_appended);
|
||||
|
||||
/* Calculate total size by reading all segments */
|
||||
offset = sizeof(image_header);
|
||||
total_size = sizeof(image_header);
|
||||
|
||||
for (int i = 0; i < image_header.segment_count; i++) {
|
||||
/* Read segment header */
|
||||
if (fseek(file, offset, SEEK_SET) != 0 ||
|
||||
fread(&segment_header, sizeof(segment_header), 1, file) != 1) {
|
||||
ESP_LOGE(TAG, "Failed to read segment %d header", i);
|
||||
fclose(file);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Segment %d: data_len=%" PRIu32 ", load_addr=0x%" PRIx32, i, segment_header.data_len, segment_header.load_addr);
|
||||
|
||||
/* Add segment header size + data size */
|
||||
total_size += sizeof(segment_header) + segment_header.data_len;
|
||||
offset += sizeof(segment_header) + segment_header.data_len;
|
||||
|
||||
/* Read app description from the first segment */
|
||||
if (i == 0) {
|
||||
size_t app_desc_offset = sizeof(image_header) + sizeof(segment_header);
|
||||
if (fseek(file, app_desc_offset, SEEK_SET) == 0 &&
|
||||
fread(&app_desc, sizeof(app_desc), 1, file) == 1) {
|
||||
strncpy(app_version_str, app_desc.version, version_str_len - 1);
|
||||
app_version_str[version_str_len - 1] = '\0';
|
||||
ESP_LOGI(TAG, "Found app description: version='%s', project_name='%s'",
|
||||
app_desc.version, app_desc.project_name);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to read app description");
|
||||
strncpy(app_version_str, "unknown", version_str_len - 1);
|
||||
app_version_str[version_str_len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add padding to align to 16 bytes */
|
||||
size_t padding = (16 - (total_size % 16)) % 16;
|
||||
if (padding > 0) {
|
||||
ESP_LOGD(TAG, "Adding %u bytes of padding for alignment", (unsigned int)padding);
|
||||
total_size += padding;
|
||||
}
|
||||
|
||||
/* Add the checksum byte (always present) */
|
||||
total_size += 1;
|
||||
ESP_LOGD(TAG, "Added 1 byte for checksum");
|
||||
|
||||
/* Add SHA256 hash if appended */
|
||||
bool has_hash = (image_header.hash_appended == 1);
|
||||
if (has_hash) {
|
||||
total_size += 32; // SHA256 hash is 32 bytes
|
||||
ESP_LOGD(TAG, "Added 32 bytes for SHA256 hash (hash_appended=1)");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "No SHA256 hash appended (hash_appended=0)");
|
||||
}
|
||||
|
||||
*firmware_size = total_size;
|
||||
ESP_LOGI(TAG, "Total image size: %u bytes", (unsigned int)*firmware_size);
|
||||
|
||||
fclose(file);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Find latest firmware file in LittleFS */
|
||||
static esp_err_t find_latest_firmware(char* firmware_path, size_t max_len)
|
||||
{
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
struct stat file_stat;
|
||||
char *latest_file = malloc(256); // Use heap instead of stack
|
||||
char *full_path = malloc(512); // Use heap for full path
|
||||
|
||||
if (!latest_file || !full_path) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for file search");
|
||||
if (latest_file) free(latest_file);
|
||||
if (full_path) free(full_path);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
memset(latest_file, 0, 256);
|
||||
|
||||
dir = opendir("/littlefs");
|
||||
if (dir == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open /littlefs directory");
|
||||
free(latest_file);
|
||||
free(full_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Successfully opened /littlefs directory");
|
||||
|
||||
/* Find the first .bin file (since timestamps might not be reliable in LittleFS) */
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
ESP_LOGI(TAG, "Found file: %s", entry->d_name);
|
||||
if (strstr(entry->d_name, ".bin") != NULL) {
|
||||
ESP_LOGI(TAG, "Found .bin file: %s", entry->d_name);
|
||||
snprintf(full_path, 512, "/littlefs/%s", entry->d_name);
|
||||
|
||||
if (stat(full_path, &file_stat) == 0) {
|
||||
ESP_LOGI(TAG, "File stat successful for %s, size: %ld", entry->d_name, file_stat.st_size);
|
||||
/* Use the first .bin file found */
|
||||
strncpy(latest_file, entry->d_name, 255);
|
||||
latest_file[255] = '\0'; // Ensure null termination
|
||||
ESP_LOGI(TAG, "Using firmware file: %s", latest_file);
|
||||
break; // Use the first .bin file found
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to stat file: %s", full_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
ESP_LOGI(TAG, "Final latest_file: '%s', length: %d", latest_file, strlen(latest_file));
|
||||
|
||||
if (strlen(latest_file) == 0) {
|
||||
ESP_LOGE(TAG, "No .bin files found in /littlefs directory. Please refer doc to know how partition is created with slave firmware at correct path.");
|
||||
free(latest_file);
|
||||
free(full_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Ensure we don't overflow the destination buffer
|
||||
if (snprintf(firmware_path, max_len, "/littlefs/%s", latest_file) >= max_len) {
|
||||
ESP_LOGE(TAG, "Firmware path too long");
|
||||
free(latest_file);
|
||||
free(full_path);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Found latest firmware: %s", firmware_path);
|
||||
|
||||
// Clean up allocated memory
|
||||
free(latest_file);
|
||||
free(full_path);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ota_littlefs_perform(bool delete_after_use)
|
||||
{
|
||||
char *firmware_path = malloc(256); // Use heap instead of stack
|
||||
FILE *firmware_file;
|
||||
uint8_t *chunk = malloc(CHUNK_SIZE); // Use heap for chunk buffer
|
||||
size_t bytes_read;
|
||||
esp_err_t ret = ESP_OK;
|
||||
|
||||
if (!firmware_path || !chunk) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory");
|
||||
if (firmware_path) free(firmware_path);
|
||||
if (chunk) free(chunk);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting LittleFS OTA process");
|
||||
|
||||
/* Initialize LittleFS */
|
||||
ESP_LOGI(TAG, "Initializing LittleFS filesystem");
|
||||
esp_vfs_littlefs_conf_t conf = {
|
||||
.base_path = "/littlefs",
|
||||
.partition_label = "storage",
|
||||
.format_if_mount_failed = true,
|
||||
.dont_mount = false,
|
||||
};
|
||||
|
||||
ret = esp_vfs_littlefs_register(&conf);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to initialize LittleFS: %s", esp_err_to_name(ret));
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "LittleFS filesystem registered successfully");
|
||||
|
||||
/* Get filesystem info */
|
||||
size_t total = 0, used = 0;
|
||||
ret = esp_littlefs_info("storage", &total, &used);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Failed to get LittleFS partition information (%s)", esp_err_to_name(ret));
|
||||
} else {
|
||||
ESP_LOGI(TAG, "LittleFS partition size: total: %d, used: %d", total, used);
|
||||
}
|
||||
|
||||
/* Find the latest firmware file */
|
||||
ESP_LOGI(TAG, "Searching for firmware files in LittleFS");
|
||||
ret = find_latest_firmware(firmware_path, 256);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to find firmware file");
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
ESP_LOGI(TAG, "Firmware file found: %s", firmware_path);
|
||||
|
||||
/* Verify image header and get firmware info */
|
||||
size_t firmware_size;
|
||||
char new_app_version[32];
|
||||
ret = parse_image_header_from_file(firmware_path, &firmware_size, new_app_version, sizeof(new_app_version));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to parse image header: %s", esp_err_to_name(ret));
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Firmware verified - Size: %u bytes, Version: %s", (unsigned int)firmware_size, new_app_version);
|
||||
|
||||
#ifdef CONFIG_OTA_VERSION_CHECK_SLAVEFW_SLAVE
|
||||
/* Get current running slave firmware version */
|
||||
esp_hosted_coprocessor_fwver_t current_slave_version = {0};
|
||||
esp_err_t version_ret = esp_hosted_get_coprocessor_fwversion(¤t_slave_version);
|
||||
|
||||
if (version_ret == ESP_OK) {
|
||||
char current_version_str[32];
|
||||
snprintf(current_version_str, sizeof(current_version_str), "%" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
current_slave_version.major1, current_slave_version.minor1, current_slave_version.patch1);
|
||||
|
||||
ESP_LOGI(TAG, "Current slave firmware version: %s", current_version_str);
|
||||
ESP_LOGI(TAG, "New slave firmware version: %s", new_app_version);
|
||||
|
||||
if (strcmp(new_app_version, current_version_str) == 0) {
|
||||
ESP_LOGW(TAG, "Current slave firmware version (%s) is the same as new version (%s). Skipping OTA.",
|
||||
current_version_str, new_app_version);
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_NOT_REQUIRED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Version differs - proceeding with OTA from %s to %s", current_version_str, new_app_version);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Could not get current slave firmware version (error: %s), proceeding with OTA",
|
||||
esp_err_to_name(version_ret));
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "Version check disabled - proceeding with OTA (new firmware version: %s)", new_app_version);
|
||||
#endif
|
||||
|
||||
/* Open firmware file */
|
||||
firmware_file = fopen(firmware_path, "rb");
|
||||
if (firmware_file == NULL) {
|
||||
ESP_LOGE(TAG, "Failed to open firmware file: %s", firmware_path);
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Starting OTA from LittleFS: %s", firmware_path);
|
||||
|
||||
/* Begin OTA */
|
||||
ret = esp_hosted_slave_ota_begin();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(ret));
|
||||
fclose(firmware_file);
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
/* Write firmware in chunks */
|
||||
while ((bytes_read = fread(chunk, 1, CHUNK_SIZE, firmware_file)) > 0) {
|
||||
ret = esp_hosted_slave_ota_write(chunk, bytes_read);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA chunk: %s", esp_err_to_name(ret));
|
||||
fclose(firmware_file);
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(firmware_file);
|
||||
|
||||
/* End OTA */
|
||||
ret = esp_hosted_slave_ota_end();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(ret));
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "LittleFS OTA completed successfully");
|
||||
|
||||
/* Delete firmware file if requested */
|
||||
if (delete_after_use) {
|
||||
if (unlink(firmware_path) == 0) {
|
||||
ESP_LOGI(TAG, "Deleted firmware file: %s", firmware_path);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to delete firmware file: %s", firmware_path);
|
||||
}
|
||||
}
|
||||
|
||||
esp_vfs_littlefs_unregister("storage");
|
||||
|
||||
/* Clean up allocated memory */
|
||||
free(firmware_path);
|
||||
free(chunk);
|
||||
|
||||
return ESP_HOSTED_SLAVE_OTA_COMPLETED;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Perform LittleFS OTA update
|
||||
*
|
||||
* @param delete_after_use Whether to delete firmware file after successful flash
|
||||
* @return esp_err_t ESP_OK on success
|
||||
*/
|
||||
esp_err_t ota_littlefs_perform(bool delete_after_use);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,86 @@
|
||||
# Partition OTA Component CMakeLists.txt
|
||||
# This component handles Partition-based OTA updates
|
||||
|
||||
idf_component_register(
|
||||
SRCS "ota_partition.c"
|
||||
INCLUDE_DIRS "."
|
||||
PRIV_REQUIRES "esp_partition" "esp_hosted" "bootloader_support" "esp_app_format"
|
||||
)
|
||||
|
||||
if(CONFIG_OTA_METHOD_PARTITION)
|
||||
# --- Step 0: Get partition offset from partition table ---
|
||||
set(SLAVE_FW_PARTITION_NAME "slave_fw")
|
||||
file(READ "${CMAKE_SOURCE_DIR}/partitions.csv" PARTITION_TABLE_CONTENT)
|
||||
string(REGEX MATCH "${SLAVE_FW_PARTITION_NAME}[^,]*,[^,]*,[^,]*,[ ]*([^,]*)" PARTITION_MATCH "${PARTITION_TABLE_CONTENT}")
|
||||
if(PARTITION_MATCH)
|
||||
string(REGEX REPLACE "${SLAVE_FW_PARTITION_NAME}[^,]*,[^,]*,[^,]*,[ ]*([^,]*)" "\\1" SLAVE_FW_OFFSET "${PARTITION_MATCH}")
|
||||
string(STRIP "${SLAVE_FW_OFFSET}" SLAVE_FW_OFFSET)
|
||||
message(STATUS "✅ Partition OTA: Found ${SLAVE_FW_PARTITION_NAME} partition at offset: ${SLAVE_FW_OFFSET}")
|
||||
else()
|
||||
set(SLAVE_FW_OFFSET "0x5F0000") # Fallback to hardcoded value
|
||||
message(WARNING "⚠️ Partition OTA: Could not find ${SLAVE_FW_PARTITION_NAME} in partitions.csv, using fallback offset: ${SLAVE_FW_OFFSET}")
|
||||
endif()
|
||||
|
||||
# --- Step 1: Check if firmware files exist (early validation) ---
|
||||
message(STATUS "🔍 Partition OTA: Checking for firmware files...")
|
||||
|
||||
file(GLOB SLAVE_FW_BIN_FILES "${CMAKE_CURRENT_SOURCE_DIR}/slave_fw_bin/*.bin")
|
||||
|
||||
if(SLAVE_FW_BIN_FILES)
|
||||
message(STATUS "✅ Partition OTA: Found firmware files for dynamic selection")
|
||||
|
||||
# Get Python and esptool path from IDF
|
||||
idf_build_get_property(PYTHON PYTHON)
|
||||
idf_build_get_property(ESPTOOL_PATH ESPTOOL_PATH)
|
||||
|
||||
# Create a target that dynamically selects the newest firmware
|
||||
add_custom_target(select_partition_firmware
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSOURCE_COMPONENT_DIR=${CMAKE_CURRENT_SOURCE_DIR}
|
||||
-DBINARY_COMPONENT_DIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
-DCOMPONENT_NAME=Partition OTA
|
||||
-DOTA_ACTION=select_for_partition
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/../common_ota_scripts/find_newest_firmware.cmake
|
||||
COMMENT "Partition OTA: Dynamically selecting newest firmware"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Create a target that flashes the selected firmware
|
||||
add_custom_target(flash_slave_fw
|
||||
DEPENDS select_partition_firmware
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DBINARY_COMPONENT_DIR=${CMAKE_CURRENT_BINARY_DIR}
|
||||
-DSLAVE_FW_OFFSET=${SLAVE_FW_OFFSET}
|
||||
-DPYTHON=${PYTHON}
|
||||
-DCOMPONENT_NAME=Partition OTA
|
||||
-P ${CMAKE_CURRENT_SOURCE_DIR}/../common_ota_scripts/flash_selected_firmware.cmake
|
||||
COMMENT "Partition OTA: Flashing selected firmware to partition"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Make flash_slave_fw depend on the main project binary being built successfully
|
||||
idf_build_get_property(project_name PROJECT_NAME)
|
||||
add_dependencies(flash_slave_fw ${project_name}.elf)
|
||||
|
||||
# Add flash_slave_fw to the flash target only after successful build
|
||||
add_dependencies(flash flash_slave_fw)
|
||||
|
||||
else()
|
||||
message(WARNING "⚠️ Partition OTA: No .bin files found in slave_fw_bin directory. Nothing to flash.")
|
||||
|
||||
# Add warning target for flash command when no slave firmware present
|
||||
add_custom_target(warn_no_slave_fw
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ""
|
||||
COMMAND ${CMAKE_COMMAND} -E echo "⚠️ WARNING: Partition OTA is enabled but no slave firmware found!"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo " Expected: .bin files in components/ota_partition/slave_fw_bin/"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo " Current flash will only flash the host application"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo " The partition at ${SLAVE_FW_OFFSET} will not be flashed this time"
|
||||
COMMAND ${CMAKE_COMMAND} -E echo ""
|
||||
COMMENT "Warning: No slave firmware binaries found"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
# Make flash target show this warning
|
||||
add_dependencies(flash warn_no_slave_fw)
|
||||
endif()
|
||||
endif()
|
||||
@@ -0,0 +1,241 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_partition.h"
|
||||
#include "esp_hosted_ota.h"
|
||||
#include "esp_app_format.h"
|
||||
#include "esp_app_desc.h"
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_api_types.h"
|
||||
|
||||
static const char* TAG = "ota_partition";
|
||||
|
||||
#ifndef CHUNK_SIZE
|
||||
#define CHUNK_SIZE 1500
|
||||
#endif
|
||||
|
||||
/* Function to parse ESP32 image header and get firmware info */
|
||||
static esp_err_t parse_image_header(const esp_partition_t* partition, size_t* firmware_size, char* app_version_str, size_t version_str_len)
|
||||
{
|
||||
esp_image_header_t image_header;
|
||||
esp_image_segment_header_t segment_header;
|
||||
esp_app_desc_t app_desc;
|
||||
esp_err_t ret;
|
||||
size_t offset = 0;
|
||||
size_t total_size = 0;
|
||||
|
||||
/* Read image header */
|
||||
ret = esp_partition_read(partition, offset, &image_header, sizeof(image_header));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read image header: %s", esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Validate magic number */
|
||||
if (image_header.magic != ESP_IMAGE_HEADER_MAGIC) {
|
||||
ESP_LOGE(TAG, "Invalid image magic: 0x%" PRIx8, image_header.magic);
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Image header: magic=0x%" PRIx8 ", segment_count=%" PRIu8 ", hash_appended=%" PRIu8,
|
||||
image_header.magic, image_header.segment_count, image_header.hash_appended);
|
||||
|
||||
/* Calculate total size by reading all segments */
|
||||
offset = sizeof(image_header);
|
||||
total_size = sizeof(image_header);
|
||||
|
||||
for (int i = 0; i < image_header.segment_count; i++) {
|
||||
/* Read segment header */
|
||||
ret = esp_partition_read(partition, offset, &segment_header, sizeof(segment_header));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read segment %d header: %s", i, esp_err_to_name(ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Segment %d: data_len=%" PRIu32 ", load_addr=0x%" PRIx32, i, segment_header.data_len, segment_header.load_addr);
|
||||
|
||||
/* Add segment header size + data size */
|
||||
total_size += sizeof(segment_header) + segment_header.data_len;
|
||||
offset += sizeof(segment_header) + segment_header.data_len;
|
||||
|
||||
/* Read app description from the first segment */
|
||||
if (i == 0) {
|
||||
size_t app_desc_offset = sizeof(image_header) + sizeof(segment_header);
|
||||
ret = esp_partition_read(partition, app_desc_offset, &app_desc, sizeof(app_desc));
|
||||
if (ret == ESP_OK) {
|
||||
strncpy(app_version_str, app_desc.version, version_str_len - 1);
|
||||
app_version_str[version_str_len - 1] = '\0';
|
||||
ESP_LOGI(TAG, "Found app description: version='%s', project_name='%s'",
|
||||
app_desc.version, app_desc.project_name);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Failed to read app description: %s", esp_err_to_name(ret));
|
||||
strncpy(app_version_str, "unknown", version_str_len - 1);
|
||||
app_version_str[version_str_len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Add padding to align to 16 bytes */
|
||||
size_t padding = (16 - (total_size % 16)) % 16;
|
||||
if (padding > 0) {
|
||||
ESP_LOGD(TAG, "Adding %u bytes of padding for alignment", (unsigned int)padding);
|
||||
total_size += padding;
|
||||
}
|
||||
|
||||
/* Add the checksum byte (always present) */
|
||||
total_size += 1;
|
||||
ESP_LOGD(TAG, "Added 1 byte for checksum");
|
||||
|
||||
/* Add SHA256 hash if appended */
|
||||
bool has_hash = (image_header.hash_appended == 1);
|
||||
if (has_hash) {
|
||||
total_size += 32; // SHA256 hash is 32 bytes
|
||||
ESP_LOGD(TAG, "Added 32 bytes for SHA256 hash (hash_appended=1)");
|
||||
} else {
|
||||
ESP_LOGD(TAG, "No SHA256 hash appended (hash_appended=0)");
|
||||
}
|
||||
|
||||
*firmware_size = total_size;
|
||||
ESP_LOGI(TAG, "Total image size: %u bytes", (unsigned int)*firmware_size);
|
||||
|
||||
/* Debug: Read last 48 bytes to verify structure */
|
||||
uint8_t tail_data[48];
|
||||
size_t tail_offset = (total_size > 48) ? (total_size - 48) : 0;
|
||||
ret = esp_partition_read(partition, tail_offset, tail_data, 48);
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGD(TAG, "Last 48 bytes of image (offset %u):", (unsigned int)tail_offset);
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, tail_data, 48, ESP_LOG_DEBUG);
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t ota_partition_perform(const char* partition_label)
|
||||
{
|
||||
const esp_partition_t* partition;
|
||||
esp_err_t ret = ESP_OK;
|
||||
uint8_t chunk[CHUNK_SIZE];
|
||||
size_t bytes_read;
|
||||
size_t offset = 0;
|
||||
size_t firmware_size;
|
||||
char new_app_version[32];
|
||||
|
||||
ESP_LOGI(TAG, "Starting Partition OTA from partition: %s", partition_label);
|
||||
|
||||
/* Find the partition */
|
||||
partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, partition_label);
|
||||
if (partition == NULL) {
|
||||
ESP_LOGE(TAG, "Partition '%s' not found", partition_label);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Found partition: %s, size: %" PRIu32 " bytes", partition->label, partition->size);
|
||||
|
||||
/* Parse image header to get firmware size and version */
|
||||
ret = parse_image_header(partition, &firmware_size, new_app_version, sizeof(new_app_version));
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to parse image header: %s", esp_err_to_name(ret));
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Firmware verified - Size: %u bytes, Version: %s", (unsigned int)firmware_size, new_app_version);
|
||||
|
||||
#ifdef CONFIG_OTA_VERSION_CHECK_SLAVEFW_SLAVE
|
||||
/* Get current running slave firmware version */
|
||||
esp_hosted_coprocessor_fwver_t current_slave_version = {0};
|
||||
esp_err_t version_ret = esp_hosted_get_coprocessor_fwversion(¤t_slave_version);
|
||||
|
||||
if (version_ret == ESP_OK) {
|
||||
char current_version_str[32];
|
||||
snprintf(current_version_str, sizeof(current_version_str), "%" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
current_slave_version.major1, current_slave_version.minor1, current_slave_version.patch1);
|
||||
|
||||
ESP_LOGI(TAG, "Current slave firmware version: %s", current_version_str);
|
||||
ESP_LOGI(TAG, "New slave firmware version: %s", new_app_version);
|
||||
|
||||
if (strcmp(new_app_version, current_version_str) == 0) {
|
||||
ESP_LOGW(TAG, "Current slave firmware version (%s) is the same as new version (%s). Skipping OTA.",
|
||||
current_version_str, new_app_version);
|
||||
return ESP_HOSTED_SLAVE_OTA_NOT_REQUIRED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Version differs - proceeding with OTA from %s to %s", current_version_str, new_app_version);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Could not get current slave firmware version (error: %s), proceeding with OTA",
|
||||
esp_err_to_name(version_ret));
|
||||
}
|
||||
#else
|
||||
ESP_LOGI(TAG, "Version check disabled - proceeding with OTA (new firmware version: %s)", new_app_version);
|
||||
#endif
|
||||
|
||||
/* Validate firmware size */
|
||||
if (firmware_size == 0) {
|
||||
ESP_LOGE(TAG, "Firmware size is 0, cannot proceed with OTA");
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
if (firmware_size > partition->size) {
|
||||
ESP_LOGE(TAG, "Firmware size (%u) exceeds partition size (%" PRIu32 ")", (unsigned int)firmware_size, partition->size);
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Proceeding with OTA - Firmware size: %u bytes", (unsigned int)firmware_size);
|
||||
|
||||
/* Begin OTA */
|
||||
ret = esp_hosted_slave_ota_begin();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to begin OTA: %s", esp_err_to_name(ret));
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
/* Read firmware from partition in chunks - only up to actual firmware size */
|
||||
uint32_t total_bytes_sent = 0;
|
||||
uint32_t chunk_count = 0;
|
||||
|
||||
while (offset < firmware_size) {
|
||||
bytes_read = (firmware_size - offset > CHUNK_SIZE) ? CHUNK_SIZE : (firmware_size - offset);
|
||||
|
||||
ret = esp_partition_read(partition, offset, chunk, bytes_read);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to read partition: %s", esp_err_to_name(ret));
|
||||
esp_hosted_slave_ota_end();
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ret = esp_hosted_slave_ota_write(chunk, bytes_read);
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write OTA chunk %" PRIu32 ": %s", chunk_count, esp_err_to_name(ret));
|
||||
esp_hosted_slave_ota_end();
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
total_bytes_sent += bytes_read;
|
||||
offset += bytes_read;
|
||||
chunk_count++;
|
||||
|
||||
/* Progress indicator */
|
||||
if (chunk_count % 50 == 0) {
|
||||
ESP_LOGD(TAG, "Progress: %" PRIu32 "/%u bytes (%.1f%%)",
|
||||
total_bytes_sent, (unsigned int)firmware_size, (float)total_bytes_sent * 100 / firmware_size);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Total chunks sent: %" PRIu32 ", Total bytes sent: %" PRIu32, chunk_count, total_bytes_sent);
|
||||
|
||||
/* End OTA */
|
||||
ret = esp_hosted_slave_ota_end();
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(ret));
|
||||
return ESP_HOSTED_SLAVE_OTA_FAILED;
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "Partition OTA completed successfully - Sent %u bytes", (unsigned int)firmware_size);
|
||||
return ESP_HOSTED_SLAVE_OTA_COMPLETED;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Perform Partition OTA update
|
||||
*
|
||||
* @param partition_label Label of the partition containing firmware
|
||||
* @return esp_err_t ESP_OK on success
|
||||
*/
|
||||
esp_err_t ota_partition_perform(const char* partition_label);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,6 @@
|
||||
# Simple component selection based on Kconfig
|
||||
set(COMPONENT_SRCS "main.c")
|
||||
set(COMPONENT_REQUIRES esp_hosted console esp_timer)
|
||||
|
||||
idf_component_register(SRCS ${COMPONENT_SRCS}
|
||||
INCLUDE_DIRS ".")
|
||||
@@ -0,0 +1,130 @@
|
||||
menu "ESP-Hosted Slave OTA Config"
|
||||
|
||||
choice OTA_METHOD
|
||||
prompt "Select OTA Method"
|
||||
default OTA_METHOD_HTTPS
|
||||
help
|
||||
Choose the OTA method for updating slave firmware.
|
||||
|
||||
config OTA_METHOD_HTTPS
|
||||
bool "HTTP OTA"
|
||||
help
|
||||
Download firmware from HTTPS URL
|
||||
|
||||
config OTA_METHOD_LITTLEFS
|
||||
bool "LittleFS OTA"
|
||||
help
|
||||
Read firmware from LittleFS filesystem
|
||||
|
||||
config OTA_METHOD_PARTITION
|
||||
bool "Partition OTA"
|
||||
help
|
||||
Read firmware from dedicated partition
|
||||
|
||||
endchoice
|
||||
|
||||
########### OTA with Partition ########
|
||||
config OTA_PARTITION_LABEL
|
||||
string "Partition Label"
|
||||
depends on OTA_METHOD_PARTITION
|
||||
default "slave_fw"
|
||||
help
|
||||
Label of the partition containing firmware
|
||||
|
||||
########### OTA with Littlefs #########
|
||||
config OTA_DELETE_FILE_AFTER_FLASH
|
||||
depends on OTA_METHOD_LITTLEFS
|
||||
bool "Delete OTA file from LittleFS/FatFS etc, once finished"
|
||||
default y
|
||||
|
||||
########### OTA with HTTPS ############
|
||||
menu "WiFi Connection"
|
||||
depends on OTA_METHOD_HTTPS
|
||||
config ESP_WIFI_SSID
|
||||
string "WiFi SSID"
|
||||
default "MyWiFiSSID"
|
||||
help
|
||||
SSID (network name) for the example to connect to.
|
||||
|
||||
config ESP_WIFI_PASSWORD
|
||||
string "WiFi Password"
|
||||
default "MyWiFiPass@123"
|
||||
help
|
||||
WiFi password (WPA or WPA2) for the example to use.
|
||||
|
||||
config ESP_MAXIMUM_RETRY
|
||||
int "Maximum retry"
|
||||
default 5
|
||||
help
|
||||
Set the Maximum retry to avoid station reconnecting to the AP unlimited when the AP is really inexistent.
|
||||
|
||||
endmenu
|
||||
|
||||
menu "HTTPS OTA Configuration"
|
||||
depends on OTA_METHOD_HTTPS
|
||||
|
||||
config OTA_USE_SELF_SIGNED_CERT
|
||||
bool "Use self-signed certificate (Testing Only)"
|
||||
default n
|
||||
help
|
||||
Enable this ONLY for testing with self-signed certificates.
|
||||
Disable for production use with CA-signed certificates.
|
||||
|
||||
When enabled, the component will use an embedded certificate
|
||||
from certs/server_cert.pem for HTTPS validation.
|
||||
|
||||
When disabled, the component will use the ESP32 CA certificate
|
||||
bundle which supports major CAs like Let's Encrypt, DigiCert, etc.
|
||||
|
||||
config OTA_SKIP_CERT_CN_CHECK
|
||||
bool "Skip certificate Common Name check"
|
||||
depends on OTA_USE_SELF_SIGNED_CERT
|
||||
default n
|
||||
help
|
||||
Skip Common Name validation in self-signed certificates.
|
||||
Only enable if you cannot match the CN with your server IP/hostname.
|
||||
|
||||
WARNING: This reduces security. Use only for testing.
|
||||
|
||||
config OTA_HTTPS_TIMEOUT_MS
|
||||
int "HTTPS connection timeout (milliseconds)"
|
||||
default 30000
|
||||
range 5000 120000
|
||||
help
|
||||
Timeout for HTTPS connection and SSL handshake.
|
||||
Increase if you have slow network or server.
|
||||
|
||||
config OTA_SERVER_URL
|
||||
string "OTA Server URL"
|
||||
default "https://192.168.1.3:8443/firmware.bin"
|
||||
help
|
||||
HTTPS URL of the OTA firmware file.
|
||||
Must start with https://
|
||||
|
||||
endmenu
|
||||
|
||||
########### OTA Verification Options ############
|
||||
menu "OTA Verification Options"
|
||||
|
||||
config OTA_VERSION_CHECK_HOST_SLAVE
|
||||
bool "Host-Slave version compatibility check"
|
||||
default y
|
||||
help
|
||||
Check if host and slave firmware have compatible major.minor versions.
|
||||
If host is X.Y.Z and slave is A.B.C, warn if X.Y != A.B.
|
||||
|
||||
This ensures compatible API versions between host and slave.
|
||||
|
||||
config OTA_VERSION_CHECK_SLAVEFW_SLAVE
|
||||
bool "Skip OTA if slave firmware versions match"
|
||||
default y
|
||||
help
|
||||
Compare current running slave firmware version with new firmware version.
|
||||
Skip OTA if versions are identical to avoid unnecessary updates.
|
||||
|
||||
Disable this to force OTA even when versions match.
|
||||
|
||||
endmenu
|
||||
|
||||
|
||||
endmenu
|
||||
@@ -0,0 +1,14 @@
|
||||
dependencies:
|
||||
cmd_nvs:
|
||||
path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_nvs
|
||||
cmd_system:
|
||||
path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_system
|
||||
espressif/esp_hosted:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
espressif/esp_wifi_remote:
|
||||
rules:
|
||||
- if: target in [esp32p4, esp32h2]
|
||||
version: '>=0.10,<2.0'
|
||||
joltwallet/littlefs:
|
||||
version: '*'
|
||||
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <inttypes.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_system.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_event.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_err.h"
|
||||
#include "esp_hosted.h"
|
||||
#include "esp_hosted_ota.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_app_desc.h"
|
||||
#include "esp_hosted_api_types.h"
|
||||
|
||||
#if CONFIG_OTA_METHOD_HTTPS
|
||||
#include "ota_https.h"
|
||||
#elif CONFIG_OTA_METHOD_LITTLEFS
|
||||
#include "ota_littlefs.h"
|
||||
#elif CONFIG_OTA_METHOD_PARTITION
|
||||
#include "ota_partition.h"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "host_performs_slave_ota";
|
||||
|
||||
#ifdef CONFIG_OTA_VERSION_CHECK_HOST_SLAVE
|
||||
/* Compare host and slave firmware versions */
|
||||
static int compare_self_version_with_slave_version(uint32_t slave_version)
|
||||
{
|
||||
uint32_t host_version = ESP_HOSTED_VERSION_VAL(ESP_HOSTED_VERSION_MAJOR_1,
|
||||
ESP_HOSTED_VERSION_MINOR_1,
|
||||
ESP_HOSTED_VERSION_PATCH_1);
|
||||
|
||||
// mask out patch level
|
||||
// compare major.minor only
|
||||
slave_version &= 0xFFFFFF00;
|
||||
host_version &= 0xFFFFFF00;
|
||||
|
||||
if (host_version == slave_version) {
|
||||
// versions match
|
||||
return 0;
|
||||
} else if (host_version > slave_version) {
|
||||
// host version > slave version
|
||||
ESP_LOGW(TAG, "=== ESP-Hosted Version Warning ===");
|
||||
printf("Version on Host is NEWER than version on co-processor\n");
|
||||
printf("RPC requests sent by host may encounter timeout errors\n");
|
||||
printf("or may not be supported by co-processor\n");
|
||||
ESP_LOGW(TAG, "=== ESP-Hosted Version Warning ===");
|
||||
return -1;
|
||||
} else {
|
||||
// host version < slave version
|
||||
ESP_LOGW(TAG, "=== ESP-Hosted Version Warning ===");
|
||||
printf("Version on Host is OLDER than version on co-processor\n");
|
||||
printf("Host may not be compatible with co-processor\n");
|
||||
ESP_LOGW(TAG, "=== ESP-Hosted Version Warning ===");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check host-slave version compatibility */
|
||||
static int compare_host_slave_version(void)
|
||||
{
|
||||
/* Get slave version via RPC */
|
||||
esp_hosted_coprocessor_fwver_t slave_version_struct = {0};
|
||||
esp_err_t ret = esp_hosted_get_coprocessor_fwversion(&slave_version_struct);
|
||||
|
||||
if (ret != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Could not get slave firmware version (error: %s)", esp_err_to_name(ret));
|
||||
ESP_LOGW(TAG, "Proceeding without version compatibility check");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* Convert slave version to 32-bit value for comparison */
|
||||
uint32_t slave_version = ESP_HOSTED_VERSION_VAL(slave_version_struct.major1,
|
||||
slave_version_struct.minor1,
|
||||
slave_version_struct.patch1);
|
||||
|
||||
/* Log versions */
|
||||
ESP_LOGI(TAG, "Host firmware version: %d.%d.%d", ESP_HOSTED_VERSION_MAJOR_1, ESP_HOSTED_VERSION_MINOR_1, ESP_HOSTED_VERSION_PATCH_1);
|
||||
ESP_LOGI(TAG, "Slave firmware version: %" PRIu32 ".%" PRIu32 ".%" PRIu32,
|
||||
slave_version_struct.major1, slave_version_struct.minor1, slave_version_struct.patch1);
|
||||
|
||||
return compare_self_version_with_slave_version(slave_version);
|
||||
}
|
||||
#endif
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
int ret;
|
||||
int host_slave_version_not_compatible = 1;
|
||||
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(esp_hosted_init());
|
||||
ESP_ERROR_CHECK(esp_hosted_connect_to_slave());
|
||||
|
||||
ESP_LOGI(TAG, "ESP-Hosted initialized successfully");
|
||||
|
||||
#ifdef CONFIG_OTA_VERSION_CHECK_HOST_SLAVE
|
||||
/* Check host-slave version compatibility */
|
||||
host_slave_version_not_compatible = compare_host_slave_version();
|
||||
#endif
|
||||
|
||||
if (!host_slave_version_not_compatible) {
|
||||
ESP_LOGW(TAG, "Slave OTA not required, so nothing to do!");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Perform OTA based on Kconfig selection */
|
||||
#if CONFIG_OTA_METHOD_HTTPS
|
||||
ESP_LOGI(TAG, "Using HTTP OTA method");
|
||||
ret = ota_https_perform(CONFIG_OTA_SERVER_URL);
|
||||
#elif CONFIG_OTA_METHOD_LITTLEFS
|
||||
uint8_t delete_post_flash = 0;
|
||||
ESP_LOGI(TAG, "Using LittleFS OTA method");
|
||||
#ifdef CONFIG_OTA_DELETE_FILE_AFTER_FLASH
|
||||
delete_post_flash = 1;
|
||||
#endif
|
||||
ret = ota_littlefs_perform(delete_post_flash);
|
||||
#elif CONFIG_OTA_METHOD_PARTITION
|
||||
ESP_LOGI(TAG, "Using Partition OTA method");
|
||||
ret = ota_partition_perform(CONFIG_OTA_PARTITION_LABEL);
|
||||
#else
|
||||
ESP_LOGE(TAG, "No OTA method selected!");
|
||||
return;
|
||||
#endif
|
||||
|
||||
if (ret == ESP_HOSTED_SLAVE_OTA_COMPLETED) {
|
||||
ESP_LOGI(TAG, "OTA completed successfully");
|
||||
|
||||
/* Activate the new firmware */
|
||||
ret = esp_hosted_slave_ota_activate();
|
||||
if (ret == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Slave will reboot with new firmware");
|
||||
ESP_LOGI(TAG, "********* Restarting host to avoid sync issues **********************");
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
esp_restart();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to activate OTA: %s", esp_err_to_name(ret));
|
||||
}
|
||||
} else if (ret == ESP_HOSTED_SLAVE_OTA_NOT_REQUIRED) {
|
||||
ESP_LOGI(TAG, "OTA not required");
|
||||
} else {
|
||||
ESP_LOGE(TAG, "OTA failed: %s", esp_err_to_name(ret));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
# Universal partition table for all OTA methods
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 16K,
|
||||
otadata, data, ota, 0xd000, 8K,
|
||||
phy_init, data, phy, 0xf000, 4K,
|
||||
ota_0, app, ota_0, 0x10000, 2M,
|
||||
ota_1, app, ota_1, 0x210000, 2M,
|
||||
storage, data, littlefs, 0x410000, 0x1E0000,
|
||||
slave_fw, data, 0x40, 0x5F0000, 0x200000,
|
||||
|
@@ -0,0 +1,17 @@
|
||||
# Default configuration for Host Slave OTA Simple Example
|
||||
|
||||
# Flash size configuration
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y
|
||||
CONFIG_ESPTOOLPY_FLASHSIZE="8MB"
|
||||
|
||||
# Force enable ESP-Hosted component
|
||||
CONFIG_ESP_WIFI_REMOTE_ENABLED=y
|
||||
CONFIG_ESP_HOSTED_ENABLED=y
|
||||
|
||||
# FreeRTOS configuration
|
||||
CONFIG_FREERTOS_HZ=1000
|
||||
|
||||
# Partition table
|
||||
CONFIG_PARTITION_TABLE_CUSTOM=y
|
||||
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"
|
||||
CONFIG_PARTITION_TABLE_FILENAME="partitions.csv"
|
||||
@@ -0,0 +1,9 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
|
||||
project(sd_card)
|
||||
@@ -0,0 +1,164 @@
|
||||
| Supported Hosts | ESP32-P4 | ESP32-S3 |
|
||||
|-----------------|----------|----------|
|
||||
|
||||
# SD Card (SDMMC) with ESP-Hosted example
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The ESP-IDF `master` branch has an issue that prevents the SD card and ESP-Hosted from working together when both are using SDMMC. See [ESP-IDF Issue 16233](https://github.com/espressif/esp-idf/issues/16233) for more information. This code contains a workaround for this issue.
|
||||
|
||||
This example demonstrates how to use an SD card on an ESP32-P4 dev board when ESP-Hosted is using SDIO to communicate with the on-board ESP32-C6. It has been modified from the standard [ESP-IDF SD Card example](https://github.com/espressif/esp-idf/tree/master/examples/storage/sd_card/sdmmc).
|
||||
|
||||
This example does the following steps:
|
||||
|
||||
1. Initialise ESP-Hosted and Wi-Fi
|
||||
1. Use an "all-in-one" `esp_vfs_fat_sdmmc_mount` function to:
|
||||
- initialize SDMMC peripheral,
|
||||
- probe and initialize an SD card,
|
||||
- mount FAT filesystem using FATFS library
|
||||
- register FAT filesystem in VFS, enabling C standard library and POSIX functions to be used.
|
||||
1. Print information about the card, such as name, type, capacity, and maximum supported frequency.
|
||||
1. Perform a Wi-Fi Scan
|
||||
1. Create a file using `fopen` and write to it using `fprintf`.
|
||||
1. Rename the file. Before renaming, check if destination file already exists using `stat` function, and remove it using `unlink` function.
|
||||
1. Open renamed file for reading, read back the line, and print it to the terminal.
|
||||
1. Perform another Wi-Fi Scan
|
||||
|
||||
This example supports SD (SDSC, SDHC, SDXC) cards and eMMC chips.
|
||||
|
||||
## Hardware
|
||||
|
||||
This example requires an ESP32-P4 development board with an SD card slot and an SD card. On this board, the SD card slot is assigned to SDMMC Slot 0, while the on-board ESP32-C6 is connected to the ESP32-P4 on SDMMC Slot 1.
|
||||
|
||||
Although it is possible to connect an SD card breakout adapter, keep in mind that connections using breakout cables are often unreliable and have poor signal integrity. You may need to use lower clock frequency when working with SD card breakout adapters.
|
||||
|
||||
This example doesn't utilize card detect (CD) and write protect (WP) signals from SD card slot.
|
||||
|
||||
### Pin assignments for SDMMC Slot 0 on ESP32-P4
|
||||
|
||||
On ESP32-P4, SDMMC Slot 0 GPIO pins cannot be customized. The GPIO assigned in the example should not be modified.
|
||||
|
||||
The table below lists the default pin assignments.
|
||||
|
||||
ESP32-P4 pin | SD card pin | Notes
|
||||
--------------|-------------|------------
|
||||
GPIO43 | CLK | 10k pullup
|
||||
GPIO44 | CMD | 10k pullup
|
||||
GPIO39 | D0 | 10k pullup
|
||||
GPIO40 | D1 | not used in 1-line SD mode; 10k pullup in 4-line mode
|
||||
GPIO41 | D2 | not used in 1-line SD mode; 10k pullup in 4-line mode
|
||||
GPIO42 | D3 | not used in 1-line SD mode, but card's D3 pin must have a 10k pullup
|
||||
|
||||
### 4-line and 1-line SD modes
|
||||
|
||||
By default, this example uses 4 line SD mode, utilizing 6 pins: CLK, CMD, D0 - D3. It is possible to use 1-line mode (CLK, CMD, D0) by changing "SD/MMC bus width" in the example configuration menu (see `CONFIG_EXAMPLE_SDMMC_BUS_WIDTH_1`).
|
||||
|
||||
Note that even if card's D3 line is not connected to the ESP chip, it still has to be pulled up, otherwise the card will go into SPI protocol mode.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Build and flash
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with serial port name.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
|
||||
|
||||
|
||||
## Example output
|
||||
|
||||
A Wi-Fi Scan is performed before and after accessing the SD Card. This shows that both SDMMC Slots work as expected.
|
||||
|
||||
```
|
||||
I (2103) transport: Received INIT event from ESP32 peripheral
|
||||
I (2103) transport: EVENT: 12
|
||||
I (2113) transport: Identified slave [esp32c6]
|
||||
I (2113) transport: EVENT: 11
|
||||
I (2113) transport: capabilities: 0xd
|
||||
I (2123) transport: Features supported are:
|
||||
I (2123) transport: * WLAN
|
||||
I (2123) transport: - HCI over SDIO
|
||||
I (2133) transport: - BLE only
|
||||
I (2133) transport: EVENT: 13
|
||||
I (2133) transport: ESP board type is : 13
|
||||
|
||||
I (2143) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
|
||||
I (2143) H_API: Transport active
|
||||
I (2143) transport: Slave chip Id[12]
|
||||
I (2153) transport: raw_tp_dir[-], flow_ctrl: low[60] high[80]
|
||||
I (2153) transport: transport_delayed_init
|
||||
I (2163) esp_cli: Remove any existing deep_sleep cmd in cli
|
||||
I (2163) esp_cli: Registering command: crash
|
||||
I (2173) esp_cli: Registering command: reboot
|
||||
I (2173) esp_cli: Registering command: mem-dump
|
||||
I (2173) esp_cli: Registering command: task-dump
|
||||
I (2183) esp_cli: Registering command: cpu-dump
|
||||
I (2183) esp_cli: Registering command: heap-trace
|
||||
I (2193) esp_cli: Registering command: sock-dump
|
||||
I (2193) esp_cli: Registering command: host-power-save
|
||||
I (2203) hci_stub_drv: Host BT Support: Disabled
|
||||
I (2203) H_SDIO_DRV: Received INIT event
|
||||
I (2203) H_SDIO_DRV: Event type: 0x22
|
||||
I (2213) H_SDIO_DRV: Write thread started
|
||||
I (2643) example: Initializing SD card
|
||||
I (2643) example: Using SDMMC peripheral
|
||||
W (2643) ldo: The voltage value 0 is out of the recommended range [500, 2700]
|
||||
I (2643) RPC_WRAP: ESP Event: wifi station started
|
||||
I (2653) RPC_WRAP: ESP Event: wifi station started
|
||||
I (2743) example: Mounting filesystem
|
||||
I (2743) sdmmc_periph: sdmmc_host_init: SDMMC host already initialized, skipping init flow
|
||||
I (2953) example: Filesystem mounted
|
||||
I (2953) example: Doing Wi-Fi Scan
|
||||
I (3653) rpc_req: Scan start Req
|
||||
|
||||
W (3653) rpc_rsp: Hosted RPC_Resp [0x21a], uid [4], resp code [12298]
|
||||
I (6183) RPC_WRAP: ESP Event: StaScanDone
|
||||
I (6203) example: Total APs scanned = 11, actual AP number ap_info holds = 10
|
||||
Name: SD16G
|
||||
Type: SDHC
|
||||
Speed: 40.00 MHz (limit: 40.00 MHz)
|
||||
Size: 14868MB
|
||||
CSD: ver=2, sector_size=512, capacity=30449664 read_bl_len=9
|
||||
SSR: bus_width=4
|
||||
I (6213) example: Opening file /sdcard/hello.txt
|
||||
I (6303) example: File written
|
||||
I (6323) example: Renaming file /sdcard/hello.txt to /sdcard/foo.txt
|
||||
I (6333) example: Reading file /sdcard/foo.txt
|
||||
I (6333) example: Read from file: 'Hello SD16G!'
|
||||
I (6333) example: Opening file /sdcard/nihao.txt
|
||||
I (6353) example: File written
|
||||
I (6353) example: Reading file /sdcard/nihao.txt
|
||||
I (6363) example: Read from file: 'Nihao SD16G!'
|
||||
I (6363) example: Card unmounted
|
||||
I (6363) example: Doing another Wi-Fi Scan
|
||||
I (6363) rpc_req: Scan start Req
|
||||
|
||||
I (8863) RPC_WRAP: ESP Event: StaScanDone
|
||||
I (8883) example: Total APs scanned = 11, actual AP number ap_info holds = 10
|
||||
I (8883) main_task: Returned from app_main()
|
||||
|
||||
Done
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Card fails to initialize with `sdmmc_init_sd_scr: send_scr (1) returned 0x107` error
|
||||
|
||||
Check connections between the card and the ESP32. For example, if you have disconnected GPIO2 to work around the flashing issue, connect it back and reset the ESP32 (using a button on the development board, or by pressing Ctrl-T Ctrl-R in IDF Monitor).
|
||||
|
||||
### Card fails to initialize with `sdmmc_check_scr: send_scr returned 0xffffffff` error
|
||||
|
||||
Connections between the card and the ESP32 are too long for the frequency used. Try using shorter connections, or try reducing the clock speed of SD interface.
|
||||
|
||||
### Failure to mount filesystem
|
||||
|
||||
```
|
||||
example: Failed to mount filesystem.
|
||||
```
|
||||
The example will be able to mount only cards formatted using FAT32 filesystem.
|
||||
@@ -0,0 +1,14 @@
|
||||
set(srcs "sd_card_example_main.c"
|
||||
"sd_card_functions.c"
|
||||
"esp_hosted_wifi.c")
|
||||
|
||||
idf_component_register(SRCS ${srcs}
|
||||
INCLUDE_DIRS "."
|
||||
REQUIRES fatfs esp_wifi
|
||||
WHOLE_ARCHIVE)
|
||||
|
||||
if(NOT CONFIG_SOC_SDMMC_HOST_SUPPORTED)
|
||||
fail_at_build_time(sdmmc ""
|
||||
"Only ESP32, ESP32-S3, ESP32-P4 targets are supported."
|
||||
"Please refer README.md for more details")
|
||||
endif()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user