Fork fix: the SDIO wedge is FIXED (esp-hosted-mcu #167)
Test, Build and Push / test-gateway (push) Successful in 12s
Test, Build and Push / release (push) Skipped
Test, Build and Push / build-gateway (push) Skipped

Root cause (verified against our exact IDF tree, not the community guess):
the "258" in "sdio_write_task: Failed to send data: 258" is NOT a timeout
(that is 263). 258 = 0x102 = ESP_ERR_INVALID_ARG. On the ESP32-P4, block-
mode CMD53 writes require the SOURCE buffer to be 64-byte (cache-line)
aligned; the IDF sdmmc driver rejects a misaligned source with INVALID_ARG
BEFORE any bus activity. esp_hosts write loop then declares "Unrecoverable
host sdio state" and reboots the whole P4. The audio TX payload is not
64-aligned, so streaming mic audio wedged on the very FIRST frame (which is
exactly what we saw: listening -> instant Failed to send -> reboot).

This also explains why buffer/queue/clock/retry tuning all did nothing: the
write never reached the bus. And why our symptom was instant, not after
~100 writes (the community block-mode-desync theory) — it is the first
misaligned buffer, every time.

Fix: vendored esp_hosted 2.12.11 as an editable local component (overrides
the registry copy) and bounce a misaligned TX payload through one aligned
DMA scratch buffer in hosted_sdio_write_block (port_esp_hosted_host_sdio.c).
TX is serialized by the bus lock so a single static bounce buffer is safe;
freed in hosted_sdio_deinit. Host-only change — no C6 reflash.

VERIFIED ON HARDWARE (autonomous self-test): 40s of continuous mic-audio
upstream streaming — the traffic that previously wedged on the first frame
— ran clean, zero timeouts, zero reboots. A guarded SDIO_TX_SELFTEST harness
is kept (compiled out) for future SDIO stress testing.

Credit: root cause + patch designed via multi-agent investigation; the
precise 258=INVALID_ARG decode (correcting the upstream community timeout
assumption) came from checking our actual esp_err.h.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 09:50:27 +02:00
co-authored by Claude Fable 5
parent ac9d24e0f9
commit cb5826e02b
666 changed files with 216925 additions and 0 deletions
@@ -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: '*'
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 chosen 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,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -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,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -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,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,265 @@
/*
* SPDX-FileCopyrightText: 2025-2026 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");
}
// get co-processor ID and name
char cp_name[30];
uint32_t cp_name_len = sizeof(cp_name);
uint32_t cp_chip_id = 0;
esp_err_t res;
memset(cp_name, 0, sizeof(cp_name));
res = esp_hosted_get_cp_info(&cp_chip_id, cp_name, cp_name_len);
if (res == ESP_OK) {
ESP_LOGI(TAG, "cp info: 0x%08"PRIx32 ", cp name: %s", cp_chip_id, cp_name);
} else {
ESP_LOGW(TAG, "failed to get cp info: %s", esp_err_to_name(res));
}
// changes to the MAC must be done before the BT Controller is initialised
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(gpio_expander_example)
@@ -0,0 +1,38 @@
# ESP-Hosted GPIO Control Example
This example demonstrates how to use the ESP-Hosted solution to control the GPIOs of the slave (co-processor) from the host MCU.
## How to Use
### Feature Switch
Feature can be controlled at Host and Slave using `CONFIG_ESP_HOSTED_ENABLE_GPIO_EXPANDER` option.
It is by default disabled at slave.
**Slave Configuration:**
* The firmware flashed on the slave board must have the GPIO RPC feature enabled. In the slave's `idf.py menuconfig`, set the following:
```
(Top) →
Example Configuration --->
[*] Enable GPIO Expander support (host can control slave GPIOs)
```
This corresponds to the `CONFIG_ESP_HOSTED_ENABLE_GPIO_EXPANDER=y` option.
### Build and Flash
1. **Slave**
* Slave build & flashed with above config
2. **Host:**
* Set your target board (e.g., `idf.py set-target esp32p4`).
* Build and flash the project: `idf.py -p <host_port> build flash monitor`.
The host application will:
1. Initialize the ESP-Hosted transport.
2. Configure a specific GPIO pin on the slave as an output.
3. Toggle the GPIO level (HIGH and LOW) every second.
4. After a few toggles, it will reconfigure the same GPIO as an input.
5. Read and print the GPIO level.
You can connect an LED to the specified GPIO on the slave board to visually verify the output functionality.
@@ -0,0 +1,3 @@
idf_component_register(SRCS "host_gpio_expander_example.c"
PRIV_REQUIRES nvs_flash
INCLUDE_DIRS ".")
@@ -0,0 +1,112 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "esp_hosted.h"
static const char* TAG = "gpio_expander_example";
#define SLAVE_GPIO_PIN 2
static esp_err_t configure_and_test_gpio(esp_hosted_cp_gpio_config_t *config, const char *demo_name)
{
esp_err_t ret;
int level;
ESP_LOGW(TAG, "---- %s ----", demo_name);
ret = esp_hosted_cp_gpio_config(config);
if (ret != ESP_OK) {
ESP_LOGE(TAG, "GPIO config failed: 0x%x", ret);
return ret;
}
// For output modes, toggle the pin
if (config->mode == H_CP_GPIO_MODE_OUTPUT ||
config->mode == H_CP_GPIO_MODE_OUTPUT_OD) {
ESP_LOGI(TAG, "Setting GPIO %d LOW", SLAVE_GPIO_PIN);
esp_hosted_cp_gpio_set_level(SLAVE_GPIO_PIN, 0);
vTaskDelay(pdMS_TO_TICKS(500));
ESP_LOGI(TAG, "Setting GPIO %d HIGH", SLAVE_GPIO_PIN);
esp_hosted_cp_gpio_set_level(SLAVE_GPIO_PIN, 1);
vTaskDelay(pdMS_TO_TICKS(500));
ESP_LOGI(TAG, "GPIO toggled successfully");
} else {
// For input modes, read the level
ret = esp_hosted_cp_gpio_get_level(SLAVE_GPIO_PIN, &level);
if (ret == ESP_OK) {
ESP_LOGI(TAG, "GPIO %d level: %d", SLAVE_GPIO_PIN, level);
}
}
return ESP_OK;
}
void app_main(void)
{
esp_err_t ret;
esp_hosted_cp_gpio_config_t gpio_config = {
.pin_bit_mask = (1ULL << SLAVE_GPIO_PIN),
};
// Initialize ESP-Hosted
ret = esp_hosted_init();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "ESP-Hosted init failed: 0x%x", ret);
return;
}
ret = esp_hosted_connect_to_slave();
if (ret != ESP_OK) {
ESP_LOGE(TAG, "Connect to slave failed: %s", esp_err_to_name(ret));
goto cleanup;
}
ESP_LOGI(TAG, "ESP-Hosted initialized successfully");
// Demo 1: Standard output
gpio_config.mode = H_CP_GPIO_MODE_OUTPUT;
gpio_config.pull_up_en = 0;
gpio_config.pull_down_en = 0;
if (configure_and_test_gpio(&gpio_config, "Demo 1: Standard Output") != ESP_OK) {
goto cleanup;
}
// Demo 2: Open-drain output with pull-up
gpio_config.mode = H_CP_GPIO_MODE_OUTPUT_OD;
gpio_config.pull_up_en = 1;
gpio_config.pull_down_en = 0;
if (configure_and_test_gpio(&gpio_config, "Demo 2: Open-Drain Output") != ESP_OK) {
goto cleanup;
}
// Demo 3: Input with pull-up
gpio_config.mode = H_CP_GPIO_MODE_INPUT;
gpio_config.pull_up_en = 1;
gpio_config.pull_down_en = 0;
if (configure_and_test_gpio(&gpio_config, "Demo 3: Input with Pull-Up") != ESP_OK) {
goto cleanup;
}
// Demo 4: Input with pull-down
gpio_config.mode = H_CP_GPIO_MODE_INPUT;
gpio_config.pull_up_en = 0;
gpio_config.pull_down_en = 1;
if (configure_and_test_gpio(&gpio_config, "Demo 4: Input with Pull-Down") != ESP_OK) {
goto cleanup;
}
ESP_LOGI(TAG, "All demos completed successfully!");
cleanup:
esp_hosted_deinit();
}
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '>=2.9.5'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,4 @@
# ESP-Hosted GPIO Expander Example Configuration
# Enable GPIO expander support
CONFIG_ESP_HOSTED_ENABLE_GPIO_EXPANDER=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(hosted_meminfo)
@@ -0,0 +1,445 @@
| 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-Hosted Co-processor Memory Info Example
This host example enables a memory monitor on the co-processor. This can be used to monitor the heap usage on the co-processor. Once enabled, the host can:
- query the heap usage on the co-processor at any time, or
- get a periodic memory report from the co-processor, or
- get a memory report only when the heap size on the co-processor falls below memory thresholds set by the host
This has been tested with the ESP32-C5 co-processor, flashed with the standard [ESP-Hosted slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave) from the ESP Component Registry, or built from the `slave` directory from ESP-Hosted Github.
To show the heap size changing on the co-processor, the example enables Wi-Fi, then connects and disconnects from an AP.
## Prerequisite
### Setting up the Co-Processor
Fetch the co-processor code using the ESP Registry [ESP-Hosted-MCU Slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave?language=en).
Quick steps:
```sh
idf.py create-project-from-example "espressif/esp_hosted:slave"
cd slave
idf.py set-target <cp_chip_type>
```
Start `idf.py menuconfig` and ensure that `Example Configuration` ---> `Enable Memory Monitor Interface` is enabled.
> [!NOTE]
> PSRAM must be enabled on the co-processor to get memory reports on external memory and to set the external memory thresholds. Ensure that `Component config` ---> `ESP PSRAM` ---> `Support for external, SPI-connected RAM` is enabled.
Now build and flash the code on the co-processor.
```sh
idf.py -p <cp_port> flash monitor
```
Where `cp_chip_type` is one of ESP32, ESP32-C Series or ESP32-S Series as the co-processor.
Alternatively, you built from the [slave directory](https://github.com/espressif/esp-hosted-mcu/tree/main/slave) of the ESP-Hosted-MCU GitHub repository.
### Building the Host
Start `idf.py menuconfig` and ensure that `Component Config` ---> `ESP-Hosted config` ---> `Enable Memory Monitor Interface` is enabled. There are three ways to configure the example:
1. **Do a one time query**: enable `Query Memory Info only once`
2. **Get a periodic report**: enter a `Memory Monitor Interval` (default is `10` seconds) and enable `Always send a memory report`
3. **Get a report only when memory falls below a threshold**: disable `Always send a memory report` and enter one or more threshold values for DMA / 8-bit for internal / external memory
**See [the section below](#how-to-use-this-example) for more information on how to configure the example.**
Now build and flash the code on the host.
```sh
idf.py -p <host_port> flash monitor
```
## Setting up the Memory Info Request
The example uses this API call to set the memory monitor:
`esp_err_t esp_hosted_set_mem_monitor(esp_hosted_config_mem_monitor_t *config, esp_hosted_curr_mem_info_t *curr_mem_info)`
where:
- `esp_hosted_config_mem_monitor_t`: pointer to the structure containing the mem monitor configuration
- `esp_hosted_curr_mem_info_t`: pointer to a structure that will hold the returned memory report when the call returns. This is valid only if the return value is `ESP_OK`
See [`esp_hosted_misc_types.h`](https://github.com/espressif/esp-hosted-mcu/blob/main/host/esp_hosted_misc_types.h) for details on the structures.
For example, to enable the memory monitor to periodically return a report every 10 seconds:
```
esp_hosted_curr_mem_info_t curr_mem_info = { 0 };
esp_hosted_config_mem_monitor_t config = {
.config = ESP_HOSTED_MEMMONITOR_ENABLE,
.report_always = true,
.interval_sec = 10,
// since we always get a report, no need to set thresholds
.internal_mem.threshold_mem_dma = 0,
.internal_mem.threshold_mem_8bit = 0,
.external_mem.threshold_mem_dma = 0,
.external_mem.threshold_mem_8bit = 0,
};
esp_err_t res = esp_hosted_set_mem_monitor(&config, &curr_mem_info);
```
`curr_mem_info` contains the current heap info from the co-processor if res is `ESP_OK`.
## Memory Info Reported
The returned memory report contains this information from the co-processor:
- the current memory report configuration on the co-processor
- the current total heap size
- the current internal and external free and largest free block for DMA or for 8/16/...-bit access
## Memory Monitor Event
If periodic reporting is enabled, or when heap memory falls below a set threshold, a memory report event is sent to the host by the co-processor, with an event base of `ESP_HOSTED_EVENT`:
- `ESP_HOSTED_EVENT_MEM_MONITOR`: sent by the co-processor to the Host with a memory usage report of type `esp_hosted_event_mem_info_t` as the event data.
The event data contains this information from the co-processor:
- the current total free heap size
- the minimum free heap size since boot
- the current internal and external free and largest free block for DMA or for 8/16/...-bit access
## How to Use This Example
The example shows how heap usage on the co-processor changes when Wi-Fi operations are performed:
1. Starts the example with Wi-Fi disabled
2. Enable Wi-Fi
3. Connect to an AP
4. Disconnected from an AP
Delays are deliberately introduced after each step to give time for memory reports to be returned (if enabled).
The example also shows how to use the memory monitor in the following ways:
1. Get a one time report: the memory report is read once on app start up.
2. Periodically generate a memory report: a memory report is generated periodically.
3. Generate a memory report when memory usage drops below a threshold: no memory report is received until after the Wi-Fi connection has cause heap space to drop below a set threshold.
The hardware used in the example is a ESP32-P4 connected to a ESP32-C5 as the co-processor with PSRAM on the C5 enabled.
### Configuring the memory report options on the ESP32-P4 host
Set the correct chip target:
```bash
idf.py set-target esp32p4
```
Open the project configuration menu:
```bash
idf.py menuconfig
```
Set the memory report configuration as required. The sections below show the configuration setup. Then, configure the Wi-Fi station parameters at needed:
- Set WiFi SSID.
- Set WiFi Password.
Optional: If you need, change the other Wi-Fi options according to your requirements.
### Build and Flash
Run `idf.py -p HOST_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
### Example 1: Generate a one time report
A one-time memory report is queried by the host.
In the Kconfig `Example Configuration`:
- enable `Query Memory Info only once` (other memory report settings are now hidden)
- set the required Wi-Fi settings
This is the example output. A report is generated only once.
<details>
<summary>Select to see output</summary>
```
[...]
I (1867) hosted_meminfo: ESP-Hosted Transport is UP
I (1997) hosted_meminfo: ESP-Hosted is ready
I (2137) RPC_WRAP: Coprocessor Boot-up
I (2137) hosted_meminfo: *** got INIT event from co-processor ***
I (2137) hosted_meminfo: *** Co-processor Reset Reason 1 ***
I (2137) hosted_meminfo: ======= Current Co-processor Mem Info =====
I (2137) hosted_meminfo: mem monitoring enabled : 0
I (2137) hosted_meminfo: report always : 0
I (2137) hosted_meminfo: reporting interval : 0
I (2137) hosted_meminfo: current heap size : 4299348
I (2137) hosted_meminfo: ----------------------------------------------
I (2137) hosted_meminfo: | | | Largest |
I (2137) hosted_meminfo: | Current Heap Size | Free Size | Free Block |
I (2137) hosted_meminfo: |-------------------|-----------|------------|
I (2137) hosted_meminfo: | Internal DMA | 123359 | 86016 |
I (2137) hosted_meminfo: | Internal 8Bit | 139303 | 86016 |
I (2137) hosted_meminfo: | External DMA | 0 | 0 |
I (2137) hosted_meminfo: | External 8Bit | 4191920 | 4128768 |
I (2137) hosted_meminfo: ----------------------------------------------
I (2137) hosted_meminfo: delaying for 10 secs before connecting
I (12137) transport: Attempt connection with slave: retry[0]
I (12137) transport: Transport is already up
I (13187) wifi station: wifi_init_sta finished.
I (13187) RPC_WRAP: ESP Event: wifi station started
I (13187) H_API: esp_wifi_remote_connect
I (13207) RPC_WRAP: ESP Event: wifi station started
I (13807) RPC_WRAP: ESP Event: Station mode: Connected
I (14217) RPC_WRAP: ESP Event: Station mode: Connected
I (14217) RPC_WRAP: ESP Event: Station mode: Connected
I (14237) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x40046e20
--- 0x40046e20: wifi_sta_receive at /home/kysoh/esp/github_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (15257) wifi station: got ip:10.10.2.152
I (15257) esp_netif_handlers: sta ip: 10.10.2.152, mask: 255.255.255.0, gw: 10.10.2.1
I (15257) wifi station: connected to ap SSID:xxx password:yyy
I (15257) hosted_meminfo: delaying for 10 secs before disconnecting
I (25277) main_task: Returned from app_main()
I (25287) RPC_WRAP: ESP Event: Station mode: Disconnected
I (25287) wifi station: do not reconnect
[...]
```
</details>
### Example 2: Periodically generate a memory report
The co-processor sends a memory report periodically.
In the `Example Configuration`:
- disable `Query Memory Info only once`
- set `Memory Monitor Interval (in seconds)`. Default is 10 seconds
- enable `Always send a memory report`
- set the required Wi-Fi settings
This is the example output. A report is generated at every reporting interval.
<details>
<summary>Select to see output</summary>
```
[...]
I (1867) hosted_meminfo: ESP-Hosted Transport is UP
I (1997) hosted_meminfo: ESP-Hosted is ready
I (2137) RPC_WRAP: Coprocessor Boot-up
I (2137) hosted_meminfo: *** got INIT event from co-processor ***
I (2137) hosted_meminfo: *** Co-processor Reset Reason 1 ***
I (2137) hosted_meminfo: ======= Current Co-processor Mem Info =====
I (2137) hosted_meminfo: mem monitoring enabled : 1
I (2137) hosted_meminfo: report always : 1
I (2137) hosted_meminfo: reporting interval : 10
I (2137) hosted_meminfo: current heap size : 4299280
I (2137) hosted_meminfo: ----------------------------------------------
I (2137) hosted_meminfo: | | | Largest |
I (2137) hosted_meminfo: | Current Heap Size | Free Size | Free Block |
I (2137) hosted_meminfo: |-------------------|-----------|------------|
I (2137) hosted_meminfo: | Internal DMA | 123291 | 86016 |
I (2137) hosted_meminfo: | Internal 8Bit | 139235 | 86016 |
I (2137) hosted_meminfo: | External DMA | 0 | 0 |
I (2137) hosted_meminfo: | External 8Bit | 4191920 | 4128768 |
I (2137) hosted_meminfo: ----------------------------------------------
I (2137) hosted_meminfo: delaying for 20 secs before connecting
I (12137) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (12137) hosted_meminfo: Total free heap size : 4299532
I (12137) hosted_meminfo: Min free heap size : 4295348
I (12137) hosted_meminfo: ----------------------------------------------
I (12137) hosted_meminfo: | | | Largest |
I (12137) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (12137) hosted_meminfo: |-------------------|-----------|------------|
I (12137) hosted_meminfo: | Internal DMA | 123699 | 86016 |
I (12137) hosted_meminfo: | Internal 8Bit | 139643 | 86016 |
I (12137) hosted_meminfo: | External DMA | 0 | 0 |
I (12137) hosted_meminfo: | External 8Bit | 4191920 | 4128768 |
I (12137) hosted_meminfo: ----------------------------------------------
I (22137) transport: Attempt connection with slave: retry[0]
I (22137) transport: Transport is already up
I (22287) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (22287) hosted_meminfo: Total free heap size : 4288288
I (22287) hosted_meminfo: Min free heap size : 4288288
I (22287) hosted_meminfo: ----------------------------------------------
I (22287) hosted_meminfo: | | | Largest |
I (22287) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (22287) hosted_meminfo: |-------------------|-----------|------------|
I (22287) hosted_meminfo: | Internal DMA | 112455 | 77824 |
I (22287) hosted_meminfo: | Internal 8Bit | 128399 | 77824 |
I (22287) hosted_meminfo: | External DMA | 0 | 0 |
I (22287) hosted_meminfo: | External 8Bit | 4191920 | 4128768 |
I (22287) hosted_meminfo: ----------------------------------------------
I (23187) wifi station: wifi_init_sta finished.
I (23197) RPC_WRAP: ESP Event: wifi station started
I (23197) H_API: esp_wifi_remote_connect
I (23207) RPC_WRAP: ESP Event: wifi station started
I (23717) RPC_WRAP: ESP Event: Station mode: Connected
I (24227) RPC_WRAP: ESP Event: Station mode: Connected
I (24227) RPC_WRAP: ESP Event: Station mode: Connected
I (24237) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x40046e38
--- 0x40046e38: wifi_sta_receive at /home/kysoh/esp/github_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (25277) wifi station: got ip:10.10.2.152
I (25277) esp_netif_handlers: sta ip: 10.10.2.152, mask: 255.255.255.0, gw: 10.10.2.1
I (25277) wifi station: connected to ap SSID:xxx password:yyy
I (25277) hosted_meminfo: delaying for 20 secs before disconnecting
I (32137) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (32137) hosted_meminfo: Total free heap size : 4256044
I (32137) hosted_meminfo: Min free heap size : 4253944
I (32137) hosted_meminfo: ----------------------------------------------
I (32137) hosted_meminfo: | | | Largest |
I (32137) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (32137) hosted_meminfo: |-------------------|-----------|------------|
I (32137) hosted_meminfo: | Internal DMA | 80279 | 45056 |
I (32137) hosted_meminfo: | Internal 8Bit | 96223 | 45056 |
I (32137) hosted_meminfo: | External DMA | 0 | 0 |
I (32137) hosted_meminfo: | External 8Bit | 4191852 | 4128768 |
I (32137) hosted_meminfo: ----------------------------------------------
I (42137) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (42137) hosted_meminfo: Total free heap size : 4256044
I (42137) hosted_meminfo: Min free heap size : 4253944
I (42137) hosted_meminfo: ----------------------------------------------
I (42137) hosted_meminfo: | | | Largest |
I (42137) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (42137) hosted_meminfo: |-------------------|-----------|------------|
I (42137) hosted_meminfo: | Internal DMA | 80279 | 45056 |
I (42137) hosted_meminfo: | Internal 8Bit | 96223 | 45056 |
I (42137) hosted_meminfo: | External DMA | 0 | 0 |
I (42137) hosted_meminfo: | External 8Bit | 4191852 | 4128768 |
I (42137) hosted_meminfo: ----------------------------------------------
I (45297) main_task: Returned from app_main()
I (45307) RPC_WRAP: ESP Event: Station mode: Disconnected
I (45307) wifi station: do not reconnect
I (52137) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (52137) hosted_meminfo: Total free heap size : 4256900
I (52137) hosted_meminfo: Min free heap size : 4253944
I (52137) hosted_meminfo: ----------------------------------------------
I (52137) hosted_meminfo: | | | Largest |
I (52137) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (52137) hosted_meminfo: |-------------------|-----------|------------|
I (52137) hosted_meminfo: | Internal DMA | 81135 | 45056 |
I (52137) hosted_meminfo: | Internal 8Bit | 97079 | 45056 |
I (52137) hosted_meminfo: | External DMA | 0 | 0 |
I (52137) hosted_meminfo: | External 8Bit | 4191852 | 4128768 |
I (52137) hosted_meminfo: ----------------------------------------------
[...]
```
</details>
### Example 3: Generate a memory report when heap memory drops below threshold
The co-processor only sends a memory report when heap memory drops below a threshold value. Here, a report is only sent while the station is connected to the AP.
In the previous example, the `Internal DMA` free size after connecting is `80279`, and after disconnecting is `81135`. We choose a value that is in between for the threshold: `81000`. Adjust this value as required based on your system.
In the `Example Configuration`:
- disable `Query Memory Info only once`
- set `Memory Monitor Interval (in seconds)`. Default is 10 seconds
- disable `Always send a memory report`
- set `Threshold for internal DMA Heap Memory (in bytes)` to `81000`
- set the threshold for other parameters to `0` to disable them
- set the required Wi-Fi settings
This is the example output. A report is generated only when the internal DMA heap memory is lower than the threshold of 81,000 bytes (only when connected to the AP).
<details>
<summary>Select to see output</summary>
```
[...]
I (1867) hosted_meminfo: ESP-Hosted Transport is UP
I (1997) hosted_meminfo: ESP-Hosted is ready
I (2137) RPC_WRAP: Coprocessor Boot-up
I (2137) hosted_meminfo: *** got INIT event from co-processor ***
I (2137) hosted_meminfo: *** Co-processor Reset Reason 1 ***
I (2137) hosted_meminfo: ======= Current Co-processor Mem Info =====
I (2137) hosted_meminfo: mem monitoring enabled : 1
I (2137) hosted_meminfo: report always : 0
I (2137) hosted_meminfo: reporting interval : 10
I (2137) hosted_meminfo: current heap size : 4299292
I (2137) hosted_meminfo: ----------------------------------------------
I (2137) hosted_meminfo: | | | Largest |
I (2137) hosted_meminfo: | Current Heap Size | Free Size | Free Block |
I (2137) hosted_meminfo: |-------------------|-----------|------------|
I (2137) hosted_meminfo: | Internal DMA | 123303 | 86016 |
I (2137) hosted_meminfo: | Internal 8Bit | 139247 | 86016 |
I (2137) hosted_meminfo: | External DMA | 0 | 0 |
I (2137) hosted_meminfo: | External 8Bit | 4191920 | 4128768 |
I (2137) hosted_meminfo: ----------------------------------------------
I (2137) hosted_meminfo: delaying for 20 secs before connecting
I (22147) transport: Attempt connection with slave: retry[0]
I (22147) transport: Transport is already up
I (23197) wifi station: wifi_init_sta finished.
I (23207) RPC_WRAP: ESP Event: wifi station started
I (23207) H_API: esp_wifi_remote_connect
I (23217) RPC_WRAP: ESP Event: wifi station started
I (23817) RPC_WRAP: ESP Event: Station mode: Connected
I (24237) RPC_WRAP: ESP Event: Station mode: Connected
I (24237) RPC_WRAP: ESP Event: Station mode: Connected
I (24247) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x40046e32
--- 0x40046e32: wifi_sta_receive at /home/kysoh/esp/github_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (25267) wifi station: got ip:10.10.2.152
I (25267) esp_netif_handlers: sta ip: 10.10.2.152, mask: 255.255.255.0, gw: 10.10.2.1
I (25267) wifi station: connected to ap SSID:xxx password:yyy
I (25267) hosted_meminfo: delaying for 20 secs before disconnecting
I (32137) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (32137) hosted_meminfo: Total free heap size : 4256020
I (32137) hosted_meminfo: Min free heap size : 4253572
I (32137) hosted_meminfo: ----------------------------------------------
I (32137) hosted_meminfo: | | | Largest |
I (32137) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (32137) hosted_meminfo: |-------------------|-----------|------------|
I (32137) hosted_meminfo: | Internal DMA | 80255 | 47104 |
I (32137) hosted_meminfo: | Internal 8Bit | 96199 | 47104 |
I (32137) hosted_meminfo: | External DMA | 0 | 0 |
I (32137) hosted_meminfo: | External 8Bit | 4191852 | 4128768 |
I (32137) hosted_meminfo: ----------------------------------------------
I (42137) hosted_meminfo: ======== Event: Co-processor Mem Info ========
I (42137) hosted_meminfo: Total free heap size : 4256020
I (42137) hosted_meminfo: Min free heap size : 4253572
I (42137) hosted_meminfo: ----------------------------------------------
I (42137) hosted_meminfo: | | | Largest |
I (42137) hosted_meminfo: | Heap Size | Free Size | Free Block |
I (42137) hosted_meminfo: |-------------------|-----------|------------|
I (42137) hosted_meminfo: | Internal DMA | 80255 | 47104 |
I (42137) hosted_meminfo: | Internal 8Bit | 96199 | 47104 |
I (42137) hosted_meminfo: | External DMA | 0 | 0 |
I (42137) hosted_meminfo: | External 8Bit | 4191852 | 4128768 |
I (42137) hosted_meminfo: ----------------------------------------------
[...]
```
</details>
# References
- ESP-IDF documentation on [Memory Capabilities](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/mem_alloc.html#memory-capabilities)
- ESP-IDF documentation on [Heap information](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/heap_debug.html#heap-information).
- ESP-IDF [capabilities of memory systems](https://github.com/espressif/esp-idf/blob/master/components/heap/include/esp_heap_caps.h)
@@ -0,0 +1,4 @@
set(srcs "main.c" "station_example.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")
@@ -0,0 +1,121 @@
menu "Example Configuration"
config EXAMPLE_MEM_MONITOR_QUERY_ONLY_ONCE
bool "Query Memory Info only once"
default n
help
Query the memory on the co-processor only once
if !EXAMPLE_MEM_MONITOR_QUERY_ONLY_ONCE
config EXAMPLE_MEM_MONITOR_INTERVAL_SEC
int "Memory Monitor Interval (in seconds)"
default 10
help
Set the interval for doing memory checks on the co-processor
config EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
bool "Always send a memory report"
default y
help
When enabled, co-processor will always send a memory report while doing the memory check at the set interval. When disable, co-processor will only send a memory report if memory check shows memory has dropped below a set threshold
config EXAMPLE_MEM_MONITOR_INTERNAL_DMA_THRESHOLD
depends on !EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
int "Threshold for internal DMA Heap Memory (in bytes)"
default 8192
help
Sets the memory threshold that will trigger a report. 0 disables the threshold.
config EXAMPLE_MEM_MONITOR_INTERNAL_8BIT_THRESHOLD
depends on !EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
int "Threshold for internal 8-Bit Heap Memory (in bytes)"
default 8192
help
Sets the memory threshold that will trigger a report. 0 disables the threshold.
config EXAMPLE_MEM_MONITOR_EXTERNAL_DMA_THRESHOLD
depends on !EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
int "Threshold for external DMA Heap Memory (in bytes)"
default 8192
help
Sets the memory threshold that will trigger a report. 0 disables the threshold.
config EXAMPLE_MEM_MONITOR_EXTERNAL_8BIT_THRESHOLD
depends on !EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
int "Threshold for external 8-Bit Heap Memory (in bytes)"
default 8192
help
Sets the memory threshold that will trigger a report. 0 disables the threshold.
endif
menu "Wifi Config"
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
choice ESP_WIFI_SAE_MODE
prompt "WPA3 SAE mode selection"
default ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
help
Select mode for SAE as Hunt and Peck, H2E or both.
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HUNT_AND_PECK
bool "HUNT AND PECK"
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT
bool "H2E"
depends on ESP_WIFI_ENABLE_SAE_H2E
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
bool "BOTH"
depends on ESP_WIFI_ENABLE_SAE_H2E
endchoice
config ESP_WIFI_PW_ID
string "PASSWORD IDENTIFIER"
depends on ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT|| ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
default ""
help
password identifier for SAE H2E
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.
choice ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD
prompt "WiFi Scan auth mode threshold"
default ESP_WIFI_AUTH_WPA2_PSK
help
The weakest authmode to accept in the scan mode.
This value defaults to ESP_WIFI_AUTH_WPA2_PSK in case password is present and ESP_WIFI_AUTH_OPEN is used.
Please select ESP_WIFI_AUTH_WEP/ESP_WIFI_AUTH_WPA_PSK in case AP is operating in WEP/WPA mode.
config ESP_WIFI_AUTH_OPEN
bool "OPEN"
config ESP_WIFI_AUTH_WEP
bool "WEP"
config ESP_WIFI_AUTH_WPA_PSK
bool "WPA PSK"
config ESP_WIFI_AUTH_WPA2_PSK
bool "WPA2 PSK"
config ESP_WIFI_AUTH_WPA_WPA2_PSK
bool "WPA/WPA2 PSK"
config ESP_WIFI_AUTH_WPA3_PSK
bool "WPA3 PSK"
config ESP_WIFI_AUTH_WPA2_WPA3_PSK
bool "WPA2/WPA3 PSK"
config ESP_WIFI_AUTH_WAPI_PSK
bool "WAPI PSK"
endchoice
endmenu
endmenu
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __APP_COMMON_H__
#define __APP_COMMON_H__
void example_wifi_init_once(void);
void example_wifi_init_sta(void);
void example_wifi_sta_disconnect(void);
void example_wifi_deinit_sta(void);
#endif
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,258 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_event.h"
#include "esp_hosted.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "app_common.h"
static SemaphoreHandle_t sem_hosted_is_up;
static const char* TAG = "hosted_meminfo";
#ifndef CONFIG_EXAMPLE_MEM_MONITOR_QUERY_ONLY_ONCE
#define MEM_MONITOR_INTERVAL_SEC CONFIG_EXAMPLE_MEM_MONITOR_INTERVAL_SEC
#if MEM_MONITOR_INTERVAL_SEC <= 0
#error "Monitor interval should be 1 sec or greater"
#endif
// we delay by twice the monitor interval to give time for the monitor event to be sent
#define WIFI_DELAY_BEFORE_CONNECTING_SEC (2 * MEM_MONITOR_INTERVAL_SEC)
#define WIFI_DELAY_BEFORE_DISCONNECTING_SEC (2 * MEM_MONITOR_INTERVAL_SEC)
#ifdef CONFIG_EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
#define MEM_MONITOR_REPORT_ALWAYS true
#else
#define MEM_MONITOR_REPORT_ALWAYS false
#endif
#ifndef CONFIG_EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
/*
* If free heap memory falls below these threshold values on the co-processor,
* a mem info event is generated.
*
* Note: external threshold values will trigger the event only if external memory (SPIRAM)
* is enabled on the co-processor.
*/
#define MEM_MONITOR_INTERNAL_DMA_THRESHOLD CONFIG_EXAMPLE_MEM_MONITOR_INTERNAL_DMA_THRESHOLD
#define MEM_MONITOR_INTERNAL_8BIT_THRESHOLD CONFIG_EXAMPLE_MEM_MONITOR_INTERNAL_8BIT_THRESHOLD
#define MEM_MONITOR_EXTERNAL_DMA_THRESHOLD CONFIG_EXAMPLE_MEM_MONITOR_EXTERNAL_DMA_THRESHOLD
#define MEM_MONITOR_EXTERNAL_8BIT_THRESHOLD CONFIG_EXAMPLE_MEM_MONITOR_EXTERNAL_8BIT_THRESHOLD
#else // ifndef CONFIG_EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
// since we are reporting always, no need to set thresholds
#define MEM_MONITOR_INTERNAL_DMA_THRESHOLD 0
#define MEM_MONITOR_INTERNAL_8BIT_THRESHOLD 0
#define MEM_MONITOR_EXTERNAL_DMA_THRESHOLD 0
#define MEM_MONITOR_EXTERNAL_8BIT_THRESHOLD 0
#endif // ifndef CONFIG_EXAMPLE_MEM_MONITOR_REPORT_ALWAYS
#else // ifndef CONFIG_EXAMPLE_MEM_MONITOR_QUERY_ONLY_ONCE
#define MEM_MONITOR_ENABLE false
#define MEM_MONITOR_INTERVAL_SEC 0
#define MEM_MONITOR_REPORT_ALWAYS false
#define MEM_MONITOR_INTERNAL_DMA_THRESHOLD 0
#define MEM_MONITOR_INTERNAL_8BIT_THRESHOLD 0
#define MEM_MONITOR_EXTERNAL_DMA_THRESHOLD 0
#define MEM_MONITOR_EXTERNAL_8BIT_THRESHOLD 0
#define WIFI_DELAY_BEFORE_CONNECTING_SEC (10)
#define WIFI_DELAY_BEFORE_DISCONNECTING_SEC (10)
#endif // ifndef CONFIG_EXAMPLE_MEM_MONITOR_QUERY_ONLY_ONCE
/**
* This event handler handles ESP-Hosted events
*
* See station_example.c for the event handler for Wi-Fi and IP events
*/
static void esp_hosted_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == ESP_HOSTED_EVENT) {
if (event_id == ESP_HOSTED_EVENT_MEM_MONITOR) {
esp_hosted_event_mem_info_t *event = (esp_hosted_event_mem_info_t *)event_data;
ESP_LOGI(TAG, "======== Event: Co-processor Mem Info ========");
ESP_LOGI(TAG, "Total free heap size : % 7"PRIu32, event->curr_total_free_heap_size);
ESP_LOGI(TAG, "Min free heap size : % 7"PRIu32, event->curr_min_free_heap_size);
ESP_LOGI(TAG, "----------------------------------------------");
ESP_LOGI(TAG, "| | | Largest |");
ESP_LOGI(TAG, "| Heap Size | Free Size | Free Block |");
ESP_LOGI(TAG, "|-------------------|-----------|------------|");
ESP_LOGI(TAG, "| Internal DMA | % 7"PRIu32" | % 7"PRIu32" |",
event->curr_internal.cap_dma.free_size,
event->curr_internal.cap_dma.largest_free_block);
ESP_LOGI(TAG, "| Internal 8Bit | % 7"PRIu32" | % 7"PRIu32" |",
event->curr_internal.cap_8bit.free_size,
event->curr_internal.cap_8bit.largest_free_block);
ESP_LOGI(TAG, "| External DMA | % 7"PRIu32" | % 7"PRIu32" |",
event->curr_external.cap_dma.free_size,
event->curr_external.cap_dma.largest_free_block);
ESP_LOGI(TAG, "| External 8Bit | % 7"PRIu32" | % 7"PRIu32" |",
event->curr_external.cap_8bit.free_size,
event->curr_external.cap_8bit.largest_free_block);
ESP_LOGI(TAG, "----------------------------------------------");
} else if (event_id == ESP_HOSTED_EVENT_CP_INIT) {
ESP_LOGI(TAG, "*** got INIT event from co-processor ***");
esp_hosted_event_init_t *event = (esp_hosted_event_init_t *)event_data;
ESP_LOGI(TAG, "*** Co-processor Reset Reason %"PRIu16" ***", event->reason);
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_UP) {
ESP_LOGI(TAG, "ESP-Hosted Transport is UP");
xSemaphoreGive(sem_hosted_is_up);
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_DOWN) {
ESP_LOGI(TAG, "ESP-Hosted Transport is DOWN");
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_FAILURE) {
ESP_LOGI(TAG, "*** Transport Failure ***");
} else if (event_id == ESP_HOSTED_EVENT_CP_HEARTBEAT) {
esp_hosted_event_heartbeat_t *event = (esp_hosted_event_heartbeat_t *)event_data;
ESP_LOGI(TAG, "*** Heartbeat %"PRIu32" ***", event->heartbeat);
} else {
ESP_LOGI(TAG, "Got UNKNOWN ESP_HOSTED event");
}
}
}
static void app_init_once(void)
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_event_handler_instance_t instance_got_hosted_event;
ESP_ERROR_CHECK(esp_event_handler_instance_register(ESP_HOSTED_EVENT,
ESP_EVENT_ANY_ID,
&esp_hosted_event_handler,
NULL,
&instance_got_hosted_event));
// create a binary semaphore for app to wait on until ESP-Hosted transport is up
sem_hosted_is_up = xSemaphoreCreateBinary();
assert(sem_hosted_is_up);
xSemaphoreGive(sem_hosted_is_up);
xSemaphoreTake(sem_hosted_is_up, portMAX_DELAY);
}
static void app_esp_hosted_init(void)
{
esp_hosted_init();
esp_hosted_connect_to_slave();
}
static esp_err_t app_mem_monitor_set(esp_hosted_curr_mem_info_t *curr_mem_info)
{
if (!curr_mem_info) {
return ESP_FAIL;
}
// set up configuration for memory monitoring
esp_hosted_config_mem_monitor_t config = {
#ifdef CONFIG_EXAMPLE_MEM_MONITOR_QUERY_ONLY_ONCE
// don't change monitor configuration for one time query
.config = ESP_HOSTED_MEMMONITOR_NO_CHANGE,
#else
.config = ESP_HOSTED_MEMMONITOR_ENABLE,
#endif
.report_always = MEM_MONITOR_REPORT_ALWAYS,
.interval_sec = MEM_MONITOR_INTERVAL_SEC,
.internal_mem.threshold_mem_dma = MEM_MONITOR_INTERNAL_DMA_THRESHOLD,
.internal_mem.threshold_mem_8bit = MEM_MONITOR_INTERNAL_8BIT_THRESHOLD,
.external_mem.threshold_mem_dma = MEM_MONITOR_EXTERNAL_DMA_THRESHOLD,
.external_mem.threshold_mem_8bit = MEM_MONITOR_EXTERNAL_8BIT_THRESHOLD,
};
return esp_hosted_set_mem_monitor(&config, curr_mem_info);
}
void print_mem_info_resp(esp_hosted_curr_mem_info_t *info)
{
char *config_str = "";
if (info->config == ESP_HOSTED_MEMMONITOR_NO_CHANGE) {
config_str = "No Change";
} else if (info->config == ESP_HOSTED_MEMMONITOR_DISABLE) {
config_str = "Disabled";
} else if (info->config == ESP_HOSTED_MEMMONITOR_ENABLE) {
config_str = "Enabled";
}
// print out current mem info
ESP_LOGI(TAG, "======= Current Co-processor Mem Info =====");
ESP_LOGI(TAG, "mem monitoring config : %s", config_str);
ESP_LOGI(TAG, "report always : %"PRIu16, info->report_always);
ESP_LOGI(TAG, "reporting interval : %"PRIu32, info->interval_sec);
ESP_LOGI(TAG, "current heap size : %"PRIu32, info->curr_total_heap_size);
#if 1
ESP_LOGI(TAG, "----------------------------------------------");
ESP_LOGI(TAG, "| | | Largest |");
ESP_LOGI(TAG, "| Current Heap Size | Free Size | Free Block |");
ESP_LOGI(TAG, "|-------------------|-----------|------------|");
ESP_LOGI(TAG, "| Internal DMA | % 7"PRIu32" | % 7"PRIu32" |",
info->curr_internal.cap_dma.free_size,
info->curr_internal.cap_dma.largest_free_block);
ESP_LOGI(TAG, "| Internal 8Bit | % 7"PRIu32" | % 7"PRIu32" |",
info->curr_internal.cap_8bit.free_size,
info->curr_internal.cap_8bit.largest_free_block);
ESP_LOGI(TAG, "| External DMA | % 7"PRIu32" | % 7"PRIu32" |",
info->curr_external.cap_dma.free_size,
info->curr_external.cap_dma.largest_free_block);
ESP_LOGI(TAG, "| External 8Bit | % 7"PRIu32" | % 7"PRIu32" |",
info->curr_external.cap_8bit.free_size,
info->curr_external.cap_8bit.largest_free_block);
ESP_LOGI(TAG, "----------------------------------------------");
#else
ESP_LOGI(TAG, "Current Internal DMA Heap:");
ESP_LOGI(TAG, " Free Size : %"PRIu32, info->curr_internal.cap_dma.free_size);
ESP_LOGI(TAG, " Largest Free Block: %"PRIu32, info->curr_internal.cap_dma.largest_free_block);
ESP_LOGI(TAG, "Current Internal 8bit Heap:");
ESP_LOGI(TAG, " Free Size : %"PRIu32, info->curr_internal.cap_8bit.free_size);
ESP_LOGI(TAG, " Largest Free Block: %"PRIu32, info->curr_internal.cap_8bit.largest_free_block);
ESP_LOGI(TAG, "Current External DMA Heap:");
ESP_LOGI(TAG, " Free Size : %"PRIu32, info->curr_external.cap_dma.free_size);
ESP_LOGI(TAG, " Largest Free Block: %"PRIu32, info->curr_external.cap_dma.largest_free_block);
ESP_LOGI(TAG, "Current External 8bit Heap:");
ESP_LOGI(TAG, " Free Size : %"PRIu32, info->curr_external.cap_8bit.free_size);
ESP_LOGI(TAG, " Largest Free Block: %"PRIu32, info->curr_external.cap_8bit.largest_free_block);
ESP_LOGI(TAG, "=========================================");
#endif
}
void app_main(void)
{
esp_err_t res;
esp_hosted_curr_mem_info_t curr_mem_info = { 0 };
app_init_once();
app_esp_hosted_init();
example_wifi_init_once(); // one time wifi init
// wait until ESP-Hosted is up before continuing
xSemaphoreTake(sem_hosted_is_up, portMAX_DELAY);
ESP_LOGI(TAG, "ESP-Hosted is ready");
res = app_mem_monitor_set(&curr_mem_info);
if (ESP_OK != res) {
ESP_LOGE(TAG, "Failed to set up mem monitor: %s", esp_err_to_name(res));
if (res == ESP_ERR_NOT_SUPPORTED) {
ESP_LOGE(TAG, "Mem Monitor Interface support on co-processor must be enabled");
}
return;
}
print_mem_info_resp(&curr_mem_info);
// wait a while to get a report before we connect
ESP_LOGI(TAG, "delaying for %"PRIu16 " secs before connecting", WIFI_DELAY_BEFORE_CONNECTING_SEC);
vTaskDelay((WIFI_DELAY_BEFORE_CONNECTING_SEC * 1000) / portTICK_PERIOD_MS);
// connect to an AP
example_wifi_init_sta();
// wait a while to get a report before we disconnect
ESP_LOGI(TAG, "delaying for %"PRIu16 " secs before disconnecting", WIFI_DELAY_BEFORE_DISCONNECTING_SEC);
vTaskDelay((WIFI_DELAY_BEFORE_DISCONNECTING_SEC * 1000) / portTICK_PERIOD_MS);
// disconnect from the AP
example_wifi_sta_disconnect();
}
@@ -0,0 +1,223 @@
/*
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* WiFi station 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 <string.h>
#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 "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "app_common.h"
/* The examples use WiFi configuration that you can set via project configuration menu
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#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
#if CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HUNT_AND_PECK
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
#define EXAMPLE_H2E_IDENTIFIER ""
#elif CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HASH_TO_ELEMENT
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#elif CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#endif
#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi station";
static int s_retry_num = 0;
static bool do_not_reconnect = false;
static bool station_connected = false;
static esp_event_handler_instance_t instance_any_id = NULL;
static esp_event_handler_instance_t instance_got_ip = NULL;
static esp_netif_t *sta_netif = NULL;
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) {
// mark that we want to auto reconnect
do_not_reconnect = false;
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (!do_not_reconnect) {
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 {
ESP_LOGI(TAG,"do not reconnect");
}
} 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 example_wifi_deinit_sta(void)
{
if (sta_netif) {
esp_netif_destroy_default_wifi(sta_netif);
sta_netif = NULL;
}
if (!instance_any_id) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT,
ESP_EVENT_ANY_ID,
instance_any_id));
instance_any_id = NULL;
}
if (!instance_got_ip) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT,
ESP_EVENT_ANY_ID,
instance_got_ip));
instance_got_ip = NULL;
}
vEventGroupDelete(s_wifi_event_group);
}
void example_wifi_init_once(void)
{
ESP_ERROR_CHECK(esp_netif_init());
}
void example_wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
if (!instance_any_id) {
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
}
if (!instance_got_ip) {
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
}
if (!sta_netif) {
sta_netif = esp_netif_create_default_wifi_sta();
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (password len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = ESP_WIFI_SAE_MODE,
.sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,
#ifdef CONFIG_ESP_WIFI_WPA3_COMPATIBLE_SUPPORT
.disable_wpa3_compatible_mode = 0,
#endif
},
};
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);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "connected to ap SSID:%s password:%s",
EXAMPLE_ESP_WIFI_SSID, EXAMPLE_ESP_WIFI_PASS);
station_connected = true;
} 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");
}
}
void example_wifi_sta_disconnect(void)
{
if (!station_connected) {
ESP_LOGW(TAG, "not connected to a station");
}
// mark that we don't want to auto reconnect
do_not_reconnect = true;
esp_wifi_disconnect();
}
@@ -0,0 +1,2 @@
# enable mem monitor configuration
CONFIG_ESP_HOSTED_MEM_MONITOR=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(hosted_events)
@@ -0,0 +1,287 @@
| 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-Hosted Events Example
This host example uses ESP-Hosted events to detect that the co-processor has reset or hanged. It then takes action to reinitialised ESP-Hosted to restore connectivity with the co-processor.
This has been tested with the ESP32-C6 co-processor, flashed with the standard [ESP-Hosted slave example](https://components.espressif.com/components/espressif/esp_hosted/examples/slave) from the ESP Component Registry, or built from the `slave` directory from ESP-Hosted Github.
It makes use of ESP-Hosted events, with event base of `ESP_HOSTED_EVENT`:
- `ESP_HOSTED_EVENT_CP_INIT`: this event is sent by the co-processor after it has started and ESP-Hosted transport has been setup. Receiving this event for a second time indicates that the co-process has restarted. An `esp_reset_reason_t` value is provided so the host can discover why the co-processor sent the event
- `ESP_HOSTED_EVENT_CP_HEARTBEAT`: when configured by the host, the co-processor will periodically send a heartbeat event at the required interval (in seconds). If this event is not received after the interval, it indicates the the co-processor may have hanged and is unable to send the event.
- `ESP_HOSTED_EVENT_TRANSPORT_FAILURE`: indicates a ESP-Hosted transport failure
This example sets up the detection of ESP-Hosted events, then connects to a station. After connecting, if the co-processor reboots (INIT event received) or hangs (timeout getting HEARTBEAT), it will tell NETIF that the connection is lost, then proceed to reinitialise the ESP-Hosted transport.
## How to Use This Example on ESP Hosts
Before project configuration and build, be sure to set the correct
chip target using:
```bash
idf.py set-target <ESP Host chip_name>
```
### Configure the project
Open the project configuration menu:
```bash
idf.py menuconfig
```
In the `Example Configuration` menu:
* set `Heartbeat Interval in Seconds`. Default is 5 seconds
* toggle `Enable heartbeat timeout detection` to enable heartbeat timeout detection
* if heartbeat timeout detection is enabled, you can set `Heartbeat Timeout in Seconds`. This value should be larger than `Heartbeat Interval in Seconds`. A value should be set based on app requirements (how long to wait after a timeout before triggering a timeout alert).
Configure the Station parameters at needed:
- Set WiFi SSID.
- Set WiFi Password.
Optional: If you need, change the other Station options according to your requirements.
### Build and Flash
Run `idf.py -p HOST_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
> [!NOTE]
> To simulate a co-processor reset or hang, a ESP-Prog is connected to the ESP32-C6 on the ESP32-P4 Dev Board. The `RST` and `BOOT` buttons on the ESP-Prog is then used to reset or put the C6 into boot mode (to simulate a hang).
### Example Output using SDIO interface
```
I (6327) esp_netif_handlers: sta ip: 192.168.50.217, mask: 255.255.255.0, gw: 192.168.50.1
I (6327) wifi station: got ip:192.168.50.217
I (6327) wifi station: connected to ap SSID:xxx password:yyy
I (7377) hosted_events: *** Heartbeat 0***
W (13377) hosted_events: *** HEARTBEAT timeout ***
I (14327) hosted_events: heartbeat timeout, transport failure or got INIT event: reinit Hosted
E (14327) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x107
E (14327) H_SDIO_DRV: sdio_write_task: 0: Failed to send data: 263 54 54
E (14327) sdmmc_io: sdmmc_io_rw_extended: sdmmc_send_cmd returned 0x107
E (14337) H_SDIO_DRV: sdio_write_task: 1: Failed to send data: 263 54 54
E (14347) H_SDIO_DRV: Unrecoverable host sdio state
I (14347) wifi station: do not reconnect
I (14347) hosted_events: *** Transport Failure ***
I (14527) H_API: ESP-Hosted deinit
I (14527) serial_ll: Clean-up serial queue
W (14527) H_SDIO_DRV: free buffer[0] 0x4ff3d8c0
W (14527) H_SDIO_DRV: free buffer[1] 0x4ff3e1c0
I (14527) transport: TRANSPORT_INACTIVE
I (14527) hosted_events: restarting ESP-Hosted
I (14537) hosted_events: init ESP-Hosted
I (14537) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
W (14547) H_API: Transport already initialized, skipping initialization
I (14557) H_API: ** add_esp_wifi_remote_channels **
I (14557) H_SDIO_DRV: sdio_data_to_rx_buf_task started
I (14567) H_API: ESP-Hosted Try to communicate with ESP-Hosted slave
I (14567) transport: Attempt connection with slave: retry[0]
W (14577) H_SDIO_DRV: Reset slave using GPIO[54]
I (14577) os_wrapper_esp: GPIO [54] configured
I (16107) sdio_wrapper: SDIO master: Slot 1, Data-Lines: 4-bit Freq(KHz)[40000 KHz]
I (16107) sdio_wrapper: GPIOs: CLK[18] CMD[19] D0[14] D1[15] D2[16] D3[17] Slave_Reset[54]
I (16107) sdio_wrapper: Queues: Tx[20] Rx[20] SDIO-Rx-Mode[1]
I (16147) sdio_wrapper: Function 0 Blocksize: 512
I (16147) sdio_wrapper: Function 1 Blocksize: 512
I (16247) H_SDIO_DRV: Card init success, TRANSPORT_RX_ACTIVE
I (16247) transport: set_transport_state: 1
I (16247) transport: Waiting for esp_hosted slave to be ready
I (16267) H_SDIO_DRV: SDIO Host operating in STREAMING MODE
I (16267) H_SDIO_DRV: Open data path at slave
I (16267) H_SDIO_DRV: Starting SDIO process rx task
I (16287) H_SDIO_DRV: Received ESP_PRIV_IF type message
I (16287) transport: Received INIT event from ESP32 peripheral
I (16287) transport: EVENT: 12
I (16287) transport: Identified slave [esp32c6]
I (16297) transport: EVENT: 11
I (16297) transport: capabilities: 0xd
I (16307) transport: Features supported are:
I (16307) transport: * WLAN
I (16307) transport: - HCI over SDIO
I (16317) transport: - BLE only
I (16317) transport: EVENT: 13
I (16317) transport: ESP board type is : 13
I (16327) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (16327) H_API: Transport active
I (16327) transport: Slave chip Id[12]
I (16337) transport: raw_tp_dir[-], flow_ctrl: low[60] high[80]
I (16337) transport: transport_delayed_init
I (16347) esp_cli: Remove any existing deep_sleep cmd in cli
I (16347) esp_cli: Registering command: crash
I (16357) esp_cli: Registering command: reboot
I (16357) esp_cli: Registering command: mem-dump
I (16367) esp_cli: Registering command: task-dump
I (16367) esp_cli: Registering command: cpu-dump
I (16367) esp_cli: Registering command: heap-trace
I (16377) esp_cli: Registering command: sock-dump
I (16377) esp_cli: Registering command: host-power-save
I (16387) hci_stub_drv: Host BT Support: Disabled
I (16387) H_SDIO_DRV: Received INIT event
I (16397) H_SDIO_DRV: Event type: 0x22
I (16397) H_SDIO_DRV: Write thread started
I (16447) hosted_events: getting fw version
I (16567) hosted_events: *** got INIT event from co-processor ***
I (16567) hosted_events: *** Co-processor Reset Reason 1***
I (16567) hosted_events: Expected INIT event
I (16567) hosted_events: FW Version: 2.8.1
I (16577) hosted_events: heartbeat timer started
I (16577) transport: Attempt connection with slave: retry[0]
I (16577) transport: Transport is already up
I (16877) wifi station: wifi_init_sta finished.
I (16877) RPC_WRAP: ESP Event: wifi station started
I (16887) RPC_WRAP: ESP Event: wifi station started
I (19327) RPC_WRAP: ESP Event: Station mode: Disconnected
I (19327) H_API: esp_wifi_remote_connect
I (19347) wifi station: retry to connect to the AP
I (21567) hosted_events: *** Heartbeat 0***
I (21757) RPC_WRAP: ESP Event: Station mode: Disconnected
I (21757) H_API: esp_wifi_remote_connect
I (21767) wifi station: retry to connect to the AP
I (24377) RPC_WRAP: ESP Event: Station mode: Connected
I (24377) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x400477f6
--- 0x400477f6: wifi_sta_receive at /home/kysoh/esp/gitlab_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (25387) esp_netif_handlers: sta ip: 192.168.50.217, mask: 255.255.255.0, gw: 192.168.50.1
I (25387) wifi station: got ip:192.168.50.217
I (25387) wifi station: connected to ap SSID:xxx password:yyy
I (26577) hosted_events: *** Heartbeat 1***
```
### Example Output using SPI-FD interface
```
I (5323) esp_netif_handlers: sta ip: 192.168.50.82, mask: 255.255.255.0, gw: 192.168.50.1
I (5323) wifi station: got ip:192.168.50.82
I (5323) wifi station: connected to ap SSID:xxx password:yyy
I (6383) hosted_events: *** Heartbeat 0***
I (11383) hosted_events: *** Heartbeat 1***
I (16383) hosted_events: *** Heartbeat 2***
I (18523) spi: rx packet ignored: len [65535], rcvd_offset[65535], exp_offset[12]
I (19173) transport: Received INIT event from ESP32 peripheral
I (19173) transport: EVENT: 12
I (19173) transport: Identified slave [esp32c6]
I (19173) transport: EVENT: 11
I (19183) transport: capabilities: 0xe8
I (19183) transport: Features supported are:
I (19183) transport: - HCI over SPI
I (19193) transport: - BLE only
I (19193) transport: EVENT: 13
I (19193) transport: ESP board type is : 13
I (19203) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (19203) H_API: Transport active
I (19213) transport: Slave chip Id[12]
I (19213) transport: raw_tp_dir[-], flow_ctrl: low[60] high[90]
I (19223) transport: transport_delayed_init
I (19223) esp_cli: Remove any existing deep_sleep cmd in cli
I (19233) esp_cli: Registering command: crash
I (19233) esp_cli: Registering command: reboot
I (19233) esp_cli: Registering command: mem-dump
I (19243) esp_cli: Registering command: task-dump
I (19243) esp_cli: Registering command: cpu-dump
I (19253) esp_cli: Registering command: heap-trace
I (19253) esp_cli: Registering command: sock-dump
I (19263) esp_cli: Registering command: host-power-save
I (19263) hci_stub_drv: Host BT Support: Disabled
I (19263) spi: Received INIT event
I (19273) hosted_events: *** got INIT event from co-processor ***
I (19273) hosted_events: *** Co-processor Reset Reason 1***
W (19283) hosted_events: *** Unexpected INIT event
I (19323) hosted_events: heartbeat timeout, transport failure or got INIT event: reinit Hosted
I (19323) wifi station: do not reconnect
I (19523) H_API: ESP-Hosted deinit
I (19523) serial_ll: Clean-up serial queue
I (19523) spi: Deinitializing SPI bus
I (19523) spi_wrapper: SPI deinitialized
I (19523) spi: SPI bus deinitialized
I (19523) transport: TRANSPORT_INACTIVE
I (19523) hosted_events: restarting ESP-Hosted
I (19533) hosted_events: init ESP-Hosted
I (19533) H_API: ESP-Hosted starting. Hosted_Tasks: prio:23, stack: 5120 RPC_task_stack: 5120
W (19543) H_API: Transport already initialized, skipping initialization
I (19553) H_API: ** add_esp_wifi_remote_channels **
I (19553) spi_wrapper: Transport: SPI, Mode:3 Freq:40MHz TxQ:20 RxQ:20
GPIOs: CLK:18 MOSI:14 MISO:15 CS:19 HS:16 DR:17 SlaveReset:54
I (19563) spi: Staring SPI task
I (19573) os_wrapper_esp: GPIO [16] configuring as Interrupt
I (19573) os_wrapper_esp: GPIO [17] configuring as Interrupt
I (19583) H_API: ESP-Hosted Try to communicate with ESP-Hosted slave
I (19583) transport: Attempt connection with slave: retry[0]
I (19593) spi: Resetting slave
I (19593) spi: Resetting slave on SPI bus with pin 54
I (19603) os_wrapper_esp: GPIO [54] configured
I (20123) transport: Waiting for esp_hosted slave to be ready
I (20183) transport: Received INIT event from ESP32 peripheral
I (20183) transport: EVENT: 12
I (20183) transport: Identified slave [esp32c6]
I (20183) transport: EVENT: 11
I (20183) transport: capabilities: 0xe8
I (20183) transport: Features supported are:
I (20193) transport: - HCI over SPI
I (20193) transport: - BLE only
I (20193) transport: EVENT: 13
I (20203) transport: ESP board type is : 13
I (20203) transport: Base transport is set-up, TRANSPORT_TX_ACTIVE
I (20213) H_API: Transport active
I (20213) transport: Slave chip Id[12]
I (20213) transport: raw_tp_dir[-], flow_ctrl: low[60] high[90]
I (20223) transport: transport_delayed_init
I (20223) esp_cli: Remove any existing deep_sleep cmd in cli
I (20233) esp_cli: Registering command: crash
I (20233) esp_cli: Registering command: reboot
I (20243) esp_cli: Registering command: mem-dump
I (20243) esp_cli: Registering command: task-dump
I (20253) esp_cli: Registering command: cpu-dump
I (20253) esp_cli: Registering command: heap-trace
I (20253) esp_cli: Registering command: sock-dump
I (20263) esp_cli: Registering command: host-power-save
I (20263) hci_stub_drv: Host BT Support: Disabled
I (20273) spi: Received INIT event
I (20323) hosted_events: getting fw version
I (20583) hosted_events: *** got INIT event from co-processor ***
I (20583) hosted_events: *** Co-processor Reset Reason 1***
I (20583) hosted_events: Expected INIT event
I (20583) hosted_events: FW Version: 2.7.4
I (20593) transport: Attempt connection with slave: retry[0]
I (20593) transport: Transport is already up
I (20893) wifi station: wifi_init_sta finished.
I (20893) RPC_WRAP: ESP Event: wifi station started
I (20903) RPC_WRAP: ESP Event: wifi station started
I (23343) RPC_WRAP: ESP Event: Station mode: Disconnected
I (23343) H_API: esp_wifi_remote_connect
I (23373) wifi station: retry to connect to the AP
I (25593) hosted_events: *** Heartbeat 0***
I (25783) RPC_WRAP: ESP Event: Station mode: Disconnected
I (25783) H_API: esp_wifi_remote_connect
I (25793) wifi station: retry to connect to the AP
I (28403) RPC_WRAP: ESP Event: Station mode: Connected
I (28403) esp_wifi_remote: esp_wifi_internal_reg_rxcb: sta: 0x400408f2
--- 0x400408f2: wifi_sta_receive at /home/kysoh/esp/gitlab_esp-idf/components/esp_wifi/src/wifi_netif.c:38
I (29423) esp_netif_handlers: sta ip: 192.168.50.82, mask: 255.255.255.0, gw: 192.168.50.1
I (29423) wifi station: got ip:192.168.50.82
I (29423) wifi station: connected to ap SSID:xxx password:yyy
I (30593) hosted_events: *** Heartbeat 1***
I (32213) spi: rx packet ignored: len [65535], rcvd_offset[65535], exp_offset[12]
```
@@ -0,0 +1,4 @@
set(srcs "main.c" "station_example.c")
idf_component_register(SRCS "${srcs}"
INCLUDE_DIRS ".")
@@ -0,0 +1,110 @@
menu "Example Configuration"
config EXAMPLE_HEARTBEAT_INTERVAL_SEC
int "Heartbeat Interval in Seconds"
default 5
help
Set the co-processor heartbeat interval
config EXAMPLE_ENABLE_HEARTBEAT_MONITOR
bool "Enable heartbeat timeout monitor"
default y
help
Detect if there is a Heartbeat timeout
config EXAMPLE_HEARTBEAT_TIMEOUT_SEC
depends on EXAMPLE_ENABLE_HEARTBEAT_MONITOR
int "Heartbeat Timeout in Seconds"
default 6
help
Set the co-processor heartbeat timeout interval (minimum value should be the Heartbeat interval)
choice EXAMPLE_HOSTED_RECOVERY_METHOD
bool "Co-processor Recovery method"
default EXAMPLE_DO_HOSTED_RECOVERY
help
Select the recovery method to use
config EXAMPLE_DO_HOSTED_RECOVERY
bool "Restart ESP-Hosted transport"
help
Restart the ESP-Hosted transport to attempt recovery
config EXAMPLE_DO_HOST_RESET
bool "Restart the Host"
help
Restart the Host to recover the system
config EXAMPLE_DO_NOTHING
bool "Do nothing"
help
Don't do anything when error is received
endchoice
config ESP_WIFI_SSID
string "WiFi SSID"
default "myssid"
help
SSID (network name) for the example to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
default "mypassword"
help
WiFi password (WPA or WPA2) for the example to use.
choice ESP_WIFI_SAE_MODE
prompt "WPA3 SAE mode selection"
default ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
help
Select mode for SAE as Hunt and Peck, H2E or both.
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HUNT_AND_PECK
bool "HUNT AND PECK"
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT
bool "H2E"
depends on ESP_WIFI_ENABLE_SAE_H2E
config ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
bool "BOTH"
depends on ESP_WIFI_ENABLE_SAE_H2E
endchoice
config ESP_WIFI_PW_ID
string "PASSWORD IDENTIFIER"
depends on ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT|| ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
default ""
help
password identifier for SAE H2E
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.
choice ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD
prompt "WiFi Scan auth mode threshold"
default ESP_WIFI_AUTH_WPA2_PSK
help
The weakest authmode to accept in the scan mode.
This value defaults to ESP_WIFI_AUTH_WPA2_PSK in case password is present and ESP_WIFI_AUTH_OPEN is used.
Please select ESP_WIFI_AUTH_WEP/ESP_WIFI_AUTH_WPA_PSK in case AP is operating in WEP/WPA mode.
config ESP_WIFI_AUTH_OPEN
bool "OPEN"
config ESP_WIFI_AUTH_WEP
bool "WEP"
config ESP_WIFI_AUTH_WPA_PSK
bool "WPA PSK"
config ESP_WIFI_AUTH_WPA2_PSK
bool "WPA2 PSK"
config ESP_WIFI_AUTH_WPA_WPA2_PSK
bool "WPA/WPA2 PSK"
config ESP_WIFI_AUTH_WPA3_PSK
bool "WPA3 PSK"
config ESP_WIFI_AUTH_WPA2_WPA3_PSK
bool "WPA2/WPA3 PSK"
config ESP_WIFI_AUTH_WAPI_PSK
bool "WAPI PSK"
endchoice
endmenu
@@ -0,0 +1,14 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef __APP_COMMON_H__
#define __APP_COMMON_H__
void example_wifi_init_once(void);
void example_wifi_init_sta(void);
void example_wifi_deinit_sta(void);
void example_wifi_sta_netif_close(void);
#endif
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,332 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "esp_log.h"
#include "esp_event.h"
#include "esp_hosted.h"
#include "esp_timer.h"
#include "esp_system.h"
#include "app_common.h"
#define HEARTBEAT_INTERVAL_SEC CONFIG_EXAMPLE_HEARTBEAT_INTERVAL_SEC
#define ENABLE_HEARTBEAT_MONITOR CONFIG_EXAMPLE_ENABLE_HEARTBEAT_MONITOR
#define DO_HOSTED_RECOVERY CONFIG_EXAMPLE_DO_HOSTED_RECOVERY
#define DO_HOST_RESET CONFIG_EXAMPLE_DO_HOST_RESET
#define DO_NOTHING CONFIG_EXAMPLE_DO_NOTHING
#if ENABLE_HEARTBEAT_MONITOR
#define HEARTBEAT_TIMEOUT_SEC CONFIG_EXAMPLE_HEARTBEAT_TIMEOUT_SEC
#define HEARTBEAT_TIMEOUT_NSEC (HEARTBEAT_TIMEOUT_SEC * 1000 * 1000)
static esp_timer_handle_t heartbeat_timer_handle = NULL;
#endif
#define ESP_HOSTED_RESET_BIT BIT0
static EventGroupHandle_t s_esp_hosted_event_group;
static bool resetting_esp_hosted_transport = false;
static bool first_init_event = false;
static bool first_heartbeat = false;
static uint32_t prev_heartbeat = 0;
static SemaphoreHandle_t sem_hosted_is_up;
static const char* TAG = "hosted_events";
#if ENABLE_HEARTBEAT_MONITOR
void my_timer_cb(void *arg)
{
ESP_LOGI(TAG, "*** TIMER: HEARTBEAT timeout ***");
if (!resetting_esp_hosted_transport) {
xEventGroupSetBits(s_esp_hosted_event_group, ESP_HOSTED_RESET_BIT);
}
// destroy the timer
esp_timer_delete(heartbeat_timer_handle);
heartbeat_timer_handle = NULL;
}
#endif
static void init_cp_error_detection(void)
{
first_init_event = false;
first_heartbeat = false;
prev_heartbeat = 0;
}
static void deinit_cp_error_detection(void)
{
// currently does nothing
}
/**
* This event handler handles ESP-Hosted events
*
* See station_example.c for the event handler for Wi-Fi and IP events
*/
static void esp_hosted_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == ESP_HOSTED_EVENT) {
if (event_id == ESP_HOSTED_EVENT_CP_INIT) {
ESP_LOGI(TAG, "*** got INIT event from co-processor ***");
esp_hosted_event_init_t *event = (esp_hosted_event_init_t *)event_data;
ESP_LOGI(TAG, "*** Co-processor Reset Reason %"PRIu16" ***", event->reason);
if (!first_init_event) {
// this is the first init event and is expected
first_init_event = true;
ESP_LOGI(TAG, "Expected INIT event");
} else if (!resetting_esp_hosted_transport) {
// unexpected init event
xEventGroupSetBits(s_esp_hosted_event_group, ESP_HOSTED_RESET_BIT);
ESP_LOGI(TAG, "*** Unexpected INIT event");
}
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_UP) {
ESP_LOGI(TAG, "ESP-Hosted Transport is UP");
xSemaphoreGive(sem_hosted_is_up);
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_DOWN) {
ESP_LOGI(TAG, "ESP-Hosted Transport is DOWN");
} else if (event_id == ESP_HOSTED_EVENT_TRANSPORT_FAILURE) {
if (!resetting_esp_hosted_transport) {
xEventGroupSetBits(s_esp_hosted_event_group, ESP_HOSTED_RESET_BIT);
ESP_LOGI(TAG, "*** Transport Failure ***");
}
} else if (event_id == ESP_HOSTED_EVENT_CP_HEARTBEAT) {
esp_hosted_event_heartbeat_t *event = (esp_hosted_event_heartbeat_t *)event_data;
ESP_LOGI(TAG, "*** Heartbeat %"PRIu32" ***", event->heartbeat);
uint32_t curr_heartbeat = event->heartbeat;
#if ENABLE_HEARTBEAT_MONITOR
if (heartbeat_timer_handle) {
if (esp_timer_is_active(heartbeat_timer_handle)) {
// restart the timer
if (ESP_OK != esp_timer_restart(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC)) {
ESP_LOGE(TAG, "Failed to restart the timer");
}
} else {
// start the timer
if (ESP_OK != esp_timer_start_once(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC)) {
ESP_LOGE(TAG, "Failed to start the timer");
}
}
}
#endif
if (!first_heartbeat) {
first_heartbeat = true;
} else if (curr_heartbeat != (prev_heartbeat + 1)) {
ESP_LOGW(TAG, "heartbeat: expected %"PRIu32", but got %"PRIu32, prev_heartbeat + 1, curr_heartbeat);
}
prev_heartbeat = curr_heartbeat;
} else {
ESP_LOGI(TAG, "Got UNKNOWN ESP_HOSTED event");
}
}
}
static void app_init_once(void)
{
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_event_handler_instance_t instance_got_hosted_event;
ESP_ERROR_CHECK(esp_event_handler_instance_register(ESP_HOSTED_EVENT,
ESP_EVENT_ANY_ID,
&esp_hosted_event_handler,
NULL,
&instance_got_hosted_event));
// set up event group, used to signal that ESP-Hosted transport has gone down
s_esp_hosted_event_group = xEventGroupCreate();
// create a binary semaphore for app to wait on until ESP-Hosted transport is up
sem_hosted_is_up = xSemaphoreCreateBinary();
assert(sem_hosted_is_up);
xSemaphoreGive(sem_hosted_is_up);
xSemaphoreTake(sem_hosted_is_up, portMAX_DELAY);
}
static void app_esp_hosted_init(void)
{
esp_hosted_init();
esp_hosted_connect_to_slave();
}
#if DO_HOSTED_RECOVERY
static void app_esp_hosted_deinit(void)
{
esp_hosted_deinit();
}
#endif
static bool app_esp_hosted_verify_up(void)
{
// to verify ESP-Hosted is UP, we get the co-processor fw info
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);
return true;
} else {
ESP_LOGE(TAG, "failed to get fw version");
return false;
}
}
#if ENABLE_HEARTBEAT_MONITOR
static esp_err_t app_setup_heartbeat_monitor(void)
{
esp_err_t res;
if (!heartbeat_timer_handle) {
// create heartbeat timer
esp_timer_create_args_t my_timer = {
.callback = my_timer_cb,
.arg = NULL,
.dispatch_method = ESP_TIMER_TASK,
.name = "Heartbeat Timer",
.skip_unhandled_events = true,
};
res = esp_timer_create(&my_timer, &heartbeat_timer_handle);
if (res == ESP_OK) {
// start the timer
res = esp_timer_start_once(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC);
if (res == ESP_OK) {
ESP_LOGI(TAG, "heartbeat timer started");
} else {
ESP_LOGE(TAG, "failed to start heartbeat timer");
return res;
}
} else {
ESP_LOGE(TAG, "failed to create heartbeat timer");
heartbeat_timer_handle = NULL;
return res;
}
} else {
// heartbeat timer already created: restart it
if (esp_timer_is_active(heartbeat_timer_handle)) {
// restart the timer
res = esp_timer_restart(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to restart the timer");
return res;
}
} else {
// start the timer
res = esp_timer_start_once(heartbeat_timer_handle, HEARTBEAT_TIMEOUT_NSEC);
if (res != ESP_OK) {
ESP_LOGE(TAG, "Failed to start the timer");
return res;
}
}
}
return ESP_OK;
}
#endif
/**
* this function handles what to do when a co-processor error is encountered
*
* here, we either try to recover from the error, or reset the host
*/
static bool app_do_cp_recovery(void)
{
#if DO_HOSTED_RECOVERY
example_wifi_sta_netif_close(); // inform netif for sta that connection was lost
example_wifi_deinit_sta(); // deinit the station
app_esp_hosted_deinit();
return true;
#elif DO_HOST_RESET
ESP_LOGI(TAG, "********* Restarting host to avoid sync issues **********************");
vTaskDelay(2000 / portTICK_PERIOD_MS);
esp_restart();
return true;
#elif DO_NOTHING
ESP_LOGI(TAG, "Nothing done to recover");
return false;
#else
#error No co-processor recovery method selected
#endif
}
void app_main(void)
{
app_init_once(); // one time app init
example_wifi_init_once(); // one time wifi init
while (true) {
init_cp_error_detection();
ESP_LOGI(TAG, "init ESP-Hosted");
app_esp_hosted_init();
// wait until ESP-Hosted is up before continuing
xSemaphoreTake(sem_hosted_is_up, portMAX_DELAY);
ESP_LOGI(TAG, "ESP-Hosted is ready");
bool esp_hosted_is_okay = app_esp_hosted_verify_up();
if (esp_hosted_is_okay) {
// configure heartbeat
if (ESP_OK != esp_hosted_configure_heartbeat(true, HEARTBEAT_INTERVAL_SEC)) {
ESP_LOGE(TAG, "failed to set heartbeat");
}
#if ENABLE_HEARTBEAT_MONITOR
if (ESP_OK != app_setup_heartbeat_monitor()) {
ESP_LOGE(TAG, "failed to setup heartbeat monitor");
}
#endif
// connect to an AP
example_wifi_init_sta();
// here, you can start a thread to do data transfers with the AP
// we wait until we encounter an error and need to reset the transport
EventBits_t bits = xEventGroupWaitBits(s_esp_hosted_event_group,
ESP_HOSTED_RESET_BIT,
pdTRUE, // clear on exit
pdTRUE, // wait for all bits to be set
portMAX_DELAY);
if (bits & !ESP_HOSTED_RESET_BIT) {
ESP_LOGW(TAG, "*** Unexpected group event event bits ***");
return;
}
// here, you should tell the data transfer thread to abort data transfer
} else {
ESP_LOGE(TAG, "Failed to start up ESP-Hosted");
return;
}
/**
* should only come here if ESP-Hosted encountered a transport error
* or failed to connect
*/
// connection to ESP-Hosted has now failed
ESP_LOGI(TAG, "heartbeat timeout, transport failure or unexpected INIT event: reinit Hosted");
// restart the ESP-Hosted transport
resetting_esp_hosted_transport = true;
bool result = app_do_cp_recovery();
resetting_esp_hosted_transport = false;
deinit_cp_error_detection();
if (!result) {
// do cp recovery failed
ESP_LOGI(TAG, "Did not recover from co-processor failure. Exiting app");
break;
}
ESP_LOGI(TAG, "restarting ESP-Hosted");
}
}
@@ -0,0 +1,240 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Unlicense OR CC0-1.0
*/
/* WiFi station 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 <string.h>
#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 "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "app_common.h"
/* The examples use WiFi configuration that you can set via project configuration menu
If you'd rather not, just change the below entries to strings with
the config you want - ie #define EXAMPLE_WIFI_SSID "mywifissid"
*/
#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
#if CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HUNT_AND_PECK
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
#define EXAMPLE_H2E_IDENTIFIER ""
#elif CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_HASH_TO_ELEMENT
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HASH_TO_ELEMENT
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#elif CONFIG_ESP_STATION_EXAMPLE_WPA3_SAE_PWE_BOTH
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_BOTH
#define EXAMPLE_H2E_IDENTIFIER CONFIG_ESP_WIFI_PW_ID
#endif
#if CONFIG_ESP_WIFI_AUTH_OPEN
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_OPEN
#elif CONFIG_ESP_WIFI_AUTH_WEP
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WEP
#elif CONFIG_ESP_WIFI_AUTH_WPA_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA_WPA2_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA_WPA2_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WPA2_WPA3_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WPA2_WPA3_PSK
#elif CONFIG_ESP_WIFI_AUTH_WAPI_PSK
#define ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD WIFI_AUTH_WAPI_PSK
#endif
/* FreeRTOS event group to signal when we are connected*/
static EventGroupHandle_t s_wifi_event_group;
/* The event group allows multiple bits for each event, but we only care about two events:
* - we are connected to the AP with an IP
* - we failed to connect after the maximum amount of retries */
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "wifi station";
static int s_retry_num = 0;
static bool do_not_reconnect = false;
static esp_event_handler_instance_t instance_any_id = NULL;
static esp_event_handler_instance_t instance_got_ip = NULL;
static esp_netif_t *sta_netif = NULL;
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 (!do_not_reconnect) {
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 {
ESP_LOGI(TAG,"do not reconnect");
}
} 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);
}
}
/**
* these events will inform netif to close its current network connection
* so it is ready for the next sta connection
*/
void example_wifi_sta_netif_close(void)
{
// broadcast a wi-fi disconnect event so NETIF is aware that the connection went down
do_not_reconnect = true; // we don't try to reconnect
wifi_event_sta_disconnected_t event = { 0 };
event.reason = WIFI_REASON_CONNECTION_FAIL;
esp_event_post(WIFI_EVENT,
WIFI_EVENT_STA_DISCONNECTED,
&event,
sizeof(wifi_event_sta_disconnected_t),
portMAX_DELAY);
// sleep for a while to give NETIF time to react to the event
vTaskDelay(100 / portTICK_PERIOD_MS);
// this informs the event handler not to try to reconnect
do_not_reconnect = false;
// broadcast a wi-fi stop event so NETIF is aware the station has stopped
esp_event_post(WIFI_EVENT,
WIFI_EVENT_STA_STOP,
NULL,
0,
portMAX_DELAY);
// sleep for a while to give NETIF time to react to the event
vTaskDelay(100 / portTICK_PERIOD_MS);
}
void example_wifi_deinit_sta(void)
{
if (sta_netif) {
esp_netif_destroy_default_wifi(sta_netif);
sta_netif = NULL;
}
if (!instance_any_id) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(WIFI_EVENT,
ESP_EVENT_ANY_ID,
instance_any_id));
instance_any_id = NULL;
}
if (!instance_got_ip) {
ESP_ERROR_CHECK(esp_event_handler_instance_unregister(IP_EVENT,
ESP_EVENT_ANY_ID,
instance_got_ip));
instance_got_ip = NULL;
}
vEventGroupDelete(s_wifi_event_group);
}
void example_wifi_init_once(void)
{
ESP_ERROR_CHECK(esp_netif_init());
}
void example_wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
if (!instance_any_id) {
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
}
if (!instance_got_ip) {
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
}
if (!sta_netif) {
sta_netif = esp_netif_create_default_wifi_sta();
}
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
wifi_config_t wifi_config = {
.sta = {
.ssid = EXAMPLE_ESP_WIFI_SSID,
.password = EXAMPLE_ESP_WIFI_PASS,
/* Authmode threshold resets to WPA2 as default if password matches WPA2 standards (password len => 8).
* If you want to connect the device to deprecated WEP/WPA networks, Please set the threshold value
* to WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK and set the password with length and format matching to
* WIFI_AUTH_WEP/WIFI_AUTH_WPA_PSK standards.
*/
.threshold.authmode = ESP_WIFI_SCAN_AUTH_MODE_THRESHOLD,
.sae_pwe_h2e = ESP_WIFI_SAE_MODE,
.sae_h2e_identifier = EXAMPLE_H2E_IDENTIFIER,
#ifdef CONFIG_ESP_WIFI_WPA3_COMPATIBLE_SUPPORT
.disable_wpa3_compatible_mode = 0,
#endif
},
};
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);
/* xEventGroupWaitBits() returns the bits before the call returned, hence we can test which event actually
* happened. */
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");
}
}
@@ -0,0 +1,2 @@
### Disable transport option to restart on failure, so application will handle transport failures
CONFIG_ESP_HOSTED_TRANSPORT_RESTART_ON_FAILURE=n
@@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(host_controls_coprocessor_external_coex)
@@ -0,0 +1,203 @@
# ESP-Hosted: Co-Processor External Coexistence (EXT_COEX)
This host side example demonstrates how to configure and use **External Coexistence (EXT_COEX)** in **ESP-Hosted** co-processor.
The ESP co-processor (slave) has a built-in hardware **Packet Traffic Arbitrator (PTA)** that manages 2.4 GHz ISM band access between its own Wi-Fi and an external peer radio (such as BLE, Zigbee, or Thread running on the host MCU). In a typical standalone setup, this PTA is configured directly on the ESP device. With ESP-Hosted, the host MCU can configure and control the slave's PTA remotely over RPC — without modifying slave firmware.
The arbitration operates over 1, 2, or 3 GPIO lines depending on the chosen mode:
- **1-wire** — The peer radio asserts **Request** whenever it needs the band; ESP Wi-Fi always yields. Simple, but can impact Wi-Fi performance.
- **2-wire** — Adds a **Grant** signal. The PTA arbitrates and returns the result: Grant high means the peer radio may transmit; Grant low means ESP Wi-Fi may transmit.
- **3-wire** — Adds a **Priority** signal, allowing the peer radio to indicate middle or high priority. The PTA compares this against the internal Wi-Fi priority before granting.
## 1 Supported Platforms
**Host MCU**
| Platform |
| -------- |
| ESP32 |
| ESP32-C Series |
| ESP32-S Series |
**Co-processor (Slave)**
| Platform |
| -------- |
| ESP32-C6 / C61 |
| ESP32-C5 |
| ESP32-S3 |
> **Note:** ESP32 as a co-processor is not supported.
>
> **Bluetooth + EXT_COEX:** On chips with advanced coexistence hardware
> (`SOC_EXTERNAL_COEX_ADVANCE` — **ESP32-C2 / C5 / C6 / C61 / H2 / H4 / H21**), external
> coexistence works **alongside** Bluetooth/BLE — both can stay enabled. On other chips
> (e.g. **ESP32-S3**), the BT controller must be disabled when EXT_COEX is enabled.
>
> ⚠️ **ESP-IDF version:** EXT_COEX with **Bluetooth disabled** is supported across all
> ESP-IDF tags. EXT_COEX **with Bluetooth enabled** currently requires **ESP-IDF `master`** —
> as of Jun-26 the stable/released tags do not yet include this support, so test on the
> latest `master`.
## 2 Setup
EXT_COEX requires configuration on both the **slave** and the **host** before use.
### 2.1 Slave Setup
Run `idf.py menuconfig` on the slave and apply the following:
**Advanced-coex chips (ESP32-C6 / C61 / C5)** — Bluetooth/BLE may stay enabled:
```
├── Component config → Bluetooth
│ └── [*] Bluetooth <---- Keep enabled if BLE needed ✔
└── Example Configuration
├── [*] Enable BT sharing via hosted <---- Keep enabled if BLE needed ✔
└── [*] External Coexistence support <---- Enable ✔
```
**Other chips (e.g. ESP32-S3)** — Bluetooth must be disabled:
```
├── Component config → Bluetooth
│ └── [ ] Bluetooth <---- DISABLE ✘
└── Example Configuration
├── [ ] Enable BT sharing via hosted <---- DISABLE ✘
└── [*] External Coexistence support <---- Enable ✔
```
Flash the slave:
```bash
idf.py -p <slave_serial_port> flash monitor
```
### 2.2 Host Setup
Run `idf.py menuconfig` on the host and enable the following:
```
Component config → ESP-Hosted config
├── [*] Co-Processor External Coexistence <---- Enable ✔
└── [*] Co-Processor External Coexistence - Advanced <---- Enable ✔
```
Flash the host:
```bash
idf.py -p <host_serial_port> flash monitor
```
## 3 Host-Side APIs
Include the following header to access all EXT_COEX APIs:
```c
#include "esp_hosted.h"
```
This pulls in `esp_hosted_cp_ext_coex.h`, which exposes the APIs below.
#### 3.1 `esp_hosted_cp_ext_coex_set_work_mode()`
```c
esp_err_t esp_hosted_cp_ext_coex_set_work_mode(
esp_hosted_ext_coex_work_mode_t work_mode
);
```
Sets the coexistence operating mode, determining whether the host leads or follows in radio arbitration.
**Parameters**
- `work_mode` — Role to assign to the host:
- `ESP_HOSTED_EXT_COEX_LEADER_ROLE` — Host acts as the leader.
- `ESP_HOSTED_EXT_COEX_FOLLOWER_ROLE` — Host acts as the follower.
- `ESP_HOSTED_EXT_COEX_UNKNOWN_ROLE` — Role is unspecified.
**Returns** `ESP_OK` on success, error code otherwise.
#### 3.2 `esp_hosted_cp_ext_coex_set_gpio_pin()`
```c
esp_err_t esp_hosted_cp_ext_coex_set_gpio_pin(
uint32_t wire_type,
const esp_hosted_ext_coex_gpio_set_t *gpio_pins
);
```
Configures the GPIO pins used for external coexistence signaling on the co-processor.
**Parameters**
- `wire_type` — Number of wires used for coexistence signaling:
- `ESP_HOSTED_EXT_COEX_WIRE_1`
- `ESP_HOSTED_EXT_COEX_WIRE_2`
- `ESP_HOSTED_EXT_COEX_WIRE_3`
- `ESP_HOSTED_EXT_COEX_WIRE_4`
- `gpio_pins` — Pointer to an `esp_hosted_ext_coex_gpio_set_t` structure with the following fields:
| Field | Description |
| ---------- | ------------------------------------ |
| `request` | GPIO number for the Request signal |
| `priority` | GPIO number for the Priority signal |
| `grant` | GPIO number for the Grant signal |
| `tx_line` | GPIO number for the TX Line signal |
**Returns** `ESP_OK` on success, error code otherwise.
#### 3.3 `esp_hosted_cp_ext_coex_set_grant_delay()` *(advanced)*
```c
esp_err_t esp_hosted_cp_ext_coex_set_grant_delay(uint8_t delay_us);
```
Sets the delay (in microseconds) applied to the GRANT signal for timing optimization. Available when `CONFIG_ESP_HOSTED_CP_EXT_COEX_ADVANCE` is enabled.
**Parameters**
- `delay_us` — Grant signal delay in microseconds.
**Returns** `ESP_OK` on success, error code otherwise.
#### 3.4 `esp_hosted_cp_ext_coex_set_validate_high()` *(advanced)*
```c
esp_err_t esp_hosted_cp_ext_coex_set_validate_high(bool is_high_valid);
```
#### 3.5 `esp_hosted_cp_ext_coex_disable()`
```c
esp_err_t esp_hosted_cp_ext_coex_disable(void);
```
Disables external coexistence on the co-processor and releases the associated GPIOs.
**Returns** `ESP_OK` on success, error code otherwise.
Configures the polarity of the validate signal. Available when `CONFIG_ESP_HOSTED_CP_EXT_COEX_ADVANCE` is enabled.
**Parameters**
- `is_high_valid`:
- `true` — Validate signal is active-high.
- `false` — Validate signal is active-low.
**Returns** `ESP_OK` on success, error code otherwise.
## 4 Host ↔ Slave API Mapping
| Host API | Slave API | Purpose |
| :------- | :-------- | :------ |
| `esp_hosted_cp_ext_coex_set_work_mode()` | `esp_external_coex_set_work_mode` | Set coexistence operating mode |
| `esp_hosted_cp_ext_coex_set_gpio_pin()` | `esp_enable_extern_coex_gpio_pin` | Configure coexistence GPIOs (REQ, PRI, GRANT, TX) |
| `esp_hosted_cp_ext_coex_set_grant_delay()` | `esp_external_coex_set_grant_delay` | Adjust GRANT signal delay for timing |
| `esp_hosted_cp_ext_coex_set_validate_high()` | `esp_external_coex_set_validate_high` | Set validate signal polarity |
| `esp_hosted_cp_ext_coex_disable()` | `esp_disable_extern_coex_gpio_pin` | Disable coexistence and release GPIOs |
## 5 References
- [External Coexistence Design (Application Note)](https://documentation.espressif.com/external_coexistence_design_en.pdf)
@@ -0,0 +1,2 @@
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,10 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
@@ -0,0 +1,99 @@
/*
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "esp_log.h"
#include "nvs_flash.h"
#include "esp_hosted.h"
static const char *TAG = "ext_coex_example";
void app_main(void)
{
/* Initialize NVS */
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);
/* Connect to Slave */
esp_hosted_init();
esp_hosted_connect_to_slave();
ESP_LOGI(TAG, "Configuring External Coexistence on Co-processor...");
#if CONFIG_ESP_HOSTED_CP_EXT_COEX
/* Example: Set Work Mode to Leader Role */
ESP_LOGI(TAG, "==> CP EXT_COEX: setting work mode to Leader Role");
esp_err_t err = esp_hosted_cp_ext_coex_set_work_mode(ESP_HOSTED_EXT_COEX_LEADER_ROLE);
if (err != ESP_OK) {
ESP_LOGE(TAG, "EXT_COEX: work mode to Leader Role failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "CP EXT_COEX: work mode to Leader Role successful");
}
/* Example: Set GPIO pins for EXT_COEX (Wire 3) */
/* The GPIO numbers are example and should be replaced with actual GPIOs
available on your co-processor. */
esp_hosted_ext_coex_gpio_set_t gpio_pins_config = {
.request = 11, // GPIO for Request
.priority = 4, // GPIO for Priority
.grant = 16, // GPIO for Grant
.tx_line = 17 // GPIO for Tx Line
};
ESP_LOGI(TAG, "==> CP EXT_COEX: Co-processor GPIO pins: request: %" PRId32 ", priority: %" PRId32 ", grant: %" PRId32 ", tx_line: %" PRId32 "",
gpio_pins_config.request, gpio_pins_config.priority, gpio_pins_config.grant, gpio_pins_config.tx_line);
err = esp_hosted_cp_ext_coex_set_gpio_pin(ESP_HOSTED_EXT_COEX_WIRE_3, &gpio_pins_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "EXT_COEX: GPIO pins setting failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "CP EXT_COEX: GPIO pins setting successful");
}
#if CONFIG_ESP_HOSTED_CP_EXT_COEX_ADVANCE
ESP_LOGI(TAG, "==> CP EXT_COEX: Advanced: setting grant delay to 10 ms");
/* Example: Set Grant Delay to 10 microseconds */
err = esp_hosted_cp_ext_coex_set_grant_delay(10);
if (err != ESP_OK) {
ESP_LOGE(TAG, "EXT_COEX: grant delay setting failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "CP EXT_COEX: grant delay setting successful");
}
/* Example: Set Validate High to true */
ESP_LOGI(TAG, "==> CP EXT_COEX: Advanced: setting validate high to true");
err = esp_hosted_cp_ext_coex_set_validate_high(true);
if (err != ESP_OK) {
ESP_LOGE(TAG, "EXT_COEX: validate high setting failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "CP EXT_COEX: validate high setting successful");
}
#endif
#if 0
/* Showcase: Disabling External Coexistence */
ESP_LOGI(TAG, "==> CP EXT_COEX: disabling");
err = esp_hosted_cp_ext_coex_disable();
if (err != ESP_OK) {
ESP_LOGE(TAG, "EXT_COEX: disabling failed: %s", esp_err_to_name(err));
} else {
ESP_LOGI(TAG, "CP EXT_COEX: disabling successful");
}
#endif
#else
ESP_LOGW(TAG, "CONFIG_ESP_HOSTED_CP_EXT_COEX is not enabled.");
#endif
ESP_LOGI(TAG, "Co-processor External Coexistence configuration completed.");
}
@@ -0,0 +1,3 @@
CONFIG_ESP_HOSTED_CP_EXT_COEX=y
CONFIG_ESP_HOSTED_CP_EXT_COEX_ADVANCE=y
CONFIG_FREERTOS_HZ=1000
@@ -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,219 @@
# 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
#### Network-Based Wakeup
To trigger host wakeup via network, send a UDP or TCP packet to any host-reserved port (49152–61439). A demo utility script is provided for testing:
```bash
# Navigate to the utility directory
cd host_wakeup_demo_using_udp_packet
# Send UDP packet to host port (default port 123) - will wake up host
./host_wakeup_demo_using_udp_packet.sh <device_ip_address>
# Send UDP packet to specific host port - will wake up host
./host_wakeup_demo_using_udp_packet.sh <device_ip_address> 123
# Send UDP packet to slave port - will NOT wake up host
./host_wakeup_demo_using_udp_packet.sh <device_ip_address> 62000
```
> [!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/nw_split_router.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
## Light Sleep Integration
For enhanced power savings, you can configure the slave device to enter **Light Sleep** when the host enters power save mode. This significantly reduces slave power consumption while maintaining fast wake-up times.
See the **[Light Sleep Integration Guide](README_light_sleep.md)** for detailed configuration instructions, dependency requirements, and troubleshooting.
**Quick Summary:**
- Light sleep significantly reduces slave power consumption compared to active mode
- Requires Power Management, FreeRTOS Tickless Idle, and optional peripheral powerdown
- Integrates automatically with host power save callbacks
- UART console may be unavailable during sleep if peripheral powerdown is enabled
## References
- [Network Split Documentation](../../docs/feature_network_split.md)
- [Host Power Save Documentation](../../docs/feature_host_power_save.md)
- [Light Sleep Integration Guide](README_light_sleep.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,201 @@
# Light Sleep Integration Guide
This feature integrates light sleep support on the **coprocessor** side.
This guide explains how to integrate **Light Sleep** power management for the ESP slave device when the host enters power save mode. Light sleep provides a balance between power savings and fast wake-up times.
## Overview
When the host enters power save mode, the slave can automatically enter light sleep to further reduce power consumption. This feature requires proper configuration of ESP-IDF power management components and ESP-Hosted light sleep options.
**Key Benefits:**
* **Power Savings**: Significantly reduces slave power consumption compared to active mode.
* **Fast Wake-up**: Much faster wake-up time compared to deep sleep.
* **Network Maintained**: WiFi and/or Bluetooth connection stays active during light sleep.
* **Automatic Integration**: Works seamlessly with host power save callbacks.
---
## 1. Slave Light Sleep API ([`slave_light_sleep.h`](https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/slave_light_sleep.h))
These core functions provide manual control over the power state. They are exposed to the application and can be used independently of the Host Power Save feature.
| Function | Purpose |
| --- | --- |
| `slave_light_sleep_init()` | Configures the Power Management (PM) framework and frequency scaling. **Must be called once during initialization.** |
| `slave_light_sleep_start()` | Releases the PM lock to allow the CPU to enter light sleep automatically during FreeRTOS idle periods. |
| `slave_light_sleep_stop()` | Acquires the PM lock, forcing the CPU to stay at maximum frequency and preventing entry into sleep. |
| `slave_light_sleep_is_configured()` | Checks if the light sleep component is initialized and ready. |
| `slave_light_sleep_deinit()` | Cleans up resources, stops active sleep, and deletes PM locks. |
---
## 2. Mandatory Dependencies
Before the APIs can function, the underlying ESP-IDF and PHY components must be configured correctly.
* **Power Management** ([`CONFIG_PM_ENABLE`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/power_management.html)): Enables the base framework for CPU frequency scaling and sleep.
* **Tickless Idle** ([`CONFIG_FREERTOS_USE_TICKLESS_IDLE`](https://www.freertos.org/low-power-tickless-rtos.html)): Required for the RTOS to skip tick interrupts, allowing the CPU to enter a low-power state during idle.
* **MAC/BB Power Down** (`CONFIG_ESP_PHY_MAC_BB_PD`): **Strictly mandatory if Bluetooth/BLE is enabled.** Without this, the hardware will stay awake to maintain the radio, preventing light sleep.
* **Bluetooth Modem Sleep** (if Bluetooth is enabled):
* **ESP32**: Enable `CONFIG_BTDM_CTRL_MODEM_SLEEP` (Component config → Bluetooth → Modem Sleep)
* **ESP32-C3/C5/C6/C61/S3/H2**: Enable `CONFIG_BT_LE_SLEEP_ENABLE` (Component config → Bluetooth → Controller Options → Enable BLE sleep)
---
## 3. Power Saving Recommendations
For maximum power savings, consider these additional optimizations:
* **Disable Bluetooth if Unused**: If Bluetooth/BLE is not required, disable it entirely:
* Via menuconfig: Component config → Bluetooth → [ ] Bluetooth
* Via API: `esp_bt_controller_disable()`, `esp_bt_controller_deinit()`, `esp_bt_mem_release()`
* This provides significant power savings by removing radio overhead.
* **WiFi Modem Sleep**: Enable WiFi power management to allow WiFi to sleep between packets:
* Via menuconfig: Component config → Wi-Fi → WiFi Power Management
* Via API: `esp_wifi_set_ps(WIFI_PS_MIN_MODEM)`
* This allows WiFi to maintain connection while reducing power consumption.
* **WiFi ITWT (Immediate Target Wake Time)**: For Wi-Fi 6 (802.11ax) networks, enable ITWT for scheduled wake times:
* Check example: [`examples/host_wifi_itwt`](https://github.com/espressif/esp-hosted-mcu/tree/main/examples/host_wifi_itwt)
* Requires WiFi HE (802.11ax) support and AP support
* Provides advanced power saving through scheduled wake times
---
## 4. Integration with Host Power Save
The automated integration hooks the Light Sleep APIs into the **[Host Power Save](https://github.com/espressif/esp-hosted-mcu/tree/main/docs/feature_host_power_save.md)** lifecycle. This ensures the slave only sleeps when the communication bus (SPI/SDIO/UART) is inactive.
### Hooking the Callbacks
The integration relies on four callbacks. You hook the `slave_light_sleep` APIs as follows:
1. **`host_power_save_on_prepare_cb()`** (Host Preparing to Sleep)
* **Slave Hook**: Application cleanup. Save state to NVS or flush data buffers while the bus is still **UP**.
2. **`host_power_save_on_ready_cb()`** (Host is Asleep)
* **Slave Hook**: Call **`slave_light_sleep_start()`**. Since the bus is now **DOWN**, it is safe to sleep.
* *Note*: If using Peripheral Powerdown, stop the CLI/UART here.
3. **`host_power_save_off_prepare_cb()`** (Host Wakeup Detected)
* **Slave Hook**: Call **`slave_light_sleep_stop()`**. This ensures the CPU is at full speed *before* the host re-initializes the bus.
4. **`host_power_save_off_ready_cb()`** (Host Fully Awake)
* **Slave Hook**: Resume services. Restart CLI/UART if they were stopped. The bus is now **UP**.
---
## 5. Configuration Tree & Options
Follow this tree structure in `idf.py menuconfig`. The tree illustrates how the Hosted sleep options depend on the base IDF components.
```text
# Slave side configuration: idf.py menuconfig
├── Component config
│ ├── Power Management
│ │ └── [*] Support for power management (CONFIG_PM_ENABLE)
│ │ └── [*] Power down Digital peripherals in light sleep (CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP)
│ │
│ ├── FreeRTOS
│ │ └── Kernel
│ │ └── [*] configUSE_TICKLESS_IDLE (CONFIG_FREERTOS_USE_TICKLESS_IDLE)
│ │
│ ├── PHY
│ │ └── [*] Power down MAC and baseband of Wi-Fi and Bluetooth... (CONFIG_ESP_PHY_MAC_BB_PD) (if Bluetooth is enabled)
│ │
│ └── Bluetooth (if Bluetooth is enabled)
│ ├── [*] Bluetooth (CONFIG_BT_ENABLED)
│ └── Controller Options
│ └── [*] Modem Sleep (ESP32 only: CONFIG_BTDM_CTRL_MODEM_SLEEP, C3/C5/C6/C61/S3/H2: CONFIG_BT_LE_SLEEP_ENABLE)
│
└── Example Configuration
├── [*] Allow host to power save
│ └── Host Power Save Configuration
│ └── [*] Unload low level BUS driver... (CONFIG_ESP_HOSTED_UNLOAD_BUS_DRIVER_DURING_HOST_SLEEP)
│
└── Light Sleep Power Management
├── [*] Enable light sleep power management (CONFIG_ESP_HOSTED_LIGHT_SLEEP_ENABLE)
└── Light Sleep Parameters
├── (<frequency>) Minimum CPU frequency (MHz)
└── [*] Power down peripherals (CONFIG_ESP_HOSTED_LIGHT_SLEEP_PERIPHERAL_POWERDOWN)
```
### Technical Ranges & Logic
* **Minimum CPU Frequency**: Sets the frequency when idle. Lower frequency = more power savings.
* *ESP32/S/C/H series*: 10 MHz - 240 MHz (C2/H2 max 80 MHz).
* *ESP32-C5*: 12 MHz - 96 MHz.
* **Peripheral Powerdown**: Enabling `CONFIG_PM_POWER_DOWN_PERIPHERAL_IN_LIGHT_SLEEP` allows digital peripherals (like SDIO & UART) to be powered off.
* *Note*: Requires `CONFIG_ESP_HOSTED_UNLOAD_BUS_DRIVER_DURING_HOST_SLEEP` to be enabled.
* *Trade-off*: UART console will be unavailable during sleep.
### Automated Config Checks
The `idf.py menuconfig` interface provides real-time feedback on your configuration:
* **✓ ESP-Hosted Light sleep allowed to configure**: All prerequisites are met, light sleep can be enabled.
* **✗ ESP-Hosted Light sleep not allowed**: Missing prerequisites are listed above this message.
* **⚠️ Prerequisite**: Missing mandatory configuration items are highlighted.
* **ⓘ Recommendation**: Optional optimizations for maximum power savings.
* **✓ Maximum Power Saving**: All optimizations are enabled when both bus driver unload and peripheral powerdown are configured.
---
## 6. Implementation Example
The system includes a reference implementation demonstrating the complete handshake.
* **Header**: [`slave/main/example_light_sleep.h`](https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/example_light_sleep.h)
* **Source**: [`slave/main/example_light_sleep.c`](https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/example_light_sleep.c)
### Manual Integration Snippet
If you are not using the automatic example (`CONFIG_ESP_HOSTED_COPROCESSOR_EXAMPLE_LIGHT_SLEEP`), manually hook the APIs in your `app_main()`:
```c
#include "slave_light_sleep.h" // See: https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/slave_light_sleep.h
#include "host_power_save.h" // See: https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/host_power_save.h
void app_main(void) {
// 1. Initialize Light Sleep component
slave_light_sleep_init();
// 2. Setup Host Power Save Callbacks
host_power_save_config_t ps_config = HOST_POWER_SAVE_DEFAULT_CONFIG();
ps_config.callbacks = (host_power_save_callbacks_t) {
.host_power_save_on_prepare_cb = my_app_cleanup,
.host_power_save_on_ready_cb = slave_light_sleep_start, // Enter Sleep
.host_power_save_off_prepare_cb = slave_light_sleep_stop, // Exit Sleep
.host_power_save_off_ready_cb = my_app_resume
};
// 3. Start Host monitoring
host_power_save_init(&ps_config);
}
```
---
## References
- [Host Power Save Documentation](https://github.com/espressif/esp-hosted-mcu/tree/main/docs/feature_host_power_save.md)
- [ESP-IDF Power Management](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/power_management.html)
- [FreeRTOS Tickless Idle](https://www.freertos.org/low-power-tickless-rtos.html)
- [Slave Light Sleep API](https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/slave_light_sleep.h)
- [Example Implementation](https://github.com/espressif/esp-hosted-mcu/tree/main/slave/main/example_light_sleep.c)
- [ESP-Hosted-MCU Repository](https://github.com/espressif/esp-hosted-mcu)
- [Slave Directory](https://github.com/espressif/esp-hosted-mcu/tree/main/slave)
@@ -0,0 +1,60 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* This is just sample program to send sample udp packet */
#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;
}
@@ -0,0 +1,58 @@
#!/bin/bash
# Simple script to build and run host_wakeup_demo_using_udp_packet.c
# 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="123"
# Get the directory where this script is located
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 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 "$SCRIPT_DIR/host_wakeup_demo_using_udp_packet" "$SCRIPT_DIR/host_wakeup_demo_using_udp_packet.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
"$SCRIPT_DIR/host_wakeup_demo_using_udp_packet" $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
@@ -0,0 +1,2 @@
idf_component_register(SRCS "iperf_example_main.c"
INCLUDE_DIRS ".")
@@ -0,0 +1,30 @@
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]
- if: idf_version <6.1
version: '>=1.3.1'
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,144 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
/* 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)
#error "Disable extconn as per esp-hosted-mcu doc and try again"
#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_HOST_WAKEUP_GPIO=6
@@ -0,0 +1 @@
CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y
@@ -0,0 +1 @@
CONFIG_ESP_HOSTED_SPI_HOST_INTERFACE=y
@@ -0,0 +1,2 @@
CONFIG_ESP_HOSTED_SPI_HD_HOST_INTERFACE=y
CONFIG_ESP_HOSTED_HOST_WAKEUP_GPIO=4
@@ -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,20 @@
#
# 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_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
@@ -0,0 +1,23 @@
#
# 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_LWIP_TCPIP_CORE_LOCKING=y
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
@@ -0,0 +1,23 @@
#
# 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_LWIP_TCPIP_CORE_LOCKING=y
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
@@ -0,0 +1,37 @@
#
# 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
#
# 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,26 @@
#
# 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_LWIP_EXTRA_IRAM_OPTIMIZATION=y
CONFIG_LWIP_TCPIP_CORE_LOCKING=y
CONFIG_LWIP_TCPIP_CORE_LOCKING_INPUT=y
@@ -0,0 +1,37 @@
#
# 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
#
# 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,22 @@
#
# ESP32-P4 specific
#
CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=10
CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=32
CONFIG_WIFI_RMT_DYNAMIC_TX_BUFFER_NUM=32
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=16
# TCP/IP Performance
CONFIG_LWIP_TCP_SND_BUF_DEFAULT=11520
CONFIG_LWIP_TCP_WND_DEFAULT=32768
CONFIG_LWIP_TCP_RECVMBOX_SIZE=48
CONFIG_LWIP_UDP_RECVMBOX_SIZE=48
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=48
CONFIG_LWIP_TCP_SACK_OUT=y
#CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y
@@ -0,0 +1,27 @@
#
# 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_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,24 @@
#
# 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_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.
�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,12 @@
dependencies:
espressif/esp_hosted:
rules:
- if: target in [esp32p4, esp32h2]
version: '*'
espressif/esp_wifi_remote:
rules:
- if: target in [esp32p4, esp32h2]
- if: idf_version <6.1
version: '>=1.3.1'
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 (&params, 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, &params, 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)

Some files were not shown because too many files have changed in this diff Show More