diff --git a/firmware/esp-idf/wumei-smart-firmware/.gitignore b/firmware/esp-idf/wumei-smart-firmware/.gitignore new file mode 100644 index 00000000..7b7190fd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/.gitignore @@ -0,0 +1,42 @@ +build/ +.settings +.config +*.o +*.pyc +*.d +*.old +*.cproject + +# vscode setting +.vscode/ + +# eclipse setting +.settings + +# MacOS directory files +.DS_Store + +# Doc build artifacts +docs/*/_build/ +docs/*/doxygen-warning-log.txt +docs/*/sphinx-warning-log.txt +docs/*/sphinx-warning-log-sanitized.txt +docs/*/xml/ +docs/*/xml_in/ +docs/*/man/ +docs/doxygen_sqlite3.db + +# Example project files +examples/**/sdkconfig +examples/**/sdkconfig.old +examples/**/build + +# Unit test app files +tools/unit-test-app/sdkconfig +tools/unit-test-app/sdkconfig.old +tools/unit-test-app/build +tools/unit-test-app/builds +tools/unit-test-app/output + +# ESP-IDF library +build \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/CMakeLists.txt new file mode 100644 index 00000000..95286d5c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.5) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(wumei-open) + +target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/client.crt" TEXT) +target_add_binary_data(${CMAKE_PROJECT_NAME}.elf "main/client.key" TEXT) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/Makefile b/firmware/esp-idf/wumei-smart-firmware/Makefile new file mode 100644 index 00000000..71660436 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/Makefile @@ -0,0 +1,2 @@ +PROJECT_NAME := empty_project +include $(IDF_PATH)/make/project.mk \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/README.md b/firmware/esp-idf/wumei-smart-firmware/README.md new file mode 100644 index 00000000..9fcdee93 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/README.md @@ -0,0 +1,5 @@ + +#### ESP-IDF空项目,提供基本的框架。
+ + + diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/bus/CMakeLists.txt new file mode 100644 index 00000000..26e31d47 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRC_DIRS "." + INCLUDE_DIRS "include" ) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/Kconfig b/firmware/esp-idf/wumei-smart-firmware/components/bus/Kconfig new file mode 100644 index 00000000..e2c3a11c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/Kconfig @@ -0,0 +1,19 @@ +menu "Bus Options" + + menu "I2C Bus Options" + config I2C_BUS_DYNAMIC_CONFIG + bool "enable dynamic configuration" + default y + help + If enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer, + hence multiple devices with different configs on a single bus can be supported. + + config I2C_MS_TO_WAIT + int "mutex block time" + default 200 + range 50 5000 + help + task block time when try to take the bus, unit:milliseconds + endmenu + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/bus/component.mk new file mode 100644 index 00000000..3733299d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/component.mk @@ -0,0 +1,7 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := . \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/i2c_bus.c b/firmware/esp-idf/wumei-smart-firmware/components/bus/i2c_bus.c new file mode 100644 index 00000000..f24f4252 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/i2c_bus.c @@ -0,0 +1,487 @@ +// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#include "esp_log.h" +#include "i2c_bus.h" + +#define I2C_ACK_CHECK_EN 0x1 /*!< I2C master will check ack from slave*/ +#define I2C_ACK_CHECK_DIS 0x0 /*!< I2C master will not check ack from slave */ +#define I2C_BUS_FLG_DEFAULT (0) +#define I2C_BUS_MASTER_BUF_LEN (0) +#define I2C_BUS_MS_TO_WAIT CONFIG_I2C_MS_TO_WAIT +#define I2C_BUS_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS) +#define I2C_BUS_MUTEX_TICKS_TO_WAIT (I2C_BUS_MS_TO_WAIT/portTICK_RATE_MS) + +typedef struct { + i2c_port_t i2c_port; /*!mode == I2C_MODE_MASTER, "i2c_bus only supports master mode", NULL); + + if (s_i2c_bus[port].is_init) { + /**if i2c_bus has been inited and configs not changed, return the handle directly**/ + if (i2c_config_compare(port, conf)) { + ESP_LOGW(TAG, "i2c%d has been inited, return handle directly, ref_counter=%d", port, s_i2c_bus[port].ref_counter); + return (i2c_bus_handle_t)&s_i2c_bus[port]; + } + } else { + s_i2c_bus[port].mutex = xSemaphoreCreateMutex(); + I2C_BUS_CHECK(s_i2c_bus[port].mutex != NULL, "i2c_bus xSemaphoreCreateMutex failed", NULL); + s_i2c_bus[port].ref_counter = 0; + } + + esp_err_t ret = i2c_driver_reinit(port, conf); + I2C_BUS_CHECK(ret == ESP_OK, "init error", NULL); + s_i2c_bus[port].conf_active = *conf; + s_i2c_bus[port].i2c_port = port; + return (i2c_bus_handle_t)&s_i2c_bus[port]; +} + +esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus) +{ + I2C_BUS_CHECK(p_bus != NULL && *p_bus != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG); + i2c_bus_t *i2c_bus = (i2c_bus_t *)(*p_bus); + I2C_BUS_INIT_CHECK(i2c_bus->is_init, ESP_FAIL); + I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, ESP_ERR_TIMEOUT); + + /** if ref_counter == 0, de-init the bus**/ + if ((i2c_bus->ref_counter) > 0) { + ESP_LOGW(TAG, "i2c%d is also handled by others ref_counter=%u, won't be de-inited", i2c_bus->i2c_port, i2c_bus->ref_counter); + return ESP_OK; + } + + esp_err_t ret = i2c_driver_deinit(i2c_bus->i2c_port); + I2C_BUS_CHECK(ret == ESP_OK, "deinit error", ret); + vSemaphoreDelete(i2c_bus->mutex); + *p_bus = NULL; + return ESP_OK; +} + +uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num) +{ + I2C_BUS_CHECK(bus_handle != NULL, "Handle error", 0); + i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; + I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); + uint8_t device_count = 0; + I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, 0); + for (uint8_t dev_address = 1; dev_address < 127; dev_address++) { + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (dev_address << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin(i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT); + + if (ret == ESP_OK) { + ESP_LOGI(TAG, "found i2c device address = 0x%02x", dev_address); + if (buf != NULL && device_count < num) { + *(buf + device_count) = dev_address; + } + device_count++; + } + + i2c_cmd_link_delete(cmd); + } + I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, 0); + return device_count; +} + +uint32_t i2c_bus_get_current_clk_speed(i2c_bus_handle_t bus_handle) +{ + I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", 0); + i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; + I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); + return i2c_bus->conf_active.master.clk_speed; +} + +uint8_t i2c_bus_get_created_device_num(i2c_bus_handle_t bus_handle) +{ + I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", 0); + i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; + I2C_BUS_INIT_CHECK(i2c_bus->is_init, 0); + return i2c_bus->ref_counter; +} + +i2c_bus_device_handle_t i2c_bus_device_create(i2c_bus_handle_t bus_handle, uint8_t dev_addr, uint32_t clk_speed) +{ + I2C_BUS_CHECK(bus_handle != NULL, "Null Bus Handle", NULL); + I2C_BUS_CHECK(clk_speed <= 400000, "clk_speed must <= 400000", NULL); + i2c_bus_t *i2c_bus = (i2c_bus_t *)bus_handle; + I2C_BUS_INIT_CHECK(i2c_bus->is_init, NULL); + i2c_bus_device_t *i2c_device = calloc(1, sizeof(i2c_bus_device_t)); + I2C_BUS_CHECK(i2c_device != NULL, "calloc memory failed", NULL); + I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_bus->mutex, NULL); + i2c_device->dev_addr = dev_addr; + i2c_device->conf = i2c_bus->conf_active; + + /*if clk_speed == 0, current active clock speed will be used, else set a specified value*/ + if (clk_speed != 0) { + i2c_device->conf.master.clk_speed = clk_speed; + } + + i2c_device->i2c_bus = i2c_bus; + i2c_bus->ref_counter++; + I2C_BUS_MUTEX_GIVE(i2c_bus->mutex, NULL); + return (i2c_bus_device_handle_t)i2c_device; +} + +esp_err_t i2c_bus_device_delete(i2c_bus_device_handle_t *p_dev_handle) +{ + I2C_BUS_CHECK(p_dev_handle != NULL && *p_dev_handle != NULL, "Null Device Handle", ESP_ERR_INVALID_ARG); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)(*p_dev_handle); + I2C_BUS_MUTEX_TAKE_MAX_DELAY(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); + i2c_device->i2c_bus->ref_counter--; + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + free(i2c_device); + *p_dev_handle = NULL; + return ESP_OK; +} + +uint8_t i2c_bus_device_get_address(i2c_bus_device_handle_t dev_handle) +{ + I2C_BUS_CHECK(dev_handle != NULL, "device handle error", NULL_I2C_DEV_ADDR); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + return i2c_device->dev_addr; +} + +esp_err_t i2c_bus_read_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data) +{ + return i2c_bus_read_reg8(dev_handle, mem_address, data_len, data); +} + +esp_err_t i2c_bus_read_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t *data) +{ + return i2c_bus_read_reg8(dev_handle, mem_address, 1, data); +} + +esp_err_t i2c_bus_read_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t *data) +{ + uint8_t byte = 0; + esp_err_t ret = i2c_bus_read_reg8(dev_handle, mem_address, 1, &byte); + *data = byte & (1 << bit_num); + *data = (*data != 0) ? 1 : 0; + return ret; +} + +esp_err_t i2c_bus_read_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t *data) +{ + uint8_t byte = 0; + esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte); + + if (ret != ESP_OK) { + return ret; + } + + uint8_t mask = ((1 << length) - 1) << (bit_start - length + 1); + byte &= mask; + byte >>= (bit_start - length + 1); + *data = byte; + return ret; +} + +esp_err_t i2c_bus_write_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t data) +{ + return i2c_bus_write_reg8(dev_handle, mem_address, 1, &data); +} + +esp_err_t i2c_bus_write_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data) +{ + return i2c_bus_write_reg8(dev_handle, mem_address, data_len, data); +} + +esp_err_t i2c_bus_write_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t data) +{ + uint8_t byte = 0; + esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte); + + if (ret != ESP_OK) { + return ret; + } + + byte = (data != 0) ? (byte | (1 << bit_num)) : (byte & ~(1 << bit_num)); + return i2c_bus_write_byte(dev_handle, mem_address, byte); +} + +esp_err_t i2c_bus_write_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t data) +{ + uint8_t byte = 0; + esp_err_t ret = i2c_bus_read_byte(dev_handle, mem_address, &byte); + + if (ret != ESP_OK) { + return ret; + } + + uint8_t mask = ((1 << length) - 1) << (bit_start - length + 1); + data <<= (bit_start - length + 1); // shift data into correct position + data &= mask; // zero all non-important bits in data + byte &= ~(mask); // zero all important bits in existing byte + byte |= data; // combine data with existing byte + return i2c_bus_write_byte(dev_handle, mem_address, byte); +} + +/** + * @brief I2C master send queued commands. + * This function will trigger sending all queued commands. + * The task will be blocked until all the commands have been sent out. + * If I2C_BUS_DYNAMIC_CONFIG enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer, + * hence multiple devices with different configs on a single bus can be supported. + * @note + * Only call this function in I2C master mode + * + * @param i2c_num I2C port number + * @param cmd_handle I2C command handler + * @param ticks_to_wait maximum wait ticks. + * @param conf pointer to I2C parameter settings + * @return esp_err_t + */ +inline static esp_err_t i2c_master_cmd_begin_with_conf(i2c_port_t i2c_num, i2c_cmd_handle_t cmd_handle, TickType_t ticks_to_wait, const i2c_config_t *conf) +{ + esp_err_t ret; +#ifdef CONFIG_I2C_BUS_DYNAMIC_CONFIG + /*if configs changed, i2c driver will reinit with new configuration*/ + if (conf != NULL && false == i2c_config_compare(i2c_num, conf)) { + ret = i2c_driver_reinit(i2c_num, conf); + I2C_BUS_CHECK(ret == ESP_OK, "reinit error", ret); + s_i2c_bus[i2c_num].conf_active = *conf; + } +#endif + ret = i2c_master_cmd_begin(i2c_num, cmd_handle, ticks_to_wait); + return ret; +} + +/**************************************** Public Functions (Low level)*********************************************/ + +esp_err_t i2c_bus_cmd_begin(i2c_bus_device_handle_t dev_handle, i2c_cmd_handle_t cmd) +{ + I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(cmd != NULL, "I2C command error", ESP_ERR_INVALID_ARG); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); + I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); + esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ret; +} + +static esp_err_t i2c_bus_read_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data) +{ + I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); + I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + + if (mem_address != NULL_I2C_MEM_ADDR) { + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); + } + + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); + i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ret; +} + +esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, uint8_t *data) +{ + I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); + uint8_t memAddress8[2]; + memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF); + memAddress8[1] = (uint8_t)(mem_address & 0x00FF); + I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + + if (mem_address != NULL_I2C_MEM_ADDR) { + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); + } + + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_READ, I2C_ACK_CHECK_EN); + i2c_master_read(cmd, data, data_len, I2C_MASTER_LAST_NACK); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ret; +} + +static esp_err_t i2c_bus_write_reg8(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data) +{ + I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); + I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + + if (mem_address != NULL_I2C_MEM_ADDR) { + i2c_master_write_byte(cmd, mem_address, I2C_ACK_CHECK_EN); + } + + i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ret; +} + +esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, const uint8_t *data) +{ + I2C_BUS_CHECK(dev_handle != NULL, "device handle error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(data != NULL, "data pointer error", ESP_ERR_INVALID_ARG); + i2c_bus_device_t *i2c_device = (i2c_bus_device_t *)dev_handle; + I2C_BUS_INIT_CHECK(i2c_device->i2c_bus->is_init, ESP_ERR_INVALID_STATE); + uint8_t memAddress8[2]; + memAddress8[0] = (uint8_t)((mem_address >> 8) & 0x00FF); + memAddress8[1] = (uint8_t)(mem_address & 0x00FF); + I2C_BUS_MUTEX_TAKE(i2c_device->i2c_bus->mutex, ESP_ERR_TIMEOUT); + i2c_cmd_handle_t cmd = i2c_cmd_link_create(); + i2c_master_start(cmd); + i2c_master_write_byte(cmd, (i2c_device->dev_addr << 1) | I2C_MASTER_WRITE, I2C_ACK_CHECK_EN); + + if (mem_address != NULL_I2C_MEM_ADDR) { + i2c_master_write(cmd, memAddress8, 2, I2C_ACK_CHECK_EN); + } + + i2c_master_write(cmd, (uint8_t *)data, data_len, I2C_ACK_CHECK_EN); + i2c_master_stop(cmd); + esp_err_t ret = i2c_master_cmd_begin_with_conf(i2c_device->i2c_bus->i2c_port, cmd, I2C_BUS_TICKS_TO_WAIT, &i2c_device->conf); + i2c_cmd_link_delete(cmd); + I2C_BUS_MUTEX_GIVE(i2c_device->i2c_bus->mutex, ESP_FAIL); + return ret; +} + +/**************************************** Private Functions*********************************************/ +static esp_err_t i2c_driver_reinit(i2c_port_t port, const i2c_config_t *conf) +{ + I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(conf != NULL, "pointer = NULL error", ESP_ERR_INVALID_ARG); + + if (s_i2c_bus[port].is_init) { + i2c_driver_delete(port); + s_i2c_bus[port].is_init = false; + ESP_LOGI(TAG, "i2c%d bus deinited", port); + } + + esp_err_t ret = i2c_param_config(port, conf); + I2C_BUS_CHECK(ret == ESP_OK, "i2c param config failed", ret); + ret = i2c_driver_install(port, conf->mode, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_MASTER_BUF_LEN, I2C_BUS_FLG_DEFAULT); + I2C_BUS_CHECK(ret == ESP_OK, "i2c driver install failed", ret); + s_i2c_bus[port].is_init = true; + ESP_LOGI(TAG, "i2c%d bus inited", port); + return ESP_OK; +} + +static esp_err_t i2c_driver_deinit(i2c_port_t port) +{ + I2C_BUS_CHECK(port < I2C_NUM_MAX, "i2c port error", ESP_ERR_INVALID_ARG); + I2C_BUS_CHECK(s_i2c_bus[port].is_init == true, "i2c not inited", ESP_ERR_INVALID_STATE); + i2c_driver_delete(port); //always return ESP_OK + s_i2c_bus[port].is_init = false; + ESP_LOGI(TAG,"i2c%d bus deinited",port); + return ESP_OK; +} + +/** + * @brief compare with active i2c_bus configuration + * + * @param port choose which i2c_port's configuration will be compared + * @param conf new configuration + * @return true new configuration is equal to active configuration + * @return false new configuration is not equal to active configuration + */ +inline static bool i2c_config_compare(i2c_port_t port, const i2c_config_t *conf) +{ + if (s_i2c_bus[port].conf_active.master.clk_speed == conf->master.clk_speed + && s_i2c_bus[port].conf_active.sda_io_num == conf->sda_io_num + && s_i2c_bus[port].conf_active.scl_io_num == conf->scl_io_num + && s_i2c_bus[port].conf_active.scl_pullup_en == conf->scl_pullup_en + && s_i2c_bus[port].conf_active.sda_pullup_en == conf->sda_pullup_en) { + return true; + } + + return false; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/i2s_lcd_esp32_driver.c b/firmware/esp-idf/wumei-smart-firmware/components/bus/i2s_lcd_esp32_driver.c new file mode 100644 index 00000000..3941bab4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/i2s_lcd_esp32_driver.c @@ -0,0 +1,599 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32 + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_heap_caps.h" +#include "esp32/rom/lldesc.h" +#include "soc/dport_access.h" +#include "soc/dport_reg.h" +#include "soc/i2s_struct.h" +#include "hal/gpio_ll.h" +#include "esp_log.h" +#include "i2s_lcd_driver.h" + +static const char *TAG = "ESP32_I2S_LCD"; + +#define I2S_CHECK(a, str, ret) if (!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return (ret); \ + } + +#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4000) // 4-byte aligned +#define LCD_DATA_MAX_WIDTH (24) /*!< Maximum width of LCD data bus */ + +typedef struct { + uint32_t dma_buffer_size; + uint32_t dma_half_buffer_size; + uint32_t dma_node_buffer_size; + uint32_t dma_node_cnt; + uint32_t dma_half_node_cnt; + lldesc_t *dma; + uint8_t *dma_buffer; + QueueHandle_t event_queue; + uint8_t width; + bool swap_data; +} lcd_obj_t; + +typedef struct { + lcd_obj_t lcd; + intr_handle_t lcd_cam_intr_handle; + i2s_dev_t *i2s_dev; +} lcd_cam_obj_t; + +typedef struct { + void (*i2s_write_data_func)(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len); + int rs_io_num; + lcd_cam_obj_t *lcd_cam_obj; + SemaphoreHandle_t mutex; +} i2s_lcd_driver_t; + +static void IRAM_ATTR i2s_isr(void *arg) +{ + BaseType_t HPTaskAwoken = pdFALSE; + lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t *)arg; + i2s_dev_t *i2s_dev = lcd_cam_obj->i2s_dev; + + typeof(i2s_dev->int_st) status = i2s_dev->int_st; + i2s_dev->int_clr.val = status.val; + if (status.val == 0) { + return; + } + + if (status.out_eof) { + xQueueSendFromISR(lcd_cam_obj->lcd.event_queue, (void *)&status.val, &HPTaskAwoken); + } + + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + + +static void lcd_dma_set_int(lcd_cam_obj_t *lcd_cam_obj) +{ + // Generate a data DMA linked list + for (int x = 0; x < lcd_cam_obj->lcd.dma_node_cnt; x++) { + lcd_cam_obj->lcd.dma[x].size = lcd_cam_obj->lcd.dma_node_buffer_size; + lcd_cam_obj->lcd.dma[x].length = lcd_cam_obj->lcd.dma_node_buffer_size; + lcd_cam_obj->lcd.dma[x].buf = (lcd_cam_obj->lcd.dma_buffer + lcd_cam_obj->lcd.dma_node_buffer_size * x); + lcd_cam_obj->lcd.dma[x].eof = !((x + 1) % lcd_cam_obj->lcd.dma_half_node_cnt); + lcd_cam_obj->lcd.dma[x].empty = (uint32_t)&lcd_cam_obj->lcd.dma[(x + 1) % lcd_cam_obj->lcd.dma_node_cnt]; + } + lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_half_node_cnt - 1].empty = (uint32_t)NULL; + lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_node_cnt - 1].empty = (uint32_t)NULL; +} + +static void lcd_dma_set_left(lcd_cam_obj_t *lcd_cam_obj, int pos, size_t len) +{ + int end_pos = 0, size = 0; + // Processing data length is an integer multiple of lcd_cam_obj->lcd.dma_node_buffer_size + if (len % lcd_cam_obj->lcd.dma_node_buffer_size) { + end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size; + size = len % lcd_cam_obj->lcd.dma_node_buffer_size; + } else { + end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size - 1; + size = lcd_cam_obj->lcd.dma_node_buffer_size; + } + // Process the tail node to make it a DMA tail + lcd_cam_obj->lcd.dma[end_pos].size = size; + lcd_cam_obj->lcd.dma[end_pos].length = size; + lcd_cam_obj->lcd.dma[end_pos].eof = 1; + lcd_cam_obj->lcd.dma[end_pos].empty = (uint32_t)NULL; +} + +static void lcd_i2s_start(i2s_dev_t *i2s_dev, uint8_t fifo_mode, uint32_t addr, size_t len) +{ + while (!i2s_dev->state.tx_idle); + i2s_dev->fifo_conf.tx_fifo_mod = fifo_mode; + i2s_dev->conf.tx_start = 0; + i2s_dev->conf.tx_reset = 1; + i2s_dev->conf.tx_reset = 0; + i2s_dev->lc_conf.out_rst = 1; + i2s_dev->lc_conf.out_rst = 0; + i2s_dev->conf.tx_fifo_reset = 1; + i2s_dev->conf.tx_fifo_reset = 0; + i2s_dev->out_link.addr = addr; + i2s_dev->out_link.start = 1; + ets_delay_us(1); + i2s_dev->conf.tx_start = 1; +} + +static void i2s_write_8bit_data(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len) +{ + int event = 0; + int x = 0, y = 0, left = 0, cnt = 0; + if (len <= 0) { + ESP_LOGE(TAG, "wrong len!"); + return; + } + len = len * 2; + lcd_dma_set_int(lcd_cam_obj); + uint8_t fifo_mode = 1; + // Start signal + xQueueSend(lcd_cam_obj->lcd.event_queue, &event, 0); + cnt = len / lcd_cam_obj->lcd.dma_half_buffer_size; + // Process a complete piece of data, ping-pong operation + for (x = 0; x < cnt; x++) { + uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf; + uint8_t *in = data; + if (lcd_cam_obj->lcd.swap_data) { + for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) { + out[y + 3] = in[(y >> 1) + 0]; + out[y + 1] = in[(y >> 1) + 1]; + } + } else { + for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) { + out[y + 1] = in[(y >> 1) + 0]; + out[y + 3] = in[(y >> 1) + 1]; + } + } + data += lcd_cam_obj->lcd.dma_half_buffer_size >> 1; + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); + lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, lcd_cam_obj->lcd.dma_half_buffer_size); + } + left = len % lcd_cam_obj->lcd.dma_half_buffer_size; + // Process remaining incomplete segment data + while (left) { + uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf; + uint8_t *in = data; + if (left > 2) { + cnt = left - left % 4; + left = left % 4; + data += cnt >> 1; + if (lcd_cam_obj->lcd.swap_data) { + for (y = 0; y < cnt; y += 4) { + out[y + 3] = in[(y >> 1) + 0]; + out[y + 1] = in[(y >> 1) + 1]; + } + } else { + for (y = 0; y < cnt; y += 4) { + out[y + 1] = in[(y >> 1) + 0]; + out[y + 3] = in[(y >> 1) + 1]; + } + } + } else { + cnt = 4; + left = 0; + fifo_mode = 3; + out[3] = in[0]; + } + lcd_dma_set_left(lcd_cam_obj, x, cnt); + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); + lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, cnt); + x++; + } + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); +} + +static void i2s_write_16bit_data(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len) +{ + int event = 0; + int x = 0, y = 0, left = 0, cnt = 0; + if (len <= 0 || len % 2 != 0) { + ESP_LOGE(TAG, "wrong len!"); + return; + } + lcd_dma_set_int(lcd_cam_obj); + uint8_t fifo_mode = 1; + // Start signal + xQueueSend(lcd_cam_obj->lcd.event_queue, &event, 0); + cnt = len / lcd_cam_obj->lcd.dma_half_buffer_size; + // Process a complete piece of data, ping-pong operation + for (x = 0; x < cnt; x++) { + uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf; + uint8_t *in = data; + if (lcd_cam_obj->lcd.swap_data) { + for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) { + out[y + 3] = in[y + 0]; + out[y + 2] = in[y + 1]; + out[y + 1] = in[y + 2]; + out[y + 0] = in[y + 3]; + } + } else { + for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y += 4) { + out[y + 2] = in[y + 0]; + out[y + 3] = in[y + 1]; + out[y + 0] = in[y + 2]; + out[y + 1] = in[y + 3]; + } + } + data += lcd_cam_obj->lcd.dma_half_buffer_size; + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); + lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, lcd_cam_obj->lcd.dma_half_buffer_size); + } + left = len % lcd_cam_obj->lcd.dma_half_buffer_size; + // Process remaining incomplete segment data + while (left) { + uint8_t *out = (uint8_t *)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf; + uint8_t *in = data; + if (left > 2) { + cnt = left - left % 4; + left = left % 4; + data += cnt; + if (lcd_cam_obj->lcd.swap_data) { + for (y = 0; y < cnt; y += 4) { + out[y + 3] = in[y + 0]; + out[y + 2] = in[y + 1]; + out[y + 1] = in[y + 2]; + out[y + 0] = in[y + 3]; + } + } else { + for (y = 0; y < cnt; y += 4) { + out[y + 2] = in[y + 0]; + out[y + 3] = in[y + 1]; + out[y + 0] = in[y + 2]; + out[y + 1] = in[y + 3]; + } + } + } else { + cnt = 4; + left = 0; + fifo_mode = 3; + if (lcd_cam_obj->lcd.swap_data) { + out[3] = in[0]; + out[2] = in[1]; + } else { + out[2] = in[0]; + out[3] = in[1]; + } + } + lcd_dma_set_left(lcd_cam_obj, x, cnt); + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); + lcd_i2s_start(lcd_cam_obj->i2s_dev, fifo_mode, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, cnt); + x++; + } + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); +} + + +static esp_err_t i2s_lcd_reg_config(i2s_dev_t *i2s_dev, uint16_t data_width, uint32_t clk_freq) +{ + // Configure the clock + i2s_dev->clkm_conf.clkm_div_num = 2; // 160MHz / 2 = 80MHz + i2s_dev->clkm_conf.clkm_div_b = 0; + i2s_dev->clkm_conf.clkm_div_a = 10; + i2s_dev->clkm_conf.clk_en = 1; + + i2s_dev->conf.val = 0; + i2s_dev->fifo_conf.val = 0; + i2s_dev->fifo_conf.dscr_en = 1; + + i2s_dev->conf2.lcd_en = 1; + i2s_dev->conf2.camera_en = 1; + + i2s_dev->lc_conf.ahbm_fifo_rst = 1; + i2s_dev->lc_conf.ahbm_fifo_rst = 0; + i2s_dev->lc_conf.ahbm_rst = 1; + i2s_dev->lc_conf.ahbm_rst = 0; + i2s_dev->lc_conf.check_owner = 0; + i2s_dev->lc_conf.out_loop_test = 0; + i2s_dev->lc_conf.out_auto_wrback = 0; + i2s_dev->lc_conf.out_data_burst_en = 1; + i2s_dev->lc_conf.out_no_restart_clr = 0; + i2s_dev->lc_conf.indscr_burst_en = 0; + i2s_dev->lc_conf.out_eof_mode = 1; + + i2s_dev->timing.val = 0; + + i2s_dev->int_ena.val = 0; + i2s_dev->int_clr.val = ~0; + + // Configure sampling rate + i2s_dev->sample_rate_conf.tx_bck_div_num = 40000000 / clk_freq; // Fws = Fbck / 2 + i2s_dev->sample_rate_conf.tx_bits_mod = (data_width == 8) ? 0 : 1; + // Configuration data format + i2s_dev->conf.tx_start = 0; + i2s_dev->conf.tx_reset = 1; + i2s_dev->conf.tx_reset = 0; + i2s_dev->conf.tx_fifo_reset = 1; + i2s_dev->conf.tx_fifo_reset = 0; + i2s_dev->conf.tx_slave_mod = 0; + i2s_dev->conf.tx_right_first = 1; // Must be set to 1, otherwise the clock line will change during reset + i2s_dev->conf.tx_msb_right = 0; + i2s_dev->conf.tx_short_sync = 0; + i2s_dev->conf.tx_mono = 0; + i2s_dev->conf.tx_msb_shift = 0; + + i2s_dev->conf1.tx_pcm_bypass = 1; + i2s_dev->conf1.tx_stop_en = 1; + + i2s_dev->conf_chan.tx_chan_mod = 1; + + i2s_dev->fifo_conf.tx_fifo_mod_force_en = 1; + i2s_dev->fifo_conf.tx_data_num = 32; + i2s_dev->fifo_conf.tx_fifo_mod = 1; + + i2s_dev->lc_conf.out_rst = 1; + i2s_dev->lc_conf.out_rst = 0; + + i2s_dev->int_ena.out_eof = 1; + + + return ESP_OK; +} + +static esp_err_t lcd_set_pin(const i2s_lcd_config_t *config) +{ + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_num_wr], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_num_wr, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(config->pin_num_wr, GPIO_FLOATING); + gpio_matrix_out(config->pin_num_wr, I2S0O_WS_OUT_IDX, true, false); + + for (int i = 0; i < config->data_width; i++) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data_num[i]], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_data_num[i], GPIO_MODE_OUTPUT); + gpio_set_pull_mode(config->pin_data_num[i], GPIO_FLOATING); + // High bit aligned, OUT23 is always the highest bit + gpio_matrix_out(config->pin_data_num[i], I2S0O_DATA_OUT0_IDX + (LCD_DATA_MAX_WIDTH - config->data_width) + i, false, false); + } + + return ESP_OK; +} + +static esp_err_t lcd_dma_config(lcd_cam_obj_t *lcd_cam_obj, uint32_t max_dma_buffer_size) +{ + int cnt = 0; + if (LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE % 2 != 0) { + ESP_LOGE(TAG, "ESP32 only supports 2-byte aligned data length"); + return ESP_FAIL; + } + if (max_dma_buffer_size >= LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE * 2) { + lcd_cam_obj->lcd.dma_node_buffer_size = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE; + for (cnt = 0; cnt < max_dma_buffer_size - 8; cnt++) { // Find a buffer size that can divide dma_size + if ((max_dma_buffer_size - cnt) % (lcd_cam_obj->lcd.dma_node_buffer_size * 2) == 0) { + break; + } + } + lcd_cam_obj->lcd.dma_buffer_size = max_dma_buffer_size - cnt; + } else { + lcd_cam_obj->lcd.dma_node_buffer_size = max_dma_buffer_size / 2; + lcd_cam_obj->lcd.dma_buffer_size = lcd_cam_obj->lcd.dma_node_buffer_size * 2; + } + + lcd_cam_obj->lcd.dma_half_buffer_size = lcd_cam_obj->lcd.dma_buffer_size / 2; + lcd_cam_obj->lcd.dma_node_cnt = (lcd_cam_obj->lcd.dma_buffer_size) / lcd_cam_obj->lcd.dma_node_buffer_size; // Number of DMA nodes + lcd_cam_obj->lcd.dma_half_node_cnt = lcd_cam_obj->lcd.dma_node_cnt / 2; + + ESP_LOGI(TAG, "lcd_buffer_size: %d, lcd_dma_size: %d, lcd_dma_node_cnt: %d", lcd_cam_obj->lcd.dma_buffer_size, lcd_cam_obj->lcd.dma_node_buffer_size, lcd_cam_obj->lcd.dma_node_cnt); + + lcd_cam_obj->lcd.dma = (lldesc_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_node_cnt * sizeof(lldesc_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + lcd_cam_obj->lcd.dma_buffer = (uint8_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + return ESP_OK; +} + +esp_err_t lcd_cam_deinit(i2s_lcd_driver_t *drv) +{ + if (!drv->lcd_cam_obj) { + return ESP_FAIL; + } + + if (drv->lcd_cam_obj->lcd.event_queue) { + vQueueDelete(drv->lcd_cam_obj->lcd.event_queue); + } + if (drv->lcd_cam_obj->lcd.dma) { + heap_caps_free(drv->lcd_cam_obj->lcd.dma); + } + if (drv->lcd_cam_obj->lcd.dma_buffer) { + heap_caps_free(drv->lcd_cam_obj->lcd.dma_buffer); + } + + if (drv->lcd_cam_obj->lcd_cam_intr_handle) { + esp_intr_free(drv->lcd_cam_obj->lcd_cam_intr_handle); + } + + heap_caps_free(drv->lcd_cam_obj); + drv->lcd_cam_obj = NULL; + return ESP_OK; +} + +static esp_err_t lcd_cam_init(i2s_lcd_driver_t *drv, const i2s_lcd_config_t *config) +{ + esp_err_t ret = ESP_OK; + + lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t *)heap_caps_calloc(1, sizeof(lcd_cam_obj_t), MALLOC_CAP_DMA); + if (lcd_cam_obj == NULL) { + ESP_LOGE(TAG, "lcd_cam object malloc failed"); + return ESP_ERR_NO_MEM; + } + drv->lcd_cam_obj = lcd_cam_obj; + + if (I2S_NUM_0 == config->i2s_port) { + lcd_cam_obj->i2s_dev = &I2S0; + periph_module_enable(PERIPH_I2S0_MODULE); + ESP_LOGI(TAG, "Enable I2S0"); + } else if (I2S_NUM_1 == config->i2s_port) { + lcd_cam_obj->i2s_dev = &I2S1; + periph_module_enable(PERIPH_I2S1_MODULE); + ESP_LOGI(TAG, "Enable I2S1"); + } else { + ESP_LOGE(TAG, "Designated I2S peripheral not found"); + } + + do { + ret |= i2s_lcd_reg_config(lcd_cam_obj->i2s_dev, config->data_width, config->clk_freq); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "lcd_cam config fail!"); + break; + } + + ret |= lcd_set_pin(config); + ret |= lcd_dma_config(lcd_cam_obj, config->buffer_size); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "lcd config fail!"); + break; + } + + lcd_cam_obj->lcd.event_queue = xQueueCreate(1, sizeof(int)); + lcd_cam_obj->lcd.width = config->data_width; + lcd_cam_obj->lcd.swap_data = config->swap_data;; + + if (lcd_cam_obj->lcd.event_queue == NULL) { + ESP_LOGE(TAG, "lcd config fail!"); + break; + } + + if (I2S_NUM_0 == config->i2s_port) { + ret |= esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, lcd_cam_obj, &lcd_cam_obj->lcd_cam_intr_handle); + } else if (I2S_NUM_1 == config->i2s_port) { + ret |= esp_intr_alloc(ETS_I2S1_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, lcd_cam_obj, &lcd_cam_obj->lcd_cam_intr_handle); + } + if (ret != ESP_OK) { + ESP_LOGE(TAG, "lcd_cam intr alloc fail!"); + break; + } + ESP_LOGI(TAG, "i2s lcd driver init ok"); + return ESP_OK; + } while (0); + + lcd_cam_deinit(drv); + return ESP_FAIL; +} + +/**< Public functions */ + +i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config) +{ + I2S_CHECK(NULL != config, "config pointer invalid", NULL); + I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_num_wr), "GPIO WR invalid", NULL); + I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_num_rs), "GPIO RS invalid", NULL); + I2S_CHECK(config->data_width > 0 && config->data_width <= 16, "Bit width out of range", NULL); + I2S_CHECK(0 == (config->data_width % 8), "Bit width must be a multiple of 8", NULL); + uint64_t pin_mask = 0; + for (size_t i = 0; i < config->data_width; i++) { + uint64_t mask = 1ULL << config->pin_data_num[i]; + I2S_CHECK(!(pin_mask & mask), "Data bus GPIO has a duplicate", NULL); + I2S_CHECK(GPIO_IS_VALID_OUTPUT_GPIO(config->pin_data_num[i]), "Data bus gpio invalid", NULL); + pin_mask |= mask; + } + + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)heap_caps_malloc(sizeof(i2s_lcd_driver_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + I2S_CHECK(NULL != i2s_lcd_drv, "Error malloc handle of i2s lcd driver", NULL); + + esp_err_t ret = lcd_cam_init(i2s_lcd_drv, config); + if (ESP_OK != ret) { + ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "i2s lcd driver initialize failed"); + heap_caps_free(i2s_lcd_drv); + return NULL; + } + + i2s_lcd_drv->mutex = xSemaphoreCreateMutex(); + if (i2s_lcd_drv->mutex == NULL) { + ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "lcd create mutex failed"); + lcd_cam_deinit(i2s_lcd_drv); + heap_caps_free(i2s_lcd_drv); + return NULL; + } + + if (8 == config->data_width) { + i2s_lcd_drv->i2s_write_data_func = i2s_write_8bit_data; + } else if (16 == config->data_width) { + i2s_lcd_drv->i2s_write_data_func = i2s_write_16bit_data; + } + + if (config->pin_num_cs >= 0) { + gpio_pad_select_gpio(config->pin_num_cs); + gpio_set_direction(config->pin_num_cs, GPIO_MODE_OUTPUT); + gpio_set_level(config->pin_num_cs, 0); + } + + gpio_pad_select_gpio(config->pin_num_rs); + gpio_set_direction(config->pin_num_rs, GPIO_MODE_OUTPUT); + i2s_lcd_drv->rs_io_num = config->pin_num_rs; + return (i2s_lcd_handle_t)i2s_lcd_drv; +} + +esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + lcd_cam_deinit(i2s_lcd_drv); + vSemaphoreDelete(i2s_lcd_drv->mutex); + heap_caps_free(handle); + return ESP_OK; +} + +esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&data, 2); + return ESP_OK; +} + +esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_CMD_LEV); + i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&cmd, 2); + gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_DATA_LEV); + return ESP_OK; +} + +esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + i2s_lcd_drv->i2s_write_data_func(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)data, length); + + return ESP_OK; +} + +esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + BaseType_t ret = xSemaphoreTake(i2s_lcd_drv->mutex, portMAX_DELAY); + I2S_CHECK(pdTRUE == ret, "Take semaphore failed", ESP_FAIL); + return ESP_OK; +} + +esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + BaseType_t ret = xSemaphoreGive(i2s_lcd_drv->mutex); + I2S_CHECK(pdTRUE == ret, "Give semaphore failed", ESP_FAIL); + return ESP_OK; +} + +#endif // CONFIG_IDF_TARGET_ESP32 diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/i2s_lcd_esp32s2_driver.c b/firmware/esp-idf/wumei-smart-firmware/components/bus/i2s_lcd_esp32s2_driver.c new file mode 100644 index 00000000..92f15c64 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/i2s_lcd_esp32s2_driver.c @@ -0,0 +1,481 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "sdkconfig.h" +#if CONFIG_IDF_TARGET_ESP32S2 + +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/semphr.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/i2s.h" +#include "esp_heap_caps.h" +#include "esp32s2/rom/lldesc.h" +#include "soc/system_reg.h" +#include "i2s_lcd_driver.h" + + +static const char *TAG = "ESP32S2_I2S_LCD"; + +#define I2S_CHECK(a, str, ret) if (!(a)) { \ + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, str); \ + return (ret); \ + } + +#define LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE (4000) // 4-byte aligned +#define LCD_DATA_MAX_WIDTH (24) /*!< Maximum width of LCD data bus */ + +typedef struct { + uint32_t dma_buffer_size; + uint32_t dma_half_buffer_size; + uint32_t dma_node_buffer_size; + uint32_t dma_node_cnt; + uint32_t dma_half_node_cnt; + lldesc_t *dma; + uint8_t *dma_buffer; + QueueHandle_t event_queue; + uint8_t width; + bool swap_data; +} lcd_obj_t; + +typedef struct { + lcd_obj_t lcd; + intr_handle_t lcd_cam_intr_handle; + i2s_dev_t *i2s_dev; +} lcd_cam_obj_t; + +typedef struct { + int rs_io_num; + lcd_cam_obj_t *lcd_cam_obj; + SemaphoreHandle_t mutex; +} i2s_lcd_driver_t; + +static void IRAM_ATTR i2s_isr(void *arg) +{ + BaseType_t HPTaskAwoken = pdFALSE; + lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t*)arg; + i2s_dev_t *i2s_dev = lcd_cam_obj->i2s_dev; + + typeof(i2s_dev->int_st) status = i2s_dev->int_st; + i2s_dev->int_clr.val = status.val; + if (status.val == 0) { + return; + } + + if (status.out_eof) { + xQueueSendFromISR(lcd_cam_obj->lcd.event_queue, (void*)&status.val, &HPTaskAwoken); + } + + if (HPTaskAwoken == pdTRUE) { + portYIELD_FROM_ISR(); + } +} + + +static void lcd_dma_set_int(lcd_cam_obj_t *lcd_cam_obj) +{ + // Generate a data DMA linked list + for (int x = 0; x < lcd_cam_obj->lcd.dma_node_cnt; x++) { + lcd_cam_obj->lcd.dma[x].size = lcd_cam_obj->lcd.dma_node_buffer_size; + lcd_cam_obj->lcd.dma[x].length = lcd_cam_obj->lcd.dma_node_buffer_size; + lcd_cam_obj->lcd.dma[x].buf = (lcd_cam_obj->lcd.dma_buffer + lcd_cam_obj->lcd.dma_node_buffer_size * x); + lcd_cam_obj->lcd.dma[x].eof = !((x + 1) % lcd_cam_obj->lcd.dma_half_node_cnt); + lcd_cam_obj->lcd.dma[x].empty = (uint32_t)&lcd_cam_obj->lcd.dma[(x + 1) % lcd_cam_obj->lcd.dma_node_cnt]; + } + lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_half_node_cnt - 1].empty = (uint32_t)NULL; + lcd_cam_obj->lcd.dma[lcd_cam_obj->lcd.dma_node_cnt - 1].empty = (uint32_t)NULL; +} + +static void lcd_dma_set_left(lcd_cam_obj_t *lcd_cam_obj, int pos, size_t len) +{ + int end_pos = 0, size = 0; + // Processing data length is an integer multiple of lcd_cam_obj->lcd.dma_node_buffer_size + if (len % lcd_cam_obj->lcd.dma_node_buffer_size) { + end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size; + size = len % lcd_cam_obj->lcd.dma_node_buffer_size; + } else { + end_pos = (pos % 2) * lcd_cam_obj->lcd.dma_half_node_cnt + len / lcd_cam_obj->lcd.dma_node_buffer_size - 1; + size = lcd_cam_obj->lcd.dma_node_buffer_size; + } + // Process the tail node to make it a DMA tail + lcd_cam_obj->lcd.dma[end_pos].size = size; + lcd_cam_obj->lcd.dma[end_pos].length = size; + lcd_cam_obj->lcd.dma[end_pos].eof = 1; + lcd_cam_obj->lcd.dma[end_pos].empty = (uint32_t)NULL; +} + +static void lcd_i2s_start(i2s_dev_t *i2s_dev, uint32_t addr, size_t len) +{ + while (!i2s_dev->state.tx_idle); + i2s_dev->conf.tx_reset = 1; + i2s_dev->conf.tx_reset = 0; + i2s_dev->conf.tx_fifo_reset = 1; + i2s_dev->conf.tx_fifo_reset = 0; + i2s_dev->out_link.addr = addr; + i2s_dev->out_link.start = 1; + ets_delay_us(1); + i2s_dev->conf.tx_start = 1; +} + +static void i2s_write_data(lcd_cam_obj_t *lcd_cam_obj, uint8_t *data, size_t len) +{ + int event = 0; + int x = 0, y = 0, left = 0, cnt = 0; + if (len <= 0) { + ESP_LOGE(TAG, "wrong len!"); + return; + } + lcd_dma_set_int(lcd_cam_obj); + cnt = len / lcd_cam_obj->lcd.dma_half_buffer_size; + // Start signal + xQueueSend(lcd_cam_obj->lcd.event_queue, &event, 0); + // Process a complete piece of data, ping-pong operation + for (x = 0; x < cnt; x++) { + uint8_t *out = (uint8_t*)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf; + uint8_t *in = data; + if (lcd_cam_obj->lcd.swap_data) { + for (y = 0; y < lcd_cam_obj->lcd.dma_half_buffer_size; y+=2) { + out[y+1] = in[y+0]; + out[y+0] = in[y+1]; + } + } else { + memcpy(out, in, lcd_cam_obj->lcd.dma_half_buffer_size); + } + data += lcd_cam_obj->lcd.dma_half_buffer_size; + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); + lcd_i2s_start(lcd_cam_obj->i2s_dev, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, lcd_cam_obj->lcd.dma_half_buffer_size); + } + left = len % lcd_cam_obj->lcd.dma_half_buffer_size; + // Process remaining incomplete segment data + if (left) { + uint8_t *out = (uint8_t*)lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt].buf; + uint8_t *in = data; + cnt = left - left % 2; + if (cnt) { + if (lcd_cam_obj->lcd.swap_data) { + for (y = 0; y < cnt; y+=2) { + out[y+1] = in[y+0]; + out[y+0] = in[y+1]; + } + } else { + memcpy(out, in, cnt); + } + } + + if (left % 2) { + out[cnt] = in[cnt]; + } + lcd_dma_set_left(lcd_cam_obj, x, left); + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); + lcd_i2s_start(lcd_cam_obj->i2s_dev, ((uint32_t)&lcd_cam_obj->lcd.dma[(x % 2) * lcd_cam_obj->lcd.dma_half_node_cnt]) & 0xfffff, left); + } + xQueueReceive(lcd_cam_obj->lcd.event_queue, (void *)&event, portMAX_DELAY); +} + + +static esp_err_t i2s_lcd_reg_config(i2s_dev_t *i2s_dev, uint16_t data_width, uint32_t clk_freq) +{ + // Configure the clock + i2s_dev->clkm_conf.clkm_div_num = 2; // 160MHz / 2 = 80MHz + i2s_dev->clkm_conf.clkm_div_b = 0; + i2s_dev->clkm_conf.clkm_div_a = 0; + i2s_dev->clkm_conf.clk_sel = 2; + i2s_dev->clkm_conf.clk_en = 1; + + i2s_dev->conf.val = 0; + i2s_dev->fifo_conf.val = 0; + i2s_dev->fifo_conf.dscr_en = 1; + + i2s_dev->lc_conf.ahbm_fifo_rst = 1; + i2s_dev->lc_conf.ahbm_fifo_rst = 0; + i2s_dev->lc_conf.ahbm_rst = 1; + i2s_dev->lc_conf.ahbm_rst = 0; + i2s_dev->lc_conf.check_owner = 0; + + i2s_dev->timing.val = 0; + + i2s_dev->int_ena.val = 0; + i2s_dev->int_clr.val = ~0; + + i2s_dev->conf2.lcd_en = 1; + + // Configure sampling rate + i2s_dev->sample_rate_conf.tx_bck_div_num = 40000000 / clk_freq; // Fws = Fbck / 2 + i2s_dev->sample_rate_conf.tx_bits_mod = data_width; + // Configuration data format + + i2s_dev->conf.tx_right_first = 1; + i2s_dev->conf.tx_msb_right = 1; + i2s_dev->conf.tx_dma_equal = 1; + + i2s_dev->conf1.tx_pcm_bypass = 1; + i2s_dev->conf1.tx_stop_en = 1; + + i2s_dev->conf2.lcd_en = 1; + + i2s_dev->conf_chan.tx_chan_mod = 1; + + i2s_dev->fifo_conf.tx_fifo_mod_force_en = 1; + i2s_dev->fifo_conf.tx_data_num = 32; + i2s_dev->fifo_conf.tx_fifo_mod = 2; + i2s_dev->fifo_conf.tx_24msb_en = 0; + + i2s_dev->lc_conf.out_rst = 1; + i2s_dev->lc_conf.out_rst = 0; + + i2s_dev->int_ena.out_eof = 1; + + + return ESP_OK; +} + +static esp_err_t lcd_set_pin(const i2s_lcd_config_t *config) +{ + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_num_wr], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_num_wr, GPIO_MODE_OUTPUT); + gpio_set_pull_mode(config->pin_num_wr, GPIO_FLOATING); + gpio_matrix_out(config->pin_num_wr, I2S0O_WS_OUT_IDX, true, false); + + for (int i = 0; i < config->data_width; i++) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[config->pin_data_num[i]], PIN_FUNC_GPIO); + gpio_set_direction(config->pin_data_num[i], GPIO_MODE_OUTPUT); + gpio_set_pull_mode(config->pin_data_num[i], GPIO_FLOATING); + // High bit aligned, OUT23 is always the highest bit + gpio_matrix_out(config->pin_data_num[i], I2S0O_DATA_OUT0_IDX + (LCD_DATA_MAX_WIDTH - config->data_width) + i, false, false); + } + + return ESP_OK; +} + +static esp_err_t lcd_dma_config(lcd_cam_obj_t *lcd_cam_obj, uint32_t max_dma_buffer_size) +{ + int cnt = 0; + if (LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE % 2 != 0) { + ESP_LOGE(TAG, "ESP32 only supports 2-byte aligned data length"); + return ESP_FAIL; + } + if (max_dma_buffer_size >= LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE * 2) { + lcd_cam_obj->lcd.dma_node_buffer_size = LCD_CAM_DMA_NODE_BUFFER_MAX_SIZE; + for (cnt = 0; cnt < max_dma_buffer_size - 8; cnt++) { // Find a buffer size that can divide dma_size + if ((max_dma_buffer_size - cnt) % (lcd_cam_obj->lcd.dma_node_buffer_size * 2) == 0) { + break; + } + } + lcd_cam_obj->lcd.dma_buffer_size = max_dma_buffer_size - cnt; + } else { + lcd_cam_obj->lcd.dma_node_buffer_size = max_dma_buffer_size / 2; + lcd_cam_obj->lcd.dma_buffer_size = lcd_cam_obj->lcd.dma_node_buffer_size * 2; + } + + lcd_cam_obj->lcd.dma_half_buffer_size = lcd_cam_obj->lcd.dma_buffer_size / 2; + lcd_cam_obj->lcd.dma_node_cnt = (lcd_cam_obj->lcd.dma_buffer_size) / lcd_cam_obj->lcd.dma_node_buffer_size; // Number of DMA nodes + lcd_cam_obj->lcd.dma_half_node_cnt = lcd_cam_obj->lcd.dma_node_cnt / 2; + + ESP_LOGI(TAG, "lcd_buffer_size: %d, lcd_dma_size: %d, lcd_dma_node_cnt: %d", lcd_cam_obj->lcd.dma_buffer_size, lcd_cam_obj->lcd.dma_node_buffer_size, lcd_cam_obj->lcd.dma_node_cnt); + + lcd_cam_obj->lcd.dma = (lldesc_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_node_cnt * sizeof(lldesc_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + lcd_cam_obj->lcd.dma_buffer = (uint8_t *)heap_caps_malloc(lcd_cam_obj->lcd.dma_buffer_size * sizeof(uint8_t), MALLOC_CAP_DMA | MALLOC_CAP_8BIT); + return ESP_OK; +} + +static esp_err_t lcd_cam_deinit(i2s_lcd_driver_t *drv) +{ + if (!drv->lcd_cam_obj) { + return ESP_FAIL; + } + + if (drv->lcd_cam_obj->lcd.event_queue) { + vQueueDelete(drv->lcd_cam_obj->lcd.event_queue); + } + if (drv->lcd_cam_obj->lcd.dma) { + heap_caps_free(drv->lcd_cam_obj->lcd.dma); + } + if (drv->lcd_cam_obj->lcd.dma_buffer) { + heap_caps_free(drv->lcd_cam_obj->lcd.dma_buffer); + } + + if (drv->lcd_cam_obj->lcd_cam_intr_handle) { + esp_intr_free(drv->lcd_cam_obj->lcd_cam_intr_handle); + } + + heap_caps_free(drv->lcd_cam_obj); + drv->lcd_cam_obj = NULL; + return ESP_OK; +} + +static esp_err_t lcd_cam_init(i2s_lcd_driver_t *drv, const i2s_lcd_config_t *config) +{ + esp_err_t ret = ESP_OK; + + lcd_cam_obj_t *lcd_cam_obj = (lcd_cam_obj_t *)heap_caps_calloc(1, sizeof(lcd_cam_obj_t), MALLOC_CAP_DMA); + if (lcd_cam_obj == NULL) { + ESP_LOGE(TAG, "lcd_cam object malloc error"); + return ESP_ERR_NO_MEM; + } + drv->lcd_cam_obj = lcd_cam_obj; + + if (I2S_NUM_0 == config->i2s_port) { + lcd_cam_obj->i2s_dev = &I2S0; + periph_module_enable(PERIPH_I2S0_MODULE); + } else { + ESP_LOGE(TAG, "Designated I2S peripheral not found"); + } + + ret |= i2s_lcd_reg_config(lcd_cam_obj->i2s_dev, config->data_width, config->clk_freq); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "lcd_cam config fail!"); + lcd_cam_deinit(drv); + return ESP_FAIL; + } + + ret |= lcd_set_pin(config); + ret |= lcd_dma_config(lcd_cam_obj, config->buffer_size); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "lcd config fail!"); + lcd_cam_deinit(drv); + return ESP_FAIL; + } + + lcd_cam_obj->lcd.event_queue = xQueueCreate(1, sizeof(int)); + lcd_cam_obj->lcd.width = config->data_width; + lcd_cam_obj->lcd.swap_data = config->swap_data; + + if (lcd_cam_obj->lcd.event_queue == NULL) { + ESP_LOGE(TAG, "lcd config fail!"); + lcd_cam_deinit(drv); + return ESP_FAIL; + } + + ret |= esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_IRAM, i2s_isr, lcd_cam_obj, &lcd_cam_obj->lcd_cam_intr_handle); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "lcd_cam intr alloc fail!"); + lcd_cam_deinit(drv); + return ESP_FAIL; + } + + ESP_LOGI(TAG, "lcd init ok"); + + return ESP_OK; +} + + +/**< Public functions */ + +i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config) +{ + I2S_CHECK(NULL != config, "config pointer invalid", NULL); + I2S_CHECK(GPIO_IS_VALID_GPIO(config->pin_num_wr), "GPIO WR invalid", NULL); + I2S_CHECK(GPIO_IS_VALID_GPIO(config->pin_num_rs), "GPIO RS invalid", NULL); + I2S_CHECK(config->data_width > 0 && config->data_width <= 16, "Bit width out of range", NULL); + I2S_CHECK(0 == (config->data_width % 8), "Bit width must be a multiple of 8", NULL); + uint64_t pin_mask = 0; + for (size_t i = 0; i < config->data_width; i++) { + uint64_t mask = 1ULL << config->pin_data_num[i]; + I2S_CHECK(!(pin_mask & mask), "Data bus GPIO has a duplicate", NULL); + I2S_CHECK(GPIO_IS_VALID_GPIO(config->pin_data_num[i]), "Data bus gpio invalid", NULL); + pin_mask |= mask; + } + + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)heap_caps_malloc(sizeof(i2s_lcd_driver_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + I2S_CHECK(NULL != i2s_lcd_drv, "Error malloc handle of i2s lcd driver", NULL); + + esp_err_t ret = lcd_cam_init(i2s_lcd_drv, config); + if(ESP_OK != ret) { + ESP_LOGE(TAG,"%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "i2s lcd driver initialize failed"); + heap_caps_free(i2s_lcd_drv); + return NULL; + } + + i2s_lcd_drv->mutex = xSemaphoreCreateMutex(); + if (i2s_lcd_drv->mutex == NULL) { + ESP_LOGE(TAG, "%s:%d (%s):%s", __FILE__, __LINE__, __FUNCTION__, "lcd create mutex failed"); + lcd_cam_deinit(i2s_lcd_drv); + heap_caps_free(i2s_lcd_drv); + return NULL; + } + + if (config->pin_num_cs >= 0) { + gpio_pad_select_gpio(config->pin_num_cs); + gpio_set_direction(config->pin_num_cs, GPIO_MODE_OUTPUT); + gpio_set_level(config->pin_num_cs, 0); + } + + gpio_pad_select_gpio(config->pin_num_rs); + gpio_set_direction(config->pin_num_rs, GPIO_MODE_OUTPUT); + i2s_lcd_drv->rs_io_num = config->pin_num_rs; + return (i2s_lcd_handle_t)i2s_lcd_drv; +} + +esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + lcd_cam_deinit(i2s_lcd_drv); + vSemaphoreDelete(i2s_lcd_drv->mutex); + heap_caps_free(handle); + return ESP_OK; +} + +esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + i2s_write_data(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&data, 2); + return ESP_OK; +} + +esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_CMD_LEV); + i2s_write_data(i2s_lcd_drv->lcd_cam_obj, (uint8_t *)&cmd, 2); + gpio_set_level(i2s_lcd_drv->rs_io_num, LCD_DATA_LEV); + return ESP_OK; +} + +esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + i2s_write_data(i2s_lcd_drv->lcd_cam_obj, (uint8_t*)data, length); + + return ESP_OK; +} + +esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + BaseType_t ret = xSemaphoreTake(i2s_lcd_drv->mutex, portMAX_DELAY); + I2S_CHECK(pdTRUE == ret, "Take semaphore failed", ESP_FAIL); + return ESP_OK; +} + +esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle) +{ + i2s_lcd_driver_t *i2s_lcd_drv = (i2s_lcd_driver_t *)handle; + I2S_CHECK(NULL != i2s_lcd_drv, "handle pointer invalid", ESP_ERR_INVALID_ARG); + BaseType_t ret = xSemaphoreGive(i2s_lcd_drv->mutex); + I2S_CHECK(pdTRUE == ret, "Give semaphore failed", ESP_FAIL); + return ESP_OK; +} + +#endif // CONFIG_IDF_TARGET_ESP32S2 diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/include/i2c_bus.h b/firmware/esp-idf/wumei-smart-firmware/components/bus/include/i2c_bus.h new file mode 100644 index 00000000..923563ff --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/include/i2c_bus.h @@ -0,0 +1,296 @@ +// Copyright 2019-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _I2C_BUS_H_ +#define _I2C_BUS_H_ +#include "driver/i2c.h" + +#define NULL_I2C_MEM_ADDR 0xFF /*!< set mem_address to NULL_I2C_MEM_ADDR if i2c device has no internal address during read/write */ +#define NULL_I2C_DEV_ADDR 0xFF /*!< invalid i2c device address */ +typedef void *i2c_bus_handle_t; /*!< i2c bus handle */ +typedef void *i2c_bus_device_handle_t; /*!< i2c device handle */ + +#ifdef __cplusplus +extern "C" +{ +#endif + +/**************************************** Public Functions (Application level)*********************************************/ + +/** + * @brief Create an I2C bus instance then return a handle if created successfully. Each I2C bus works in a singleton mode, + * which means for an i2c port only one group parameter works. When i2c_bus_create is called more than one time for the + * same i2c port, following parameter will override the previous one. + * + * @param port I2C port number + * @param conf Pointer to I2C bus configuration + * @return i2c_bus_handle_t Return the I2C bus handle if created successfully, return NULL if failed. + */ +i2c_bus_handle_t i2c_bus_create(i2c_port_t port, const i2c_config_t *conf); + +/** + * @brief Delete and release the I2C bus resource. + * + * @param p_bus_handle Point to the I2C bus handle, if delete succeed handle will set to NULL. + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t i2c_bus_delete(i2c_bus_handle_t *p_bus_handle); + +/** + * @brief Scan i2c devices attached on i2c bus + * + * @param bus_handle I2C bus handle + * @param buf Pointer to a buffer to save devices' address, if NULL no address will be saved. + * @param num Maximum number of addresses to save, invalid if buf set to NULL, + * higer addresses will be discarded if num less-than the total number found on the I2C bus. + * @return uint8_t Total number of devices found on the I2C bus + */ +uint8_t i2c_bus_scan(i2c_bus_handle_t bus_handle, uint8_t *buf, uint8_t num); + +/** + * @brief Get current active clock speed. + * + * @param bus_handle I2C bus handle + * @return uint32_t current clock speed + */ +uint32_t i2c_bus_get_current_clk_speed(i2c_bus_handle_t bus_handle); + +/** + * @brief Get created device number of the bus. + * + * @param bus_handle I2C bus handle + * @return uint8_t created device number of the bus + */ +uint8_t i2c_bus_get_created_device_num(i2c_bus_handle_t bus_handle); + +/** + * @brief Create an I2C device on specific bus. + * Dynamic configuration must be enable to achieve multiple devices with different configs on a single bus. + * menuconfig:Bus Options->I2C Bus Options->enable dynamic configuration + * + * @param bus_handle Point to the I2C bus handle + * @param dev_addr i2c device address + * @param clk_speed device specified clock frequency the i2c_bus will switch to during each transfer. 0 if use current bus speed. + * @return i2c_bus_device_handle_t return a device handle if created successfully, return NULL if failed. + */ +i2c_bus_device_handle_t i2c_bus_device_create(i2c_bus_handle_t bus_handle, uint8_t dev_addr, uint32_t clk_speed); + +/** + * @brief Delete and release the I2C device resource, i2c_bus_device_delete should be used in pairs with i2c_bus_device_create. + * + * @param p_dev_handle Point to the I2C device handle, if delete succeed handle will set to NULL. + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t i2c_bus_device_delete(i2c_bus_device_handle_t *p_dev_handle); + +/** + * @brief Get device's I2C address + * + * @param dev_handle I2C device handle + * @return uint8_t I2C address, return NULL_I2C_DEV_ADDR if dev_handle is invalid. + */ +uint8_t i2c_bus_device_get_address(i2c_bus_device_handle_t dev_handle); + +/** + * @brief Read single byte from i2c device with 8-bit internal register/memory address + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address. + * @param data Pointer to a buffer to save the data that was read + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_read_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t *data); + +/** + * @brief Read multiple bytes from i2c device with 8-bit internal register/memory address. + * If internal reg/mem address is 16-bit, please refer i2c_bus_read_reg16 + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address. + * @param data_len Number of bytes to read + * @param data Pointer to a buffer to save the data that was read + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_read_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, uint8_t *data); + +/** + * @brief Read single bit of a byte from i2c device with 8-bit internal register/memory address + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address. + * @param bit_num The bit number 0 - 7 to read + * @param data Pointer to a buffer to save the data that was read. *data == 0 -> bit = 0, *data !=0 -> bit = 1. + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_read_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t *data); + +/** + * @brief Read multiple bits of a byte from i2c device with 8-bit internal register/memory address + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address. + * @param bit_start The bit to start from, 0 - 7, MSB at 0 + * @param length The number of bits to read, 1 - 8 + * @param data Pointer to a buffer to save the data that was read + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_read_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t *data); + +/** + * @brief Write single byte to i2c device with 8-bit internal register/memory address + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address. + * @param data The byte to write. + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_write_byte(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t data); + +/** + * @brief Write multiple byte to i2c device with 8-bit internal register/memory address + * If internal reg/mem address is 16-bit, please refer i2c_bus_write_reg16 + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address. + * @param data_len Number of bytes to write + * @param data Pointer to the bytes to write. + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_write_bytes(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, size_t data_len, const uint8_t *data); + +/** + * @brief Write single bit of a byte to an i2c device with 8-bit internal register/memory address + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address. + * @param bit_num The bit number 0 - 7 to write + * @param data The bit to write, data == 0 means set bit = 0, data !=0 means set bit = 1. + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_write_bit(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_num, uint8_t data); + +/** + * @brief Write multiple bits of a byte to an i2c device with 8-bit internal register/memory address + * + * @param dev_handle I2C device handle + * @param mem_address The internal reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address. + * @param bit_start The bit to start from, 0 - 7, MSB at 0 + * @param length The number of bits to write, 1 - 8 + * @param data The bits to write. + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_write_bits(i2c_bus_device_handle_t dev_handle, uint8_t mem_address, uint8_t bit_start, uint8_t length, uint8_t data); + +/**************************************** Public Functions (Low level)*********************************************/ + +/** + * @brief I2C master send queued commands create by ``i2c_cmd_link_create`` . + * This function will trigger sending all queued commands. + * The task will be blocked until all the commands have been sent out. + * If I2C_BUS_DYNAMIC_CONFIG enable, i2c_bus will dynamically check configs and re-install i2c driver before each transfer, + * hence multiple devices with different configs on a single bus can be supported. + * @note + * Only call this function when ``i2c_bus_read/write_xx`` do not meet the requirements + * + * @param dev_handle I2C device handle + * @param cmd I2C command handler + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_cmd_begin(i2c_bus_device_handle_t dev_handle, i2c_cmd_handle_t cmd); + +/** + * @brief Write date to an i2c device with 16-bit internal reg/mem address + * + * @param dev_handle I2C device handle + * @param mem_address The internal 16-bit reg/mem address to write to, set to NULL_I2C_MEM_ADDR if no internal address. + * @param data_len Number of bytes to write + * @param data Pointer to the bytes to write. + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_write_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, const uint8_t *data); + +/** + * @brief Read date from i2c device with 16-bit internal reg/mem address + * + * @param dev_handle I2C device handle + * @param mem_address The internal 16-bit reg/mem address to read from, set to NULL_I2C_MEM_ADDR if no internal address. + * @param data_len Number of bytes to read + * @param data Pointer to a buffer to save the data that was read + * @return esp_err_t + * - ESP_OK Success + * - ESP_ERR_INVALID_ARG Parameter error + * - ESP_FAIL Sending command error, slave doesn't ACK the transfer. + * - ESP_ERR_INVALID_STATE I2C driver not installed or not in master mode. + * - ESP_ERR_TIMEOUT Operation timeout because the bus is busy. + */ +esp_err_t i2c_bus_read_reg16(i2c_bus_device_handle_t dev_handle, uint16_t mem_address, size_t data_len, uint8_t *data); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/include/i2s_lcd_driver.h b/firmware/esp-idf/wumei-smart-firmware/components/bus/include/i2s_lcd_driver.h new file mode 100644 index 00000000..6b934102 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/include/i2s_lcd_driver.h @@ -0,0 +1,125 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef __I2S_LCD_DRIVER_H__ +#define __I2S_LCD_DRIVER_H__ + +#include "driver/i2s.h" + +#ifdef __cplusplus +extern "C" +{ +#endif + +#define LCD_CMD_LEV (0) +#define LCD_DATA_LEV (1) + +typedef void * i2s_lcd_handle_t; /** Handle of i2s lcd driver */ + +/** + * @brief Configuration of i2s lcd mode + * + */ +typedef struct { + int8_t data_width; /*!< Parallel data width, 16bit or 8bit available */ + int8_t pin_data_num[16]; /*!< Parallel data output IO*/ + int8_t pin_num_cs; /*!< CS io num */ + int8_t pin_num_wr; /*!< Write clk io*/ + int8_t pin_num_rs; /*!< RS io num */ + int clk_freq; /*!< I2s clock frequency */ + i2s_port_t i2s_port; /*!< I2S port number */ + bool swap_data; /*!< Swap the 2 bytes of RGB565 color */ + uint32_t buffer_size; /*!< DMA buffer size */ +} i2s_lcd_config_t; + +/** + * @brief Initilize i2s lcd driver. + * + * @param config configuration of i2s + * + * @return A handle to the created i2s lcd driver, or NULL in case of error. + */ +i2s_lcd_handle_t i2s_lcd_driver_init(const i2s_lcd_config_t *config); + +/** + * @brief Deinit i2s lcd driver. + * + * @param handle i2s lcd driver handle to deinitilize + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG handle is invalid + */ +esp_err_t i2s_lcd_driver_deinit(i2s_lcd_handle_t handle); + +/** + * @brief Write a data to LCD + * + * @param handle i2s lcd driver handle + * @param data Data to write + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG handle is invalid + */ +esp_err_t i2s_lcd_write_data(i2s_lcd_handle_t handle, uint16_t data); + +/** + * @brief Write a command to LCD + * + * @param handle Handle of i2s lcd driver + * @param cmd command to write + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG handle is invalid + */ +esp_err_t i2s_lcd_write_cmd(i2s_lcd_handle_t handle, uint16_t cmd); + +/** + * @brief Write block data to LCD + * + * @param handle Handle of i2s lcd driver + * @param data Pointer of data + * @param length length of data + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG handle is invalid + */ +esp_err_t i2s_lcd_write(i2s_lcd_handle_t handle, const uint8_t *data, uint32_t length); + +/** + * @brief acquire a lock + * + * @param handle Handle of i2s lcd driver + * + * @return Always return ESP_OK + */ +esp_err_t i2s_lcd_acquire(i2s_lcd_handle_t handle); + +/** + * @brief release a lock + * + * @param handle Handle of i2s lcd driver + * + * @return Always return ESP_OK + */ +esp_err_t i2s_lcd_release(i2s_lcd_handle_t handle); + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/include/spi_bus.h b/firmware/esp-idf/wumei-smart-firmware/components/bus/include/spi_bus.h new file mode 100644 index 00000000..a4761f51 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/include/spi_bus.h @@ -0,0 +1,164 @@ +// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IOT_SPI_BUS_H_ +#define _IOT_SPI_BUS_H_ + +#include "driver/spi_master.h" +#include "driver/gpio.h" + +#define NULL_SPI_CS_PIN -1 /*!< set cs_io_num to NULL_SPI_CS_PIN if spi device has no CP pin */ +typedef void *spi_bus_handle_t; /*!< spi bus handle */ +typedef void *spi_bus_device_handle_t; /*!< spi device handle */ + +/** + * spi bus initialization parameters. + * */ +typedef struct { + gpio_num_t miso_io_num; /*!< GPIO pin for Master In Slave Out (=spi_q) signal, or -1 if not used.*/ + gpio_num_t mosi_io_num; /*!< GPIO pin for Master Out Slave In (=spi_d) signal, or -1 if not used.*/ + gpio_num_t sclk_io_num; /*!< GPIO pin for Spi CLocK signal, or -1 if not used*/ + int max_transfer_sz; /*!< +#include +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "driver/spi_master.h" +#include "driver/spi_common.h" +#include "spi_bus.h" + +typedef struct { + spi_host_device_t host_id; /*!mutex, ESP_SPI_MUTEX_TICKS_TO_WAIT)) { \ + ESP_LOGE(TAG, "spi device(%d) take mutex timeout, max wait = %d ticks", (int32_t)((p_spi_dev)->handle), ESP_SPI_MUTEX_TICKS_TO_WAIT); \ + return (ret); \ + } + +#define SPI_DEVICE_MUTEX_GIVE(p_spi_dev, ret) if (!xSemaphoreGive((p_spi_dev)->mutex)) { \ + ESP_LOGE(TAG, "spi device(%d) give mutex failed", (int32_t)((p_spi_dev)->handle)); \ + return (ret); \ + } + +spi_bus_handle_t spi_bus_create(spi_host_device_t host_id, const spi_config_t *bus_conf) +{ + SPI_BUS_CHECK(SPI1_HOST < host_id && host_id <= SPI3_HOST, "Invalid spi host_id", NULL); + uint8_t index = host_id - 1; //find related index + spi_bus_config_t buscfg = { + .miso_io_num = bus_conf->miso_io_num, + .mosi_io_num = bus_conf->mosi_io_num, + .sclk_io_num = bus_conf->sclk_io_num, + .quadwp_io_num = -1, + .quadhd_io_num = -1, + .max_transfer_sz = bus_conf->max_transfer_sz, + }; + int dma_chan = host_id; //set dma channel equals to host_id by default + esp_err_t ret = spi_bus_initialize(host_id, &buscfg, dma_chan); + SPI_BUS_CHECK(ESP_OK == ret, "spi bus create failed", NULL); + s_spi_bus[index].host_id = host_id; + memcpy(&s_spi_bus[index].conf, &buscfg, sizeof(spi_bus_config_t)); + s_spi_bus[index].is_init = true; + ESP_LOGI(TAG, "SPI%d bus created", host_id + 1); + return (spi_bus_handle_t)&s_spi_bus[index]; +} + +esp_err_t spi_bus_delete(spi_bus_handle_t *p_bus_handle) +{ + SPI_BUS_CHECK((NULL != p_bus_handle) && (NULL != *p_bus_handle), "Handle error", ESP_ERR_INVALID_ARG); + _spi_bus_t *spi_bus = (_spi_bus_t *)(*p_bus_handle); + + if (!spi_bus->is_init) { + ESP_LOGW(TAG, "spi_bus%d has been de-inited", spi_bus->host_id); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t ret = spi_bus_free(spi_bus->host_id); + SPI_BUS_CHECK(ESP_OK == ret, "spi bus delete failed", ESP_FAIL); + ESP_LOGI(TAG, "SPI%d bus delete", spi_bus->host_id + 1); + memset(spi_bus, 0, sizeof(_spi_bus_t)); + *p_bus_handle = NULL; + return ESP_OK; +} + +spi_bus_device_handle_t spi_bus_device_create(spi_bus_device_handle_t bus_handle, const spi_device_config_t *device_conf) +{ + SPI_BUS_CHECK(NULL != bus_handle, "Pointer error", NULL); + _spi_bus_t *spi_bus = (_spi_bus_t *)bus_handle; + + _spi_device_t *spi_dev = malloc(sizeof(_spi_device_t)); + spi_device_interface_config_t devcfg = { + .command_bits = 0, + .address_bits = 0, + .dummy_bits = 0, + .clock_speed_hz = device_conf->clock_speed_hz, + .duty_cycle_pos = 128, //50% duty cycle + .mode = device_conf->mode, + .spics_io_num = device_conf->cs_io_num, + .cs_ena_posttrans = 3, //Keep the CS low 3 cycles after transaction, to stop slave from missing the last bit when CS has less propagation delay than CLK + .queue_size = 3 + }; + esp_err_t ret = spi_bus_add_device(spi_bus->host_id, &devcfg, &spi_dev->handle); + SPI_BUS_CHECK_GOTO(ESP_OK == ret, "add spi device failed", cleanup_device); + spi_dev->mutex = xSemaphoreCreateMutex(); + SPI_BUS_CHECK_GOTO(NULL != spi_dev->mutex, "spi device create mutex failed", cleanup_device); + spi_dev->spi_bus = bus_handle; + memcpy(&spi_dev->conf, &devcfg, sizeof(spi_device_interface_config_t)); + ESP_LOGI(TAG, "SPI%d bus device added, CS=%d Mode=%u Speed=%d", spi_bus->host_id + 1, device_conf->cs_io_num, device_conf->mode, device_conf->clock_speed_hz); + return (spi_bus_device_handle_t)spi_dev; + +cleanup_device: + free(spi_dev); + return NULL; +} + +esp_err_t spi_bus_device_delete(spi_bus_device_handle_t *p_dev_handle) +{ + SPI_BUS_CHECK((NULL != p_dev_handle) && (NULL != *p_dev_handle), "Pointer error", ESP_ERR_INVALID_ARG); + _spi_device_t *spi_dev = (_spi_device_t *)(*p_dev_handle); + _spi_bus_t *spi_bus = (_spi_bus_t *)(spi_dev->spi_bus); + SPI_DEVICE_MUTEX_TAKE(spi_dev, ESP_FAIL); + esp_err_t ret = spi_bus_remove_device(spi_dev->handle); + SPI_DEVICE_MUTEX_GIVE(spi_dev, ESP_FAIL); + SPI_BUS_CHECK(ESP_OK == ret, "spi bus delete device failed", ret); + vSemaphoreDelete(spi_dev->mutex); + ESP_LOGI(TAG, "SPI%d device removed, CS=%d", spi_bus->host_id + 1, spi_dev->conf.spics_io_num); + free(spi_dev); + *p_dev_handle = NULL; + return ESP_OK; +} + +/* this function should lable with inline*/ +inline static esp_err_t _spi_device_polling_transmit(spi_bus_device_handle_t dev_handle, spi_transaction_t *trans) +{ + SPI_BUS_CHECK(NULL != dev_handle, "Pointer error", ESP_ERR_INVALID_ARG); + _spi_device_t *spi_dev = (_spi_device_t *)(dev_handle); + esp_err_t ret; + SPI_DEVICE_MUTEX_TAKE(spi_dev, ESP_FAIL); + ret = spi_device_polling_transmit(spi_dev->handle, trans); + SPI_DEVICE_MUTEX_GIVE(spi_dev, ESP_FAIL); + return ret; +} + +esp_err_t spi_bus_transfer_byte(spi_bus_device_handle_t dev_handle, uint8_t data_out, uint8_t *data_in) +{ + esp_err_t ret; + spi_transaction_t trans = { + .length = 8, + .flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA, + .tx_data = { + [0] = data_out + } + }; + ret = _spi_device_polling_transmit(dev_handle, &trans); + SPI_BUS_CHECK(ret == ESP_OK, "spi transfer byte failed", ret); + + if (data_in) { + *data_in = trans.rx_data[0]; + } + + return ESP_OK; +} + +esp_err_t spi_bus_transfer_bytes(spi_bus_device_handle_t dev_handle, const uint8_t *data_out, uint8_t *data_in, uint32_t data_len) +{ + esp_err_t ret; + spi_transaction_t trans = { + .length = data_len * 8, + .tx_buffer = NULL, + .rx_buffer = NULL + }; + + if (data_out) { + trans.tx_buffer = data_out; + } + + if (data_in) { + trans.rx_buffer = data_in; + } + + ret = _spi_device_polling_transmit(dev_handle, &trans); + SPI_BUS_CHECK(ret == ESP_OK, "spi transfer bytes failed", ret); + + return ESP_OK; +} + + /**************************************** Public Functions (Low level)*********************************************/ + +esp_err_t spi_bus_transmit_begin(spi_bus_device_handle_t dev_handle, spi_transaction_t *p_trans) +{ + return _spi_device_polling_transmit(dev_handle, p_trans); +} + +esp_err_t spi_bus_transfer_reg16(spi_bus_device_handle_t dev_handle, uint16_t data_out, uint16_t *data_in) +{ + esp_err_t ret; + spi_transaction_t trans = { + .length = 16, + .flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA, + /* default MSB first */ + .tx_data = { + [0] = (data_out >> 8) & 0xff, + [1] = data_out & 0xff, + } + }; + ret = _spi_device_polling_transmit(dev_handle, &trans); + SPI_BUS_CHECK(ret == ESP_OK, "spi transfer reg16 failed", ret); + + if (data_in) { + *data_in = (trans.rx_data[0] << 8) | (trans.rx_data[1]); + } + + return ESP_OK; +} + +esp_err_t spi_bus_transfer_reg32(spi_bus_device_handle_t dev_handle, uint32_t data_out, uint32_t *data_in) +{ + esp_err_t ret; + spi_transaction_t trans = { + .length = 32, + .flags = SPI_TRANS_USE_RXDATA | SPI_TRANS_USE_TXDATA, + /* default MSB first */ + .tx_data = { + [0] = (data_out >> 24) & 0xff, + [1] = (data_out >> 16) & 0xff, + [2] = (data_out >> 8) & 0xff, + [3] = data_out & 0xff + } + }; + ret = _spi_device_polling_transmit(dev_handle, &trans); + SPI_BUS_CHECK(ret == ESP_OK, "spi transfer reg32 failed", ret); + + if (data_in) { + *data_in = (trans.rx_data[0] << 24) | (trans.rx_data[1] << 16) | (trans.rx_data[2] << 8) | (trans.rx_data[3]); + } + + return ESP_OK; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/test/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/bus/test/CMakeLists.txt new file mode 100644 index 00000000..61713206 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/test/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "test_i2c_bus.c" "test_spi_bus.c" + INCLUDE_DIRS . + REQUIRES test_utils bus) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/test/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/bus/test/component.mk new file mode 100644 index 00000000..5dd172bd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/firmware/esp-idf/wumei-smart-firmware/components/bus/test/test_i2c_bus.c b/firmware/esp-idf/wumei-smart-firmware/components/bus/test/test_i2c_bus.c new file mode 100644 index 00000000..cf76d945 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/bus/test/test_i2c_bus.c @@ -0,0 +1,269 @@ +// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "unity.h" +#include "test_utils.h" +#include "unity_config.h" +#include "i2c_bus.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define I2C_MASTER_SCL_IO (gpio_num_t)22 /*!< gpio number for I2C master clock */ +#define I2C_MASTER_SDA_IO (gpio_num_t)21 /*!< gpio number for I2C master data */ + +#define DATA_LENGTH 512 /*! +#include "unity.h" +#include "unity_config.h" +#include "spi_bus.h" +#include "esp_system.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +#define SPI_SCK_IO 18 +#define SPI_MOSI_IO 23 +#define SPI_MISO_IO 19 + +void spi_bus_init_deinit_test() +{ + spi_bus_handle_t bus_handle = NULL; + spi_config_t bus_conf = { + .miso_io_num = SPI_MISO_IO, + .mosi_io_num = SPI_MOSI_IO, + .sclk_io_num = SPI_SCK_IO, + }; + bus_handle = spi_bus_create(SPI2_HOST, &bus_conf); + TEST_ASSERT(bus_handle != NULL); + TEST_ASSERT(ESP_OK == spi_bus_delete(&bus_handle)); + TEST_ASSERT(bus_handle == NULL); + bus_handle = spi_bus_create(SPI3_HOST, &bus_conf); + TEST_ASSERT(bus_handle != NULL); + TEST_ASSERT(ESP_OK == spi_bus_delete(&bus_handle)); + TEST_ASSERT(bus_handle == NULL); +} + +/* connect mosi with miso for transfer test */ +void spi_bus_transfer_test() +{ + spi_bus_handle_t bus_handle = NULL; + spi_config_t bus_conf = { + .miso_io_num = SPI_MISO_IO, + .mosi_io_num = SPI_MOSI_IO, + .sclk_io_num = SPI_SCK_IO, + }; + + spi_device_config_t device_conf = { + .cs_io_num = NULL_SPI_CS_PIN, + .mode = 0, + .clock_speed_hz = 20 * 1000 * 1000, + }; + + bus_handle = spi_bus_create(SPI2_HOST, &bus_conf); + TEST_ASSERT(bus_handle != NULL); + spi_bus_device_handle_t device_handle = NULL; + device_handle = spi_bus_device_create(bus_handle, &device_conf); + TEST_ASSERT(device_handle != NULL); + + printf("************byte transfer test***************\n"); + for (uint8_t i = 0; i < 200; i++) { + uint8_t in = 0; + TEST_ASSERT(ESP_OK == spi_bus_transfer_byte(device_handle, i, &in)); + TEST_ASSERT_EQUAL_UINT8(i, in); + printf("in=%u\n", in); + } + + vTaskDelay(2); + printf("************bytes transfer test***************\n"); + uint8_t data[200] = {0}; + uint8_t data_in[200] = {0}; + for (uint8_t i = 0; i < 200; i++) { + data[i] = i; + } + TEST_ASSERT(ESP_OK == spi_bus_transfer_bytes(device_handle, data, data_in, 200)); + for (uint8_t i = 0; i < 200; i++) { + printf("%u ", data_in[i]); + } + printf("\n"); + + vTaskDelay(2); + printf("************reg16 transfer test***************\n"); + for (uint16_t i = (0xffff - 200); i < 0xffff; i++) { + uint16_t in = 0; + TEST_ASSERT(ESP_OK == spi_bus_transfer_reg16(device_handle, i, &in)); + TEST_ASSERT_EQUAL_UINT16(i, in); + printf("in=%u\n", in); + } + + vTaskDelay(2); + printf("************reg32 transfer test***************\n"); + for (uint32_t i = (0xffffffff - 200); i < 0xffffffff; i++) { + uint32_t in = 0; + TEST_ASSERT(ESP_OK == spi_bus_transfer_reg32(device_handle, i, &in)); + TEST_ASSERT_EQUAL_UINT32(i, in); + printf("in=%x\n", in); + } + + TEST_ASSERT(ESP_OK == spi_bus_device_delete(&device_handle)); + TEST_ASSERT(device_handle == NULL); + TEST_ASSERT(ESP_OK == spi_bus_delete(&bus_handle)); + TEST_ASSERT(bus_handle == NULL); +} + +TEST_CASE("spi bus init-deinit test", "[bus]spi_bus]") +{ + spi_bus_init_deinit_test(); +} + +TEST_CASE("spi bus transfer test", "[bus][spi_bus]") +{ + spi_bus_transfer_test(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/button/CMakeLists.txt new file mode 100644 index 00000000..3658af10 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "button_adc.c" "button_gpio.c" "iot_button.c" + INCLUDE_DIRS include + PRIV_REQUIRES esp_adc_cal) diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/Kconfig b/firmware/esp-idf/wumei-smart-firmware/components/button/Kconfig new file mode 100644 index 00000000..5949357d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/Kconfig @@ -0,0 +1,46 @@ +menu "IoT Button" + + config BUTTON_PERIOD_TIME_MS + int "BUTTON PERIOD TIME (MS)" + range 2 20 + default 5 + help + "Button scan interval" + + config BUTTON_DEBOUNCE_TICKS + int "BUTTON DEBOUNCE TICKS" + range 1 8 + default 2 + + config BUTTON_SHORT_PRESS_TIME_MS + int "BUTTON SHORT PRESS TIME (MS)" + range 50 800 + default 180 + + config BUTTON_LONG_PRESS_TIME_MS + int "BUTTON LONG PRESS TIME (MS)" + range 500 5000 + default 1500 + + config ADC_BUTTON_MAX_CHANNEL + int "ADC BUTTON MAX CHANNEL" + range 1 5 + default 3 + help + "Maximum number of channels for ADC buttons" + + config ADC_BUTTON_MAX_BUTTON_PER_CHANNEL + int "ADC BUTTON MAX BUTTON PER CHANNEL" + range 1 10 + default 8 + help + "Maximum number of buttons per channel" + + config ADC_BUTTON_SAMPLE_TIMES + int "ADC BUTTON SAMPLE TIMES" + range 1 4 + default 1 + help + "Number of samples per scan" + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/button_adc.c b/firmware/esp-idf/wumei-smart-firmware/components/button/button_adc.c new file mode 100644 index 00000000..5c63d3fe --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/button_adc.c @@ -0,0 +1,208 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/adc.h" +#include "esp_adc_cal.h" +#include "button_adc.h" +#include "esp_timer.h" + +static const char *TAG = "adc button"; + +#define ADC_BTN_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +#define DEFAULT_VREF 1100 +#define NO_OF_SAMPLES CONFIG_ADC_BUTTON_SAMPLE_TIMES //Multisampling + +#if CONFIG_IDF_TARGET_ESP32 +#define ADC_BUTTON_WIDTH ADC_WIDTH_BIT_12 +#elif CONFIG_IDF_TARGET_ESP32S2 +#define ADC_BUTTON_WIDTH ADC_WIDTH_BIT_13 +#endif +#define ADC_BUTTON_ATTEN ADC_ATTEN_DB_11 +#define ADC_BUTTON_ADC_UNIT ADC_UNIT_1 +#define ADC_BUTTON_MAX_CHANNEL CONFIG_ADC_BUTTON_MAX_CHANNEL +#define ADC_BUTTON_MAX_BUTTON CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL + +typedef struct { + uint16_t min; + uint16_t max; +} button_data_t; + +typedef struct { + adc1_channel_t channel; + uint8_t is_init; + button_data_t btns[ADC_BUTTON_MAX_BUTTON]; /* all button on the channel */ + uint64_t last_time; /* the last time of adc sample */ +} btn_adc_channel_t; + +typedef struct { + bool is_configured; + esp_adc_cal_characteristics_t adc_chars; + btn_adc_channel_t ch[ADC_BUTTON_MAX_CHANNEL]; + uint8_t ch_num; +} adc_button_t; + +static adc_button_t g_button = {0}; + +static int find_unused_channel(void) +{ + for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) { + if (0 == g_button.ch[i].is_init) { + return i; + } + } + return -1; +} + +static int find_channel(adc1_channel_t channel) +{ + for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) { + if (channel == g_button.ch[i].channel) { + return i; + } + } + return -1; +} + +esp_err_t button_adc_init(const button_adc_config_t *config) +{ + ADC_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG); + ADC_BTN_CHECK(config->adc_channel < ADC1_CHANNEL_MAX, "channel out of range", ESP_ERR_NOT_SUPPORTED); + ADC_BTN_CHECK(config->button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_NOT_SUPPORTED); + ADC_BTN_CHECK(config->max > 0, "key max voltage invalid", ESP_ERR_INVALID_ARG); + + int ch_index = find_channel(config->adc_channel); + if (ch_index >= 0) { /**< the channel has been initialized */ + ADC_BTN_CHECK(g_button.ch[ch_index].btns[config->button_index].max == 0, "The button_index has been used", ESP_ERR_INVALID_STATE); + } else { /**< this is a new channel */ + int unused_ch_index = find_unused_channel(); + ADC_BTN_CHECK(unused_ch_index >= 0, "exceed max channel number, can't create a new channel", ESP_ERR_INVALID_STATE); + ch_index = unused_ch_index; + } + + /** initialize adc */ + if (0 == g_button.is_configured) { + //Configure ADC + adc1_config_width(ADC_BUTTON_WIDTH); + //Characterize ADC + esp_adc_cal_value_t val_type = esp_adc_cal_characterize(ADC_BUTTON_ADC_UNIT, ADC_BUTTON_ATTEN, ADC_BUTTON_WIDTH, DEFAULT_VREF, &g_button.adc_chars); + if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { + ESP_LOGI(TAG, "Characterized using Two Point Value"); + } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { + ESP_LOGI(TAG, "Characterized using eFuse Vref"); + } else { + ESP_LOGI(TAG, "Characterized using Default Vref"); + } + g_button.is_configured = 1; + } + + /** initialize adc channel */ + if (0 == g_button.ch[ch_index].is_init) { + adc1_config_channel_atten(config->adc_channel, ADC_BUTTON_ATTEN); + g_button.ch[ch_index].channel = config->adc_channel; + g_button.ch[ch_index].is_init = 1; + g_button.ch[ch_index].last_time = 0; + } + g_button.ch[ch_index].btns[config->button_index].max = config->max; + g_button.ch[ch_index].btns[config->button_index].min = config->min; + g_button.ch_num++; + + return ESP_OK; +} + +esp_err_t button_adc_deinit(adc1_channel_t channel, int button_index) +{ + ADC_BTN_CHECK(channel < ADC1_CHANNEL_MAX, "channel out of range", ESP_ERR_INVALID_ARG); + ADC_BTN_CHECK(button_index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", ESP_ERR_INVALID_ARG); + + int ch_index = find_channel(channel); + ADC_BTN_CHECK(ch_index >= 0, "can't find the channel", ESP_ERR_INVALID_ARG); + + g_button.ch[ch_index].btns[button_index].max = 0; + g_button.ch[ch_index].btns[button_index].min = 0; + + /** check button usage on the channel*/ + uint8_t unused_button = 0; + for (size_t i = 0; i < ADC_BUTTON_MAX_BUTTON; i++) { + if (0 == g_button.ch[ch_index].btns[i].max) { + unused_button++; + } + } + if (unused_button == ADC_BUTTON_MAX_BUTTON && g_button.ch[ch_index].is_init) { /**< if all button is unused, deinit the channel */ + /* TODO: to deinit the channel */ + g_button.ch[ch_index].is_init = 0; + g_button.ch[ch_index].channel = ADC1_CHANNEL_MAX; + ESP_LOGD(TAG, "all button is unused on channel%d, deinit the channel", g_button.ch[ch_index].channel); + } + + /** check channel usage on the adc*/ + uint8_t unused_ch = 0; + for (size_t i = 0; i < ADC_BUTTON_MAX_CHANNEL; i++) { + if (0 == g_button.ch[i].is_init) { + unused_ch++; + } + } + if (unused_ch == ADC_BUTTON_MAX_CHANNEL && g_button.is_configured) { /**< if all channel is unused, deinit the adc */ + /* TODO: to deinit the peripheral adc */ + g_button.is_configured = false; + memset(&g_button, 0, sizeof(adc_button_t)); + ESP_LOGD(TAG, "all channel is unused, , deinit adc"); + } + + return ESP_OK; +} + +static uint32_t get_adc_volatge(adc1_channel_t channel) +{ + uint32_t adc_reading = 0; + //Multisampling + for (int i = 0; i < NO_OF_SAMPLES; i++) { + adc_reading += adc1_get_raw(channel); + } + adc_reading /= NO_OF_SAMPLES; + //Convert adc_reading to voltage in mV + uint32_t voltage = esp_adc_cal_raw_to_voltage(adc_reading, &g_button.adc_chars); + ESP_LOGV(TAG, "Raw: %d\tVoltage: %dmV", adc_reading, voltage); + return voltage; +} + +uint8_t button_adc_get_key_level(void *button_index) +{ + static uint16_t vol = 0; + uint32_t ch = ADC_BUTTON_SPLIT_CHANNEL(button_index); + uint32_t index = ADC_BUTTON_SPLIT_INDEX(button_index); + ADC_BTN_CHECK(ch < ADC1_CHANNEL_MAX, "channel out of range", 0); + ADC_BTN_CHECK(index < ADC_BUTTON_MAX_BUTTON, "button_index out of range", 0); + int ch_index = find_channel(ch); + ADC_BTN_CHECK(ch_index >= 0, "The button_index is not init", 0); + + /** It starts only when the elapsed time is more than 1ms */ + if ((esp_timer_get_time() - g_button.ch[ch_index].last_time) > 1000) { + vol = get_adc_volatge(ch); + g_button.ch[ch_index].last_time = esp_timer_get_time(); + } + + if (vol <= g_button.ch[ch_index].btns[index].max && + vol > g_button.ch[ch_index].btns[index].min) { + return 1; + } + return 0; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/button_gpio.c b/firmware/esp-idf/wumei-smart-firmware/components/button/button_gpio.c new file mode 100644 index 00000000..721c1665 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/button_gpio.c @@ -0,0 +1,65 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_log.h" +#include "driver/gpio.h" +#include "button_gpio.h" + +static const char *TAG = "gpio button"; + +#define GPIO_BTN_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +esp_err_t button_gpio_init(const button_gpio_config_t *config) +{ + GPIO_BTN_CHECK(NULL != config, "Pointer of config is invalid", ESP_ERR_INVALID_ARG); + + gpio_config_t gpio_conf; + gpio_conf.intr_type = GPIO_INTR_DISABLE; + gpio_conf.mode = GPIO_MODE_INPUT; + gpio_conf.pin_bit_mask = (1ULL << config->gpio_num); + if (config->active_level) { + gpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE; + gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE; + } else { + gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE; + } + gpio_config(&gpio_conf); + + return ESP_OK; +} + +esp_err_t button_gpio_deinit(int gpio_num) +{ + /** both disable pullup and pulldown */ + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + .mode = GPIO_MODE_INPUT, + .pin_bit_mask = (1ULL << gpio_num), + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .pull_up_en = GPIO_PULLUP_DISABLE, + }; + gpio_config(&gpio_conf); + return ESP_OK; +} + +uint8_t button_gpio_get_key_level(void *gpio_num) +{ + return (uint8_t)gpio_get_level((uint32_t)gpio_num); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/button/component.mk new file mode 100644 index 00000000..a36d02e0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/component.mk @@ -0,0 +1,8 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + + +COMPONENT_ADD_INCLUDEDIRS := ./include +COMPONENT_SRCDIRS := . diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/include/button_adc.h b/firmware/esp-idf/wumei-smart-firmware/components/button/include/button_adc.h new file mode 100644 index 00000000..f1b00410 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/include/button_adc.h @@ -0,0 +1,79 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IOT_BUTTON_ADC_H_ +#define _IOT_BUTTON_ADC_H_ + +#include "driver/gpio.h" +#include "driver/adc.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ADC_BUTTON_COMBINE(channel, index) ((channel)<<8 | (index)) +#define ADC_BUTTON_SPLIT_INDEX(data) ((uint32_t)(data)&0xff) +#define ADC_BUTTON_SPLIT_CHANNEL(data) (((uint32_t)(data) >> 8) & 0xff) + +/** + * @brief adc button configuration + * + */ +typedef struct { + adc1_channel_t adc_channel; /**< Channel of ADC */ + uint8_t button_index; /**< button index on the channel */ + uint16_t min; /**< min voltage in mv corresponding to the button */ + uint16_t max; /**< max voltage in mv corresponding to the button */ +} button_adc_config_t; + +/** + * @brief Initialize gpio button + * + * @param config pointer of configuration struct + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is NULL. + * - ESP_ERR_NOT_SUPPORTED Arguments out of range. + * - ESP_ERR_INVALID_STATE State is error. + */ +esp_err_t button_adc_init(const button_adc_config_t *config); + +/** + * @brief Deinitialize gpio button + * + * @param channel ADC channel + * @param button_index Button index on the channel + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + */ +esp_err_t button_adc_deinit(adc1_channel_t channel, int button_index); + +/** + * @brief Get the adc button level + * + * @param button_index It is compressed by ADC channel and button index, use the macro ADC_BUTTON_COMBINE to generate. It will be treated as a uint32_t variable. + * + * @return + * - 0 Not pressed + * - 1 Pressed + */ +uint8_t button_adc_get_key_level(void *button_index); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/include/button_gpio.h b/firmware/esp-idf/wumei-smart-firmware/components/button/include/button_gpio.h new file mode 100644 index 00000000..78bb9856 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/include/button_gpio.h @@ -0,0 +1,65 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IOT_BUTTON_GPIO_H_ +#define _IOT_BUTTON_GPIO_H_ + +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief gpio button configuration + * + */ +typedef struct { + int32_t gpio_num; + uint8_t active_level; +} button_gpio_config_t; + +/** + * @brief Initialize gpio button + * + * @param config pointer of configuration struct + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is NULL. + */ +esp_err_t button_gpio_init(const button_gpio_config_t *config); + +/** + * @brief Deinitialize gpio button + * + * @param gpio_num gpio number of button + * + * @return Always return ESP_OK + */ +esp_err_t button_gpio_deinit(int gpio_num); + +/** + * @brief Get current level on button gpio + * + * @param gpio_num gpio number of button, it will be treated as a uint32_t variable. + * + * @return Level on gpio + */ +uint8_t button_gpio_get_key_level(void *gpio_num); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/include/iot_button.h b/firmware/esp-idf/wumei-smart-firmware/components/button/include/iot_button.h new file mode 100644 index 00000000..7c68e4a0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/include/iot_button.h @@ -0,0 +1,131 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#ifndef _IOT_BUTTON_H_ +#define _IOT_BUTTON_H_ + +#include "button_adc.h" +#include "button_gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (* button_cb_t)(void *); +typedef void *button_handle_t; + +/** + * @brief Button events + * + */ +typedef enum { + BUTTON_PRESS_DOWN = 0, + BUTTON_PRESS_UP, + BUTTON_PRESS_REPEAT, + BUTTON_SINGLE_CLICK, + BUTTON_DOUBLE_CLICK, + BUTTON_LONG_PRESS_START, + BUTTON_LONG_PRESS_HOLD, + BUTTON_EVENT_MAX, + BUTTON_NONE_PRESS, +} button_event_t; + +/** + * @brief Supported button type + * + */ +typedef enum { + BUTTON_TYPE_GPIO, + BUTTON_TYPE_ADC, +} button_type_t; + +/** + * @brief Button configuration + * + */ +typedef struct { + button_type_t type; /**< button type, The corresponding button configuration must be filled */ + union { + button_gpio_config_t gpio_button_config; /**< gpio button configuration */ + button_adc_config_t adc_button_config; /**< adc button configuration */ + }; /**< button configuration */ +} button_config_t; + +/** + * @brief Create a button + * + * @param config pointer of button configuration, must corresponding the button type + * + * @return A handle to the created button, or NULL in case of error. + */ +button_handle_t iot_button_create(const button_config_t *config); + +/** + * @brief Delete a button + * + * @param btn_handle A button handle to delete + * + * @return + * - ESP_OK Success + * - ESP_FAIL Failure + */ +esp_err_t iot_button_delete(button_handle_t btn_handle); + +/** + * @brief Register the button event callback function. + * + * @param btn_handle A button handle to register + * @param event Button event + * @param cb Callback function. + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + */ +esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb); + +/** + * @brief Unregister the button event callback function. + * + * @param btn_handle A button handle to unregister + * @param event Button event + * + * @return + * - ESP_OK on success + * - ESP_ERR_INVALID_ARG Arguments is invalid. + */ +esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event); + +/** + * @brief Get button event + * + * @param btn_handle Button handle + * + * @return Current button event. See button_event_t + */ +button_event_t iot_button_get_event(button_handle_t btn_handle); + +/** + * @brief Get button repeat times + * + * @param btn_handle Button handle + * + * @return button pressed times. For example, double-click return 2, triple-click return 3, etc. + */ +uint8_t iot_button_get_repeat(button_handle_t btn_handle); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/iot_button.c b/firmware/esp-idf/wumei-smart-firmware/components/button/iot_button.c new file mode 100644 index 00000000..a5b3526f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/iot_button.c @@ -0,0 +1,306 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "iot_button.h" +#include "esp_timer.h" +#include "sdkconfig.h" + +static const char *TAG = "button"; + +#define BTN_CHECK(a, str, ret_val) \ + if (!(a)) \ + { \ + ESP_LOGE(TAG, "%s(%d): %s", __FUNCTION__, __LINE__, str); \ + return (ret_val); \ + } + +typedef struct Button { + uint16_t ticks; + uint8_t repeat; + button_event_t event; + uint8_t state: 3; + uint8_t debounce_cnt: 3; + uint8_t active_level: 1; + uint8_t button_level: 1; + uint8_t (*hal_button_Level)(void *usr_data); + void *usr_data; + button_type_t type; + button_cb_t cb[BUTTON_EVENT_MAX]; + struct Button *next; +} button_dev_t; + +//button handle list head. +static button_dev_t *g_head_handle = NULL; +static esp_timer_handle_t g_button_timer_handle; +static bool g_is_timer_running = false; + +#define TICKS_INTERVAL CONFIG_BUTTON_PERIOD_TIME_MS +#define DEBOUNCE_TICKS CONFIG_BUTTON_DEBOUNCE_TICKS //MAX 8 +#define SHORT_TICKS (CONFIG_BUTTON_SHORT_PRESS_TIME_MS /TICKS_INTERVAL) +#define LONG_TICKS (CONFIG_BUTTON_LONG_PRESS_TIME_MS /TICKS_INTERVAL) + +#define CALL_EVENT_CB(ev) if(btn->cb[ev])btn->cb[ev](btn) + +/** + * @brief Button driver core function, driver state machine. + */ +static void button_handler(button_dev_t *btn) +{ + uint8_t read_gpio_level = btn->hal_button_Level(btn->usr_data); + + /** ticks counter working.. */ + if ((btn->state) > 0) { + btn->ticks++; + } + + /**< button debounce handle */ + if (read_gpio_level != btn->button_level) { + if (++(btn->debounce_cnt) >= DEBOUNCE_TICKS) { + btn->button_level = read_gpio_level; + btn->debounce_cnt = 0; + } + } else { + btn->debounce_cnt = 0; + } + + /** State machine */ + switch (btn->state) { + case 0: + if (btn->button_level == btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_DOWN; + CALL_EVENT_CB(BUTTON_PRESS_DOWN); + btn->ticks = 0; + btn->repeat = 1; + btn->state = 1; + } else { + btn->event = (uint8_t)BUTTON_NONE_PRESS; + } + break; + case 1: + if (btn->button_level != btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_UP; + CALL_EVENT_CB(BUTTON_PRESS_UP); + btn->ticks = 0; + btn->state = 2; + + } else if (btn->ticks > LONG_TICKS) { + btn->event = (uint8_t)BUTTON_LONG_PRESS_START; + CALL_EVENT_CB(BUTTON_LONG_PRESS_START); + btn->state = 5; + } + break; + case 2: + if (btn->button_level == btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_DOWN; + CALL_EVENT_CB(BUTTON_PRESS_DOWN); + btn->repeat++; + // CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit + btn->ticks = 0; + btn->state = 3; + } else if (btn->ticks > SHORT_TICKS) { + if (btn->repeat == 1) { + btn->event = (uint8_t)BUTTON_SINGLE_CLICK; + CALL_EVENT_CB(BUTTON_SINGLE_CLICK); + } + // else if (btn->repeat == 2) { + // btn->event = (uint8_t)BUTTON_DOUBLE_CLICK; + // CALL_EVENT_CB(BUTTON_DOUBLE_CLICK); // repeat hit + // ESP_LOGE(TAG, "003:double click"); + // } + else { + CALL_EVENT_CB(BUTTON_PRESS_REPEAT); // repeat hit + } + btn->state = 0; + } + break; + case 3: + if (btn->button_level != btn->active_level) { + btn->event = (uint8_t)BUTTON_PRESS_UP; + CALL_EVENT_CB(BUTTON_PRESS_UP); + if (btn->ticks < SHORT_TICKS) { + btn->ticks = 0; + btn->state = 2; //repeat press + } else { + btn->state = 0; + } + } + break; + case 5: + if (btn->button_level == btn->active_level) { + //continue hold trigger + btn->event = (uint8_t)BUTTON_LONG_PRESS_HOLD; + CALL_EVENT_CB(BUTTON_LONG_PRESS_HOLD); + } else { //releasd + btn->event = (uint8_t)BUTTON_PRESS_UP; + CALL_EVENT_CB(BUTTON_PRESS_UP); + btn->state = 0; //reset + } + break; + } +} + +static void button_cb(void *args) +{ + button_dev_t *target; + for (target = g_head_handle; target; target = target->next) { + button_handler(target); + } +} + +static button_dev_t *button_create_com(uint8_t active_level, uint8_t (*hal_get_key_state)(void *usr_data), void *usr_data) +{ + BTN_CHECK(NULL != hal_get_key_state, "Function pointer is invalid", NULL); + + button_dev_t *btn = (button_dev_t *) calloc(1, sizeof(button_dev_t)); + BTN_CHECK(NULL != btn, "Button memory alloc failed", NULL); + btn->usr_data = usr_data; + btn->event = BUTTON_NONE_PRESS; + btn->active_level = active_level; + btn->hal_button_Level = hal_get_key_state; + btn->button_level = !active_level; + + /** Add handle to list */ + btn->next = g_head_handle; + g_head_handle = btn; + + if (false == g_is_timer_running) { + esp_timer_create_args_t button_timer; + button_timer.arg = NULL; + button_timer.callback = button_cb; + button_timer.dispatch_method = ESP_TIMER_TASK; + button_timer.name = "button_timer"; + esp_timer_create(&button_timer, &g_button_timer_handle); + esp_timer_start_periodic(g_button_timer_handle, TICKS_INTERVAL * 1000U); + g_is_timer_running = true; + } + + return btn; +} + +static esp_err_t button_delete_com(button_dev_t *btn) +{ + BTN_CHECK(NULL != btn, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + + button_dev_t **curr; + for (curr = &g_head_handle; *curr; ) { + button_dev_t *entry = *curr; + if (entry == btn) { + *curr = entry->next; + free(entry); + } else { + curr = &entry->next; + } + } + + /* count button number */ + uint16_t number = 0; + button_dev_t *target = g_head_handle; + while (target) { + target = target->next; + number++; + } + ESP_LOGD(TAG, "remain btn number=%d", number); + + if (0 == number && g_is_timer_running) { /**< if all button is deleted, stop the timer */ + esp_timer_stop(g_button_timer_handle); + esp_timer_delete(g_button_timer_handle); + g_is_timer_running = false; + } + return ESP_OK; +} + +button_handle_t iot_button_create(const button_config_t *config) +{ + esp_err_t ret = ESP_OK; + button_dev_t *btn = NULL; + switch (config->type) { + case BUTTON_TYPE_GPIO: { + const button_gpio_config_t *cfg = &(config->gpio_button_config); + ret = button_gpio_init(cfg); + BTN_CHECK(ESP_OK == ret, "gpio button init failed", NULL); + btn = button_create_com(cfg->active_level, button_gpio_get_key_level, (void *)cfg->gpio_num); + } break; + case BUTTON_TYPE_ADC: { + const button_adc_config_t *cfg = &(config->adc_button_config); + ret = button_adc_init(cfg); + BTN_CHECK(ESP_OK == ret, "adc button init failed", NULL); + btn = button_create_com(1, button_adc_get_key_level, (void *)ADC_BUTTON_COMBINE(cfg->adc_channel, cfg->button_index)); + } break; + + default: + ESP_LOGE(TAG, "Unsupported button type"); + break; + } + BTN_CHECK(NULL != btn, "button create failed", NULL); + btn->type = config->type; + return (button_handle_t)btn; +} + +esp_err_t iot_button_delete(button_handle_t btn_handle) +{ + esp_err_t ret = ESP_OK; + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *)btn_handle; + switch (btn->type) { + case BUTTON_TYPE_GPIO: + ret = button_gpio_deinit((int)(btn->usr_data)); + break; + case BUTTON_TYPE_ADC: + ret = button_adc_deinit(ADC_BUTTON_SPLIT_CHANNEL(btn->usr_data), ADC_BUTTON_SPLIT_INDEX(btn->usr_data)); + break; + default: + break; + } + BTN_CHECK(ESP_OK == ret, "button deinit failed", ESP_FAIL); + button_delete_com(btn); + return ESP_OK; +} + +esp_err_t iot_button_register_cb(button_handle_t btn_handle, button_event_t event, button_cb_t cb) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + btn->cb[event] = cb; + return ESP_OK; +} + +esp_err_t iot_button_unregister_cb(button_handle_t btn_handle, button_event_t event) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", ESP_ERR_INVALID_ARG); + BTN_CHECK(event < BUTTON_EVENT_MAX, "event is invalid", ESP_ERR_INVALID_ARG); + button_dev_t *btn = (button_dev_t *) btn_handle; + btn->cb[event] = NULL; + return ESP_OK; +} + +button_event_t iot_button_get_event(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", BUTTON_NONE_PRESS); + button_dev_t *btn = (button_dev_t *) btn_handle; + return btn->event; +} + +uint8_t iot_button_get_repeat(button_handle_t btn_handle) +{ + BTN_CHECK(NULL != btn_handle, "Pointer of handle is invalid", 0); + button_dev_t *btn = (button_dev_t *) btn_handle; + return btn->repeat; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/test/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/button/test/CMakeLists.txt new file mode 100644 index 00000000..3ab71c30 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/test/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRC_DIRS "." + PRIV_INCLUDE_DIRS "." + PRIV_REQUIRES unity test_utils button) diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/test/button_test.c b/firmware/esp-idf/wumei-smart-firmware/components/button/test/button_test.c new file mode 100644 index 00000000..bb81c385 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/test/button_test.c @@ -0,0 +1,234 @@ +// Copyright 2020 Espressif Systems (Shanghai) Co. Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "stdio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/timers.h" +#include "esp_log.h" +#include "unity.h" +#include "iot_button.h" + +static const char *TAG = "BUTTON TEST"; + +#define BUTTON_IO_NUM 0 +#define BUTTON_ACTIVE_LEVEL 0 +#define BUTTON_NUM 16 + +static button_handle_t g_btns[BUTTON_NUM] = {0}; + +static int get_btn_index(button_handle_t btn) +{ + for (size_t i = 0; i < BUTTON_NUM; i++) { + if (btn == g_btns[i]) { + return i; + } + } + return -1; +} + +static void button_press_down_cb(void *arg) +{ + TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_DOWN, iot_button_get_event(arg)); + ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_DOWN", get_btn_index((button_handle_t)arg)); +} + +static void button_press_up_cb(void *arg) +{ + TEST_ASSERT_EQUAL_HEX(BUTTON_PRESS_UP, iot_button_get_event(arg)); + ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_UP", get_btn_index((button_handle_t)arg)); +} + +static void button_press_repeat_cb(void *arg) +{ + ESP_LOGI(TAG, "BTN%d: BUTTON_PRESS_REPEAT[%d]", get_btn_index((button_handle_t)arg), iot_button_get_repeat((button_handle_t)arg)); +} + +static void button_single_click_cb(void *arg) +{ + TEST_ASSERT_EQUAL_HEX(BUTTON_SINGLE_CLICK, iot_button_get_event(arg)); + ESP_LOGI(TAG, "BTN%d: BUTTON_SINGLE_CLICK", get_btn_index((button_handle_t)arg)); +} + +static void button_double_click_cb(void *arg) +{ + TEST_ASSERT_EQUAL_HEX(BUTTON_DOUBLE_CLICK, iot_button_get_event(arg)); + ESP_LOGI(TAG, "BTN%d: BUTTON_DOUBLE_CLICK", get_btn_index((button_handle_t)arg)); +} + +static void button_long_press_start_cb(void *arg) +{ + TEST_ASSERT_EQUAL_HEX(BUTTON_LONG_PRESS_START, iot_button_get_event(arg)); + ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_START", get_btn_index((button_handle_t)arg)); +} + +static void button_long_press_hold_cb(void *arg) +{ + TEST_ASSERT_EQUAL_HEX(BUTTON_LONG_PRESS_HOLD, iot_button_get_event(arg)); + ESP_LOGI(TAG, "BTN%d: BUTTON_LONG_PRESS_HOLD", get_btn_index((button_handle_t)arg)); +} + +static void print_button_event(button_handle_t btn) +{ + button_event_t evt = iot_button_get_event(btn); + switch (evt) { + case BUTTON_PRESS_DOWN: + ESP_LOGI(TAG, "BUTTON_PRESS_DOWN"); + break; + case BUTTON_PRESS_UP: + ESP_LOGI(TAG, "BUTTON_PRESS_UP"); + break; + case BUTTON_PRESS_REPEAT: + ESP_LOGI(TAG, "BUTTON_PRESS_REPEAT"); + break; + case BUTTON_SINGLE_CLICK: + ESP_LOGI(TAG, "BUTTON_SINGLE_CLICK"); + break; + case BUTTON_DOUBLE_CLICK: + ESP_LOGI(TAG, "BUTTON_DOUBLE_CLICK"); + break; + case BUTTON_LONG_PRESS_START: + ESP_LOGI(TAG, "BUTTON_LONG_PRESS_START"); + break; + case BUTTON_LONG_PRESS_HOLD: + ESP_LOGI(TAG, "BUTTON_LONG_PRESS_HOLD"); + break; + + default: + break; + } +} + +TEST_CASE("gpio button test", "[button][iot]") +{ + button_config_t cfg = { + .type = BUTTON_TYPE_GPIO, + .gpio_button_config = { + .gpio_num = 0, + .active_level = 0, + }, + }; + g_btns[0] = iot_button_create(&cfg); + TEST_ASSERT_NOT_NULL(g_btns[0]); + iot_button_register_cb(g_btns[0], BUTTON_PRESS_DOWN, button_press_down_cb); + iot_button_register_cb(g_btns[0], BUTTON_PRESS_UP, button_press_up_cb); + iot_button_register_cb(g_btns[0], BUTTON_PRESS_REPEAT, button_press_repeat_cb); + iot_button_register_cb(g_btns[0], BUTTON_SINGLE_CLICK, button_single_click_cb); + iot_button_register_cb(g_btns[0], BUTTON_DOUBLE_CLICK, button_double_click_cb); + iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_START, button_long_press_start_cb); + iot_button_register_cb(g_btns[0], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb); + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + + iot_button_delete(g_btns[0]); +} + +TEST_CASE("adc button test", "[button][iot]") +{ + /** ESP32-LyraT-Mini board */ + const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410}; + button_config_t cfg = {0}; + cfg.type = BUTTON_TYPE_ADC; + for (size_t i = 0; i < 6; i++) { + cfg.adc_button_config.adc_channel = 3, + cfg.adc_button_config.button_index = i; + if (i == 0) { + cfg.adc_button_config.min = (0 + vol[i]) / 2; + } else { + cfg.adc_button_config.min = (vol[i - 1] + vol[i]) / 2; + } + + if (i == 5) { + cfg.adc_button_config.max = (vol[i] + 3000) / 2; + } else { + cfg.adc_button_config.max = (vol[i] + vol[i + 1]) / 2; + } + + g_btns[i] = iot_button_create(&cfg); + TEST_ASSERT_NOT_NULL(g_btns[i]); + iot_button_register_cb(g_btns[i], BUTTON_PRESS_DOWN, button_press_down_cb); + iot_button_register_cb(g_btns[i], BUTTON_PRESS_UP, button_press_up_cb); + iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT, button_press_repeat_cb); + iot_button_register_cb(g_btns[i], BUTTON_SINGLE_CLICK, button_single_click_cb); + iot_button_register_cb(g_btns[i], BUTTON_DOUBLE_CLICK, button_double_click_cb); + iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_START, button_long_press_start_cb); + iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb); + } + + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + for (size_t i = 0; i < 6; i++) { + iot_button_delete(g_btns[i]); + } +} + +TEST_CASE("adc gpio button test", "[button][iot]") +{ + button_config_t cfg = { + .type = BUTTON_TYPE_GPIO, + .gpio_button_config = { + .gpio_num = 0, + .active_level = 0, + }, + }; + g_btns[8] = iot_button_create(&cfg); + TEST_ASSERT_NOT_NULL(g_btns[8]); + iot_button_register_cb(g_btns[8], BUTTON_PRESS_DOWN, button_press_down_cb); + iot_button_register_cb(g_btns[8], BUTTON_PRESS_UP, button_press_up_cb); + iot_button_register_cb(g_btns[8], BUTTON_PRESS_REPEAT, button_press_repeat_cb); + iot_button_register_cb(g_btns[8], BUTTON_SINGLE_CLICK, button_single_click_cb); + iot_button_register_cb(g_btns[8], BUTTON_DOUBLE_CLICK, button_double_click_cb); + iot_button_register_cb(g_btns[8], BUTTON_LONG_PRESS_START, button_long_press_start_cb); + iot_button_register_cb(g_btns[8], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb); + + /** ESP32-LyraT-Mini board */ + const uint16_t vol[6] = {380, 820, 1180, 1570, 1980, 2410}; + // button_config_t cfg = {0}; + cfg.type = BUTTON_TYPE_ADC; + for (size_t i = 0; i < 6; i++) { + cfg.adc_button_config.adc_channel = 3, + cfg.adc_button_config.button_index = i; + if (i == 0) { + cfg.adc_button_config.min = (0 + vol[i]) / 2; + } else { + cfg.adc_button_config.min = (vol[i - 1] + vol[i]) / 2; + } + + if (i == 5) { + cfg.adc_button_config.max = (vol[i] + 3000) / 2; + } else { + cfg.adc_button_config.max = (vol[i] + vol[i + 1]) / 2; + } + + g_btns[i] = iot_button_create(&cfg); + TEST_ASSERT_NOT_NULL(g_btns[i]); + iot_button_register_cb(g_btns[i], BUTTON_PRESS_DOWN, button_press_down_cb); + iot_button_register_cb(g_btns[i], BUTTON_PRESS_UP, button_press_up_cb); + iot_button_register_cb(g_btns[i], BUTTON_PRESS_REPEAT, button_press_repeat_cb); + iot_button_register_cb(g_btns[i], BUTTON_SINGLE_CLICK, button_single_click_cb); + iot_button_register_cb(g_btns[i], BUTTON_DOUBLE_CLICK, button_double_click_cb); + iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_START, button_long_press_start_cb); + iot_button_register_cb(g_btns[i], BUTTON_LONG_PRESS_HOLD, button_long_press_hold_cb); + } + + while (1) { + vTaskDelay(pdMS_TO_TICKS(1000)); + } + for (size_t i = 0; i < 6; i++) { + iot_button_delete(g_btns[i]); + } +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/button/test/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/button/test/component.mk new file mode 100644 index 00000000..5dd172bd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/button/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/firmware/esp-idf/wumei-smart-firmware/components/cjson/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/cjson/CMakeLists.txt new file mode 100644 index 00000000..817807f1 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/cjson/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "cJSON_Utils.c" "cJSON.c") +set(COMPONENT_ADD_INCLUDEDIRS ". include") +register_component() diff --git a/firmware/esp-idf/wumei-smart-firmware/components/cjson/cJSON.c b/firmware/esp-idf/wumei-smart-firmware/components/cjson/cJSON.c new file mode 100644 index 00000000..3b37355f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/cjson/cJSON.c @@ -0,0 +1,3107 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* cJSON */ +/* JSON parser in C. */ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include + +#ifdef ENABLE_LOCALES +#include +#endif + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUC__ +#pragma GCC visibility pop +#endif + +#include "cJSON.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +/* define isnan and isinf for ANSI C, if in C99 or above, isnan and isinf has been defined in math.h */ +#ifndef isinf +#define isinf(d) (isnan((d - d)) && !isnan(d)) +#endif +#ifndef isnan +#define isnan(d) (d != d) +#endif + +#ifndef NAN +#ifdef _WIN32 +#define NAN sqrt(-1.0) +#else +#define NAN 0.0/0.0 +#endif +#endif + +typedef struct { + const unsigned char *json; + size_t position; +} error; +static error global_error = { NULL, 0 }; + +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void) +{ + return (const char*) (global_error.json + global_error.position); +} + +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item) +{ + if (!cJSON_IsString(item)) + { + return NULL; + } + + return item->valuestring; +} + +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item) +{ + if (!cJSON_IsNumber(item)) + { + return (double) NAN; + } + + return item->valuedouble; +} + +/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */ +#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 14) + #error cJSON.h and cJSON.c have different versions. Make sure that both have the same. +#endif + +CJSON_PUBLIC(const char*) cJSON_Version(void) +{ + static char version[15]; + sprintf(version, "%i.%i.%i", CJSON_VERSION_MAJOR, CJSON_VERSION_MINOR, CJSON_VERSION_PATCH); + + return version; +} + +/* Case insensitive string comparison, doesn't consider two NULL pointers equal though */ +static int case_insensitive_strcmp(const unsigned char *string1, const unsigned char *string2) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +typedef struct internal_hooks +{ + void *(CJSON_CDECL *allocate)(size_t size); + void (CJSON_CDECL *deallocate)(void *pointer); + void *(CJSON_CDECL *reallocate)(void *pointer, size_t size); +} internal_hooks; + +#if defined(_MSC_VER) +/* work around MSVC error C2322: '...' address of dllimport '...' is not static */ +static void * CJSON_CDECL internal_malloc(size_t size) +{ + return malloc(size); +} +static void CJSON_CDECL internal_free(void *pointer) +{ + free(pointer); +} +static void * CJSON_CDECL internal_realloc(void *pointer, size_t size) +{ + return realloc(pointer, size); +} +#else +#define internal_malloc malloc +#define internal_free free +#define internal_realloc realloc +#endif + +/* strlen of character literals resolved at compile time */ +#define static_strlen(string_literal) (sizeof(string_literal) - sizeof("")) + +static internal_hooks global_hooks = { internal_malloc, internal_free, internal_realloc }; + +static unsigned char* cJSON_strdup(const unsigned char* string, const internal_hooks * const hooks) +{ + size_t length = 0; + unsigned char *copy = NULL; + + if (string == NULL) + { + return NULL; + } + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*)hooks->allocate(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks) +{ + if (hooks == NULL) + { + /* Reset hooks */ + global_hooks.allocate = malloc; + global_hooks.deallocate = free; + global_hooks.reallocate = realloc; + return; + } + + global_hooks.allocate = malloc; + if (hooks->malloc_fn != NULL) + { + global_hooks.allocate = hooks->malloc_fn; + } + + global_hooks.deallocate = free; + if (hooks->free_fn != NULL) + { + global_hooks.deallocate = hooks->free_fn; + } + + /* use realloc only if both free and malloc are used */ + global_hooks.reallocate = NULL; + if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free)) + { + global_hooks.reallocate = realloc; + } +} + +/* Internal constructor. */ +static cJSON *cJSON_New_Item(const internal_hooks * const hooks) +{ + cJSON* node = (cJSON*)hooks->allocate(sizeof(cJSON)); + if (node) + { + memset(node, '\0', sizeof(cJSON)); + } + + return node; +} + +/* Delete a cJSON structure. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item) +{ + cJSON *next = NULL; + while (item != NULL) + { + next = item->next; + if (!(item->type & cJSON_IsReference) && (item->child != NULL)) + { + cJSON_Delete(item->child); + } + if (!(item->type & cJSON_IsReference) && (item->valuestring != NULL)) + { + global_hooks.deallocate(item->valuestring); + } + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + global_hooks.deallocate(item->string); + } + global_hooks.deallocate(item); + item = next; + } +} + +/* get the decimal point character of the current locale */ +static unsigned char get_decimal_point(void) +{ +#ifdef ENABLE_LOCALES + struct lconv *lconv = localeconv(); + return (unsigned char) lconv->decimal_point[0]; +#else + return '.'; +#endif +} + +typedef struct +{ + const unsigned char *content; + size_t length; + size_t offset; + size_t depth; /* How deeply nested (in arrays/objects) is the input at the current offset. */ + internal_hooks hooks; +} parse_buffer; + +/* check if the given size is left to read in a given parse buffer (starting with 1) */ +#define can_read(buffer, size) ((buffer != NULL) && (((buffer)->offset + size) <= (buffer)->length)) +/* check if the buffer can be accessed at the given index (starting with 0) */ +#define can_access_at_index(buffer, index) ((buffer != NULL) && (((buffer)->offset + index) < (buffer)->length)) +#define cannot_access_at_index(buffer, index) (!can_access_at_index(buffer, index)) +/* get a pointer to the buffer at the position */ +#define buffer_at_offset(buffer) ((buffer)->content + (buffer)->offset) + +/* Parse the input text to generate a number, and populate the result into item. */ +static cJSON_bool parse_number(cJSON * const item, parse_buffer * const input_buffer) +{ + double number = 0; + unsigned char *after_end = NULL; + unsigned char number_c_string[64]; + unsigned char decimal_point = get_decimal_point(); + size_t i = 0; + + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; + } + + /* copy the number into a temporary buffer and replace '.' with the decimal point + * of the current locale (for strtod) + * This also takes care of '\0' not necessarily being available for marking the end of the input */ + for (i = 0; (i < (sizeof(number_c_string) - 1)) && can_access_at_index(input_buffer, i); i++) + { + switch (buffer_at_offset(input_buffer)[i]) + { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + case '-': + case 'e': + case 'E': + number_c_string[i] = buffer_at_offset(input_buffer)[i]; + break; + + case '.': + number_c_string[i] = decimal_point; + break; + + default: + goto loop_end; + } + } +loop_end: + number_c_string[i] = '\0'; + + number = strtod((const char*)number_c_string, (char**)&after_end); + if (number_c_string == after_end) + { + return false; /* parse_error */ + } + + item->valuedouble = number; + + /* use saturation in case of overflow */ + if (number >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)number; + } + + item->type = cJSON_Number; + + input_buffer->offset += (size_t)(after_end - number_c_string); + return true; +} + +/* don't ask me, but the original cJSON_SetNumberValue returns an integer or double */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number) +{ + if (number >= INT_MAX) + { + object->valueint = INT_MAX; + } + else if (number <= (double)INT_MIN) + { + object->valueint = INT_MIN; + } + else + { + object->valueint = (int)number; + } + + return object->valuedouble = number; +} + +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) +{ + char *copy = NULL; + /* if object's type is not cJSON_String or is cJSON_IsReference, it should not set valuestring */ + if (!(object->type & cJSON_String) || (object->type & cJSON_IsReference)) + { + return NULL; + } + if (strlen(valuestring) <= strlen(object->valuestring)) + { + strcpy(object->valuestring, valuestring); + return object->valuestring; + } + copy = (char*) cJSON_strdup((const unsigned char*)valuestring, &global_hooks); + if (copy == NULL) + { + return NULL; + } + if (object->valuestring != NULL) + { + cJSON_free(object->valuestring); + } + object->valuestring = copy; + + return copy; +} + +typedef struct +{ + unsigned char *buffer; + size_t length; + size_t offset; + size_t depth; /* current nesting depth (for formatted printing) */ + cJSON_bool noalloc; + cJSON_bool format; /* is this print a formatted print */ + internal_hooks hooks; +} printbuffer; + +/* realloc printbuffer if necessary to have at least "needed" bytes more */ +static unsigned char* ensure(printbuffer * const p, size_t needed) +{ + unsigned char *newbuffer = NULL; + size_t newsize = 0; + + if ((p == NULL) || (p->buffer == NULL)) + { + return NULL; + } + + if ((p->length > 0) && (p->offset >= p->length)) + { + /* make sure that offset is valid */ + return NULL; + } + + if (needed > INT_MAX) + { + /* sizes bigger than INT_MAX are currently not supported */ + return NULL; + } + + needed += p->offset + 1; + if (needed <= p->length) + { + return p->buffer + p->offset; + } + + if (p->noalloc) { + return NULL; + } + + /* calculate new buffer size */ + if (needed > (INT_MAX / 2)) + { + /* overflow of int, use INT_MAX if possible */ + if (needed <= INT_MAX) + { + newsize = INT_MAX; + } + else + { + return NULL; + } + } + else + { + newsize = needed * 2; + } + + if (p->hooks.reallocate != NULL) + { + /* reallocate with realloc if available */ + newbuffer = (unsigned char*)p->hooks.reallocate(p->buffer, newsize); + if (newbuffer == NULL) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + } + else + { + /* otherwise reallocate manually */ + newbuffer = (unsigned char*)p->hooks.allocate(newsize); + if (!newbuffer) + { + p->hooks.deallocate(p->buffer); + p->length = 0; + p->buffer = NULL; + + return NULL; + } + + memcpy(newbuffer, p->buffer, p->offset + 1); + p->hooks.deallocate(p->buffer); + } + p->length = newsize; + p->buffer = newbuffer; + + return newbuffer + p->offset; +} + +/* calculate the new length of the string in a printbuffer and update the offset */ +static void update_offset(printbuffer * const buffer) +{ + const unsigned char *buffer_pointer = NULL; + if ((buffer == NULL) || (buffer->buffer == NULL)) + { + return; + } + buffer_pointer = buffer->buffer + buffer->offset; + + buffer->offset += strlen((const char*)buffer_pointer); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + +/* Render the number nicely from the given item into a string. */ +static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + double d = item->valuedouble; + int length = 0; + size_t i = 0; + unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ + unsigned char decimal_point = get_decimal_point(); + double test = 0.0; + + if (output_buffer == NULL) + { + return false; + } + + /* This checks for NaN and Infinity */ + if (isnan(d) || isinf(d)) + { + length = sprintf((char*)number_buffer, "null"); + } + else + { + /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ + length = sprintf((char*)number_buffer, "%1.15g", d); + + /* Check whether the original double can be recovered */ + if ((sscanf((char*)number_buffer, "%lg", &test) != 1) || !compare_double((double)test, d)) + { + /* If not, print with 17 decimal places of precision */ + length = sprintf((char*)number_buffer, "%1.17g", d); + } + } + + /* sprintf failed or buffer overrun occurred */ + if ((length < 0) || (length > (int)(sizeof(number_buffer) - 1))) + { + return false; + } + + /* reserve appropriate space in the output */ + output_pointer = ensure(output_buffer, (size_t)length + sizeof("")); + if (output_pointer == NULL) + { + return false; + } + + /* copy the printed number to the output and replace locale + * dependent decimal point with '.' */ + for (i = 0; i < ((size_t)length); i++) + { + if (number_buffer[i] == decimal_point) + { + output_pointer[i] = '.'; + continue; + } + + output_pointer[i] = number_buffer[i]; + } + output_pointer[i] = '\0'; + + output_buffer->offset += (size_t)length; + + return true; +} + +/* parse 4 digit hexadecimal number */ +static unsigned parse_hex4(const unsigned char * const input) +{ + unsigned int h = 0; + size_t i = 0; + + for (i = 0; i < 4; i++) + { + /* parse digit */ + if ((input[i] >= '0') && (input[i] <= '9')) + { + h += (unsigned int) input[i] - '0'; + } + else if ((input[i] >= 'A') && (input[i] <= 'F')) + { + h += (unsigned int) 10 + input[i] - 'A'; + } + else if ((input[i] >= 'a') && (input[i] <= 'f')) + { + h += (unsigned int) 10 + input[i] - 'a'; + } + else /* invalid */ + { + return 0; + } + + if (i < 3) + { + /* shift left to make place for the next nibble */ + h = h << 4; + } + } + + return h; +} + +/* converts a UTF-16 literal to UTF-8 + * A literal can be one or two sequences of the form \uXXXX */ +static unsigned char utf16_literal_to_utf8(const unsigned char * const input_pointer, const unsigned char * const input_end, unsigned char **output_pointer) +{ + long unsigned int codepoint = 0; + unsigned int first_code = 0; + const unsigned char *first_sequence = input_pointer; + unsigned char utf8_length = 0; + unsigned char utf8_position = 0; + unsigned char sequence_length = 0; + unsigned char first_byte_mark = 0; + + if ((input_end - first_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + /* get the first utf16 sequence */ + first_code = parse_hex4(first_sequence + 2); + + /* check that the code is valid */ + if (((first_code >= 0xDC00) && (first_code <= 0xDFFF))) + { + goto fail; + } + + /* UTF16 surrogate pair */ + if ((first_code >= 0xD800) && (first_code <= 0xDBFF)) + { + const unsigned char *second_sequence = first_sequence + 6; + unsigned int second_code = 0; + sequence_length = 12; /* \uXXXX\uXXXX */ + + if ((input_end - second_sequence) < 6) + { + /* input ends unexpectedly */ + goto fail; + } + + if ((second_sequence[0] != '\\') || (second_sequence[1] != 'u')) + { + /* missing second half of the surrogate pair */ + goto fail; + } + + /* get the second utf16 sequence */ + second_code = parse_hex4(second_sequence + 2); + /* check that the code is valid */ + if ((second_code < 0xDC00) || (second_code > 0xDFFF)) + { + /* invalid second half of the surrogate pair */ + goto fail; + } + + + /* calculate the unicode codepoint from the surrogate pair */ + codepoint = 0x10000 + (((first_code & 0x3FF) << 10) | (second_code & 0x3FF)); + } + else + { + sequence_length = 6; /* \uXXXX */ + codepoint = first_code; + } + + /* encode as UTF-8 + * takes at maximum 4 bytes to encode: + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ + if (codepoint < 0x80) + { + /* normal ascii, encoding 0xxxxxxx */ + utf8_length = 1; + } + else if (codepoint < 0x800) + { + /* two bytes, encoding 110xxxxx 10xxxxxx */ + utf8_length = 2; + first_byte_mark = 0xC0; /* 11000000 */ + } + else if (codepoint < 0x10000) + { + /* three bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx */ + utf8_length = 3; + first_byte_mark = 0xE0; /* 11100000 */ + } + else if (codepoint <= 0x10FFFF) + { + /* four bytes, encoding 1110xxxx 10xxxxxx 10xxxxxx 10xxxxxx */ + utf8_length = 4; + first_byte_mark = 0xF0; /* 11110000 */ + } + else + { + /* invalid unicode codepoint */ + goto fail; + } + + /* encode as utf8 */ + for (utf8_position = (unsigned char)(utf8_length - 1); utf8_position > 0; utf8_position--) + { + /* 10xxxxxx */ + (*output_pointer)[utf8_position] = (unsigned char)((codepoint | 0x80) & 0xBF); + codepoint >>= 6; + } + /* encode first byte */ + if (utf8_length > 1) + { + (*output_pointer)[0] = (unsigned char)((codepoint | first_byte_mark) & 0xFF); + } + else + { + (*output_pointer)[0] = (unsigned char)(codepoint & 0x7F); + } + + *output_pointer += utf8_length; + + return sequence_length; + +fail: + return 0; +} + +/* Parse the input text into an unescaped cinput, and populate item. */ +static cJSON_bool parse_string(cJSON * const item, parse_buffer * const input_buffer) +{ + const unsigned char *input_pointer = buffer_at_offset(input_buffer) + 1; + const unsigned char *input_end = buffer_at_offset(input_buffer) + 1; + unsigned char *output_pointer = NULL; + unsigned char *output = NULL; + + /* not a string */ + if (buffer_at_offset(input_buffer)[0] != '\"') + { + goto fail; + } + + { + /* calculate approximate size of the output (overestimate) */ + size_t allocation_length = 0; + size_t skipped_bytes = 0; + while (((size_t)(input_end - input_buffer->content) < input_buffer->length) && (*input_end != '\"')) + { + /* is escape sequence */ + if (input_end[0] == '\\') + { + if ((size_t)(input_end + 1 - input_buffer->content) >= input_buffer->length) + { + /* prevent buffer overflow when last input character is a backslash */ + goto fail; + } + skipped_bytes++; + input_end++; + } + input_end++; + } + if (((size_t)(input_end - input_buffer->content) >= input_buffer->length) || (*input_end != '\"')) + { + goto fail; /* string ended unexpectedly */ + } + + /* This is at most how much we need for the output */ + allocation_length = (size_t) (input_end - buffer_at_offset(input_buffer)) - skipped_bytes; + output = (unsigned char*)input_buffer->hooks.allocate(allocation_length + sizeof("")); + if (output == NULL) + { + goto fail; /* allocation failure */ + } + } + + output_pointer = output; + /* loop through the string literal */ + while (input_pointer < input_end) + { + if (*input_pointer != '\\') + { + *output_pointer++ = *input_pointer++; + } + /* escape sequence */ + else + { + unsigned char sequence_length = 2; + if ((input_end - input_pointer) < 1) + { + goto fail; + } + + switch (input_pointer[1]) + { + case 'b': + *output_pointer++ = '\b'; + break; + case 'f': + *output_pointer++ = '\f'; + break; + case 'n': + *output_pointer++ = '\n'; + break; + case 'r': + *output_pointer++ = '\r'; + break; + case 't': + *output_pointer++ = '\t'; + break; + case '\"': + case '\\': + case '/': + *output_pointer++ = input_pointer[1]; + break; + + /* UTF-16 literal */ + case 'u': + sequence_length = utf16_literal_to_utf8(input_pointer, input_end, &output_pointer); + if (sequence_length == 0) + { + /* failed to convert UTF16-literal to UTF-8 */ + goto fail; + } + break; + + default: + goto fail; + } + input_pointer += sequence_length; + } + } + + /* zero terminate the output */ + *output_pointer = '\0'; + + item->type = cJSON_String; + item->valuestring = (char*)output; + + input_buffer->offset = (size_t) (input_end - input_buffer->content); + input_buffer->offset++; + + return true; + +fail: + if (output != NULL) + { + input_buffer->hooks.deallocate(output); + } + + if (input_pointer != NULL) + { + input_buffer->offset = (size_t)(input_pointer - input_buffer->content); + } + + return false; +} + +/* Render the cstring provided to an escaped version that can be printed. */ +static cJSON_bool print_string_ptr(const unsigned char * const input, printbuffer * const output_buffer) +{ + const unsigned char *input_pointer = NULL; + unsigned char *output = NULL; + unsigned char *output_pointer = NULL; + size_t output_length = 0; + /* numbers of additional characters needed for escaping */ + size_t escape_characters = 0; + + if (output_buffer == NULL) + { + return false; + } + + /* empty string */ + if (input == NULL) + { + output = ensure(output_buffer, sizeof("\"\"")); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "\"\""); + + return true; + } + + /* set "flag" to 1 if something needs to be escaped */ + for (input_pointer = input; *input_pointer; input_pointer++) + { + switch (*input_pointer) + { + case '\"': + case '\\': + case '\b': + case '\f': + case '\n': + case '\r': + case '\t': + /* one character escape sequence */ + escape_characters++; + break; + default: + if (*input_pointer < 32) + { + /* UTF-16 escape sequence uXXXX */ + escape_characters += 5; + } + break; + } + } + output_length = (size_t)(input_pointer - input) + escape_characters; + + output = ensure(output_buffer, output_length + sizeof("\"\"")); + if (output == NULL) + { + return false; + } + + /* no characters have to be escaped */ + if (escape_characters == 0) + { + output[0] = '\"'; + memcpy(output + 1, input, output_length); + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; + } + + output[0] = '\"'; + output_pointer = output + 1; + /* copy the string */ + for (input_pointer = input; *input_pointer != '\0'; (void)input_pointer++, output_pointer++) + { + if ((*input_pointer > 31) && (*input_pointer != '\"') && (*input_pointer != '\\')) + { + /* normal character, copy */ + *output_pointer = *input_pointer; + } + else + { + /* character needs to be escaped */ + *output_pointer++ = '\\'; + switch (*input_pointer) + { + case '\\': + *output_pointer = '\\'; + break; + case '\"': + *output_pointer = '\"'; + break; + case '\b': + *output_pointer = 'b'; + break; + case '\f': + *output_pointer = 'f'; + break; + case '\n': + *output_pointer = 'n'; + break; + case '\r': + *output_pointer = 'r'; + break; + case '\t': + *output_pointer = 't'; + break; + default: + /* escape and print as unicode codepoint */ + sprintf((char*)output_pointer, "u%04x", *input_pointer); + output_pointer += 4; + break; + } + } + } + output[output_length + 1] = '\"'; + output[output_length + 2] = '\0'; + + return true; +} + +/* Invoke print_string_ptr (which is useful) on an item. */ +static cJSON_bool print_string(const cJSON * const item, printbuffer * const p) +{ + return print_string_ptr((unsigned char*)item->valuestring, p); +} + +/* Predeclare these prototypes. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer); +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer); +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer); + +/* Utility to jump whitespace and cr/lf */ +static parse_buffer *buffer_skip_whitespace(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL)) + { + return NULL; + } + + if (cannot_access_at_index(buffer, 0)) + { + return buffer; + } + + while (can_access_at_index(buffer, 0) && (buffer_at_offset(buffer)[0] <= 32)) + { + buffer->offset++; + } + + if (buffer->offset == buffer->length) + { + buffer->offset--; + } + + return buffer; +} + +/* skip the UTF-8 BOM (byte order mark) if it is at the beginning of a buffer */ +static parse_buffer *skip_utf8_bom(parse_buffer * const buffer) +{ + if ((buffer == NULL) || (buffer->content == NULL) || (buffer->offset != 0)) + { + return NULL; + } + + if (can_access_at_index(buffer, 4) && (strncmp((const char*)buffer_at_offset(buffer), "\xEF\xBB\xBF", 3) == 0)) + { + buffer->offset += 3; + } + + return buffer; +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + size_t buffer_length; + + if (NULL == value) + { + return NULL; + } + + /* Adding null character size due to require_null_terminated. */ + buffer_length = strlen(value) + sizeof(""); + + return cJSON_ParseWithLengthOpts(value, buffer_length, return_parse_end, require_null_terminated); +} + +/* Parse an object - create a new root, and populate. */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated) +{ + parse_buffer buffer = { 0, 0, 0, 0, { 0, 0, 0 } }; + cJSON *item = NULL; + + /* reset error position */ + global_error.json = NULL; + global_error.position = 0; + + if (value == NULL || 0 == buffer_length) + { + goto fail; + } + + buffer.content = (const unsigned char*)value; + buffer.length = buffer_length; + buffer.offset = 0; + buffer.hooks = global_hooks; + + item = cJSON_New_Item(&global_hooks); + if (item == NULL) /* memory fail */ + { + goto fail; + } + + if (!parse_value(item, buffer_skip_whitespace(skip_utf8_bom(&buffer)))) + { + /* parse failure. ep is set. */ + goto fail; + } + + /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ + if (require_null_terminated) + { + buffer_skip_whitespace(&buffer); + if ((buffer.offset >= buffer.length) || buffer_at_offset(&buffer)[0] != '\0') + { + goto fail; + } + } + if (return_parse_end) + { + *return_parse_end = (const char*)buffer_at_offset(&buffer); + } + + return item; + +fail: + if (item != NULL) + { + cJSON_Delete(item); + } + + if (value != NULL) + { + error local_error; + local_error.json = (const unsigned char*)value; + local_error.position = 0; + + if (buffer.offset < buffer.length) + { + local_error.position = buffer.offset; + } + else if (buffer.length > 0) + { + local_error.position = buffer.length - 1; + } + + if (return_parse_end != NULL) + { + *return_parse_end = (const char*)local_error.json + local_error.position; + } + + global_error = local_error; + } + + return NULL; +} + +/* Default options for cJSON_Parse */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value) +{ + return cJSON_ParseWithOpts(value, 0, 0); +} + +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length) +{ + return cJSON_ParseWithLengthOpts(value, buffer_length, 0, 0); +} + +#define cjson_min(a, b) (((a) < (b)) ? (a) : (b)) + +static unsigned char *print(const cJSON * const item, cJSON_bool format, const internal_hooks * const hooks) +{ + static const size_t default_buffer_size = 256; + printbuffer buffer[1]; + unsigned char *printed = NULL; + + memset(buffer, 0, sizeof(buffer)); + + /* create buffer */ + buffer->buffer = (unsigned char*) hooks->allocate(default_buffer_size); + buffer->length = default_buffer_size; + buffer->format = format; + buffer->hooks = *hooks; + if (buffer->buffer == NULL) + { + goto fail; + } + + /* print the value */ + if (!print_value(item, buffer)) + { + goto fail; + } + update_offset(buffer); + + /* check if reallocate is available */ + if (hooks->reallocate != NULL) + { + printed = (unsigned char*) hooks->reallocate(buffer->buffer, buffer->offset + 1); + if (printed == NULL) { + goto fail; + } + buffer->buffer = NULL; + } + else /* otherwise copy the JSON over to a new buffer */ + { + printed = (unsigned char*) hooks->allocate(buffer->offset + 1); + if (printed == NULL) + { + goto fail; + } + memcpy(printed, buffer->buffer, cjson_min(buffer->length, buffer->offset + 1)); + printed[buffer->offset] = '\0'; /* just to be sure */ + + /* free the buffer */ + hooks->deallocate(buffer->buffer); + } + + return printed; + +fail: + if (buffer->buffer != NULL) + { + hooks->deallocate(buffer->buffer); + } + + if (printed != NULL) + { + hooks->deallocate(printed); + } + + return NULL; +} + +/* Render a cJSON item/entity/structure to text. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item) +{ + return (char*)print(item, true, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item) +{ + return (char*)print(item, false, &global_hooks); +} + +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if (prebuffer < 0) + { + return NULL; + } + + p.buffer = (unsigned char*)global_hooks.allocate((size_t)prebuffer); + if (!p.buffer) + { + return NULL; + } + + p.length = (size_t)prebuffer; + p.offset = 0; + p.noalloc = false; + p.format = fmt; + p.hooks = global_hooks; + + if (!print_value(item, &p)) + { + global_hooks.deallocate(p.buffer); + return NULL; + } + + return (char*)p.buffer; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format) +{ + printbuffer p = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } }; + + if ((length < 0) || (buffer == NULL)) + { + return false; + } + + p.buffer = (unsigned char*)buffer; + p.length = (size_t)length; + p.offset = 0; + p.noalloc = true; + p.format = format; + p.hooks = global_hooks; + + return print_value(item, &p); +} + +/* Parser core - when encountering text, process appropriately. */ +static cJSON_bool parse_value(cJSON * const item, parse_buffer * const input_buffer) +{ + if ((input_buffer == NULL) || (input_buffer->content == NULL)) + { + return false; /* no input */ + } + + /* parse the different types of values */ + /* null */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "null", 4) == 0)) + { + item->type = cJSON_NULL; + input_buffer->offset += 4; + return true; + } + /* false */ + if (can_read(input_buffer, 5) && (strncmp((const char*)buffer_at_offset(input_buffer), "false", 5) == 0)) + { + item->type = cJSON_False; + input_buffer->offset += 5; + return true; + } + /* true */ + if (can_read(input_buffer, 4) && (strncmp((const char*)buffer_at_offset(input_buffer), "true", 4) == 0)) + { + item->type = cJSON_True; + item->valueint = 1; + input_buffer->offset += 4; + return true; + } + /* string */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '\"')) + { + return parse_string(item, input_buffer); + } + /* number */ + if (can_access_at_index(input_buffer, 0) && ((buffer_at_offset(input_buffer)[0] == '-') || ((buffer_at_offset(input_buffer)[0] >= '0') && (buffer_at_offset(input_buffer)[0] <= '9')))) + { + return parse_number(item, input_buffer); + } + /* array */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '[')) + { + return parse_array(item, input_buffer); + } + /* object */ + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '{')) + { + return parse_object(item, input_buffer); + } + + return false; +} + +/* Render a value to text. */ +static cJSON_bool print_value(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output = NULL; + + if ((item == NULL) || (output_buffer == NULL)) + { + return false; + } + + switch ((item->type) & 0xFF) + { + case cJSON_NULL: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "null"); + return true; + + case cJSON_False: + output = ensure(output_buffer, 6); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "false"); + return true; + + case cJSON_True: + output = ensure(output_buffer, 5); + if (output == NULL) + { + return false; + } + strcpy((char*)output, "true"); + return true; + + case cJSON_Number: + return print_number(item, output_buffer); + + case cJSON_Raw: + { + size_t raw_length = 0; + if (item->valuestring == NULL) + { + return false; + } + + raw_length = strlen(item->valuestring) + sizeof(""); + output = ensure(output_buffer, raw_length); + if (output == NULL) + { + return false; + } + memcpy(output, item->valuestring, raw_length); + return true; + } + + case cJSON_String: + return print_string(item, output_buffer); + + case cJSON_Array: + return print_array(item, output_buffer); + + case cJSON_Object: + return print_object(item, output_buffer); + + default: + return false; + } +} + +/* Build an array from input text. */ +static cJSON_bool parse_array(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* head of the linked list */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (buffer_at_offset(input_buffer)[0] != '[') + { + /* not an array */ + goto fail; + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ']')) + { + /* empty array */ + goto success; + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse next value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || buffer_at_offset(input_buffer)[0] != ']') + { + goto fail; /* expected end of array */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Array; + item->child = head; + + input_buffer->offset++; + + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an array to text */ +static cJSON_bool print_array(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_element = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output array. */ + /* opening square bracket */ + output_pointer = ensure(output_buffer, 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer = '['; + output_buffer->offset++; + output_buffer->depth++; + + while (current_element != NULL) + { + if (!print_value(current_element, output_buffer)) + { + return false; + } + update_offset(output_buffer); + if (current_element->next) + { + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ','; + if(output_buffer->format) + { + *output_pointer++ = ' '; + } + *output_pointer = '\0'; + output_buffer->offset += length; + } + current_element = current_element->next; + } + + output_pointer = ensure(output_buffer, 2); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ']'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Build an object from the text. */ +static cJSON_bool parse_object(cJSON * const item, parse_buffer * const input_buffer) +{ + cJSON *head = NULL; /* linked list head */ + cJSON *current_item = NULL; + + if (input_buffer->depth >= CJSON_NESTING_LIMIT) + { + return false; /* to deeply nested */ + } + input_buffer->depth++; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '{')) + { + goto fail; /* not an object */ + } + + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == '}')) + { + goto success; /* empty object */ + } + + /* check if we skipped to the end of the buffer */ + if (cannot_access_at_index(input_buffer, 0)) + { + input_buffer->offset--; + goto fail; + } + + /* step back to character in front of the first element */ + input_buffer->offset--; + /* loop through the comma separated array elements */ + do + { + /* allocate next item */ + cJSON *new_item = cJSON_New_Item(&(input_buffer->hooks)); + if (new_item == NULL) + { + goto fail; /* allocation failure */ + } + + /* attach next item to list */ + if (head == NULL) + { + /* start the linked list */ + current_item = head = new_item; + } + else + { + /* add to the end and advance */ + current_item->next = new_item; + new_item->prev = current_item; + current_item = new_item; + } + + /* parse the name of the child */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_string(current_item, input_buffer)) + { + goto fail; /* failed to parse name */ + } + buffer_skip_whitespace(input_buffer); + + /* swap valuestring and string, because we parsed the name */ + current_item->string = current_item->valuestring; + current_item->valuestring = NULL; + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != ':')) + { + goto fail; /* invalid object */ + } + + /* parse the value */ + input_buffer->offset++; + buffer_skip_whitespace(input_buffer); + if (!parse_value(current_item, input_buffer)) + { + goto fail; /* failed to parse value */ + } + buffer_skip_whitespace(input_buffer); + } + while (can_access_at_index(input_buffer, 0) && (buffer_at_offset(input_buffer)[0] == ',')); + + if (cannot_access_at_index(input_buffer, 0) || (buffer_at_offset(input_buffer)[0] != '}')) + { + goto fail; /* expected end of object */ + } + +success: + input_buffer->depth--; + + if (head != NULL) { + head->prev = current_item; + } + + item->type = cJSON_Object; + item->child = head; + + input_buffer->offset++; + return true; + +fail: + if (head != NULL) + { + cJSON_Delete(head); + } + + return false; +} + +/* Render an object to text. */ +static cJSON_bool print_object(const cJSON * const item, printbuffer * const output_buffer) +{ + unsigned char *output_pointer = NULL; + size_t length = 0; + cJSON *current_item = item->child; + + if (output_buffer == NULL) + { + return false; + } + + /* Compose the output: */ + length = (size_t) (output_buffer->format ? 2 : 1); /* fmt: {\n */ + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + + *output_pointer++ = '{'; + output_buffer->depth++; + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + output_buffer->offset += length; + + while (current_item) + { + if (output_buffer->format) + { + size_t i; + output_pointer = ensure(output_buffer, output_buffer->depth); + if (output_pointer == NULL) + { + return false; + } + for (i = 0; i < output_buffer->depth; i++) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += output_buffer->depth; + } + + /* print key */ + if (!print_string_ptr((unsigned char*)current_item->string, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + length = (size_t) (output_buffer->format ? 2 : 1); + output_pointer = ensure(output_buffer, length); + if (output_pointer == NULL) + { + return false; + } + *output_pointer++ = ':'; + if (output_buffer->format) + { + *output_pointer++ = '\t'; + } + output_buffer->offset += length; + + /* print value */ + if (!print_value(current_item, output_buffer)) + { + return false; + } + update_offset(output_buffer); + + /* print comma if not last */ + length = ((size_t)(output_buffer->format ? 1 : 0) + (size_t)(current_item->next ? 1 : 0)); + output_pointer = ensure(output_buffer, length + 1); + if (output_pointer == NULL) + { + return false; + } + if (current_item->next) + { + *output_pointer++ = ','; + } + + if (output_buffer->format) + { + *output_pointer++ = '\n'; + } + *output_pointer = '\0'; + output_buffer->offset += length; + + current_item = current_item->next; + } + + output_pointer = ensure(output_buffer, output_buffer->format ? (output_buffer->depth + 1) : 2); + if (output_pointer == NULL) + { + return false; + } + if (output_buffer->format) + { + size_t i; + for (i = 0; i < (output_buffer->depth - 1); i++) + { + *output_pointer++ = '\t'; + } + } + *output_pointer++ = '}'; + *output_pointer = '\0'; + output_buffer->depth--; + + return true; +} + +/* Get Array size/item / object item. */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array) +{ + cJSON *child = NULL; + size_t size = 0; + + if (array == NULL) + { + return 0; + } + + child = array->child; + + while(child != NULL) + { + size++; + child = child->next; + } + + /* FIXME: Can overflow here. Cannot be fixed without breaking the API */ + + return (int)size; +} + +static cJSON* get_array_item(const cJSON *array, size_t index) +{ + cJSON *current_child = NULL; + + if (array == NULL) + { + return NULL; + } + + current_child = array->child; + while ((current_child != NULL) && (index > 0)) + { + index--; + current_child = current_child->next; + } + + return current_child; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index) +{ + if (index < 0) + { + return NULL; + } + + return get_array_item(array, (size_t)index); +} + +static cJSON *get_object_item(const cJSON * const object, const char * const name, const cJSON_bool case_sensitive) +{ + cJSON *current_element = NULL; + + if ((object == NULL) || (name == NULL)) + { + return NULL; + } + + current_element = object->child; + if (case_sensitive) + { + while ((current_element != NULL) && (current_element->string != NULL) && (strcmp(name, current_element->string) != 0)) + { + current_element = current_element->next; + } + } + else + { + while ((current_element != NULL) && (case_insensitive_strcmp((const unsigned char*)name, (const unsigned char*)(current_element->string)) != 0)) + { + current_element = current_element->next; + } + } + + if ((current_element == NULL) || (current_element->string == NULL)) { + return NULL; + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, false); +} + +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string) +{ + return get_object_item(object, string, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string) +{ + return cJSON_GetObjectItem(object, string) ? 1 : 0; +} + +/* Utility for array list handling. */ +static void suffix_object(cJSON *prev, cJSON *item) +{ + prev->next = item; + item->prev = prev; +} + +/* Utility for handling references. */ +static cJSON *create_reference(const cJSON *item, const internal_hooks * const hooks) +{ + cJSON *reference = NULL; + if (item == NULL) + { + return NULL; + } + + reference = cJSON_New_Item(hooks); + if (reference == NULL) + { + return NULL; + } + + memcpy(reference, item, sizeof(cJSON)); + reference->string = NULL; + reference->type |= cJSON_IsReference; + reference->next = reference->prev = NULL; + return reference; +} + +static cJSON_bool add_item_to_array(cJSON *array, cJSON *item) +{ + cJSON *child = NULL; + + if ((item == NULL) || (array == NULL) || (array == item)) + { + return false; + } + + child = array->child; + /* + * To find the last item in array quickly, we use prev in array + */ + if (child == NULL) + { + /* list is empty, start new one */ + array->child = item; + item->prev = item; + item->next = NULL; + } + else + { + /* append to the end */ + if (child->prev) + { + suffix_object(child->prev, item); + array->child->prev = item; + } + } + + return true; +} + +/* Add item to array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item) +{ + return add_item_to_array(array, item); +} + +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic push +#endif +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-qual" +#endif +/* helper function to cast away const */ +static void* cast_away_const(const void* string) +{ + return (void*)string; +} +#if defined(__clang__) || (defined(__GNUC__) && ((__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ > 5)))) + #pragma GCC diagnostic pop +#endif + + +static cJSON_bool add_item_to_object(cJSON * const object, const char * const string, cJSON * const item, const internal_hooks * const hooks, const cJSON_bool constant_key) +{ + char *new_key = NULL; + int new_type = cJSON_Invalid; + + if ((object == NULL) || (string == NULL) || (item == NULL) || (object == item)) + { + return false; + } + + if (constant_key) + { + new_key = (char*)cast_away_const(string); + new_type = item->type | cJSON_StringIsConst; + } + else + { + new_key = (char*)cJSON_strdup((const unsigned char*)string, hooks); + if (new_key == NULL) + { + return false; + } + + new_type = item->type & ~cJSON_StringIsConst; + } + + if (!(item->type & cJSON_StringIsConst) && (item->string != NULL)) + { + hooks->deallocate(item->string); + } + + item->string = new_key; + item->type = new_type; + + return add_item_to_array(object, item); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, false); +} + +/* Add an item to an object with constant string as key */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item) +{ + return add_item_to_object(object, string, item, &global_hooks, true); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) +{ + if (array == NULL) + { + return false; + } + + return add_item_to_array(array, create_reference(item, &global_hooks)); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item) +{ + if ((object == NULL) || (string == NULL)) + { + return false; + } + + return add_item_to_object(object, string, create_reference(item, &global_hooks), &global_hooks, false); +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name) +{ + cJSON *null = cJSON_CreateNull(); + if (add_item_to_object(object, name, null, &global_hooks, false)) + { + return null; + } + + cJSON_Delete(null); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name) +{ + cJSON *true_item = cJSON_CreateTrue(); + if (add_item_to_object(object, name, true_item, &global_hooks, false)) + { + return true_item; + } + + cJSON_Delete(true_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name) +{ + cJSON *false_item = cJSON_CreateFalse(); + if (add_item_to_object(object, name, false_item, &global_hooks, false)) + { + return false_item; + } + + cJSON_Delete(false_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean) +{ + cJSON *bool_item = cJSON_CreateBool(boolean); + if (add_item_to_object(object, name, bool_item, &global_hooks, false)) + { + return bool_item; + } + + cJSON_Delete(bool_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number) +{ + cJSON *number_item = cJSON_CreateNumber(number); + if (add_item_to_object(object, name, number_item, &global_hooks, false)) + { + return number_item; + } + + cJSON_Delete(number_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string) +{ + cJSON *string_item = cJSON_CreateString(string); + if (add_item_to_object(object, name, string_item, &global_hooks, false)) + { + return string_item; + } + + cJSON_Delete(string_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw) +{ + cJSON *raw_item = cJSON_CreateRaw(raw); + if (add_item_to_object(object, name, raw_item, &global_hooks, false)) + { + return raw_item; + } + + cJSON_Delete(raw_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name) +{ + cJSON *object_item = cJSON_CreateObject(); + if (add_item_to_object(object, name, object_item, &global_hooks, false)) + { + return object_item; + } + + cJSON_Delete(object_item); + return NULL; +} + +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name) +{ + cJSON *array = cJSON_CreateArray(); + if (add_item_to_object(object, name, array, &global_hooks, false)) + { + return array; + } + + cJSON_Delete(array); + return NULL; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item) +{ + if ((parent == NULL) || (item == NULL)) + { + return NULL; + } + + if (item != parent->child) + { + /* not the first element */ + item->prev->next = item->next; + } + if (item->next != NULL) + { + /* not the last element */ + item->next->prev = item->prev; + } + + if (item == parent->child) + { + /* first element */ + parent->child = item->next; + } + else if (item->next == NULL) + { + /* last element */ + parent->child->prev = item->prev; + } + + /* make sure the detached item doesn't point anywhere anymore */ + item->prev = NULL; + item->next = NULL; + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which) +{ + if (which < 0) + { + return NULL; + } + + return cJSON_DetachItemViaPointer(array, get_array_item(array, (size_t)which)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which) +{ + cJSON_Delete(cJSON_DetachItemFromArray(array, which)); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItem(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON *to_detach = cJSON_GetObjectItemCaseSensitive(object, string); + + return cJSON_DetachItemViaPointer(object, to_detach); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObject(object, string)); +} + +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string) +{ + cJSON_Delete(cJSON_DetachItemFromObjectCaseSensitive(object, string)); +} + +/* Replace array/object items with new ones. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem) +{ + cJSON *after_inserted = NULL; + + if (which < 0) + { + return false; + } + + after_inserted = get_array_item(array, (size_t)which); + if (after_inserted == NULL) + { + return add_item_to_array(array, newitem); + } + + newitem->next = after_inserted; + newitem->prev = after_inserted->prev; + after_inserted->prev = newitem; + if (after_inserted == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement) +{ + if ((parent == NULL) || (replacement == NULL) || (item == NULL)) + { + return false; + } + + if (replacement == item) + { + return true; + } + + replacement->next = item->next; + replacement->prev = item->prev; + + if (replacement->next != NULL) + { + replacement->next->prev = replacement; + } + if (parent->child == item) + { + if (parent->child->prev == parent->child) + { + replacement->prev = replacement; + } + parent->child = replacement; + } + else + { /* + * To find the last item in array quickly, we use prev in array. + * We can't modify the last item's next pointer where this item was the parent's child + */ + if (replacement->prev != NULL) + { + replacement->prev->next = replacement; + } + if (replacement->next == NULL) + { + parent->child->prev = replacement; + } + } + + item->next = NULL; + item->prev = NULL; + cJSON_Delete(item); + + return true; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem) +{ + if (which < 0) + { + return false; + } + + return cJSON_ReplaceItemViaPointer(array, get_array_item(array, (size_t)which), newitem); +} + +static cJSON_bool replace_item_in_object(cJSON *object, const char *string, cJSON *replacement, cJSON_bool case_sensitive) +{ + if ((replacement == NULL) || (string == NULL)) + { + return false; + } + + /* replace the name in the replacement */ + if (!(replacement->type & cJSON_StringIsConst) && (replacement->string != NULL)) + { + cJSON_free(replacement->string); + } + replacement->string = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + replacement->type &= ~cJSON_StringIsConst; + + return cJSON_ReplaceItemViaPointer(object, get_object_item(object, string, case_sensitive), replacement); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, false); +} + +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object, const char *string, cJSON *newitem) +{ + return replace_item_in_object(object, string, newitem, true); +} + +/* Create basic types: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_NULL; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_True; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = boolean ? cJSON_True : cJSON_False; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Number; + item->valuedouble = num; + + /* use saturation in case of overflow */ + if (num >= INT_MAX) + { + item->valueint = INT_MAX; + } + else if (num <= (double)INT_MIN) + { + item->valueint = INT_MIN; + } + else + { + item->valueint = (int)num; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_String; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)string, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) + { + item->type = cJSON_String | cJSON_IsReference; + item->valuestring = (char*)cast_away_const(string); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Object | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child) { + cJSON *item = cJSON_New_Item(&global_hooks); + if (item != NULL) { + item->type = cJSON_Array | cJSON_IsReference; + item->child = (cJSON*)cast_away_const(child); + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type = cJSON_Raw; + item->valuestring = (char*)cJSON_strdup((const unsigned char*)raw, &global_hooks); + if(!item->valuestring) + { + cJSON_Delete(item); + return NULL; + } + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if(item) + { + item->type=cJSON_Array; + } + + return item; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void) +{ + cJSON *item = cJSON_New_Item(&global_hooks); + if (item) + { + item->type = cJSON_Object; + } + + return item; +} + +/* Create Arrays: */ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if (!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber((double)numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (numbers == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for(i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateNumber(numbers[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p, n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count) +{ + size_t i = 0; + cJSON *n = NULL; + cJSON *p = NULL; + cJSON *a = NULL; + + if ((count < 0) || (strings == NULL)) + { + return NULL; + } + + a = cJSON_CreateArray(); + + for (i = 0; a && (i < (size_t)count); i++) + { + n = cJSON_CreateString(strings[i]); + if(!n) + { + cJSON_Delete(a); + return NULL; + } + if(!i) + { + a->child = n; + } + else + { + suffix_object(p,n); + } + p = n; + } + + if (a && a->child) { + a->child->prev = n; + } + + return a; +} + +/* Duplication */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse) +{ + cJSON *newitem = NULL; + cJSON *child = NULL; + cJSON *next = NULL; + cJSON *newchild = NULL; + + /* Bail on bad ptr */ + if (!item) + { + goto fail; + } + /* Create new item */ + newitem = cJSON_New_Item(&global_hooks); + if (!newitem) + { + goto fail; + } + /* Copy over all vars */ + newitem->type = item->type & (~cJSON_IsReference); + newitem->valueint = item->valueint; + newitem->valuedouble = item->valuedouble; + if (item->valuestring) + { + newitem->valuestring = (char*)cJSON_strdup((unsigned char*)item->valuestring, &global_hooks); + if (!newitem->valuestring) + { + goto fail; + } + } + if (item->string) + { + newitem->string = (item->type&cJSON_StringIsConst) ? item->string : (char*)cJSON_strdup((unsigned char*)item->string, &global_hooks); + if (!newitem->string) + { + goto fail; + } + } + /* If non-recursive, then we're done! */ + if (!recurse) + { + return newitem; + } + /* Walk the ->next chain for the child. */ + child = item->child; + while (child != NULL) + { + newchild = cJSON_Duplicate(child, true); /* Duplicate (with recurse) each item in the ->next chain */ + if (!newchild) + { + goto fail; + } + if (next != NULL) + { + /* If newitem->child already set, then crosswire ->prev and ->next and move on */ + next->next = newchild; + newchild->prev = next; + next = newchild; + } + else + { + /* Set newitem->child and move to it */ + newitem->child = newchild; + next = newchild; + } + child = child->next; + } + if (newitem && newitem->child) + { + newitem->child->prev = newchild; + } + + return newitem; + +fail: + if (newitem != NULL) + { + cJSON_Delete(newitem); + } + + return NULL; +} + +static void skip_oneline_comment(char **input) +{ + *input += static_strlen("//"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if ((*input)[0] == '\n') { + *input += static_strlen("\n"); + return; + } + } +} + +static void skip_multiline_comment(char **input) +{ + *input += static_strlen("/*"); + + for (; (*input)[0] != '\0'; ++(*input)) + { + if (((*input)[0] == '*') && ((*input)[1] == '/')) + { + *input += static_strlen("*/"); + return; + } + } +} + +static void minify_string(char **input, char **output) { + (*output)[0] = (*input)[0]; + *input += static_strlen("\""); + *output += static_strlen("\""); + + + for (; (*input)[0] != '\0'; (void)++(*input), ++(*output)) { + (*output)[0] = (*input)[0]; + + if ((*input)[0] == '\"') { + (*output)[0] = '\"'; + *input += static_strlen("\""); + *output += static_strlen("\""); + return; + } else if (((*input)[0] == '\\') && ((*input)[1] == '\"')) { + (*output)[1] = (*input)[1]; + *input += static_strlen("\""); + *output += static_strlen("\""); + } + } +} + +CJSON_PUBLIC(void) cJSON_Minify(char *json) +{ + char *into = json; + + if (json == NULL) + { + return; + } + + while (json[0] != '\0') + { + switch (json[0]) + { + case ' ': + case '\t': + case '\r': + case '\n': + json++; + break; + + case '/': + if (json[1] == '/') + { + skip_oneline_comment(&json); + } + else if (json[1] == '*') + { + skip_multiline_comment(&json); + } else { + json++; + } + break; + + case '\"': + minify_string(&json, (char**)&into); + break; + + default: + into[0] = json[0]; + json++; + into++; + } + } + + /* and null-terminate. */ + *into = '\0'; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Invalid; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_False; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xff) == cJSON_True; +} + + +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & (cJSON_True | cJSON_False)) != 0; +} +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_NULL; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Number; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_String; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Array; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Object; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item) +{ + if (item == NULL) + { + return false; + } + + return (item->type & 0xFF) == cJSON_Raw; +} + +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF)) || cJSON_IsInvalid(a)) + { + return false; + } + + /* check if type is valid */ + switch (a->type & 0xFF) + { + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + case cJSON_Number: + case cJSON_String: + case cJSON_Raw: + case cJSON_Array: + case cJSON_Object: + break; + + default: + return false; + } + + /* identical objects are equal */ + if (a == b) + { + return true; + } + + switch (a->type & 0xFF) + { + /* in these cases and equal type is enough */ + case cJSON_False: + case cJSON_True: + case cJSON_NULL: + return true; + + case cJSON_Number: + if (compare_double(a->valuedouble, b->valuedouble)) + { + return true; + } + return false; + + case cJSON_String: + case cJSON_Raw: + if ((a->valuestring == NULL) || (b->valuestring == NULL)) + { + return false; + } + if (strcmp(a->valuestring, b->valuestring) == 0) + { + return true; + } + + return false; + + case cJSON_Array: + { + cJSON *a_element = a->child; + cJSON *b_element = b->child; + + for (; (a_element != NULL) && (b_element != NULL);) + { + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + + a_element = a_element->next; + b_element = b_element->next; + } + + /* one of the arrays is longer than the other */ + if (a_element != b_element) { + return false; + } + + return true; + } + + case cJSON_Object: + { + cJSON *a_element = NULL; + cJSON *b_element = NULL; + cJSON_ArrayForEach(a_element, a) + { + /* TODO This has O(n^2) runtime, which is horrible! */ + b_element = get_object_item(b, a_element->string, case_sensitive); + if (b_element == NULL) + { + return false; + } + + if (!cJSON_Compare(a_element, b_element, case_sensitive)) + { + return false; + } + } + + /* doing this twice, once on a and b to prevent true comparison if a subset of b + * TODO: Do this the proper way, this is just a fix for now */ + cJSON_ArrayForEach(b_element, b) + { + a_element = get_object_item(a, b_element->string, case_sensitive); + if (a_element == NULL) + { + return false; + } + + if (!cJSON_Compare(b_element, a_element, case_sensitive)) + { + return false; + } + } + + return true; + } + + default: + return false; + } +} + +CJSON_PUBLIC(void *) cJSON_malloc(size_t size) +{ + return global_hooks.allocate(size); +} + +CJSON_PUBLIC(void) cJSON_free(void *object) +{ + global_hooks.deallocate(object); +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/cjson/cJSON_Utils.c b/firmware/esp-idf/wumei-smart-firmware/components/cjson/cJSON_Utils.c new file mode 100644 index 00000000..15d4b64d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/cjson/cJSON_Utils.c @@ -0,0 +1,1477 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +/* disable warnings about old C89 functions in MSVC */ +#if !defined(_CRT_SECURE_NO_DEPRECATE) && defined(_MSC_VER) +#define _CRT_SECURE_NO_DEPRECATE +#endif + +#ifdef __GNUCC__ +#pragma GCC visibility push(default) +#endif +#if defined(_MSC_VER) +#pragma warning (push) +/* disable warning about single line comments in system headers */ +#pragma warning (disable : 4001) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_MSC_VER) +#pragma warning (pop) +#endif +#ifdef __GNUCC__ +#pragma GCC visibility pop +#endif + +#include "cJSON_Utils.h" + +/* define our own boolean type */ +#ifdef true +#undef true +#endif +#define true ((cJSON_bool)1) + +#ifdef false +#undef false +#endif +#define false ((cJSON_bool)0) + +static unsigned char* cJSONUtils_strdup(const unsigned char* const string) +{ + size_t length = 0; + unsigned char *copy = NULL; + + length = strlen((const char*)string) + sizeof(""); + copy = (unsigned char*) cJSON_malloc(length); + if (copy == NULL) + { + return NULL; + } + memcpy(copy, string, length); + + return copy; +} + +/* string comparison which doesn't consider NULL pointers equal */ +static int compare_strings(const unsigned char *string1, const unsigned char *string2, const cJSON_bool case_sensitive) +{ + if ((string1 == NULL) || (string2 == NULL)) + { + return 1; + } + + if (string1 == string2) + { + return 0; + } + + if (case_sensitive) + { + return strcmp((const char*)string1, (const char*)string2); + } + + for(; tolower(*string1) == tolower(*string2); (void)string1++, string2++) + { + if (*string1 == '\0') + { + return 0; + } + } + + return tolower(*string1) - tolower(*string2); +} + +/* securely comparison of floating-point variables */ +static cJSON_bool compare_double(double a, double b) +{ + double maxVal = fabs(a) > fabs(b) ? fabs(a) : fabs(b); + return (fabs(a - b) <= maxVal * DBL_EPSILON); +} + + +/* Compare the next path element of two JSON pointers, two NULL pointers are considered unequal: */ +static cJSON_bool compare_pointers(const unsigned char *name, const unsigned char *pointer, const cJSON_bool case_sensitive) +{ + if ((name == NULL) || (pointer == NULL)) + { + return false; + } + + for (; (*name != '\0') && (*pointer != '\0') && (*pointer != '/'); (void)name++, pointer++) /* compare until next '/' */ + { + if (*pointer == '~') + { + /* check for escaped '~' (~0) and '/' (~1) */ + if (((pointer[1] != '0') || (*name != '~')) && ((pointer[1] != '1') || (*name != '/'))) + { + /* invalid escape sequence or wrong character in *name */ + return false; + } + else + { + pointer++; + } + } + else if ((!case_sensitive && (tolower(*name) != tolower(*pointer))) || (case_sensitive && (*name != *pointer))) + { + return false; + } + } + if (((*pointer != 0) && (*pointer != '/')) != (*name != 0)) + { + /* one string has ended, the other not */ + return false;; + } + + return true; +} + +/* calculate the length of a string if encoded as JSON pointer with ~0 and ~1 escape sequences */ +static size_t pointer_encoded_length(const unsigned char *string) +{ + size_t length; + for (length = 0; *string != '\0'; (void)string++, length++) + { + /* character needs to be escaped? */ + if ((*string == '~') || (*string == '/')) + { + length++; + } + } + + return length; +} + +/* copy a string while escaping '~' and '/' with ~0 and ~1 JSON pointer escape codes */ +static void encode_string_as_pointer(unsigned char *destination, const unsigned char *source) +{ + for (; source[0] != '\0'; (void)source++, destination++) + { + if (source[0] == '/') + { + destination[0] = '~'; + destination[1] = '1'; + destination++; + } + else if (source[0] == '~') + { + destination[0] = '~'; + destination[1] = '0'; + destination++; + } + else + { + destination[0] = source[0]; + } + } + + destination[0] = '\0'; +} + +CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target) +{ + size_t child_index = 0; + cJSON *current_child = 0; + + if ((object == NULL) || (target == NULL)) + { + return NULL; + } + + if (object == target) + { + /* found */ + return (char*)cJSONUtils_strdup((const unsigned char*)""); + } + + /* recursively search all children of the object or array */ + for (current_child = object->child; current_child != NULL; (void)(current_child = current_child->next), child_index++) + { + unsigned char *target_pointer = (unsigned char*)cJSONUtils_FindPointerFromObjectTo(current_child, target); + /* found the target? */ + if (target_pointer != NULL) + { + if (cJSON_IsArray(object)) + { + /* reserve enough memory for a 64 bit integer + '/' and '\0' */ + unsigned char *full_pointer = (unsigned char*)cJSON_malloc(strlen((char*)target_pointer) + 20 + sizeof("/")); + /* check if conversion to unsigned long is valid + * This should be eliminated at compile time by dead code elimination + * if size_t is an alias of unsigned long, or if it is bigger */ + if (child_index > ULONG_MAX) + { + cJSON_free(target_pointer); + cJSON_free(full_pointer); + return NULL; + } + sprintf((char*)full_pointer, "/%lu%s", (unsigned long)child_index, target_pointer); /* / */ + cJSON_free(target_pointer); + + return (char*)full_pointer; + } + + if (cJSON_IsObject(object)) + { + unsigned char *full_pointer = (unsigned char*)cJSON_malloc(strlen((char*)target_pointer) + pointer_encoded_length((unsigned char*)current_child->string) + 2); + full_pointer[0] = '/'; + encode_string_as_pointer(full_pointer + 1, (unsigned char*)current_child->string); + strcat((char*)full_pointer, (char*)target_pointer); + cJSON_free(target_pointer); + + return (char*)full_pointer; + } + + /* reached leaf of the tree, found nothing */ + cJSON_free(target_pointer); + return NULL; + } + } + + /* not found */ + return NULL; +} + +/* non broken version of cJSON_GetArrayItem */ +static cJSON *get_array_item(const cJSON *array, size_t item) +{ + cJSON *child = array ? array->child : NULL; + while ((child != NULL) && (item > 0)) + { + item--; + child = child->next; + } + + return child; +} + +static cJSON_bool decode_array_index_from_pointer(const unsigned char * const pointer, size_t * const index) +{ + size_t parsed_index = 0; + size_t position = 0; + + if ((pointer[0] == '0') && ((pointer[1] != '\0') && (pointer[1] != '/'))) + { + /* leading zeroes are not permitted */ + return 0; + } + + for (position = 0; (pointer[position] >= '0') && (pointer[0] <= '9'); position++) + { + parsed_index = (10 * parsed_index) + (size_t)(pointer[position] - '0'); + + } + + if ((pointer[position] != '\0') && (pointer[position] != '/')) + { + return 0; + } + + *index = parsed_index; + + return 1; +} + +static cJSON *get_item_from_pointer(cJSON * const object, const char * pointer, const cJSON_bool case_sensitive) +{ + cJSON *current_element = object; + + if (pointer == NULL) + { + return NULL; + } + + /* follow path of the pointer */ + while ((pointer[0] == '/') && (current_element != NULL)) + { + pointer++; + if (cJSON_IsArray(current_element)) + { + size_t index = 0; + if (!decode_array_index_from_pointer((const unsigned char*)pointer, &index)) + { + return NULL; + } + + current_element = get_array_item(current_element, index); + } + else if (cJSON_IsObject(current_element)) + { + current_element = current_element->child; + /* GetObjectItem. */ + while ((current_element != NULL) && !compare_pointers((unsigned char*)current_element->string, (const unsigned char*)pointer, case_sensitive)) + { + current_element = current_element->next; + } + } + else + { + return NULL; + } + + /* skip to the next path token or end of string */ + while ((pointer[0] != '\0') && (pointer[0] != '/')) + { + pointer++; + } + } + + return current_element; +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer) +{ + return get_item_from_pointer(object, pointer, false); +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointerCaseSensitive(cJSON * const object, const char *pointer) +{ + return get_item_from_pointer(object, pointer, true); +} + +/* JSON Patch implementation. */ +static void decode_pointer_inplace(unsigned char *string) +{ + unsigned char *decoded_string = string; + + if (string == NULL) { + return; + } + + for (; *string; (void)decoded_string++, string++) + { + if (string[0] == '~') + { + if (string[1] == '0') + { + decoded_string[0] = '~'; + } + else if (string[1] == '1') + { + decoded_string[1] = '/'; + } + else + { + /* invalid escape sequence */ + return; + } + + string++; + } + } + + decoded_string[0] = '\0'; +} + +/* non-broken cJSON_DetachItemFromArray */ +static cJSON *detach_item_from_array(cJSON *array, size_t which) +{ + cJSON *c = array->child; + while (c && (which > 0)) + { + c = c->next; + which--; + } + if (!c) + { + /* item doesn't exist */ + return NULL; + } + if (c != array->child) + { + /* not the first element */ + c->prev->next = c->next; + } + if (c->next) + { + c->next->prev = c->prev; + } + if (c == array->child) + { + array->child = c->next; + } + else if (c->next == NULL) + { + array->child->prev = c->prev; + } + /* make sure the detached item doesn't point anywhere anymore */ + c->prev = c->next = NULL; + + return c; +} + +/* detach an item at the given path */ +static cJSON *detach_path(cJSON *object, const unsigned char *path, const cJSON_bool case_sensitive) +{ + unsigned char *parent_pointer = NULL; + unsigned char *child_pointer = NULL; + cJSON *parent = NULL; + cJSON *detached_item = NULL; + + /* copy path and split it in parent and child */ + parent_pointer = cJSONUtils_strdup(path); + if (parent_pointer == NULL) { + goto cleanup; + } + + child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); /* last '/' */ + if (child_pointer == NULL) + { + goto cleanup; + } + /* split strings */ + child_pointer[0] = '\0'; + child_pointer++; + + parent = get_item_from_pointer(object, (char*)parent_pointer, case_sensitive); + decode_pointer_inplace(child_pointer); + + if (cJSON_IsArray(parent)) + { + size_t index = 0; + if (!decode_array_index_from_pointer(child_pointer, &index)) + { + goto cleanup; + } + detached_item = detach_item_from_array(parent, index); + } + else if (cJSON_IsObject(parent)) + { + detached_item = cJSON_DetachItemFromObject(parent, (char*)child_pointer); + } + else + { + /* Couldn't find object to remove child from. */ + goto cleanup; + } + +cleanup: + if (parent_pointer != NULL) + { + cJSON_free(parent_pointer); + } + + return detached_item; +} + +/* sort lists using mergesort */ +static cJSON *sort_list(cJSON *list, const cJSON_bool case_sensitive) +{ + cJSON *first = list; + cJSON *second = list; + cJSON *current_item = list; + cJSON *result = list; + cJSON *result_tail = NULL; + + if ((list == NULL) || (list->next == NULL)) + { + /* One entry is sorted already. */ + return result; + } + + while ((current_item != NULL) && (current_item->next != NULL) && (compare_strings((unsigned char*)current_item->string, (unsigned char*)current_item->next->string, case_sensitive) < 0)) + { + /* Test for list sorted. */ + current_item = current_item->next; + } + if ((current_item == NULL) || (current_item->next == NULL)) + { + /* Leave sorted lists unmodified. */ + return result; + } + + /* reset pointer to the beginning */ + current_item = list; + while (current_item != NULL) + { + /* Walk two pointers to find the middle. */ + second = second->next; + current_item = current_item->next; + /* advances current_item two steps at a time */ + if (current_item != NULL) + { + current_item = current_item->next; + } + } + if ((second != NULL) && (second->prev != NULL)) + { + /* Split the lists */ + second->prev->next = NULL; + second->prev = NULL; + } + + /* Recursively sort the sub-lists. */ + first = sort_list(first, case_sensitive); + second = sort_list(second, case_sensitive); + result = NULL; + + /* Merge the sub-lists */ + while ((first != NULL) && (second != NULL)) + { + cJSON *smaller = NULL; + if (compare_strings((unsigned char*)first->string, (unsigned char*)second->string, case_sensitive) < 0) + { + smaller = first; + } + else + { + smaller = second; + } + + if (result == NULL) + { + /* start merged list with the smaller element */ + result_tail = smaller; + result = smaller; + } + else + { + /* add smaller element to the list */ + result_tail->next = smaller; + smaller->prev = result_tail; + result_tail = smaller; + } + + if (first == smaller) + { + first = first->next; + } + else + { + second = second->next; + } + } + + if (first != NULL) + { + /* Append rest of first list. */ + if (result == NULL) + { + return first; + } + result_tail->next = first; + first->prev = result_tail; + } + if (second != NULL) + { + /* Append rest of second list */ + if (result == NULL) + { + return second; + } + result_tail->next = second; + second->prev = result_tail; + } + + return result; +} + +static void sort_object(cJSON * const object, const cJSON_bool case_sensitive) +{ + if (object == NULL) + { + return; + } + object->child = sort_list(object->child, case_sensitive); +} + +static cJSON_bool compare_json(cJSON *a, cJSON *b, const cJSON_bool case_sensitive) +{ + if ((a == NULL) || (b == NULL) || ((a->type & 0xFF) != (b->type & 0xFF))) + { + /* mismatched type. */ + return false; + } + switch (a->type & 0xFF) + { + case cJSON_Number: + /* numeric mismatch. */ + if ((a->valueint != b->valueint) || (!compare_double(a->valuedouble, b->valuedouble))) + { + return false; + } + else + { + return true; + } + + case cJSON_String: + /* string mismatch. */ + if (strcmp(a->valuestring, b->valuestring) != 0) + { + return false; + } + else + { + return true; + } + + case cJSON_Array: + for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) + { + cJSON_bool identical = compare_json(a, b, case_sensitive); + if (!identical) + { + return false; + } + } + + /* array size mismatch? (one of both children is not NULL) */ + if ((a != NULL) || (b != NULL)) + { + return false; + } + else + { + return true; + } + + case cJSON_Object: + sort_object(a, case_sensitive); + sort_object(b, case_sensitive); + for ((void)(a = a->child), b = b->child; (a != NULL) && (b != NULL); (void)(a = a->next), b = b->next) + { + cJSON_bool identical = false; + /* compare object keys */ + if (compare_strings((unsigned char*)a->string, (unsigned char*)b->string, case_sensitive)) + { + /* missing member */ + return false; + } + identical = compare_json(a, b, case_sensitive); + if (!identical) + { + return false; + } + } + + /* object length mismatch (one of both children is not null) */ + if ((a != NULL) || (b != NULL)) + { + return false; + } + else + { + return true; + } + + default: + break; + } + + /* null, true or false */ + return true; +} + +/* non broken version of cJSON_InsertItemInArray */ +static cJSON_bool insert_item_in_array(cJSON *array, size_t which, cJSON *newitem) +{ + cJSON *child = array->child; + while (child && (which > 0)) + { + child = child->next; + which--; + } + if (which > 0) + { + /* item is after the end of the array */ + return 0; + } + if (child == NULL) + { + cJSON_AddItemToArray(array, newitem); + return 1; + } + + /* insert into the linked list */ + newitem->next = child; + newitem->prev = child->prev; + child->prev = newitem; + + /* was it at the beginning */ + if (child == array->child) + { + array->child = newitem; + } + else + { + newitem->prev->next = newitem; + } + + return 1; +} + +static cJSON *get_object_item(const cJSON * const object, const char* name, const cJSON_bool case_sensitive) +{ + if (case_sensitive) + { + return cJSON_GetObjectItemCaseSensitive(object, name); + } + + return cJSON_GetObjectItem(object, name); +} + +enum patch_operation { INVALID, ADD, REMOVE, REPLACE, MOVE, COPY, TEST }; + +static enum patch_operation decode_patch_operation(const cJSON * const patch, const cJSON_bool case_sensitive) +{ + cJSON *operation = get_object_item(patch, "op", case_sensitive); + if (!cJSON_IsString(operation)) + { + return INVALID; + } + + if (strcmp(operation->valuestring, "add") == 0) + { + return ADD; + } + + if (strcmp(operation->valuestring, "remove") == 0) + { + return REMOVE; + } + + if (strcmp(operation->valuestring, "replace") == 0) + { + return REPLACE; + } + + if (strcmp(operation->valuestring, "move") == 0) + { + return MOVE; + } + + if (strcmp(operation->valuestring, "copy") == 0) + { + return COPY; + } + + if (strcmp(operation->valuestring, "test") == 0) + { + return TEST; + } + + return INVALID; +} + +/* overwrite and existing item with another one and free resources on the way */ +static void overwrite_item(cJSON * const root, const cJSON replacement) +{ + if (root == NULL) + { + return; + } + + if (root->string != NULL) + { + cJSON_free(root->string); + } + if (root->valuestring != NULL) + { + cJSON_free(root->valuestring); + } + if (root->child != NULL) + { + cJSON_Delete(root->child); + } + + memcpy(root, &replacement, sizeof(cJSON)); +} + +static int apply_patch(cJSON *object, const cJSON *patch, const cJSON_bool case_sensitive) +{ + cJSON *path = NULL; + cJSON *value = NULL; + cJSON *parent = NULL; + enum patch_operation opcode = INVALID; + unsigned char *parent_pointer = NULL; + unsigned char *child_pointer = NULL; + int status = 0; + + path = get_object_item(patch, "path", case_sensitive); + if (!cJSON_IsString(path)) + { + /* malformed patch. */ + status = 2; + goto cleanup; + } + + opcode = decode_patch_operation(patch, case_sensitive); + if (opcode == INVALID) + { + status = 3; + goto cleanup; + } + else if (opcode == TEST) + { + /* compare value: {...} with the given path */ + status = !compare_json(get_item_from_pointer(object, path->valuestring, case_sensitive), get_object_item(patch, "value", case_sensitive), case_sensitive); + goto cleanup; + } + + /* special case for replacing the root */ + if (path->valuestring[0] == '\0') + { + if (opcode == REMOVE) + { + static const cJSON invalid = { NULL, NULL, NULL, cJSON_Invalid, NULL, 0, 0, NULL}; + + overwrite_item(object, invalid); + + status = 0; + goto cleanup; + } + + if ((opcode == REPLACE) || (opcode == ADD)) + { + value = get_object_item(patch, "value", case_sensitive); + if (value == NULL) + { + /* missing "value" for add/replace. */ + status = 7; + goto cleanup; + } + + value = cJSON_Duplicate(value, 1); + if (value == NULL) + { + /* out of memory for add/replace. */ + status = 8; + goto cleanup; + } + + overwrite_item(object, *value); + + /* delete the duplicated value */ + cJSON_free(value); + value = NULL; + + /* the string "value" isn't needed */ + if (object->string != NULL) + { + cJSON_free(object->string); + object->string = NULL; + } + + status = 0; + goto cleanup; + } + } + + if ((opcode == REMOVE) || (opcode == REPLACE)) + { + /* Get rid of old. */ + cJSON *old_item = detach_path(object, (unsigned char*)path->valuestring, case_sensitive); + if (old_item == NULL) + { + status = 13; + goto cleanup; + } + cJSON_Delete(old_item); + if (opcode == REMOVE) + { + /* For Remove, this job is done. */ + status = 0; + goto cleanup; + } + } + + /* Copy/Move uses "from". */ + if ((opcode == MOVE) || (opcode == COPY)) + { + cJSON *from = get_object_item(patch, "from", case_sensitive); + if (from == NULL) + { + /* missing "from" for copy/move. */ + status = 4; + goto cleanup; + } + + if (opcode == MOVE) + { + value = detach_path(object, (unsigned char*)from->valuestring, case_sensitive); + } + if (opcode == COPY) + { + value = get_item_from_pointer(object, from->valuestring, case_sensitive); + } + if (value == NULL) + { + /* missing "from" for copy/move. */ + status = 5; + goto cleanup; + } + if (opcode == COPY) + { + value = cJSON_Duplicate(value, 1); + } + if (value == NULL) + { + /* out of memory for copy/move. */ + status = 6; + goto cleanup; + } + } + else /* Add/Replace uses "value". */ + { + value = get_object_item(patch, "value", case_sensitive); + if (value == NULL) + { + /* missing "value" for add/replace. */ + status = 7; + goto cleanup; + } + value = cJSON_Duplicate(value, 1); + if (value == NULL) + { + /* out of memory for add/replace. */ + status = 8; + goto cleanup; + } + } + + /* Now, just add "value" to "path". */ + + /* split pointer in parent and child */ + parent_pointer = cJSONUtils_strdup((unsigned char*)path->valuestring); + if (parent_pointer) { + child_pointer = (unsigned char*)strrchr((char*)parent_pointer, '/'); + } + if (child_pointer != NULL) + { + child_pointer[0] = '\0'; + child_pointer++; + } + parent = get_item_from_pointer(object, (char*)parent_pointer, case_sensitive); + decode_pointer_inplace(child_pointer); + + /* add, remove, replace, move, copy, test. */ + if ((parent == NULL) || (child_pointer == NULL)) + { + /* Couldn't find object to add to. */ + status = 9; + goto cleanup; + } + else if (cJSON_IsArray(parent)) + { + if (strcmp((char*)child_pointer, "-") == 0) + { + cJSON_AddItemToArray(parent, value); + value = NULL; + } + else + { + size_t index = 0; + if (!decode_array_index_from_pointer(child_pointer, &index)) + { + status = 11; + goto cleanup; + } + + if (!insert_item_in_array(parent, index, value)) + { + status = 10; + goto cleanup; + } + value = NULL; + } + } + else if (cJSON_IsObject(parent)) + { + if (case_sensitive) + { + cJSON_DeleteItemFromObjectCaseSensitive(parent, (char*)child_pointer); + } + else + { + cJSON_DeleteItemFromObject(parent, (char*)child_pointer); + } + cJSON_AddItemToObject(parent, (char*)child_pointer, value); + value = NULL; + } + else /* parent is not an object */ + { + /* Couldn't find object to add to. */ + status = 9; + goto cleanup; + } + +cleanup: + if (value != NULL) + { + cJSON_Delete(value); + } + if (parent_pointer != NULL) + { + cJSON_free(parent_pointer); + } + + return status; +} + +CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches) +{ + const cJSON *current_patch = NULL; + int status = 0; + + if (!cJSON_IsArray(patches)) + { + /* malformed patches. */ + return 1; + } + + if (patches != NULL) + { + current_patch = patches->child; + } + + while (current_patch != NULL) + { + status = apply_patch(object, current_patch, false); + if (status != 0) + { + return status; + } + current_patch = current_patch->next; + } + + return 0; +} + +CJSON_PUBLIC(int) cJSONUtils_ApplyPatchesCaseSensitive(cJSON * const object, const cJSON * const patches) +{ + const cJSON *current_patch = NULL; + int status = 0; + + if (!cJSON_IsArray(patches)) + { + /* malformed patches. */ + return 1; + } + + if (patches != NULL) + { + current_patch = patches->child; + } + + while (current_patch != NULL) + { + status = apply_patch(object, current_patch, true); + if (status != 0) + { + return status; + } + current_patch = current_patch->next; + } + + return 0; +} + +static void compose_patch(cJSON * const patches, const unsigned char * const operation, const unsigned char * const path, const unsigned char *suffix, const cJSON * const value) +{ + cJSON *patch = NULL; + + if ((patches == NULL) || (operation == NULL) || (path == NULL)) + { + return; + } + + patch = cJSON_CreateObject(); + if (patch == NULL) + { + return; + } + cJSON_AddItemToObject(patch, "op", cJSON_CreateString((const char*)operation)); + + if (suffix == NULL) + { + cJSON_AddItemToObject(patch, "path", cJSON_CreateString((const char*)path)); + } + else + { + size_t suffix_length = pointer_encoded_length(suffix); + size_t path_length = strlen((const char*)path); + unsigned char *full_path = (unsigned char*)cJSON_malloc(path_length + suffix_length + sizeof("/")); + + sprintf((char*)full_path, "%s/", (const char*)path); + encode_string_as_pointer(full_path + path_length + 1, suffix); + + cJSON_AddItemToObject(patch, "path", cJSON_CreateString((const char*)full_path)); + cJSON_free(full_path); + } + + if (value != NULL) + { + cJSON_AddItemToObject(patch, "value", cJSON_Duplicate(value, 1)); + } + cJSON_AddItemToArray(patches, patch); +} + +CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON * const array, const char * const operation, const char * const path, const cJSON * const value) +{ + compose_patch(array, (const unsigned char*)operation, (const unsigned char*)path, NULL, value); +} + +static void create_patches(cJSON * const patches, const unsigned char * const path, cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive) +{ + if ((from == NULL) || (to == NULL)) + { + return; + } + + if ((from->type & 0xFF) != (to->type & 0xFF)) + { + compose_patch(patches, (const unsigned char*)"replace", path, 0, to); + return; + } + + switch (from->type & 0xFF) + { + case cJSON_Number: + if ((from->valueint != to->valueint) || !compare_double(from->valuedouble, to->valuedouble)) + { + compose_patch(patches, (const unsigned char*)"replace", path, NULL, to); + } + return; + + case cJSON_String: + if (strcmp(from->valuestring, to->valuestring) != 0) + { + compose_patch(patches, (const unsigned char*)"replace", path, NULL, to); + } + return; + + case cJSON_Array: + { + size_t index = 0; + cJSON *from_child = from->child; + cJSON *to_child = to->child; + unsigned char *new_path = (unsigned char*)cJSON_malloc(strlen((const char*)path) + 20 + sizeof("/")); /* Allow space for 64bit int. log10(2^64) = 20 */ + + /* generate patches for all array elements that exist in both "from" and "to" */ + for (index = 0; (from_child != NULL) && (to_child != NULL); (void)(from_child = from_child->next), (void)(to_child = to_child->next), index++) + { + /* check if conversion to unsigned long is valid + * This should be eliminated at compile time by dead code elimination + * if size_t is an alias of unsigned long, or if it is bigger */ + if (index > ULONG_MAX) + { + cJSON_free(new_path); + return; + } + sprintf((char*)new_path, "%s/%lu", path, (unsigned long)index); /* path of the current array element */ + create_patches(patches, new_path, from_child, to_child, case_sensitive); + } + + /* remove leftover elements from 'from' that are not in 'to' */ + for (; (from_child != NULL); (void)(from_child = from_child->next)) + { + /* check if conversion to unsigned long is valid + * This should be eliminated at compile time by dead code elimination + * if size_t is an alias of unsigned long, or if it is bigger */ + if (index > ULONG_MAX) + { + cJSON_free(new_path); + return; + } + sprintf((char*)new_path, "%lu", (unsigned long)index); + compose_patch(patches, (const unsigned char*)"remove", path, new_path, NULL); + } + /* add new elements in 'to' that were not in 'from' */ + for (; (to_child != NULL); (void)(to_child = to_child->next), index++) + { + compose_patch(patches, (const unsigned char*)"add", path, (const unsigned char*)"-", to_child); + } + cJSON_free(new_path); + return; + } + + case cJSON_Object: + { + cJSON *from_child = NULL; + cJSON *to_child = NULL; + sort_object(from, case_sensitive); + sort_object(to, case_sensitive); + + from_child = from->child; + to_child = to->child; + /* for all object values in the object with more of them */ + while ((from_child != NULL) || (to_child != NULL)) + { + int diff; + if (from_child == NULL) + { + diff = 1; + } + else if (to_child == NULL) + { + diff = -1; + } + else + { + diff = compare_strings((unsigned char*)from_child->string, (unsigned char*)to_child->string, case_sensitive); + } + + if (diff == 0) + { + /* both object keys are the same */ + size_t path_length = strlen((const char*)path); + size_t from_child_name_length = pointer_encoded_length((unsigned char*)from_child->string); + unsigned char *new_path = (unsigned char*)cJSON_malloc(path_length + from_child_name_length + sizeof("/")); + + sprintf((char*)new_path, "%s/", path); + encode_string_as_pointer(new_path + path_length + 1, (unsigned char*)from_child->string); + + /* create a patch for the element */ + create_patches(patches, new_path, from_child, to_child, case_sensitive); + cJSON_free(new_path); + + from_child = from_child->next; + to_child = to_child->next; + } + else if (diff < 0) + { + /* object element doesn't exist in 'to' --> remove it */ + compose_patch(patches, (const unsigned char*)"remove", path, (unsigned char*)from_child->string, NULL); + + from_child = from_child->next; + } + else + { + /* object element doesn't exist in 'from' --> add it */ + compose_patch(patches, (const unsigned char*)"add", path, (unsigned char*)to_child->string, to_child); + + to_child = to_child->next; + } + } + return; + } + + default: + break; + } +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON * const from, cJSON * const to) +{ + cJSON *patches = NULL; + + if ((from == NULL) || (to == NULL)) + { + return NULL; + } + + patches = cJSON_CreateArray(); + create_patches(patches, (const unsigned char*)"", from, to, false); + + return patches; +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatchesCaseSensitive(cJSON * const from, cJSON * const to) +{ + cJSON *patches = NULL; + + if ((from == NULL) || (to == NULL)) + { + return NULL; + } + + patches = cJSON_CreateArray(); + create_patches(patches, (const unsigned char*)"", from, to, true); + + return patches; +} + +CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON * const object) +{ + sort_object(object, false); +} + +CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object) +{ + sort_object(object, true); +} + +static cJSON *merge_patch(cJSON *target, const cJSON * const patch, const cJSON_bool case_sensitive) +{ + cJSON *patch_child = NULL; + + if (!cJSON_IsObject(patch)) + { + /* scalar value, array or NULL, just duplicate */ + cJSON_Delete(target); + return cJSON_Duplicate(patch, 1); + } + + if (!cJSON_IsObject(target)) + { + cJSON_Delete(target); + target = cJSON_CreateObject(); + } + + patch_child = patch->child; + while (patch_child != NULL) + { + if (cJSON_IsNull(patch_child)) + { + /* NULL is the indicator to remove a value, see RFC7396 */ + if (case_sensitive) + { + cJSON_DeleteItemFromObjectCaseSensitive(target, patch_child->string); + } + else + { + cJSON_DeleteItemFromObject(target, patch_child->string); + } + } + else + { + cJSON *replace_me = NULL; + cJSON *replacement = NULL; + + if (case_sensitive) + { + replace_me = cJSON_DetachItemFromObjectCaseSensitive(target, patch_child->string); + } + else + { + replace_me = cJSON_DetachItemFromObject(target, patch_child->string); + } + + replacement = merge_patch(replace_me, patch_child, case_sensitive); + if (replacement == NULL) + { + return NULL; + } + + cJSON_AddItemToObject(target, patch_child->string, replacement); + } + patch_child = patch_child->next; + } + return target; +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch) +{ + return merge_patch(target, patch, false); +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch) +{ + return merge_patch(target, patch, true); +} + +static cJSON *generate_merge_patch(cJSON * const from, cJSON * const to, const cJSON_bool case_sensitive) +{ + cJSON *from_child = NULL; + cJSON *to_child = NULL; + cJSON *patch = NULL; + if (to == NULL) + { + /* patch to delete everything */ + return cJSON_CreateNull(); + } + if (!cJSON_IsObject(to) || !cJSON_IsObject(from)) + { + return cJSON_Duplicate(to, 1); + } + + sort_object(from, case_sensitive); + sort_object(to, case_sensitive); + + from_child = from->child; + to_child = to->child; + patch = cJSON_CreateObject(); + if (patch == NULL) + { + return NULL; + } + while (from_child || to_child) + { + int diff; + if (from_child != NULL) + { + if (to_child != NULL) + { + diff = strcmp(from_child->string, to_child->string); + } + else + { + diff = -1; + } + } + else + { + diff = 1; + } + + if (diff < 0) + { + /* from has a value that to doesn't have -> remove */ + cJSON_AddItemToObject(patch, from_child->string, cJSON_CreateNull()); + + from_child = from_child->next; + } + else if (diff > 0) + { + /* to has a value that from doesn't have -> add to patch */ + cJSON_AddItemToObject(patch, to_child->string, cJSON_Duplicate(to_child, 1)); + + to_child = to_child->next; + } + else + { + /* object key exists in both objects */ + if (!compare_json(from_child, to_child, case_sensitive)) + { + /* not identical --> generate a patch */ + cJSON_AddItemToObject(patch, to_child->string, cJSONUtils_GenerateMergePatch(from_child, to_child)); + } + + /* next key in the object */ + from_child = from_child->next; + to_child = to_child->next; + } + } + if (patch->child == NULL) + { + /* no patch generated */ + cJSON_Delete(patch); + return NULL; + } + + return patch; +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to) +{ + return generate_merge_patch(from, to, false); +} + +CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to) +{ + return generate_merge_patch(from, to, true); +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/cjson/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/cjson/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/cjson/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/components/cjson/include/cJSON.h b/firmware/esp-idf/wumei-smart-firmware/components/cjson/include/cJSON.h new file mode 100644 index 00000000..628359c8 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/cjson/include/cJSON.h @@ -0,0 +1,285 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON__h +#define cJSON__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32)) +#define __WINDOWS__ +#endif + +#ifdef __WINDOWS__ + +/* When compiling for windows, we specify a specific calling convention to avoid issues where we are being called from a project with a different default calling convention. For windows you have 3 define options: +CJSON_HIDE_SYMBOLS - Define this in the case where you don't want to ever dllexport symbols +CJSON_EXPORT_SYMBOLS - Define this on library build when you want to dllexport symbols (default) +CJSON_IMPORT_SYMBOLS - Define this if you want to dllimport symbol +For *nix builds that support visibility attribute, you can define similar behavior by +setting default visibility to hidden by adding +-fvisibility=hidden (for gcc) +or +-xldscope=hidden (for sun cc) +to CFLAGS +then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJSON_EXPORT_SYMBOLS does +*/ + +#define CJSON_CDECL __cdecl +#define CJSON_STDCALL __stdcall + +/* export symbols by default, this is necessary for copy pasting the C and header file */ +#if !defined(CJSON_HIDE_SYMBOLS) && !defined(CJSON_IMPORT_SYMBOLS) && !defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_EXPORT_SYMBOLS +#endif + +#if defined(CJSON_HIDE_SYMBOLS) +#define CJSON_PUBLIC(type) type CJSON_STDCALL +#elif defined(CJSON_EXPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllexport) type CJSON_STDCALL +#elif defined(CJSON_IMPORT_SYMBOLS) +#define CJSON_PUBLIC(type) __declspec(dllimport) type CJSON_STDCALL +#endif +#else /* !__WINDOWS__ */ +#define CJSON_CDECL +#define CJSON_STDCALL + +#if (defined(__GNUC__) || defined(__SUNPRO_CC) || defined (__SUNPRO_C)) && defined(CJSON_API_VISIBILITY) +#define CJSON_PUBLIC(type) __attribute__((visibility("default"))) type +#else +#define CJSON_PUBLIC(type) type +#endif +#endif + +/* project version */ +#define CJSON_VERSION_MAJOR 1 +#define CJSON_VERSION_MINOR 7 +#define CJSON_VERSION_PATCH 14 + +#include + +/* cJSON Types: */ +#define cJSON_Invalid (0) +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference 256 +#define cJSON_StringIsConst 512 + +/* The cJSON structure: */ +typedef struct cJSON +{ + /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ + struct cJSON *next; + struct cJSON *prev; + /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ + struct cJSON *child; + + /* The type of the item, as above. */ + int type; + + /* The item's string, if type==cJSON_String and type == cJSON_Raw */ + char *valuestring; + /* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */ + int valueint; + /* The item's number, if type==cJSON_Number */ + double valuedouble; + + /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ + char *string; +} cJSON; + +typedef struct cJSON_Hooks +{ + /* malloc/free are CDECL on Windows regardless of the default calling convention of the compiler, so ensure the hooks allow passing those functions directly. */ + void *(CJSON_CDECL *malloc_fn)(size_t sz); + void (CJSON_CDECL *free_fn)(void *ptr); +} cJSON_Hooks; + +typedef int cJSON_bool; + +/* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. + * This is to prevent stack overflows. */ +#ifndef CJSON_NESTING_LIMIT +#define CJSON_NESTING_LIMIT 1000 +#endif + +/* returns the version of cJSON as a string */ +CJSON_PUBLIC(const char*) cJSON_Version(void); + +/* Supply malloc, realloc and free functions to cJSON */ +CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks); + +/* Memory Management: the caller is always responsible to free the results from all variants of cJSON_Parse (with cJSON_Delete) and cJSON_Print (with stdlib free, cJSON_Hooks.free_fn, or cJSON_free as appropriate). The exception is cJSON_PrintPreallocated, where the caller has full responsibility of the buffer. */ +/* Supply a block of JSON, and this returns a cJSON object you can interrogate. */ +CJSON_PUBLIC(cJSON *) cJSON_Parse(const char *value); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLength(const char *value, size_t buffer_length); +/* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ +/* If you supply a ptr in return_parse_end and parsing fails, then return_parse_end will contain a pointer to the error so will match cJSON_GetErrorPtr(). */ +CJSON_PUBLIC(cJSON *) cJSON_ParseWithOpts(const char *value, const char **return_parse_end, cJSON_bool require_null_terminated); +CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated); + +/* Render a cJSON entity to text for transfer/storage. */ +CJSON_PUBLIC(char *) cJSON_Print(const cJSON *item); +/* Render a cJSON entity to text for transfer/storage without any formatting. */ +CJSON_PUBLIC(char *) cJSON_PrintUnformatted(const cJSON *item); +/* Render a cJSON entity to text using a buffered strategy. prebuffer is a guess at the final size. guessing well reduces reallocation. fmt=0 gives unformatted, =1 gives formatted */ +CJSON_PUBLIC(char *) cJSON_PrintBuffered(const cJSON *item, int prebuffer, cJSON_bool fmt); +/* Render a cJSON entity to text using a buffer already allocated in memory with given length. Returns 1 on success and 0 on failure. */ +/* NOTE: cJSON is not always 100% accurate in estimating how much memory it will use, so to be safe allocate 5 bytes more than you actually need */ +CJSON_PUBLIC(cJSON_bool) cJSON_PrintPreallocated(cJSON *item, char *buffer, const int length, const cJSON_bool format); +/* Delete a cJSON entity and all subentities. */ +CJSON_PUBLIC(void) cJSON_Delete(cJSON *item); + +/* Returns the number of items in an array (or object). */ +CJSON_PUBLIC(int) cJSON_GetArraySize(const cJSON *array); +/* Retrieve item number "index" from array "array". Returns NULL if unsuccessful. */ +CJSON_PUBLIC(cJSON *) cJSON_GetArrayItem(const cJSON *array, int index); +/* Get item "string" from object. Case insensitive. */ +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItem(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON *) cJSON_GetObjectItemCaseSensitive(const cJSON * const object, const char * const string); +CJSON_PUBLIC(cJSON_bool) cJSON_HasObjectItem(const cJSON *object, const char *string); +/* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ +CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void); + +/* Check item type and return its value */ +CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item); +CJSON_PUBLIC(double) cJSON_GetNumberValue(const cJSON * const item); + +/* These functions check the type of an item */ +CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsFalse(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsTrue(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsBool(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNull(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsNumber(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsString(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsArray(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsObject(const cJSON * const item); +CJSON_PUBLIC(cJSON_bool) cJSON_IsRaw(const cJSON * const item); + +/* These calls create a cJSON item of the appropriate type. */ +CJSON_PUBLIC(cJSON *) cJSON_CreateNull(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateTrue(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateFalse(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateBool(cJSON_bool boolean); +CJSON_PUBLIC(cJSON *) cJSON_CreateNumber(double num); +CJSON_PUBLIC(cJSON *) cJSON_CreateString(const char *string); +/* raw json */ +CJSON_PUBLIC(cJSON *) cJSON_CreateRaw(const char *raw); +CJSON_PUBLIC(cJSON *) cJSON_CreateArray(void); +CJSON_PUBLIC(cJSON *) cJSON_CreateObject(void); + +/* Create a string where valuestring references a string so + * it will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateStringReference(const char *string); +/* Create an object/array that only references it's elements so + * they will not be freed by cJSON_Delete */ +CJSON_PUBLIC(cJSON *) cJSON_CreateObjectReference(const cJSON *child); +CJSON_PUBLIC(cJSON *) cJSON_CreateArrayReference(const cJSON *child); + +/* These utilities create an Array of count items. + * The parameter count cannot be greater than the number of elements in the number array, otherwise array access will be out of bounds.*/ +CJSON_PUBLIC(cJSON *) cJSON_CreateIntArray(const int *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateFloatArray(const float *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateDoubleArray(const double *numbers, int count); +CJSON_PUBLIC(cJSON *) cJSON_CreateStringArray(const char *const *strings, int count); + +/* Append item to the specified array/object. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObject(cJSON *object, const char *string, cJSON *item); +/* Use this when string is definitely const (i.e. a literal, or as good as), and will definitely survive the cJSON object. + * WARNING: When this function was used, make sure to always check that (item->type & cJSON_StringIsConst) is zero before + * writing to `item->string` */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemToObjectCS(cJSON *object, const char *string, cJSON *item); +/* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); +CJSON_PUBLIC(cJSON_bool) cJSON_AddItemReferenceToObject(cJSON *object, const char *string, cJSON *item); + +/* Remove/Detach items from Arrays/Objects. */ +CJSON_PUBLIC(cJSON *) cJSON_DetachItemViaPointer(cJSON *parent, cJSON * const item); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(void) cJSON_DeleteItemFromArray(cJSON *array, int which); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(cJSON *) cJSON_DetachItemFromObjectCaseSensitive(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObject(cJSON *object, const char *string); +CJSON_PUBLIC(void) cJSON_DeleteItemFromObjectCaseSensitive(cJSON *object, const char *string); + +/* Update array items. */ +CJSON_PUBLIC(cJSON_bool) cJSON_InsertItemInArray(cJSON *array, int which, cJSON *newitem); /* Shifts pre-existing items to the right. */ +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemViaPointer(cJSON * const parent, cJSON * const item, cJSON * replacement); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInArray(cJSON *array, int which, cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); +CJSON_PUBLIC(cJSON_bool) cJSON_ReplaceItemInObjectCaseSensitive(cJSON *object,const char *string,cJSON *newitem); + +/* Duplicate a cJSON item */ +CJSON_PUBLIC(cJSON *) cJSON_Duplicate(const cJSON *item, cJSON_bool recurse); +/* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will + * need to be released. With recurse!=0, it will duplicate any children connected to the item. + * The item->next and ->prev pointers are always zero on return from Duplicate. */ +/* Recursively compare two cJSON items for equality. If either a or b is NULL or invalid, they will be considered unequal. + * case_sensitive determines if object keys are treated case sensitive (1) or case insensitive (0) */ +CJSON_PUBLIC(cJSON_bool) cJSON_Compare(const cJSON * const a, const cJSON * const b, const cJSON_bool case_sensitive); + +/* Minify a strings, remove blank characters(such as ' ', '\t', '\r', '\n') from strings. + * The input pointer json cannot point to a read-only address area, such as a string constant, + * but should point to a readable and writable adress area. */ +CJSON_PUBLIC(void) cJSON_Minify(char *json); + +/* Helper functions for creating and adding items to an object at the same time. + * They return the added item or NULL on failure. */ +CJSON_PUBLIC(cJSON*) cJSON_AddNullToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddTrueToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddFalseToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddBoolToObject(cJSON * const object, const char * const name, const cJSON_bool boolean); +CJSON_PUBLIC(cJSON*) cJSON_AddNumberToObject(cJSON * const object, const char * const name, const double number); +CJSON_PUBLIC(cJSON*) cJSON_AddStringToObject(cJSON * const object, const char * const name, const char * const string); +CJSON_PUBLIC(cJSON*) cJSON_AddRawToObject(cJSON * const object, const char * const name, const char * const raw); +CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char * const name); +CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name); + +/* When assigning an integer value, it needs to be propagated to valuedouble too. */ +#define cJSON_SetIntValue(object, number) ((object) ? (object)->valueint = (object)->valuedouble = (number) : (number)) +/* helper for the cJSON_SetNumberValue macro */ +CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); +#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (double)number) : (number)) +/* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ +CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); + +/* Macro for iterating over an array or object */ +#define cJSON_ArrayForEach(element, array) for(element = (array != NULL) ? (array)->child : NULL; element != NULL; element = element->next) + +/* malloc/free objects using the malloc/free functions that have been set with cJSON_InitHooks */ +CJSON_PUBLIC(void *) cJSON_malloc(size_t size); +CJSON_PUBLIC(void) cJSON_free(void *object); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/cjson/include/cJSON_Utils.h b/firmware/esp-idf/wumei-smart-firmware/components/cjson/include/cJSON_Utils.h new file mode 100644 index 00000000..60f8c20b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/cjson/include/cJSON_Utils.h @@ -0,0 +1,85 @@ +/* + Copyright (c) 2009-2017 Dave Gamble and cJSON contributors + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ + +#ifndef cJSON_Utils__h +#define cJSON_Utils__h + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "cJSON.h" + +/* Implement RFC6901 (https://tools.ietf.org/html/rfc6901) JSON Pointer spec. */ +CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointer(cJSON * const object, const char *pointer); +CJSON_PUBLIC(cJSON *) cJSONUtils_GetPointerCaseSensitive(cJSON * const object, const char *pointer); + +/* Implement RFC6902 (https://tools.ietf.org/html/rfc6902) JSON Patch spec. */ +/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */ +CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatches(cJSON * const from, cJSON * const to); +CJSON_PUBLIC(cJSON *) cJSONUtils_GeneratePatchesCaseSensitive(cJSON * const from, cJSON * const to); +/* Utility for generating patch array entries. */ +CJSON_PUBLIC(void) cJSONUtils_AddPatchToArray(cJSON * const array, const char * const operation, const char * const path, const cJSON * const value); +/* Returns 0 for success. */ +CJSON_PUBLIC(int) cJSONUtils_ApplyPatches(cJSON * const object, const cJSON * const patches); +CJSON_PUBLIC(int) cJSONUtils_ApplyPatchesCaseSensitive(cJSON * const object, const cJSON * const patches); + +/* +// Note that ApplyPatches is NOT atomic on failure. To implement an atomic ApplyPatches, use: +//int cJSONUtils_AtomicApplyPatches(cJSON **object, cJSON *patches) +//{ +// cJSON *modme = cJSON_Duplicate(*object, 1); +// int error = cJSONUtils_ApplyPatches(modme, patches); +// if (!error) +// { +// cJSON_Delete(*object); +// *object = modme; +// } +// else +// { +// cJSON_Delete(modme); +// } +// +// return error; +//} +// Code not added to library since this strategy is a LOT slower. +*/ + +/* Implement RFC7386 (https://tools.ietf.org/html/rfc7396) JSON Merge Patch spec. */ +/* target will be modified by patch. return value is new ptr for target. */ +CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatch(cJSON *target, const cJSON * const patch); +CJSON_PUBLIC(cJSON *) cJSONUtils_MergePatchCaseSensitive(cJSON *target, const cJSON * const patch); +/* generates a patch to move from -> to */ +/* NOTE: This modifies objects in 'from' and 'to' by sorting the elements by their key */ +CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatch(cJSON * const from, cJSON * const to); +CJSON_PUBLIC(cJSON *) cJSONUtils_GenerateMergePatchCaseSensitive(cJSON * const from, cJSON * const to); + +/* Given a root object and a target object, construct a pointer from one to the other. */ +CJSON_PUBLIC(char *) cJSONUtils_FindPointerFromObjectTo(const cJSON * const object, const cJSON * const target); + +/* Sorts the members of the object into alphabetical order. */ +CJSON_PUBLIC(void) cJSONUtils_SortObject(cJSON * const object); +CJSON_PUBLIC(void) cJSONUtils_SortObjectCaseSensitive(cJSON * const object); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/components/example/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/example/CMakeLists.txt new file mode 100644 index 00000000..573e91dc --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/example/CMakeLists.txt @@ -0,0 +1,3 @@ +set(COMPONENT_SRCS "example.c") +set(COMPONENT_ADD_INCLUDEDIRS ". include") +register_component() diff --git a/firmware/esp-idf/wumei-smart-firmware/components/example/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/example/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/example/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/components/example/example.c b/firmware/esp-idf/wumei-smart-firmware/components/example/example.c new file mode 100644 index 00000000..48f8ea8d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/example/example.c @@ -0,0 +1,7 @@ + +#include "example.h" +#include +void example() +{ + printf("example\n"); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/components/example/include/example.h b/firmware/esp-idf/wumei-smart-firmware/components/example/include/example.h new file mode 100644 index 00000000..5960526f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/example/include/example.h @@ -0,0 +1,9 @@ + +#ifndef _IOT_EXAMPLE2_H_ +#define _IOT_EXAMPLE2_H_ + + +void example(); + + +#endif diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/CMakeLists.txt new file mode 100644 index 00000000..1d8070fc --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "sht3x.c" + INCLUDE_DIRS include + REQUIRES bus) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/README.md b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/README.md new file mode 100644 index 00000000..6a31572e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/README.md @@ -0,0 +1,17 @@ +# SHT3x + +- This component will show you how to use I2C module read external i2c sensor data, here we use SHT3x-series temperature and humidity sensor(SHT30 is used this component). +- Pin assignment: + - GPIO21 is assigned as the data signal of i2c master port + - GPIO22 is assigned as the clock signal of i2c master port + - Connection: + * connect sda of sensor with GPIO21 + * connect scl of sensor with GPIO22 +- SHT3x measurement mode: + * single shot data acquisition mode: in this mode one issued measurement command triggers the acquisition of one data pair. + * periodic data acquisition mode: in this mode one issued measurement command yields a stream of data pairs. when use periodic data acquisition mode, you should send hex code 0xE000 firstly, and send 0x3093 to stop periodic mode. + +# Notice + +- SHT3x uses 8-bit CRC checksum, you can see `CheckCrc8() `for detail. +- The raw measurement data needs to be convert to physical scale. formulas are shown in `sht3x_get_humiture()` \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/component.mk new file mode 100644 index 00000000..ad13f55d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/component.mk @@ -0,0 +1,8 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + + +COMPONENT_ADD_INCLUDEDIRS := include +COMPONENT_SRCDIRS := . \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/include/sht3x.h b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/include/sht3x.h new file mode 100644 index 00000000..f17f59c0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/include/sht3x.h @@ -0,0 +1,233 @@ +// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef _SHT3x_H_ +#define _SHT3x_H_ + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "driver/i2c.h" +#include "i2c_bus.h" +#include "esp_log.h" +#include "math.h" + +typedef enum { + + SOFT_RESET_CMD = 0x30A2, /*!< Command to soft reset*/ + READOUT_FOR_PERIODIC_MODE = 0xE000, /*!< Command to read Periodic*/ + READ_SERIAL_NUMBER = 0x3780, /*!< Command to read senser number*/ + SHT3x_STOP_PERIODIC = 0x3093, /*!< Command to break or stop periodic mode*/ + SHT3x_ART_CMD = 0x2B32, /*!< Command to accelerated response time*/ + + /* Single Shot Data Acquisition Mode*/ + SHT3x_SINGLE_HIGH_ENABLED = 0x2C06, /*!< Command to set measure mode as Single Shot Data Acquisition mode in high repeatability and Clock Stretching enabled*/ + SHT3x_SINGLE_MEDIUM_ENABLED = 0x2C0D, /*!< Command to set measure mode as Single Shot Data Acquisition mode in medium repeatability and Clock Stretching enabled*/ + SHT3x_SINGLE_LOW_ENABLED = 0x2C10, /*!< Command to set measure mode as Single Shot Data Acquisition mode in low repeatability and Clock Stretching enabled*/ + SHT3x_SINGLE_HIGH_DISABLED = 0x2400, /*!< Command to set measure mode as Single Shot Data Acquisition mode in high repeatability and Clock Stretching disabled*/ + SHT3x_SINGLE_MEDIUM_DISABLED = 0x240B, /*!< Command to set measure mode as Single Shot Data Acquisition mode in medium repeatability and Clock Stretching disabled*/ + SHT3x_SINGLE_LOW_DISABLED = 0x2416, /*!< Command to set measure mode as Single Shot Data Acquisition mode in low repeatability and Clock Stretching disabled*/ + + /* Periodic Data Acquisition mode*/ + SHT3x_PER_0_5_HIGH = 0x2032, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 0.5 mps*/ + SHT3x_PER_0_5_MEDIUM = 0x2024, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 0.5 mps*/ + SHT3x_PER_0_5_LOW = 0x202F, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 0.5 mps*/ + SHT3x_PER_1_HIGH = 0x2130, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 1 mps*/ + SHT3x_PER_1_MEDIUM = 0x2126, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 1 mps*/ + SHT3x_PER_1_LOW = 0x212D, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 1 mps*/ + SHT3x_PER_2_HIGH = 0x2236, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 2 mps*/ + SHT3x_PER_2_MEDIUM = 0x2220, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 2 mps*/ + SHT3x_PER_2_LOW = 0x222B, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 2 mps*/ + SHT3x_PER_4_HIGH = 0x2334, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 4 mps*/ + SHT3x_PER_4_MEDIUM = 0x2322, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 4 mps*/ + SHT3x_PER_4_LOW = 0x2329, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 4 mps*/ + SHT3x_PER_10_HIGH = 0x2737, /*!< Command to set measure mode as Periodic Data Acquisition mode in high repeatability and 10 mps*/ + SHT3x_PER_10_MEDIUM = 0x2721, /*!< Command to set measure mode as Periodic Data Acquisition mode in medium repeatability and 10 mps*/ + SHT3x_PER_10_LOW = 0x272A, /*!< Command to set measure mode as Periodic Data Acquisition mode in low repeatability and 10 mps*/ + + /* cmd for sht3x heater condition*/ + SHT3x_HEATER_ENABLE = 0x306D, /*!< Command to enable the heater*/ + SHT3x_HEATER_DISABLED = 0x3066, /*!< Command to disable the heater*/ +} sht3x_cmd_measure_t; + +typedef enum { + SHT3x_ADDR_PIN_SELECT_VSS = 0x44, /*!< set address PIN select VSS */ + SHT3x_ADDR_PIN_SELECT_VDD = 0x45, /*!< set address PIN select VDD */ +} sht3x_set_address_t; + +typedef void *sht3x_handle_t; + +/** + * @brief Create sht3x handle_t + * + * @param bus sensorice object handle of sht3x + * @param dev_addr sensorice address + * + * @return + * - sht3x handle_t + */ +sht3x_handle_t sht3x_create(i2c_bus_handle_t bus, uint8_t dev_addr); + +/** + * @brief Delete sht3x handle_t + * + * @param sensor point to sensorice object handle of sht3x + * + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_delete(sht3x_handle_t *sensor); + +/** + * @brief Get temperature and humidity + * + * @param sensor object handle of shd3x + * @param Tem_val temperature data buffer + * @param Hum_val humidity data buffer + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_get_humiture(sht3x_handle_t sensor, float *Tem_val, float *Hum_val); + +/** + * @brief Get temperature and humidity just once + * + * @param sensor object handle of shd3x + * @param Tem_val temperature data + * @param Hum_val humidity data + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_get_single_shot(sht3x_handle_t sensor, float *Tem_val, float *Hum_val); + +/** + * @brief Soft reset for sht3x + * + * @param sensor object handle of sht3x + * + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_soft_reset(sht3x_handle_t sensor); + +/** + * @brief stop or break or stop periodic mode + * + * @param sensor object handle of sht3x + * + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_stop_periodic(sht3x_handle_t sensor); + +/** + * @brief accelerated response time + * + * @param sensor object handle of sht3x + * + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_art(sht3x_handle_t sensor); + +/** + * @brief set measure mode of sht3x + * + * @param sensor object handle of shd3x + * @param sht3x_cmd_measure_t the instruction to set measurement mode + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t sht3x_set_measure_mode(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_measure_mode); + +/** + * @brief change the condition of sht3x heater + * + * @param sensor object handle of shd3x + * @param sht3x_cmd_measure_t the instruction to turn on/off heater of sht3x + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + * @note + * the default condition of heater is disabled + */ +esp_err_t sht3x_heater(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_heater_condition); + +/***implements of humiture hal interface****/ +#ifdef CONFIG_SENSOR_HUMITURE_INCLUDED_SHT3X + +/** + * @brief initialize sht3x with default configurations + * + * @param i2c_bus i2c bus handle the sensor will attached to + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t humiture_sht3x_init(i2c_bus_handle_t handle); + +/** + * @brief de-initialize sht3x + * + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t humiture_sht3x_deinit(void); + +/** + * @brief test if sht3x is active + * + * @return + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t humiture_sht3x_test(void); + +/** + * @brief acquire relative humidity result one time. + * + * @param h point to result data (unit:percentage) + * @return esp_err_t + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t humiture_sht3x_acquire_humidity(float *h); + +/** + * @brief acquire temperature result one time. + * + * @param t point to result data (unit:dCelsius) + * @return esp_err_t + * - ESP_OK Success + * - ESP_FAIL Fail + */ +esp_err_t humiture_sht3x_acquire_temperature(float *t); + +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/sht3x.c b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/sht3x.c new file mode 100644 index 00000000..50357914 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/sht3x.c @@ -0,0 +1,318 @@ +// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include "driver/i2c.h" +#include "i2c_bus.h" +#include "esp_log.h" +#include "esp_system.h" +#include "sht3x.h" + +typedef struct { + i2c_bus_device_handle_t i2c_dev; + uint8_t dev_addr; +} sht3x_sensor_t; + +sht3x_handle_t sht3x_create(i2c_bus_handle_t bus, uint8_t dev_addr) +{ + sht3x_sensor_t *sens = (sht3x_sensor_t *) calloc(1, sizeof(sht3x_sensor_t)); + sens->i2c_dev = i2c_bus_device_create(bus, dev_addr, i2c_bus_get_current_clk_speed(bus)); + if (sens->i2c_dev == NULL) { + free(sens); + return NULL; + } + sens->dev_addr = dev_addr; + return (sht3x_handle_t) sens; +} + +esp_err_t sht3x_delete(sht3x_handle_t *sensor) +{ + if (*sensor == NULL) { + return ESP_OK; + } + + sht3x_sensor_t *sens = (sht3x_sensor_t *)(*sensor); + i2c_bus_device_delete(&sens->i2c_dev); + free(sens); + *sensor = NULL; + return ESP_OK; +} + +static esp_err_t sht3x_write_cmd(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_cmd) +{ + sht3x_sensor_t *sens = (sht3x_sensor_t *) sensor; + uint8_t cmd_buffer[2]; + cmd_buffer[0] = sht3x_cmd >> 8; + cmd_buffer[1] = sht3x_cmd; + esp_err_t ret = i2c_bus_write_bytes(sens->i2c_dev, NULL_I2C_MEM_ADDR, 2, cmd_buffer); + return ret; +} + +static esp_err_t sht3x_get_data(sht3x_handle_t sensor, uint8_t data_len, uint8_t *data_arr) +{ + sht3x_sensor_t *sens = (sht3x_sensor_t *) sensor; + esp_err_t ret = i2c_bus_read_bytes(sens->i2c_dev, NULL_I2C_MEM_ADDR, data_len, data_arr); + return ret; +} + +static uint8_t CheckCrc8(uint8_t *const message, uint8_t initial_value) +{ + uint8_t crc; + int i = 0, j = 0; + crc = initial_value; + + for (j = 0; j < 2; j++) { + crc ^= message[j]; + for (i = 0; i < 8; i++) { + if (crc & 0x80) { + crc = (crc << 1) ^ 0x31; /*!< 0x31 is Polynomial for 8-bit CRC checksum*/ + } else { + crc = (crc << 1); + } + } + } + + return crc; +} + +esp_err_t sht3x_measure_period(bool set, uint16_t *min_delay) +{ + static uint16_t s_min_delay = 250; + + //get value + if (!set) { + *min_delay = s_min_delay; + return ESP_OK; + } + + //set value + uint8_t delay_mode = *min_delay >> 8; + switch (delay_mode) { + case 0x20: + s_min_delay = 2000; + break; + case 0x21: + s_min_delay = 1000; + break; + case 0x22: + s_min_delay = 500; + break; + case 0x24: + s_min_delay = 250; + break; + case 0x27: + s_min_delay = 100; + break; + default: + s_min_delay = 250; + break; + } + return ESP_OK; +} + +esp_err_t sht3x_get_humiture(sht3x_handle_t sensor, float *Tem_val, float *Hum_val) +{ + uint8_t buff[6]; + uint16_t tem, hum; + float Temperature = 0; + float Humidity = 0; + + sht3x_write_cmd(sensor, READOUT_FOR_PERIODIC_MODE); /*!< if you want to read data just onetime, Comment this code*/ + sht3x_get_data(sensor, 6, buff); + + /* check crc */ + if (CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5]) { + return ESP_FAIL; + } + + tem = (((uint16_t)buff[0] << 8) | buff[1]); + Temperature = (175.0 * (float)tem / 65535.0 - 45.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Celsius °C */ + //Temperature= (315.0*(float)tem/65535.0-49.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Fahrenheit °F */ + hum = (((uint16_t)buff[3] << 8) | buff[4]); + Humidity = (100.0 * (float)hum / 65535.0); /*!< RH = hum*100 / (2^16-1) */ + + if ((Temperature >= -20) && (Temperature <= 125) && (Humidity >= 0) && (Humidity <= 100)) { + *Tem_val = Temperature; + *Hum_val = Humidity; + return ESP_OK; /*!< here is mesurement range */ + } else { + return ESP_FAIL; + } + +} + +esp_err_t sht3x_get_single_shot(sht3x_handle_t sensor, float *Tem_val, float *Hum_val) +{ + uint8_t buff[6]; + uint16_t tem, hum; + static float Temperature = 0; + static float Humidity = 0; + static int64_t last_shot_time = 0; + int64_t current_time = esp_timer_get_time(); + uint16_t min_delay = 0; + int64_t min_delay_us = 0; + sht3x_measure_period(false, &min_delay); + min_delay_us = min_delay * 1000; + + if ((current_time - last_shot_time) < min_delay_us) { + *Tem_val = Temperature; + *Hum_val = Humidity; + return ESP_OK; + } + + esp_err_t ret = sht3x_get_data(sensor, 6, buff); + + /* check crc */ + if (ret != ESP_OK || CheckCrc8(buff, 0xFF) != buff[2] || CheckCrc8(&buff[3], 0xFF) != buff[5]) { + return ESP_FAIL; + } + + last_shot_time = current_time; + tem = (((uint16_t)buff[0] << 8) | buff[1]); + Temperature = (175.0 * (float)tem / 65535.0 - 45.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Celsius °C */ + //Temperature= (315.0*(float)tem/65535.0-49.0) ; /*!< T = -45 + 175 * tem / (2^16-1), this temperature conversion formula is for Fahrenheit °F */ + hum = (((uint16_t)buff[3] << 8) | buff[4]); + Humidity = (100.0 * (float)hum / 65535.0); /*!< RH = hum*100 / (2^16-1) */ + + if ((Temperature >= -20) && (Temperature <= 125) && (Humidity >= 0) && (Humidity <= 100)) { + *Tem_val = Temperature; + *Hum_val = Humidity; + return ESP_OK; + } else { + return ESP_FAIL; + } +} + +esp_err_t sht3x_soft_reset(sht3x_handle_t sensor) +{ + esp_err_t ret = sht3x_write_cmd(sensor, SOFT_RESET_CMD); + return ret; +} + +esp_err_t sht3x_stop_periodic(sht3x_handle_t sensor) +{ + esp_err_t ret = sht3x_write_cmd(sensor, SHT3x_STOP_PERIODIC); + return ret; +} + +esp_err_t sht3x_art(sht3x_handle_t sensor) +{ + esp_err_t ret = sht3x_write_cmd(sensor, SHT3x_ART_CMD); + return ret; +} + +esp_err_t sht3x_set_measure_mode(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_measure_mode) +{ + esp_err_t ret = sht3x_write_cmd(sensor, sht3x_measure_mode); + sht3x_measure_period(true, (uint16_t *)&sht3x_measure_mode); + return ret; +} + +esp_err_t sht3x_heater(sht3x_handle_t sensor, sht3x_cmd_measure_t sht3x_heater_condition) +{ + esp_err_t ret = sht3x_write_cmd(sensor, sht3x_heater_condition); + return ret; +} + +#ifdef CONFIG_SENSOR_HUMITURE_INCLUDED_SHT3X + +static sht3x_handle_t sht3x = NULL; +static bool is_init = false; + +esp_err_t humiture_sht3x_init(i2c_bus_handle_t i2c_bus) +{ + if (is_init || !i2c_bus) { + return ESP_FAIL; + } + + sht3x = sht3x_create(i2c_bus, SHT3x_ADDR_PIN_SELECT_VSS); + + if (!sht3x) { + return ESP_FAIL; + } + + esp_err_t ret = sht3x_set_measure_mode(sht3x, SHT3x_PER_4_MEDIUM); /**medium accuracy/repeatability with 250ms period (1000ms/4)**/ + + if (ret != ESP_OK) { + return ESP_FAIL; + } + + is_init = true; + return ESP_OK; +} + +esp_err_t humiture_sht3x_deinit(void) +{ + if (!is_init) { + return ESP_FAIL; + } + + esp_err_t ret = sht3x_delete(&sht3x); + + if (ret != ESP_OK) { + return ESP_FAIL; + } + + is_init = false; + return ESP_OK; +} + +esp_err_t humiture_sht3x_test(void) +{ + if (!is_init) { + return ESP_FAIL; + } + + return ESP_OK; +} + +esp_err_t humiture_sht3x_acquire_humidity(float *h) +{ + if (!is_init) { + return ESP_FAIL; + } + + float temperature = 0; + float humidity = 0; + esp_err_t ret = sht3x_get_single_shot(sht3x, &temperature, &humidity); + + if (ret == ESP_OK) { + *h = humidity; + return ESP_OK; + } + + *h = 0; + return ESP_FAIL; +} + +esp_err_t humiture_sht3x_acquire_temperature(float *t) +{ + if (!is_init) { + return ESP_FAIL; + } + + float temperature = 0; + float humidity = 0; + esp_err_t ret = sht3x_get_single_shot(sht3x, &temperature, &humidity); + + if (ret == ESP_OK) { + *t = temperature; + return ESP_OK; + } + + *t = 0; + return ESP_FAIL; +} + +#endif \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/CMakeLists.txt new file mode 100644 index 00000000..3f28fe05 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "sht3x_test.c" + INCLUDE_DIRS . + REQUIRES unity sht3x bus) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/component.mk b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/component.mk new file mode 100644 index 00000000..5dd172bd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/component.mk @@ -0,0 +1,5 @@ +# +#Component Makefile +# + +COMPONENT_ADD_LDFLAGS = -Wl,--whole-archive -l$(COMPONENT_NAME) -Wl,--no-whole-archive diff --git a/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/sht3x_test.c b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/sht3x_test.c new file mode 100644 index 00000000..8998058a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/components/sht3x/test/sht3x_test.c @@ -0,0 +1,73 @@ +// Copyright 2020-2021 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#include +#include "unity.h" +#include "driver/i2c.h" +#include "i2c_bus.h" +#include "esp_system.h" +#include "sht3x.h" + +#define I2C_MASTER_SCL_IO (gpio_num_t)22 /*!< gpio number for I2C master clock */ +#define I2C_MASTER_SDA_IO (gpio_num_t)21 /*!< gpio number for I2C master data */ +#define I2C_MASTER_NUM I2C_NUM_1 /*!< I2C port number for master dev */ +#define I2C_MASTER_FREQ_HZ 100000 /*!< I2C master clock frequency */ + +static i2c_bus_handle_t i2c_bus = NULL; +static sht3x_handle_t sht3x = NULL; + +/** + * @brief i2c master initialization + */ +static void sht3x_init_test() +{ + i2c_config_t conf = { + .mode = I2C_MODE_MASTER, + .sda_io_num = I2C_MASTER_SDA_IO, + .sda_pullup_en = GPIO_PULLUP_ENABLE, + .scl_io_num = I2C_MASTER_SCL_IO, + .scl_pullup_en = GPIO_PULLUP_ENABLE, + .master.clk_speed = I2C_MASTER_FREQ_HZ, + }; + i2c_bus = i2c_bus_create(I2C_MASTER_NUM, &conf); + sht3x = sht3x_create(i2c_bus, SHT3x_ADDR_PIN_SELECT_VSS); + sht3x_set_measure_mode(sht3x, SHT3x_PER_2_MEDIUM); /*!< here read data in periodic mode*/ +} + +static void sht3x_deinit_test() +{ + sht3x_delete(&sht3x); + i2c_bus_delete(&i2c_bus); +} + +void sht3x_get_data_test() +{ + float Tem_val, Hum_val; + int cnt = 10; + + while (cnt--) { + if (sht3x_get_humiture(sht3x, &Tem_val, &Hum_val) == 0) { + printf("temperature %.2f°C ", Tem_val); + printf("humidity:%.2f %%\n", Hum_val); + } + vTaskDelay(1000 / portTICK_RATE_MS); + } +} + +TEST_CASE("Sensor sht3x test", "[sht3x][iot][sensor]") +{ + sht3x_init_test(); + vTaskDelay(1000 / portTICK_RATE_MS); + sht3x_get_data_test(); + sht3x_deinit_test(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/README.md new file mode 100644 index 00000000..240e2703 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/README.md @@ -0,0 +1,45 @@ +# Examples + +This directory contains a range of example ESP-IDF projects. These are intended to demonstrate parts of ESP-IDF functionality, and to provide code that you can copy and adapt into your own projects. + +# Example Layout + +The examples are grouped into subdirectories by category. Each category directory contains one or more example projects: + +* `bluetooth/bluedroid` contains Classic BT, BLE and coex examples using default Bluedroid host stack. +* `bluetooth/nimble` contains BLE examples using NimBLE host stack. +* `bluetooth/esp_ble_mesh` contains ESP BLE Mesh examples. +* `bluetooth/hci` contains HCI transport (VHCI and HCI UART) examples +* `ethernet` contains Ethernet examples. +* `get-started` contains some very simple examples with minimal functionality. +* `mesh` contains Wi-Fi Mesh examples. +* `peripherals` contains examples showing driver functionality for the various onboard ESP32 peripherals. +* `protocols` contains examples showing network protocol interactions. +* `storage` contains examples showing data storage methods using SPI flash or external storage like the SD/MMC interface. +* `system` contains examples which demonstrate some internal chip features, or debugging & development tools. +* `wifi` contains examples of advanced Wi-Fi features. (For network protocol examples, see `protocols` instead.) +* `build_system` contains examples of build system features + +# Using Examples + +Building an example is the same as building any other project: + +* Follow the Getting Started instructions which include building the "Hello World" example. +* Change into the directory of the new example you'd like to build. +* Run `idf.py menuconfig` to open the project configuration menu. Most examples have a project-specific "Example Configuration" section here (for example, to set the WiFi SSID & password to use). +* `idf.py build` to build the example. +* Follow the printed instructions to flash, or run `idf.py -p PORT flash`. + +# Copying Examples + +Each example is a standalone project. The examples *do not have to be inside the esp-idf directory*. You can copy an example directory to anywhere on your computer in order to make a copy that you can modify and work with. + +The `IDF_PATH` environment variable is the only thing that connects the example to the rest of ESP-IDF. + +If you're looking for a more bare-bones project to start from, try [esp-idf-template](https://github.com/espressif/esp-idf-template). + +# Contributing Examples + +If you have a new example you think we'd like, please consider sending it to us as a Pull Request. + +In the ESP-IDF documentation, you can find a "Creating Examples" page which lays out the steps to creating a top quality example. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/README.md new file mode 100644 index 00000000..9a8aa7f1 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/README.md @@ -0,0 +1,12 @@ +# Bluetooth Examples for Bluedroid host + +Note: To use examples in this directory, you need to have Bluetooth enabled in configuration and Bluedroid selected as the host stack. + +# Example Layout + +The examples are grouped into subdirectories by category. Each category directory contains one or more example projects: +* `classic_bt` contains Classic BT examples +* `ble` contains BLE examples +* `coex` contains Classic BT and BLE coex examples + +See the [README.md](../../README.md) file in the upper level [examples](../../) directory for more information about examples. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/CMakeLists.txt new file mode 100644 index 00000000..84faba94 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_ancs) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/Makefile new file mode 100644 index 00000000..a5208ef0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_ancs + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/README.md new file mode 100644 index 00000000..270ecc85 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/README.md @@ -0,0 +1,21 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF BLE ANCS Example +========================== + +The purpose of the Apple Notification Center Service (ANCS) is to give Bluetooth accessories (that connect to iOS devices through a Bluetooth low-energy link) a simple and convenient way to access many kinds of notifications that are generated on iOS devices. + +The Apple Notification Center Service is a primary service whose service UUID is 7905F431-B5CE-4E99-A40F-4B1E122D00D0. + +Only one instance of the ANCS may be present on an NP. Due to the nature of iOS, the ANCS is not guaranteed to always be present. As a result, the NC should look for and subscribe to the Service Changed characteristic of the GATT service in order to monitor for the potential publishing and unpublishing of the ANCS at any time. + +In its basic form, the ANCS exposes three characteristics: +Notification Source: UUID 9FBF120D-6301-42D9-8C58-25E699A21DBD (notifiable) +Control Point: UUID 69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9 (writeable with response) +Data Source: UUID 22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB (notifiable) +All these characteristics require authorization for access. + + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/CMakeLists.txt new file mode 100644 index 00000000..460583de --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ble_ancs_demo.c" "ble_ancs.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c new file mode 100644 index 00000000..2035b675 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.c @@ -0,0 +1,228 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include "esp_log.h" +#include "ble_ancs.h" + +#define BLE_ANCS_TAG "BLE_ANCS" + +/* +| EventID(1 Byte) | EventFlags(1 Byte) | CategoryID(1 Byte) | CategoryCount(1 Byte) | NotificationUID(4 Bytes) | + +A GATT notification delivered through the Notification Source characteristic contains the following information: +* EventID: This field informs the accessory whether the given iOS notification was added, modified, or removed. The enumerated values for this field are defined + in EventID Values. +* EventFlags: A bitmask whose set bits inform an NC of specificities with the iOS notification. For example, if an iOS notification is considered “important”, + the NC may want to display a more aggressive user interface (UI) to make sure the user is properly alerted. The enumerated bits for this field + are defined in EventFlags. +* CategoryID: A numerical value providing a category in which the iOS notification can be classified. The NP will make a best effort to provide an accurate category + for each iOS notification. The enumerated values for this field are defined in CategoryID Values. +* CategoryCount: The current number of active iOS notifications in the given category. For example, if two unread emails are sitting in a user’s email inbox, and a new + email is pushed to the user’s iOS device, the value of CategoryCount is 3. +* NotificationUID: A 32-bit numerical value that is the unique identifier (UID) for the iOS notification. This value can be used as a handle in commands sent to the + Control Point characteristic to interact with the iOS notification. +*/ + +char *EventID_to_String(uint8_t EventID) +{ + char *str = NULL; + switch (EventID) + { + case EventIDNotificationAdded: + str = "New message"; + break; + case EventIDNotificationModified: + str = "Modified message"; + break; + case EventIDNotificationRemoved: + str = "Removed message"; + break; + default: + str = "unknown EventID"; + break; + } + return str; +} + +char *CategoryID_to_String(uint8_t CategoryID) +{ + char *Cidstr = NULL; + switch(CategoryID) { + case CategoryIDOther: + Cidstr = "Other"; + break; + case CategoryIDIncomingCall: + Cidstr = "IncomingCall"; + break; + case CategoryIDMissedCall: + Cidstr = "MissedCall"; + break; + case CategoryIDVoicemail: + Cidstr = "Voicemail"; + break; + case CategoryIDSocial: + Cidstr = "Social"; + break; + case CategoryIDSchedule: + Cidstr = "Schedule"; + break; + case CategoryIDEmail: + Cidstr = "Email"; + break; + case CategoryIDNews: + Cidstr = "News"; + break; + case CategoryIDHealthAndFitness: + Cidstr = "HealthAndFitness"; + break; + case CategoryIDBusinessAndFinance: + Cidstr = "BusinessAndFinance"; + break; + case CategoryIDLocation: + Cidstr = "Location"; + break; + case CategoryIDEntertainment: + Cidstr = "Entertainment"; + break; + default: + Cidstr = "Unknown CategoryID"; + break; + } + return Cidstr; +} + +/* +| EventID(1 Byte) | EventFlags(1 Byte) | CategoryID(1 Byte) | CategoryCount(1 Byte) | NotificationUID(4 Bytes) | +*/ + +void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len) +{ + if (!message || message_len < 5) { + return; + } + + uint8_t EventID = message[0]; + char *EventIDS = EventID_to_String(EventID); + uint8_t EventFlags = message[1]; + uint8_t CategoryID = message[2]; + char *Cidstr = CategoryID_to_String(CategoryID); + uint8_t CategoryCount = message[3]; + uint32_t NotificationUID = (message[4]) | (message[5]<< 8) | (message[6]<< 16) | (message[7] << 24); + ESP_LOGI(BLE_ANCS_TAG, "EventID:%s EventFlags:0x%x CategoryID:%s CategoryCount:%d NotificationUID:%d", EventIDS, EventFlags, Cidstr, CategoryCount, NotificationUID); +} + +void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len) +{ + //esp_log_buffer_hex("data source", message, message_len); + if (!message || message_len == 0) { + return; + } + uint8_t Command_id = message[0]; + switch (Command_id) + { + case CommandIDGetNotificationAttributes: { + uint32_t NotificationUID = (message[1]) | (message[2]<< 8) | (message[3]<< 16) | (message[4] << 24); + uint32_t remian_attr_len = message_len - 5; + uint8_t *attrs = &message[5]; + ESP_LOGI(BLE_ANCS_TAG, "recevice Notification Attributes response Command_id %d NotificationUID %d", Command_id, NotificationUID); + while(remian_attr_len > 0) { + uint8_t AttributeID = attrs[0]; + uint16_t len = attrs[1] | (attrs[2] << 8); + if(len > (remian_attr_len -3)) { + ESP_LOGE(BLE_ANCS_TAG, "data error"); + break; + } + switch (AttributeID) + { + case NotificationAttributeIDAppIdentifier: + esp_log_buffer_char("Identifier", &attrs[3], len); + break; + case NotificationAttributeIDTitle: + esp_log_buffer_char("Title", &attrs[3], len); + break; + case NotificationAttributeIDSubtitle: + esp_log_buffer_char("Subtitle", &attrs[3], len); + break; + case NotificationAttributeIDMessage: + esp_log_buffer_char("Message", &attrs[3], len); + break; + case NotificationAttributeIDMessageSize: + esp_log_buffer_char("MessageSize", &attrs[3], len); + break; + case NotificationAttributeIDDate: + //yyyyMMdd'T'HHmmSS + esp_log_buffer_char("Date", &attrs[3], len); + break; + case NotificationAttributeIDPositiveActionLabel: + esp_log_buffer_hex("PActionLabel", &attrs[3], len); + break; + case NotificationAttributeIDNegativeActionLabel: + esp_log_buffer_hex("NActionLabel", &attrs[3], len); + break; + default: + esp_log_buffer_hex("unknownAttributeID", &attrs[3], len); + break; + } + + attrs += (1 + 2 + len); + remian_attr_len -= (1 + 2 + len); + } + + break; + } + case CommandIDGetAppAttributes: + ESP_LOGI(BLE_ANCS_TAG, "recevice APP Attributes response"); + break; + case CommandIDPerformNotificationAction: + ESP_LOGI(BLE_ANCS_TAG, "recevice Perform Notification Action"); + break; + default: + ESP_LOGI(BLE_ANCS_TAG, "unknown Command ID"); + break; + } +} + +char *Errcode_to_String(uint16_t status) +{ + char *Errstr = NULL; + switch (status) { + case Unknown_command: + Errstr = "Unknown_command"; + break; + case Invalid_command: + Errstr = "Invalid_command"; + break; + case Invalid_parameter: + Errstr = "Invalid_parameter"; + break; + case Action_failed: + Errstr = "Action_failed"; + break; + default: + Errstr = "unknown_failed"; + break; + } + return Errstr; + +} + + + + + + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h new file mode 100644 index 00000000..64608ea0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs.h @@ -0,0 +1,124 @@ +/* + 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 +#include +#include + +//EventID values +typedef enum { + EventIDNotificationAdded = 0, + EventIDNotificationModified = 1, + EventIDNotificationRemoved = 2, + //Reserved EventID values = 3–255 +} esp_EventID; + +//EventFlags +typedef enum { + EventFlagSilent = (1 << 0), + EventFlagImportant = (1 << 1), + EventFlagPreExisting = (1 << 2), + EventFlagPositiveAction = (1 << 3), + EventFlagNegativeAction = (1 << 4), + //Reserved EventFlags = (1 << 5)–(1 << 7 +}esp_EventFlags; + +// CategoryID values +typedef enum { + CategoryIDOther = 0, + CategoryIDIncomingCall = 1, + CategoryIDMissedCall = 2, + CategoryIDVoicemail = 3, + CategoryIDSocial = 4, + CategoryIDSchedule = 5, + CategoryIDEmail = 6, + CategoryIDNews = 7, + CategoryIDHealthAndFitness = 8, + CategoryIDBusinessAndFinance = 9, + CategoryIDLocation = 10, + CategoryIDEntertainment = 11, + //Reserved CategoryID values = 12–255 +} esp_CategoryID; + +//CommandID values +typedef enum { + CommandIDGetNotificationAttributes = 0, + CommandIDGetAppAttributes = 1, + CommandIDPerformNotificationAction = 2, + //Reserved CommandID values = 3–255 +} esp_CommandID; + +//NotificationAttributeID +typedef enum { + NotificationAttributeIDAppIdentifier = 0, + NotificationAttributeIDTitle = 1, //(Needs to be followed by a 2-bytes max length parameter) + NotificationAttributeIDSubtitle = 2, //(Needs to be followed by a 2-bytes max length parameter) + NotificationAttributeIDMessage = 3, //(Needs to be followed by a 2-bytes max length parameter) + NotificationAttributeIDMessageSize = 4, + NotificationAttributeIDDate = 5, + NotificationAttributeIDPositiveActionLabel = 6, + NotificationAttributeIDNegativeActionLabel = 7, + //Reserved NotificationAttributeID values = 8–255 +} esp_NotificationAttributeID; + +/* +Note: The format of the NotificationAttributeIDMessageSize constant is a string that represents the integral value +of the message size. The format of the NotificationAttributeIDDate constant is a string that uses the Unicode Technical +Standard (UTS) #35 date format pattern yyyyMMdd'T'HHmmSS. The format of all the other constants in Table 3-5 are UTF-8 +strings. +*/ + +//ActionID values +typedef enum { + ActionIDPositive = 0, + ActionIDNegative = 1, + //Reserved ActionID values = 2–255 +} esp_ActionID; + +//AppAttributeID Values +typedef enum { + AppAttributeIDDisplayName = 0, + //Reserved AppAttributeID values = 1–255 +} esp_AppAttributeID; + +typedef struct { + uint8_t noti_attribute_id; + uint16_t attribute_len; +} esp_noti_attr_list_t; + +typedef enum { + Unknown_command = (0xA0), //The commandID was not recognized by the NP. + Invalid_command = (0xA1), //The command was improperly formatted. + Invalid_parameter = (0xA2), // One of the parameters (for example, the NotificationUID) does not refer to an existing object on the NP. + Action_failed = (0xA3), //The action was not performed +} esp_error_code; + +typedef enum { + attr_appidentifier_index = 0, //The commandID was not recognized by the NP. + attr_title_index, + attr_subtitle_index, + attr_message_index, + attr_messagesize_index, + attr_date_index, + attr_positiveactionlabel_index, + attr_negativeactionlabel_index, +} esp_attr_index; + +#define ESP_NOTIFICATIONUID_LEN 4 + + +char *EventID_to_String(uint8_t EventID); +char *CategoryID_to_String(uint8_t CategoryID); +void esp_receive_apple_notification_source(uint8_t *message, uint16_t message_len); +void esp_receive_apple_data_source(uint8_t *message, uint16_t message_len); +char *Errcode_to_String(uint16_t status); + + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c new file mode 100644 index 00000000..4dc89b83 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/ble_ancs_demo.c @@ -0,0 +1,688 @@ +/* + 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 "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_defs.h" +#include "esp_bt_main.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_gatt_common_api.h" +#include "ble_ancs.h" + +#define BLE_ANCS_TAG "BLE_ANCS" +#define EXAMPLE_DEVICE_NAME "ESP_BLE_ANCS" +#define PROFILE_A_APP_ID 0 +#define PROFILE_NUM 1 +#define ADV_CONFIG_FLAG (1 << 0) +#define SCAN_RSP_CONFIG_FLAG (1 << 1) +#define INVALID_HANDLE 0 +static uint8_t adv_config_done = 0; +static bool get_service = false; +static esp_gattc_char_elem_t *char_elem_result = NULL; +static esp_gattc_descr_elem_t *descr_elem_result = NULL; +static void periodic_timer_callback(void* arg); +esp_timer_handle_t periodic_timer; + +const esp_timer_create_args_t periodic_timer_args = { + .callback = &periodic_timer_callback, + /* name is optional, but may help identify the timer when debugging */ + .name = "periodic" +}; + +struct data_source_buffer { + uint8_t buffer[1024]; + uint16_t len; +}; + +static struct data_source_buffer data_buffer = {0}; + +//In its basic form, the ANCS exposes three characteristics: +// service UUID: 7905F431-B5CE-4E99-A40F-4B1E122D00D0 +uint8_t Apple_NC_UUID[16] = {0xD0, 0x00, 0x2D, 0x12, 0x1E, 0x4B, 0x0F, 0xA4, 0x99, 0x4E, 0xCE, 0xB5, 0x31, 0xF4, 0x05, 0x79}; +// Notification Source UUID: 9FBF120D-6301-42D9-8C58-25E699A21DBD(notifiable) +uint8_t notification_source[16] = {0xbd, 0x1d, 0xa2, 0x99, 0xe6, 0x25, 0x58, 0x8c, 0xd9, 0x42, 0x01, 0x63, 0x0d, 0x12, 0xbf, 0x9f}; +// Control Point UUID:69D1D8F3-45E1-49A8-9821-9BBDFDAAD9D9(writeable with response) +uint8_t control_point[16] = {0xd9, 0xd9, 0xaa, 0xfd, 0xbd, 0x9b, 0x21, 0x98, 0xa8, 0x49, 0xe1, 0x45, 0xf3, 0xd8, 0xd1, 0x69}; +// Data Source UUID:22EAC6E9-24D6-4BB5-BE44-B36ACE7C7BFB(notifiable) +uint8_t data_source[16] = {0xfb, 0x7b, 0x7c, 0xce, 0x6a, 0xb3, 0x44, 0xbe, 0xb5, 0x4b, 0xd6, 0x24, 0xe9, 0xc6, 0xea, 0x22}; + +/* +Note: There may be more characteristics present in the ANCS than the three listed above. That said, an NC may ignore any characteristic it does not recognize. +*/ + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + +static esp_bt_uuid_t apple_nc_uuid = { + .len = ESP_UUID_LEN_128, +}; + +static uint8_t hidd_service_uuid128[] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00, +}; + +// config adv data +static esp_ble_adv_data_t adv_config = { + .set_scan_rsp = false, + .include_txpower = false, + .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec + .appearance = ESP_BLE_APPEARANCE_GENERIC_HID, + .service_uuid_len = sizeof(hidd_service_uuid128), + .p_service_uuid = hidd_service_uuid128, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; +// config scan response data +static esp_ble_adv_data_t scan_rsp_config = { + .set_scan_rsp = true, + .include_name = true, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, +}; + +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x100, + .adv_int_max = 0x100, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t notification_source_handle; + uint16_t data_source_handle; + uint16_t contol_point_handle; + esp_bd_addr_t remote_bda; + uint16_t MTU_size; +}; + +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +esp_noti_attr_list_t p_attr[8] = { + [attr_appidentifier_index] = { + .noti_attribute_id = NotificationAttributeIDAppIdentifier, + .attribute_len = 0, + }, + [attr_title_index] = { + .noti_attribute_id = NotificationAttributeIDTitle, + .attribute_len = 0xFFFF, + }, + [attr_subtitle_index] = { + .noti_attribute_id = NotificationAttributeIDSubtitle, + .attribute_len = 0xFFFF, + }, + [attr_message_index] = { + .noti_attribute_id = NotificationAttributeIDMessage, + .attribute_len = 0xFFFF, + }, + [attr_messagesize_index] = { + .noti_attribute_id = NotificationAttributeIDMessageSize, + .attribute_len = 0, + }, + [attr_date_index] = { + .noti_attribute_id = NotificationAttributeIDDate, + .attribute_len = 0, + }, + [attr_positiveactionlabel_index] = { + .noti_attribute_id = NotificationAttributeIDPositiveActionLabel, + .attribute_len = 0, + }, + [attr_negativeactionlabel_index] = { + .noti_attribute_id = NotificationAttributeIDNegativeActionLabel, + .attribute_len = 0, + }, + +}; + +/* + | CommandID(1 Byte) | NotificationUID(4 Bytes) | AttributeIDs | +*/ + +void esp_get_notification_attributes(uint8_t *notificationUID, uint8_t num_attr, esp_noti_attr_list_t *p_attr) +{ + uint8_t cmd[600] = {0}; + uint32_t index = 0; + cmd[0] = CommandIDGetNotificationAttributes; + index ++; + memcpy(&cmd[index], notificationUID, ESP_NOTIFICATIONUID_LEN); + index += ESP_NOTIFICATIONUID_LEN; + while(num_attr > 0) { + cmd[index ++] = p_attr->noti_attribute_id; + if (p_attr->attribute_len > 0) { + cmd[index ++] = p_attr->attribute_len; + cmd[index ++] = (p_attr->attribute_len << 8); + } + p_attr ++; + num_attr --; + } + + esp_ble_gattc_write_char( gl_profile_tab[PROFILE_A_APP_ID].gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle, + index, + cmd, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); +} + +void esp_get_app_attributes(uint8_t *appidentifier, uint16_t appidentifier_len, uint8_t num_attr, uint8_t *p_app_attrs) +{ + uint8_t buffer[600] = {0}; + uint32_t index = 0; + buffer[0] = CommandIDGetAppAttributes; + index ++; + memcpy(&buffer[index], appidentifier, appidentifier_len); + index += appidentifier_len; + memcpy(&buffer[index], p_app_attrs, num_attr); + index += num_attr; + + esp_ble_gattc_write_char( gl_profile_tab[PROFILE_A_APP_ID].gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle, + index, + buffer, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); +} + +void esp_perform_notification_action(uint8_t *notificationUID, uint8_t ActionID) +{ + uint8_t buffer[600] = {0}; + uint32_t index = 0; + buffer[0] = CommandIDPerformNotificationAction; + index ++; + memcpy(&buffer[index], notificationUID, ESP_NOTIFICATIONUID_LEN); + index += ESP_NOTIFICATIONUID_LEN; + buffer[index] = ActionID; + index ++; + esp_ble_gattc_write_char( gl_profile_tab[PROFILE_A_APP_ID].gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle, + index, + buffer, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); +} + +static void periodic_timer_callback(void* arg) +{ + esp_timer_stop(periodic_timer); + if (data_buffer.len > 0) { + esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len); + memset(data_buffer.buffer, 0, data_buffer.len); + data_buffer.len = 0; + } +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + ESP_LOGV(BLE_ANCS_TAG, "GAP_EVT, event %d\n", event); + + switch (event) { + 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; + 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_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(BLE_ANCS_TAG, "advertising start failed, error status = %x", param->adv_start_cmpl.status); + break; + } + ESP_LOGI(BLE_ANCS_TAG, "advertising start success"); + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT"); + /* Call the following function to input the passkey which is displayed on the remote device */ + //esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00); + break; + case ESP_GAP_BLE_OOB_REQ_EVT: { + ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk + esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk)); + break; + } + case ESP_GAP_BLE_NC_REQ_EVT: + /* The app will receive this evt 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_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true); + ESP_LOGI(BLE_ANCS_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", 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(BLE_ANCS_TAG, "The passkey Notify number:%06d", param->ble_security.key_notif.passkey); + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: { + esp_log_buffer_hex("addr", param->ble_security.auth_cmpl.bd_addr, ESP_BD_ADDR_LEN); + ESP_LOGI(BLE_ANCS_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); + if (!param->ble_security.auth_cmpl.success) { + ESP_LOGI(BLE_ANCS_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason); + } + break; + } + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(BLE_ANCS_TAG, "config local privacy failed, error status = %x", param->local_privacy_cmpl.status); + break; + } + + esp_err_t ret = esp_ble_gap_config_adv_data(&adv_config); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "config adv data failed, error code = %x", ret); + } else { + adv_config_done |= ADV_CONFIG_FLAG; + } + + ret = esp_ble_gap_config_adv_data(&scan_rsp_config); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "config adv data failed, error code = %x", ret); + } else { + adv_config_done |= SCAN_RSP_CONFIG_FLAG; + } + + break; + default: + break; + } +} + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(BLE_ANCS_TAG, "REG_EVT"); + esp_ble_gap_set_device_name(EXAMPLE_DEVICE_NAME); + esp_ble_gap_config_local_icon (ESP_BLE_APPEARANCE_GENERIC_WATCH); + //generate a resolvable random address + esp_ble_gap_config_local_privacy(true); + break; + case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "open failed, error status = %x", param->open.status); + break; + } + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_OPEN_EVT"); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->open.conn_id; + esp_ble_set_encryption(param->open.remote_bda, ESP_BLE_SEC_ENCRYPT_MITM); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, param->open.conn_id); + if (mtu_ret) { + ESP_LOGE(BLE_ANCS_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status); + } + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + gl_profile_tab[PROFILE_A_APP_ID].MTU_size = param->cfg_mtu.mtu; + memcpy(apple_nc_uuid.uuid.uuid128, Apple_NC_UUID,16); + esp_ble_gattc_search_service(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, &apple_nc_uuid); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + if (param->search_res.srvc_id.uuid.len == ESP_UUID_LEN_128) { + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = param->search_res.start_handle; + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = param->search_res.end_handle; + get_service = true; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (param->search_cmpl.status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "search service failed, error status = %x", param->search_cmpl.status); + break; + } + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_SEARCH_CMPL_EVT"); + if (get_service) { + uint16_t count = 0; + uint16_t offset = 0; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__); + } + if (count > 0) { + char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + memset(char_elem_result, 0xff, sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result) { + ESP_LOGE(BLE_ANCS_TAG, "gattc no mem"); + } else { + ret_status = esp_ble_gattc_get_all_char(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + char_elem_result, + &count, + offset); + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_all_char error, %d", __LINE__); + } + if (count > 0) { + + for (int i = 0; i < count; i ++) { + if (char_elem_result[i].uuid.len == ESP_UUID_LEN_128) { + if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY + && memcmp(char_elem_result[i].uuid.uuid.uuid128, notification_source, 16) == 0) { + gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle = char_elem_result[i].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[i].char_handle); + ESP_LOGI(BLE_ANCS_TAG, "Find Apple noticification source char"); + + } else if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY + && memcmp(char_elem_result[i].uuid.uuid.uuid128, data_source, 16) == 0) { + gl_profile_tab[PROFILE_A_APP_ID].data_source_handle = char_elem_result[i].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[i].char_handle); + ESP_LOGI(BLE_ANCS_TAG, "Find Apple data source char"); + + } else if (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_WRITE + && memcmp(char_elem_result[i].uuid.uuid.uuid128, control_point, 16) == 0) { + gl_profile_tab[PROFILE_A_APP_ID].contol_point_handle = char_elem_result[i].char_handle; + ESP_LOGI(BLE_ANCS_TAG, "Find Apple control point char"); + + } + } + } + } + } + free(char_elem_result); + } + } else { + ESP_LOGE(BLE_ANCS_TAG, "No Apple Notification Service found"); + } + + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (param->reg_for_notify.status != ESP_GATT_OK) { + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT status %d", param->reg_for_notify.status); + break; + } + uint16_t count = 0; + uint16_t offset = 0; + //uint16_t notify_en = 1; + uint8_t notify_en[2] = {0x01, 0x00}; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + param->reg_for_notify.handle, + &count); + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__); + } + if (count > 0) { + descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result) { + ESP_LOGE(BLE_ANCS_TAG, "malloc error, gattc no mem"); + } else { + ret_status = esp_ble_gattc_get_all_descr(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + param->reg_for_notify.handle, + descr_elem_result, + &count, + offset); + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "esp_ble_gattc_get_all_descr error, %d", __LINE__); + } + + for (int i = 0; i < count; ++ i) { + if (descr_elem_result[i].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[i].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + esp_ble_gattc_write_char_descr (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result[i].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + + break; + } + } + } + free(descr_elem_result); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: + //esp_log_buffer_hex(BLE_ANCS_TAG, param->notify.value, param->notify.value_len); + if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].notification_source_handle) { + esp_receive_apple_notification_source(param->notify.value, param->notify.value_len); + uint8_t *notificationUID = ¶m->notify.value[4]; + if (param->notify.value[0] == EventIDNotificationAdded && param->notify.value[2] == CategoryIDIncomingCall) { + ESP_LOGI(BLE_ANCS_TAG, "IncomingCall, reject"); + //Call reject + esp_perform_notification_action(notificationUID, ActionIDNegative); + } else if (param->notify.value[0] == EventIDNotificationAdded) { + //get more information + ESP_LOGI(BLE_ANCS_TAG, "Get detailed information"); + esp_get_notification_attributes(notificationUID, sizeof(p_attr)/sizeof(esp_noti_attr_list_t), p_attr); + } + } else if (param->notify.handle == gl_profile_tab[PROFILE_A_APP_ID].data_source_handle) { + memcpy(&data_buffer.buffer[data_buffer.len], param->notify.value, param->notify.value_len); + data_buffer.len += param->notify.value_len; + if (param->notify.value_len == (gl_profile_tab[PROFILE_A_APP_ID].MTU_size - 3)) { + // cpoy and wait next packet, start timer 500ms + esp_timer_start_periodic(periodic_timer, 500000); + } else { + esp_timer_stop(periodic_timer); + esp_receive_apple_data_source(data_buffer.buffer, data_buffer.len); + memset(data_buffer.buffer, 0, data_buffer.len); + data_buffer.len = 0; + } + } else { + ESP_LOGI(BLE_ANCS_TAG, "unknown handle, receive notify value:"); + } + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (param->write.status != ESP_GATT_OK) { + ESP_LOGE(BLE_ANCS_TAG, "write descr failed, error status = %x", param->write.status); + break; + } + //ESP_LOGI(BLE_ANCS_TAG, "write descr successfully"); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:"); + esp_log_buffer_hex(BLE_ANCS_TAG, param->srvc_chg.remote_bda, 6); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: + if (param->write.status != ESP_GATT_OK) { + char *Errstr = Errcode_to_String(param->write.status); + if (Errstr) { + ESP_LOGE(BLE_ANCS_TAG, "write control point error %s", Errstr); + } + break; + } + //ESP_LOGI(BLE_ANCS_TAG, "Write char success "); + break; + case ESP_GATTC_DISCONNECT_EVT: + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", param->disconnect.reason); + get_service = false; + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GATTC_CONNECT_EVT: + //ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_CONNECT_EVT"); + //esp_log_buffer_hex("bda", param->connect.remote_bda, 6); + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, param->connect.remote_bda, 6); + // create gattc virtual connection + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, BLE_ADDR_TYPE_RANDOM, true); + break; + case ESP_GATTC_DIS_SRVC_CMPL_EVT: + ESP_LOGI(BLE_ANCS_TAG, "ESP_GATTC_DIS_SRVC_CMPL_EVT"); + break; + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(BLE_ANCS_TAG, "Reg app failed, app_id %04x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +void init_timer(void) +{ + ESP_ERROR_CHECK(esp_timer_create(&periodic_timer_args, &periodic_timer)); +} + +void app_main(void) +{ + esp_err_t ret; + + // Initialize NVS. + 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 ); + + // init timer + init_timer(); + + 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(BLE_ANCS_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret)); + return; + } + + ESP_LOGI(BLE_ANCS_TAG, "%s init bluetooth", __func__); + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "%s enable bluetooth failed: %s", __func__, esp_err_to_name(ret)); + return; + } + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "%s gattc register error, error code = %x\n", __func__, ret); + return; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "gap register error, error code = %x", ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "%s gattc app register error, error code = %x\n", __func__, ret); + } + + ret = esp_ble_gatt_set_local_mtu(500); + if (ret) { + ESP_LOGE(BLE_ANCS_TAG, "set local MTU failed, error code = %x", 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_NONE; //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; + //set static passkey + uint32_t passkey = 123456; + uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE; + uint8_t oob_support = ESP_BLE_OOB_DISABLE; + 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)); + esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t)); + /* If your BLE device acts 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 acts 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)); +} + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/component.mk new file mode 100644 index 00000000..f2f38c36 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/main/component.mk @@ -0,0 +1,9 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults new file mode 100644 index 00000000..ee53a228 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ancs/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/CMakeLists.txt new file mode 100644 index 00000000..a1bc6f47 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_compatibility_test) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/Makefile new file mode 100644 index 00000000..9026a1fe --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_compatibility_test + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/README.md new file mode 100644 index 00000000..091476dd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/README.md @@ -0,0 +1,19 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF BLE Compatibility Test Example +======================================= + +This demo is to test the compatibility of Bluetooth and mobile phones. + +* ESP32 Module: ESP-WROOM-32 + +* IDF version: 7c29a39d6f9f2dfbefc49d34d34e9267afc7200d + +* [Test case](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/ble_compatibility_test_case.md) + +* Test APK: LightBlue V1.1.3 + +* [Test report](https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/esp_ble_compatibility_test_report.md) + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/ble_compatibility_test_case.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/ble_compatibility_test_case.md new file mode 100644 index 00000000..84922d01 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/ble_compatibility_test_case.md @@ -0,0 +1,182 @@ +# 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] (https://github.com/espressif/esp-idf/blob/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test/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 + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/esp_ble_compatibility_test_report.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/esp_ble_compatibility_test_report.md new file mode 100644 index 00000000..dd11e242 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/esp_ble_compatibility_test_report.md @@ -0,0 +1,905 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Test Report for ESP BLE Smartphone Compatibility
ESP32 Module:ESP-WROOM-32
Commit ID:7c29a39d6f9f2dfbefc49d34d34e9267afc7200d
Test Demo:https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/ble_compatibility_test
Phone BrandModelOS VersionTest APP & VersionTest ItemNote
ADVPairingService DiscoveryRead & EncryptShort Read & WriteLong Read & WriteShort NotifyConnection Success Rate (10 times)Long Connection Stability
Samsung (三星)Galaxy S9Android 8.0.0LightBlue V1.1.3*PassPassPassPassPassPassPass100%Pass"LightBlue" here is the abbreviation of "LightBlue® Explorer"
Galaxy Note 4 Android 6.0.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Galaxy S8+Android 8.0.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
S3 GT-I9300Android 4.3nRF Connect V4.10*PassPassPassPassPassPassPass100%PassIntallation of LightBlue failed, so nRF Connect was used alternatively.
S4 GT-I9502Android 8.0.0nRF Connect V4.10*PassPassPassPassPassPassPass100%PassIntallation of LightBlue failed, so nRF Connect was used alternatively.
S4 GT-I9500Android 4.3nRF Connect V4.10*PassPassPassPassPassPassPass100%PassIntallation of LightBlue failed, so nRF Connect was used alternatively.
Apple(苹果)iPhone 5S/A1518iOS 12.1LightBlue V2.7PassPassPassPassPassPassPass100%Pass
iPhone XiOS 12.1LightBlue V2.7PassPassPassPassPassPassPass100%Pass
iPhone SEiOS 10.2.1LightBlue V2.7PassPassPassPassPassPassPass100%Pass
iPhone 6s PlusiOS 12.1LightBlue V2.7PassPassPassPassPassPassPass100%Pass
iPhone 7iOS 12.0.1LightBlue V2.7PassPassPassPassPassPassPass100%Pass
iPhone 6iOS 10.3.1LightBlue V2.7PassPassPassPassPassPassPass100%Pass
iPod TouchiOS 12.0LightBlue V2.7PassPassPassPassPassPassPass100%Pass
HUAWEI(华为)Huawei nova 3eAndroid 8.0.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Huawei Honor Enjoy 7XAndroid 7.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Huawei Mate 10Android 8.0.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Huawei G9/P9 LiteAndroid 6.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Huawei novaAndroid 7.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Huawei Honor 4XAndroid 5.0.2LightBlue V1.1.3PassPassPassPassPassPassPass100%PassSometimes the app cannot break bluetooth connection, so you need to manually switch on and off the bluetooth.
OPPO(欧珀)OPPO A83Android 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
OPPO R9sAndroid 6.0.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Xiaomi(小米)Xiaomi Mi Max 2Android 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Xiaomi 5XAndroid 7.1.2LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Xiaomi Mi Note 2Android 7.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Xiaomi Redmi Note 4Android 6.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Xiaomi Mi 5Android 7.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
vivo(步步高)vivo Y85Android 8.1.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
vivo X7Android 5.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Lenovo & Motoria (联想)Lenovo S5Android 8.0.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Lenovo K5Android 8.0.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
ZTE & Nubia(中兴)Nubia Z17 MiniAndroid 6.0.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Gionee(金立)Gionee S11Android 7.11LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Gionee GN9004Android 4.3nRF Connect V4.10*PassPassPassPassPassPassPass100%PassIntallation of LightBlue failed, so nRF Connect was used alternatively.
Google(谷歌)LG Nexus 4*Android 5.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%PassBLE scan performance of this phone is poor.
Sony(索尼)Sony Xperia XZAndroid 8.0.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
HTC(宏达电)HTC U11Android 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
EssentialEssential PhoneAndroid 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Meizu(魅族)Meilan Note 3Android 5.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Meilan EAndroid 5.2.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Smartisan(锤子)Smartisan Nut Pro 2Android 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Sharp(夏普)Sharp AQUOS S3 mini Android 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Hisense(海信)HiSense Small Dolphin 2(海信小海豚 2)Android 7.1.2LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
360(奇虎)360 N6 LiteAndroid 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
360 N5Android 6.0.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Xiaolajiao(小辣椒)Red Chilli 4A(红辣椒 4A)Android 3.2.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Red Chilli Enjoy 6A(红辣椒畅玩 6A)Android 5.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Letv(乐视)LeTV LeEcoo Le S3Android 6.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
LeTV LeEoo Le1 (X600)Android 5.0.2LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Coolpad(酷派)Coolpad Cool 1 dualAndroid 6.0.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Doov(朵唯)Doov A15SAndroid 5.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
AGM(艾捷莫)AGM X1Android 5.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
CMCC(中国移动)CMCC N3Android 7.1.2LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
Meitu(美图)Meitu M8sAndroid 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
K-Touch(天语)K-Touch X11Android 6.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
YEPEN(誉品)YEPEN I7SAndroid 6.0LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
MOTOZ2 PalyAndroid 7.1.1LightBlue V1.1.3PassPassPassPassPassPassPass100%Pass
diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/CMakeLists.txt new file mode 100644 index 00000000..99553fec --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ble_compatibility_test.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c new file mode 100644 index 00000000..7c1ca1c0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.c @@ -0,0 +1,700 @@ +/* + 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. +*/ + +/******************************************************************************** +* +* This file is for gatt server. It can send adv data, and get connected by client. +* +*********************************************************************************/ + + + #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 "ble_compatibility_test.h" +#include "esp_gatt_common_api.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, 0x01, 0x06, + /* tx power*/ + 0x02, 0x0a, 0xeb, + /* service uuid */ + 0x03, 0x03, 0xFF, 0x00, + /* device name */ + 0x0E, 0x09, 'B', 'L', 'E', '_', 'C', 'O','M', 'P', '_', 'T','E', 'S', 'T' +}; +static uint8_t raw_scan_rsp_data[] = { + /* flags */ + 0x02, 0x01, 0x06, + /* tx power */ + 0x02, 0x0a, 0xeb, + /* service uuid */ + 0x03, 0x03, 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(); + + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + 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(); + + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + 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 ***** \n"); + } + 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\n"); + } + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + EXAMPLE_DEBUG(EXAMPLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + 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:%d", 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:%d", 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 ***** \n"); + } + else { + ESP_LOGI(EXAMPLE_TAG, "***** pair status = fail, reason = 0x%x *****\n", 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 (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; + } + } else { + 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; + } + } + /*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", __func__); + } + } + 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 ***** \n"); + } + } + }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 ***** \n"); + } + if(gatt_db_handle_table[IDX_CHAR_VAL_B] == param->read.handle) { + ESP_LOGE(EXAMPLE_TAG, "(5) ***** read char2 ***** \n"); + } + 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 ***** \n"); + }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 ***** \n"); + } + } + + /* 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\n",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 ); + + ESP_ERROR_CHECK(nvs_flash_erase()); + + 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; + } + + 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)); + +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.h new file mode 100644 index 00000000..5c71ee1a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/ble_compatibility_test.h @@ -0,0 +1,33 @@ +/* + 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 +#include +#include + + +/* 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, +}; diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults new file mode 100644 index 00000000..e435f383 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_compatibility_test/sdkconfig.defaults @@ -0,0 +1,14 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y + +# +# ESP32-specific config +# +CONFIG_ESP32_ENABLE_STACK_BT=y +# CONFIG_ESP32_ENABLE_STACK_NONE is not set +CONFIG_MEMMAP_BT=y diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/CMakeLists.txt new file mode 100644 index 00000000..9c5943ef --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_eddystone_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/Makefile new file mode 100644 index 00000000..e7b35bd9 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_eddystone_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/README.md new file mode 100644 index 00000000..c4a54ed4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/README.md @@ -0,0 +1,25 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Eddystone demo +======================== +This example demonstrates Eddystone-compatible BLE scanning of eddystone frame,including UID,URL. + +Eddystone is an open beacon protocol specification from Google aimed at improving “proximity-based experiences” +with support for both Android and iOS smart device platforms. + +Learn more on https://developers.google.com/beacons and https://github.com/google/eddystone. + +esp_eddystone_protocol.h +========================== +This header file includes some eddystone-protocol related definitions. + +esp_eddystone_api.h & esp_eddystone_api.c +=========================================== +These files contains the decoding and decoded result of an eddystone frame packet. + +You just need to include esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c for development. + +esp_eddystone_demo.c +====================== +This is an example/demo of using esp_eddystone_protocol.h, esp_eddystone_api.h and esp_eddystone_api.c files to resolve eddystone packet. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/CMakeLists.txt new file mode 100644 index 00000000..cdd852c5 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "esp_eddystone_api.c" + "esp_eddystone_demo.c" + INCLUDE_DIRS "") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/esp_eddystone_api.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/esp_eddystone_api.c new file mode 100644 index 00000000..20fffb2e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/esp_eddystone_api.c @@ -0,0 +1,254 @@ +/* + 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. +*/ + + +/**************************************************************************** +* +* This file is used to decode eddystone information. +* +****************************************************************************/ + +#include +#include +#include +#include + +#include "esp_err.h" +#include "esp_gap_ble_api.h" +#include "esp_eddystone_protocol.h" +#include "esp_eddystone_api.h" + + +/* Declare static functions */ +static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res); +static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res); +static char* esp_eddystone_resolve_url_scheme(const uint8_t* url_start, const uint8_t* url_end); +static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res); +static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res); + +/* Eddystone-URL scheme prefixes */ +static const char* eddystone_url_prefix[4] = { + "http://www.", + "https://www.", + "http://", + "https://" +}; + +/* Eddystone-URL HTTP URL encoding */ +static const char* eddystone_url_encoding[14] = { + ".com/", + ".org/", + ".edu/", + ".net/", + ".info/", + ".biz/", + ".gov/", + ".com", + ".org", + ".edu", + ".net", + ".info", + ".biz", + ".gov" + }; + +/****************** Eddystone-UID ************** +Byte offset Field Description + 0 Frame Type Value = 0x00 + 1 Ranging Data Calibrated Tx power at 0 m + 2 NID[0] 10-byte Namespace + 3 NID[1] + 4 NID[2] + 5 NID[3] + 6 NID[4] + 7 NID[5] + 8 NID[6] + 9 NID[7] + 10 NID[8] + 11 NID[9] + 12 BID[0] 6-byte Instance + 13 BID[1] + 14 BID[2] + 15 BID[3] + 16 BID[4] + 17 BID[5] + 18 RFU Reserved for future use, must be0x00 + 19 RFU Reserved for future use, must be0x00 +*********************************************/ +/* decode and store received UID */ +static esp_err_t esp_eddystone_uid_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res) +{ + uint8_t pos = 0; + //1-byte Ranging Data + 10-byte Namespace + 6-byte Instance + if((len != EDDYSTONE_UID_DATA_LEN) && (len != (EDDYSTONE_UID_RFU_LEN+EDDYSTONE_UID_DATA_LEN))) { + //ERROR:uid len wrong + return -1; + } + res->inform.uid.ranging_data = buf[pos++]; + for(int i=0; iinform.uid.namespace_id[i] = buf[pos++]; + } + for(int i=0; iinform.uid.instance_id[i] = buf[pos++]; + } + return 0; +} + +/* resolve received URL to url_res pointer */ +static char* esp_eddystone_resolve_url_scheme(const uint8_t *url_start, const uint8_t *url_end) +{ + int pos = 0; + static char url_buf[100] = {0}; + const uint8_t *p = url_start; + + pos += sprintf(&url_buf[pos], "%s", eddystone_url_prefix[*p++]); + + for (; p <= url_end; p++) { + if (esp_eddystone_is_char_invalid((*p))) { + pos += sprintf(&url_buf[pos], "%s", eddystone_url_encoding[*p]); + } else { + pos += sprintf(&url_buf[pos], "%c", *p); + } + } + return url_buf; +} + +/************************** Eddystone-URL ************* +Frame Specification + Byte offset Field Description + 0 Frame Type Value = 0x10 + 1 TX Power Calibrated Tx power at 0 m + 2 URL Scheme Encoded Scheme Prefix + 3+ Encoded URL Length 1-17 +*******************************************************/ +/* decode and store received URL, the pointer url_res points to the resolved url */ +static esp_err_t esp_eddystone_url_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res) +{ + char *url_res = NULL; + uint8_t pos = 0; + if(len-EDDYSTONE_URL_TX_POWER_LEN > EDDYSTONE_URL_MAX_LEN) { + //ERROR:too long url + return -1; + } + res->inform.url.tx_power = buf[pos++]; + url_res = esp_eddystone_resolve_url_scheme(buf+pos, buf+len-1); + memcpy(&res->inform.url.url, url_res, strlen(url_res)); + res->inform.url.url[strlen(url_res)] = '\0'; + return 0; +} + +/****************** eddystone-tlm *************** + * Unencrypted TLM Frame Specification +Byte offset Field Description + 0 Frame Type Value = 0x20 + 1 Version TLM version, value = 0x00 + 2 VBATT[0] Battery voltage, 1 mV/bit + 3 VBATT[1] + 4 TEMP[0] Beacon temperature + 5 TEMP[1] + 6 ADV_CNT[0] Advertising PDU count + 7 ADV_CNT[1] + 8 ADV_CNT[2] + 9 ADV_CNT[3] + 10 SEC_CNT[0] Time since power-on or reboot + 11 SEC_CNT[1] + 12 SEC_CNT[2] + 13 SEC_CNT[3] +************************************************/ +/* decode and store received TLM */ +static esp_err_t esp_eddystone_tlm_received(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res) +{ + uint8_t pos = 0; + if(len > EDDYSTONE_TLM_DATA_LEN) { + //ERROR:TLM too long + return -1; + } + res->inform.tlm.version = buf[pos++]; + res->inform.tlm.battery_voltage = big_endian_read_16(buf, pos); + pos += 2; + uint16_t temp = big_endian_read_16(buf, pos); + int8_t temp_integral = (int8_t)((temp >> 8) & 0xff); + float temp_decimal = (temp & 0xff) / 256.0; + res->inform.tlm.temperature = temp_integral + temp_decimal; + pos += 2; + res->inform.tlm.adv_count = big_endian_read_32(buf, pos); + pos += 4; + res->inform.tlm.time = big_endian_read_32(buf, pos); + return 0; +} + +static esp_err_t esp_eddystone_get_inform(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res) +{ + static esp_err_t ret=-1; + switch(res->common.frame_type) + { + case EDDYSTONE_FRAME_TYPE_UID: { + ret = esp_eddystone_uid_received(buf, len, res); + break; + } + case EDDYSTONE_FRAME_TYPE_URL: { + ret = esp_eddystone_url_received(buf, len, res); + break; + } + case EDDYSTONE_FRAME_TYPE_TLM: { + ret = esp_eddystone_tlm_received(buf, len, res); + break; + } + default: + break; + } + return ret; +} + +esp_err_t esp_eddystone_decode(const uint8_t* buf, uint8_t len, esp_eddystone_result_t* res) +{ + if (len == 0 || buf == NULL || res == NULL) { + return -1; + } + uint8_t pos=0; + while(res->common.srv_data_type != EDDYSTONE_SERVICE_UUID) + { + pos++; + if(pos >= len ) { + return -1; + } + uint8_t ad_type = buf[pos++]; + switch(ad_type) + { + case ESP_BLE_AD_TYPE_FLAG: { + res->common.flags = buf[pos++]; + break; + } + case ESP_BLE_AD_TYPE_16SRV_CMPL: { + uint16_t uuid = little_endian_read_16(buf, pos); + if(uuid != EDDYSTONE_SERVICE_UUID) { + return -1; + } + res->common.srv_uuid = uuid; + pos += 2; + break; + } + case ESP_BLE_AD_TYPE_SERVICE_DATA: { + uint16_t type = little_endian_read_16(buf, pos); + pos += 2; + uint8_t frame_type = buf[pos++]; + if(type != EDDYSTONE_SERVICE_UUID || !(frame_type == EDDYSTONE_FRAME_TYPE_UID || frame_type == EDDYSTONE_FRAME_TYPE_URL || + frame_type == EDDYSTONE_FRAME_TYPE_TLM)) { + return -1; + } + res->common.srv_data_type = type; + res->common.frame_type = frame_type; + break; + } + default: + break; + } + } + return esp_eddystone_get_inform(buf+pos, len-pos, res); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/esp_eddystone_api.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/esp_eddystone_api.h new file mode 100644 index 00000000..41ad278e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/main/esp_eddystone_api.h @@ -0,0 +1,70 @@ +/* + 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. +*/ + + +#ifndef __ESP_EDDYSTONE_API_H__ +#define __ESP_EDDYSTONE_API_H__ + +typedef struct { + struct { + uint8_t flags; /* +#include +#include + +#include "esp_bt.h" +#include "nvs_flash.h" +#include "esp_log.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_defs.h" +#include "esp_gattc_api.h" +#include "esp_gap_ble_api.h" +#include "freertos/FreeRTOS.h" + +#include "esp_eddystone_protocol.h" +#include "esp_eddystone_api.h" + +static const char* DEMO_TAG = "EDDYSTONE_DEMO"; + +/* declare static functions */ +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param); +static void esp_eddystone_show_inform(const esp_eddystone_result_t* res); + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +static void esp_eddystone_show_inform(const esp_eddystone_result_t* res) +{ + switch(res->common.frame_type) + { + case EDDYSTONE_FRAME_TYPE_UID: { + ESP_LOGI(DEMO_TAG, "Eddystone UID inform:"); + ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.uid.ranging_data); + ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Namespace ID:0x"); + esp_log_buffer_hex(DEMO_TAG, res->inform.uid.namespace_id, 10); + ESP_LOGI(DEMO_TAG, "EDDYSTONE_DEMO: Instance ID:0x"); + esp_log_buffer_hex(DEMO_TAG, res->inform.uid.instance_id, 6); + break; + } + case EDDYSTONE_FRAME_TYPE_URL: { + ESP_LOGI(DEMO_TAG, "Eddystone URL inform:"); + ESP_LOGI(DEMO_TAG, "Measured power(RSSI at 0m distance):%d dbm", res->inform.url.tx_power); + ESP_LOGI(DEMO_TAG, "URL: %s", res->inform.url.url); + break; + } + case EDDYSTONE_FRAME_TYPE_TLM: { + ESP_LOGI(DEMO_TAG, "Eddystone TLM inform:"); + ESP_LOGI(DEMO_TAG, "version: %d", res->inform.tlm.version); + ESP_LOGI(DEMO_TAG, "battery voltage: %d mV", res->inform.tlm.battery_voltage); + ESP_LOGI(DEMO_TAG, "beacon temperature in degrees Celsius: %6.1f", res->inform.tlm.temperature); + ESP_LOGI(DEMO_TAG, "adv pdu count since power-up: %d", res->inform.tlm.adv_count); + ESP_LOGI(DEMO_TAG, "time since power-up: %d s", (res->inform.tlm.time)/10); + break; + } + default: + break; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param) +{ + esp_err_t err; + + switch(event) + { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + uint32_t duration = 0; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: { + if((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(DEMO_TAG,"Scan start failed: %s", esp_err_to_name(err)); + } + else { + ESP_LOGI(DEMO_TAG,"Start scanning..."); + } + break; + } + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t* scan_result = (esp_ble_gap_cb_param_t*)param; + switch(scan_result->scan_rst.search_evt) + { + case ESP_GAP_SEARCH_INQ_RES_EVT: { + esp_eddystone_result_t eddystone_res; + memset(&eddystone_res, 0, sizeof(eddystone_res)); + esp_err_t ret = esp_eddystone_decode(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len, &eddystone_res); + if (ret) { + // error:The received data is not an eddystone frame packet or a correct eddystone frame packet. + // just return + return; + } else { + // The received adv data is a correct eddystone frame packet. + // Here, we get the eddystone infomation in eddystone_res, we can use the data in res to do other things. + // For example, just print them: + ESP_LOGI(DEMO_TAG, "--------Eddystone Found----------"); + esp_log_buffer_hex("EDDYSTONE_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN); + ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi); + esp_eddystone_show_inform(&eddystone_res); + } + break; + } + default: + break; + } + break; + } + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT:{ + if((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(DEMO_TAG,"Scan stop failed: %s", esp_err_to_name(err)); + } + else { + ESP_LOGI(DEMO_TAG,"Stop scan successfully"); + } + break; + } + default: + break; + } +} + +void esp_eddystone_appRegister(void) +{ + esp_err_t status; + + ESP_LOGI(DEMO_TAG,"Register callback"); + + /*= 0x00 && ch <= 0x20) || (ch >= 0x7f && ch <= 0xff); +} + +#endif /* __ESP_EDDYSTONE_PROTOCOL_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/sdkconfig.defaults new file mode 100644 index 00000000..00fb5211 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_eddystone/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/CMakeLists.txt new file mode 100644 index 00000000..f8c94800 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hidd_demos) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/Makefile new file mode 100644 index 00000000..8604de33 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := hidd_demos + +COMPONENT_ADD_INCLUDEDIRS := components/include \ + + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/README.md new file mode 100644 index 00000000..433e89cb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/README.md @@ -0,0 +1,34 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF BLE HID device demo +======================== +This example Implemented BLE HID device profile related functions, in which the HID device has +4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices). +Users can choose different reports according to their own application scenarios. +BLE HID profile inheritance and USB HID class. + +ble_hidd_demo_main.c +========================== +This file is the demo to show how to used the HID(you can used it to connected to the smart phone act as the consumer device then can used the button to +volume++ or volume-- etc., or connected to the Windows 10 PC act as a keyboard or mouse) + +hidd_le_prf_int.h +========================== +This header file includes some HID profile related definitions. + +esp_hidd_prf_api.h & esp_hidd_prf_api.c +=========================================== +These files contains the the api of the HID profile + +When you used the HID profile, you just need to added the esp_hidd_prf_api.h includes file and send the HID data used the function defined in the esp_hidd_prf_api.c file. + +hid_dev.h & hid_dev.c +====================== +These file define the HID spec related definitions + +hid_device_le_prf.c +====================== +This file is the HID profile definition file, it include the main function of the HID profile. +It mainly includes how to create HID service. If you send and receive HID data and convert the data to keyboard keys, +the mouse and consumer values are forwarded to the application. \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/CMakeLists.txt new file mode 100644 index 00000000..1c12b152 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/CMakeLists.txt @@ -0,0 +1,7 @@ +idf_component_register(SRCS "ble_hidd_demo_main.c" + "esp_hidd_prf_api.c" + "hid_dev.c" + "hid_device_le_prf.c" + INCLUDE_DIRS ".") + +target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-unused-const-variable) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/ble_hidd_demo_main.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/ble_hidd_demo_main.c new file mode 100644 index 00000000..f7900d59 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/ble_hidd_demo_main.c @@ -0,0 +1,251 @@ +/* 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 +#include +#include +#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 "esp_bt.h" + +#include "esp_hidd_prf_api.h" +#include "esp_bt_defs.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "driver/gpio.h" +#include "hid_dev.h" + +/** + * Brief: + * This example Implemented BLE HID device profile related functions, in which the HID device + * has 4 Reports (1 is mouse, 2 is keyboard and LED, 3 is Consumer Devices, 4 is Vendor devices). + * Users can choose different reports according to their own application scenarios. + * BLE HID profile inheritance and USB HID class. + */ + +/** + * Note: + * 1. Win10 does not support vendor report , So SUPPORT_REPORT_VENDOR is always set to FALSE, it defines in hidd_le_prf_int.h + * 2. Update connection parameters are not allowed during iPhone HID encryption, slave turns + * off the ability to automatically update connection parameters during encryption. + * 3. After our HID device is connected, the iPhones write 1 to the Report Characteristic Configuration Descriptor, + * even if the HID encryption is not completed. This should actually be written 1 after the HID encryption is completed. + * we modify the permissions of the Report Characteristic Configuration Descriptor to `ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED`. + * if you got `GATT_INSUF_ENCRYPTION` error, please ignore. + */ + +#define HID_DEMO_TAG "HID_DEMO" + + +static uint16_t hid_conn_id = 0; +static bool sec_conn = false; +static bool send_volum_up = false; +#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) + +static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param); + +#define HIDD_DEVICE_NAME "HID" +static uint8_t hidd_service_uuid128[] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x12, 0x18, 0x00, 0x00, +}; + +static esp_ble_adv_data_t hidd_adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec + .appearance = 0x03c0, //HID Generic, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = sizeof(hidd_service_uuid128), + .p_service_uuid = hidd_service_uuid128, + .flag = 0x6, +}; + +static esp_ble_adv_params_t hidd_adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x30, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + + +static void hidd_event_callback(esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param) +{ + switch(event) { + case ESP_HIDD_EVENT_REG_FINISH: { + if (param->init_finish.state == ESP_HIDD_INIT_OK) { + //esp_bd_addr_t rand_addr = {0x04,0x11,0x11,0x11,0x11,0x05}; + esp_ble_gap_set_device_name(HIDD_DEVICE_NAME); + esp_ble_gap_config_adv_data(&hidd_adv_data); + + } + break; + } + case ESP_BAT_EVENT_REG: { + break; + } + case ESP_HIDD_EVENT_DEINIT_FINISH: + break; + case ESP_HIDD_EVENT_BLE_CONNECT: { + ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_CONNECT"); + hid_conn_id = param->connect.conn_id; + break; + } + case ESP_HIDD_EVENT_BLE_DISCONNECT: { + sec_conn = false; + ESP_LOGI(HID_DEMO_TAG, "ESP_HIDD_EVENT_BLE_DISCONNECT"); + esp_ble_gap_start_advertising(&hidd_adv_params); + break; + } + case ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT: { + ESP_LOGI(HID_DEMO_TAG, "%s, ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT", __func__); + ESP_LOG_BUFFER_HEX(HID_DEMO_TAG, param->vendor_write.data, param->vendor_write.length); + } + default: + break; + } + return; +} + +static void gap_event_handler(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_ble_gap_start_advertising(&hidd_adv_params); + break; + case ESP_GAP_BLE_SEC_REQ_EVT: + for(int i = 0; i < ESP_BD_ADDR_LEN; i++) { + ESP_LOGD(HID_DEMO_TAG, "%x:",param->ble_security.ble_req.bd_addr[i]); + } + esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true); + break; + case ESP_GAP_BLE_AUTH_CMPL_EVT: + sec_conn = true; + esp_bd_addr_t bd_addr; + memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(HID_DEMO_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]); + ESP_LOGI(HID_DEMO_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type); + ESP_LOGI(HID_DEMO_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); + if(!param->ble_security.auth_cmpl.success) { + ESP_LOGE(HID_DEMO_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason); + } + break; + default: + break; + } +} + +void hid_demo_task(void *pvParameters) +{ + vTaskDelay(1000 / portTICK_PERIOD_MS); + while(1) { + vTaskDelay(2000 / portTICK_PERIOD_MS); + if (sec_conn) { + ESP_LOGI(HID_DEMO_TAG, "Send the volume"); + send_volum_up = true; + //uint8_t key_vaule = {HID_KEY_A}; + //esp_hidd_send_keyboard_value(hid_conn_id, 0, &key_vaule, 1); + esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_UP, true); + vTaskDelay(3000 / portTICK_PERIOD_MS); + if (send_volum_up) { + send_volum_up = false; + esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_UP, false); + esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_DOWN, true); + vTaskDelay(3000 / portTICK_PERIOD_MS); + esp_hidd_send_consumer_value(hid_conn_id, HID_CONSUMER_VOLUME_DOWN, false); + } + } + } +} + + +void app_main(void) +{ + esp_err_t ret; + + // Initialize NVS. + 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 ); + + 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(HID_DEMO_TAG, "%s initialize controller failed\n", __func__); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(HID_DEMO_TAG, "%s enable controller failed\n", __func__); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__); + return; + } + + if((ret = esp_hidd_profile_init()) != ESP_OK) { + ESP_LOGE(HID_DEMO_TAG, "%s init bluedroid failed\n", __func__); + } + + ///register the callback function to the gap module + esp_ble_gap_register_callback(gap_event_handler); + esp_hidd_register_callbacks(hidd_event_callback); + + /* 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_BOND; //bonding with peer device after authentication + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE; //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; + 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)); + + xTaskCreate(&hid_demo_task, "hid_task", 2048, NULL, 5, NULL); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/component.mk new file mode 100644 index 00000000..4c0df110 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/component.mk @@ -0,0 +1,5 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# + +hid_device_le_prf.o: CFLAGS += -Wno-unused-const-variable diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c new file mode 100644 index 00000000..d1be980c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.c @@ -0,0 +1,145 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "esp_hidd_prf_api.h" +#include "hidd_le_prf_int.h" +#include "hid_dev.h" +#include +#include +#include "esp_log.h" + +// HID keyboard input report length +#define HID_KEYBOARD_IN_RPT_LEN 8 + +// HID LED output report length +#define HID_LED_OUT_RPT_LEN 1 + +// HID mouse input report length +#define HID_MOUSE_IN_RPT_LEN 5 + +// HID consumer control input report length +#define HID_CC_IN_RPT_LEN 2 + +esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks) +{ + esp_err_t hidd_status; + + if(callbacks != NULL) { + hidd_le_env.hidd_cb = callbacks; + } else { + return ESP_FAIL; + } + + if((hidd_status = hidd_register_cb()) != ESP_OK) { + return hidd_status; + } + + esp_ble_gatts_app_register(BATTRAY_APP_ID); + + if((hidd_status = esp_ble_gatts_app_register(HIDD_APP_ID)) != ESP_OK) { + return hidd_status; + } + + return hidd_status; +} + +esp_err_t esp_hidd_profile_init(void) +{ + if (hidd_le_env.enabled) { + ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized"); + return ESP_FAIL; + } + // Reset the hid device target environment + memset(&hidd_le_env, 0, sizeof(hidd_le_env_t)); + hidd_le_env.enabled = true; + return ESP_OK; +} + +esp_err_t esp_hidd_profile_deinit(void) +{ + uint16_t hidd_svc_hdl = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]; + if (!hidd_le_env.enabled) { + ESP_LOGE(HID_LE_PRF_TAG, "HID device profile already initialized"); + return ESP_OK; + } + + if(hidd_svc_hdl != 0) { + esp_ble_gatts_stop_service(hidd_svc_hdl); + esp_ble_gatts_delete_service(hidd_svc_hdl); + } else { + return ESP_FAIL; + } + + /* register the HID device profile to the BTA_GATTS module*/ + esp_ble_gatts_app_unregister(hidd_le_env.gatt_if); + + return ESP_OK; +} + +uint16_t esp_hidd_get_version(void) +{ + return HIDD_VERSION; +} + +void esp_hidd_send_consumer_value(uint16_t conn_id, uint8_t key_cmd, bool key_pressed) +{ + uint8_t buffer[HID_CC_IN_RPT_LEN] = {0, 0}; + if (key_pressed) { + ESP_LOGD(HID_LE_PRF_TAG, "hid_consumer_build_report"); + hid_consumer_build_report(buffer, key_cmd); + } + ESP_LOGD(HID_LE_PRF_TAG, "buffer[0] = %x, buffer[1] = %x", buffer[0], buffer[1]); + hid_dev_send_report(hidd_le_env.gatt_if, conn_id, + HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT, HID_CC_IN_RPT_LEN, buffer); + return; +} + +void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, uint8_t *keyboard_cmd, uint8_t num_key) +{ + if (num_key > HID_KEYBOARD_IN_RPT_LEN - 2) { + ESP_LOGE(HID_LE_PRF_TAG, "%s(), the number key should not be more than %d", __func__, HID_KEYBOARD_IN_RPT_LEN); + return; + } + + uint8_t buffer[HID_KEYBOARD_IN_RPT_LEN] = {0}; + + buffer[0] = special_key_mask; + + for (int i = 0; i < num_key; i++) { + buffer[i+2] = keyboard_cmd[i]; + } + + ESP_LOGD(HID_LE_PRF_TAG, "the key vaule = %d,%d,%d, %d, %d, %d,%d, %d", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7]); + hid_dev_send_report(hidd_le_env.gatt_if, conn_id, + HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT, HID_KEYBOARD_IN_RPT_LEN, buffer); + return; +} + +void esp_hidd_send_mouse_value(uint16_t conn_id, uint8_t mouse_button, int8_t mickeys_x, int8_t mickeys_y) +{ + uint8_t buffer[HID_MOUSE_IN_RPT_LEN]; + + buffer[0] = mouse_button; // Buttons + buffer[1] = mickeys_x; // X + buffer[2] = mickeys_y; // Y + buffer[3] = 0; // Wheel + buffer[4] = 0; // AC Pan + + hid_dev_send_report(hidd_le_env.gatt_if, conn_id, + HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT, HID_MOUSE_IN_RPT_LEN, buffer); + return; +} + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.h new file mode 100644 index 00000000..513bcbef --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/esp_hidd_prf_api.h @@ -0,0 +1,168 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __ESP_HIDD_API_H__ +#define __ESP_HIDD_API_H__ + +#include "esp_bt_defs.h" +#include "esp_gatt_defs.h" +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + ESP_HIDD_EVENT_REG_FINISH = 0, + ESP_BAT_EVENT_REG, + ESP_HIDD_EVENT_DEINIT_FINISH, + ESP_HIDD_EVENT_BLE_CONNECT, + ESP_HIDD_EVENT_BLE_DISCONNECT, + ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT, +} esp_hidd_cb_event_t; + +/// HID config status +typedef enum { + ESP_HIDD_STA_CONN_SUCCESS = 0x00, + ESP_HIDD_STA_CONN_FAIL = 0x01, +} esp_hidd_sta_conn_state_t; + +/// HID init status +typedef enum { + ESP_HIDD_INIT_OK = 0, + ESP_HIDD_INIT_FAILED = 1, +} esp_hidd_init_state_t; + +/// HID deinit status +typedef enum { + ESP_HIDD_DEINIT_OK = 0, + ESP_HIDD_DEINIT_FAILED = 0, +} esp_hidd_deinit_state_t; + +#define LEFT_CONTROL_KEY_MASK (1 << 0) +#define LEFT_SHIFT_KEY_MASK (1 << 1) +#define LEFT_ALT_KEY_MASK (1 << 2) +#define LEFT_GUI_KEY_MASK (1 << 3) +#define RIGHT_CONTROL_KEY_MASK (1 << 4) +#define RIGHT_SHIFT_KEY_MASK (1 << 5) +#define RIGHT_ALT_KEY_MASK (1 << 6) +#define RIGHT_GUI_KEY_MASK (1 << 7) + +typedef uint8_t key_mask_t; +/** + * @brief HIDD callback parameters union + */ +typedef union { + /** + * @brief ESP_HIDD_EVENT_INIT_FINISH + */ + struct hidd_init_finish_evt_param { + esp_hidd_init_state_t state; /*!< Initial status */ + esp_gatt_if_t gatts_if; + } init_finish; /*!< HID callback param of ESP_HIDD_EVENT_INIT_FINISH */ + + /** + * @brief ESP_HIDD_EVENT_DEINIT_FINISH + */ + struct hidd_deinit_finish_evt_param { + esp_hidd_deinit_state_t state; /*!< De-initial status */ + } deinit_finish; /*!< HID callback param of ESP_HIDD_EVENT_DEINIT_FINISH */ + + /** + * @brief ESP_HIDD_EVENT_CONNECT + */ + struct hidd_connect_evt_param { + uint16_t conn_id; + esp_bd_addr_t remote_bda; /*!< HID Remote bluetooth connection index */ + } connect; /*!< HID callback param of ESP_HIDD_EVENT_CONNECT */ + + /** + * @brief ESP_HIDD_EVENT_DISCONNECT + */ + struct hidd_disconnect_evt_param { + esp_bd_addr_t remote_bda; /*!< HID Remote bluetooth device address */ + } disconnect; /*!< HID callback param of ESP_HIDD_EVENT_DISCONNECT */ + + /** + * @brief ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT + */ + struct hidd_vendor_write_evt_param { + uint16_t conn_id; /*!< HID connection index */ + uint16_t report_id; /*!< HID report index */ + uint16_t length; /*!< data length */ + uint8_t *data; /*!< The pointer to the data */ + } vendor_write; /*!< HID callback param of ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT */ + +} esp_hidd_cb_param_t; + + +/** + * @brief HID device event callback function type + * @param event : Event type + * @param param : Point to callback parameter, currently is union type + */ +typedef void (*esp_hidd_event_cb_t) (esp_hidd_cb_event_t event, esp_hidd_cb_param_t *param); + + + +/** + * + * @brief This function is called to receive hid device callback event + * + * @param[in] callbacks: callback functions + * + * @return ESP_OK - success, other - failed + * + */ +esp_err_t esp_hidd_register_callbacks(esp_hidd_event_cb_t callbacks); + +/** + * + * @brief This function is called to initialize hid device profile + * + * @return ESP_OK - success, other - failed + * + */ +esp_err_t esp_hidd_profile_init(void); + +/** + * + * @brief This function is called to de-initialize hid device profile + * + * @return ESP_OK - success, other - failed + * + */ +esp_err_t esp_hidd_profile_deinit(void); + +/** + * + * @brief Get hidd profile version + * + * @return Most 8bit significant is Great version, Least 8bit is Sub version + * + */ +uint16_t esp_hidd_get_version(void); + +void esp_hidd_send_consumer_value(uint16_t conn_id, uint8_t key_cmd, bool key_pressed); + +void esp_hidd_send_keyboard_value(uint16_t conn_id, key_mask_t special_key_mask, uint8_t *keyboard_cmd, uint8_t num_key); + +void esp_hidd_send_mouse_value(uint16_t conn_id, uint8_t mouse_button, int8_t mickeys_x, int8_t mickeys_y); + +#ifdef __cplusplus +} +#endif + +#endif /* __ESP_HIDD_API_H__ */ + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_dev.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_dev.c new file mode 100644 index 00000000..64352f22 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_dev.c @@ -0,0 +1,138 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "hid_dev.h" +#include +#include +#include +#include +#include "esp_log.h" + +static hid_report_map_t *hid_dev_rpt_tbl; +static uint8_t hid_dev_rpt_tbl_Len; + +static hid_report_map_t *hid_dev_rpt_by_id(uint8_t id, uint8_t type) +{ + hid_report_map_t *rpt = hid_dev_rpt_tbl; + + for (uint8_t i = hid_dev_rpt_tbl_Len; i > 0; i--, rpt++) { + if (rpt->id == id && rpt->type == type && rpt->mode == hidProtocolMode) { + return rpt; + } + } + + return NULL; +} + +void hid_dev_register_reports(uint8_t num_reports, hid_report_map_t *p_report) +{ + hid_dev_rpt_tbl = p_report; + hid_dev_rpt_tbl_Len = num_reports; + return; +} + +void hid_dev_send_report(esp_gatt_if_t gatts_if, uint16_t conn_id, + uint8_t id, uint8_t type, uint8_t length, uint8_t *data) +{ + hid_report_map_t *p_rpt; + + // get att handle for report + if ((p_rpt = hid_dev_rpt_by_id(id, type)) != NULL) { + // if notifications are enabled + ESP_LOGD(HID_LE_PRF_TAG, "%s(), send the report, handle = %d", __func__, p_rpt->handle); + esp_ble_gatts_send_indicate(gatts_if, conn_id, p_rpt->handle, length, data, false); + } + + return; +} + +void hid_consumer_build_report(uint8_t *buffer, consumer_cmd_t cmd) +{ + if (!buffer) { + ESP_LOGE(HID_LE_PRF_TAG, "%s(), the buffer is NULL, hid build report failed.", __func__); + return; + } + + switch (cmd) { + case HID_CONSUMER_CHANNEL_UP: + HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_UP); + break; + + case HID_CONSUMER_CHANNEL_DOWN: + HID_CC_RPT_SET_CHANNEL(buffer, HID_CC_RPT_CHANNEL_DOWN); + break; + + case HID_CONSUMER_VOLUME_UP: + HID_CC_RPT_SET_VOLUME_UP(buffer); + break; + + case HID_CONSUMER_VOLUME_DOWN: + HID_CC_RPT_SET_VOLUME_DOWN(buffer); + break; + + case HID_CONSUMER_MUTE: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_MUTE); + break; + + case HID_CONSUMER_POWER: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_POWER); + break; + + case HID_CONSUMER_RECALL_LAST: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_LAST); + break; + + case HID_CONSUMER_ASSIGN_SEL: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_ASSIGN_SEL); + break; + + case HID_CONSUMER_PLAY: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PLAY); + break; + + case HID_CONSUMER_PAUSE: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_PAUSE); + break; + + case HID_CONSUMER_RECORD: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_RECORD); + break; + + case HID_CONSUMER_FAST_FORWARD: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_FAST_FWD); + break; + + case HID_CONSUMER_REWIND: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_REWIND); + break; + + case HID_CONSUMER_SCAN_NEXT_TRK: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_NEXT_TRK); + break; + + case HID_CONSUMER_SCAN_PREV_TRK: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_SCAN_PREV_TRK); + break; + + case HID_CONSUMER_STOP: + HID_CC_RPT_SET_BUTTON(buffer, HID_CC_RPT_STOP); + break; + + default: + break; + } + + return; +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_dev.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_dev.h new file mode 100644 index 00000000..9954a834 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_dev.h @@ -0,0 +1,261 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef HID_DEV_H__ +#define HID_DEV_H__ + +#include "hidd_le_prf_int.h" + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* HID Report type */ +#define HID_TYPE_INPUT 1 +#define HID_TYPE_OUTPUT 2 +#define HID_TYPE_FEATURE 3 + +// HID Keyboard/Keypad Usage IDs (subset of the codes available in the USB HID Usage Tables spec) +#define HID_KEY_RESERVED 0 // No event inidicated +#define HID_KEY_A 4 // Keyboard a and A +#define HID_KEY_B 5 // Keyboard b and B +#define HID_KEY_C 6 // Keyboard c and C +#define HID_KEY_D 7 // Keyboard d and D +#define HID_KEY_E 8 // Keyboard e and E +#define HID_KEY_F 9 // Keyboard f and F +#define HID_KEY_G 10 // Keyboard g and G +#define HID_KEY_H 11 // Keyboard h and H +#define HID_KEY_I 12 // Keyboard i and I +#define HID_KEY_J 13 // Keyboard j and J +#define HID_KEY_K 14 // Keyboard k and K +#define HID_KEY_L 15 // Keyboard l and L +#define HID_KEY_M 16 // Keyboard m and M +#define HID_KEY_N 17 // Keyboard n and N +#define HID_KEY_O 18 // Keyboard o and O +#define HID_KEY_P 19 // Keyboard p and p +#define HID_KEY_Q 20 // Keyboard q and Q +#define HID_KEY_R 21 // Keyboard r and R +#define HID_KEY_S 22 // Keyboard s and S +#define HID_KEY_T 23 // Keyboard t and T +#define HID_KEY_U 24 // Keyboard u and U +#define HID_KEY_V 25 // Keyboard v and V +#define HID_KEY_W 26 // Keyboard w and W +#define HID_KEY_X 27 // Keyboard x and X +#define HID_KEY_Y 28 // Keyboard y and Y +#define HID_KEY_Z 29 // Keyboard z and Z +#define HID_KEY_1 30 // Keyboard 1 and ! +#define HID_KEY_2 31 // Keyboard 2 and @ +#define HID_KEY_3 32 // Keyboard 3 and # +#define HID_KEY_4 33 // Keyboard 4 and % +#define HID_KEY_5 34 // Keyboard 5 and % +#define HID_KEY_6 35 // Keyboard 6 and ^ +#define HID_KEY_7 36 // Keyboard 7 and & +#define HID_KEY_8 37 // Keyboard 8 and * +#define HID_KEY_9 38 // Keyboard 9 and ( +#define HID_KEY_0 39 // Keyboard 0 and ) +#define HID_KEY_RETURN 40 // Keyboard Return (ENTER) +#define HID_KEY_ESCAPE 41 // Keyboard ESCAPE +#define HID_KEY_DELETE 42 // Keyboard DELETE (Backspace) +#define HID_KEY_TAB 43 // Keyboard Tab +#define HID_KEY_SPACEBAR 44 // Keyboard Spacebar +#define HID_KEY_MINUS 45 // Keyboard - and (underscore) +#define HID_KEY_EQUAL 46 // Keyboard = and + +#define HID_KEY_LEFT_BRKT 47 // Keyboard [ and { +#define HID_KEY_RIGHT_BRKT 48 // Keyboard ] and } +#define HID_KEY_BACK_SLASH 49 // Keyboard \ and | +#define HID_KEY_SEMI_COLON 51 // Keyboard ; and : +#define HID_KEY_SGL_QUOTE 52 // Keyboard ' and " +#define HID_KEY_GRV_ACCENT 53 // Keyboard Grave Accent and Tilde +#define HID_KEY_COMMA 54 // Keyboard , and < +#define HID_KEY_DOT 55 // Keyboard . and > +#define HID_KEY_FWD_SLASH 56 // Keyboard / and ? +#define HID_KEY_CAPS_LOCK 57 // Keyboard Caps Lock +#define HID_KEY_F1 58 // Keyboard F1 +#define HID_KEY_F2 59 // Keyboard F2 +#define HID_KEY_F3 60 // Keyboard F3 +#define HID_KEY_F4 61 // Keyboard F4 +#define HID_KEY_F5 62 // Keyboard F5 +#define HID_KEY_F6 63 // Keyboard F6 +#define HID_KEY_F7 64 // Keyboard F7 +#define HID_KEY_F8 65 // Keyboard F8 +#define HID_KEY_F9 66 // Keyboard F9 +#define HID_KEY_F10 67 // Keyboard F10 +#define HID_KEY_F11 68 // Keyboard F11 +#define HID_KEY_F12 69 // Keyboard F12 +#define HID_KEY_PRNT_SCREEN 70 // Keyboard Print Screen +#define HID_KEY_SCROLL_LOCK 71 // Keyboard Scroll Lock +#define HID_KEY_PAUSE 72 // Keyboard Pause +#define HID_KEY_INSERT 73 // Keyboard Insert +#define HID_KEY_HOME 74 // Keyboard Home +#define HID_KEY_PAGE_UP 75 // Keyboard PageUp +#define HID_KEY_DELETE_FWD 76 // Keyboard Delete Forward +#define HID_KEY_END 77 // Keyboard End +#define HID_KEY_PAGE_DOWN 78 // Keyboard PageDown +#define HID_KEY_RIGHT_ARROW 79 // Keyboard RightArrow +#define HID_KEY_LEFT_ARROW 80 // Keyboard LeftArrow +#define HID_KEY_DOWN_ARROW 81 // Keyboard DownArrow +#define HID_KEY_UP_ARROW 82 // Keyboard UpArrow +#define HID_KEY_NUM_LOCK 83 // Keypad Num Lock and Clear +#define HID_KEY_DIVIDE 84 // Keypad / +#define HID_KEY_MULTIPLY 85 // Keypad * +#define HID_KEY_SUBTRACT 86 // Keypad - +#define HID_KEY_ADD 87 // Keypad + +#define HID_KEY_ENTER 88 // Keypad ENTER +#define HID_KEYPAD_1 89 // Keypad 1 and End +#define HID_KEYPAD_2 90 // Keypad 2 and Down Arrow +#define HID_KEYPAD_3 91 // Keypad 3 and PageDn +#define HID_KEYPAD_4 92 // Keypad 4 and Lfet Arrow +#define HID_KEYPAD_5 93 // Keypad 5 +#define HID_KEYPAD_6 94 // Keypad 6 and Right Arrow +#define HID_KEYPAD_7 95 // Keypad 7 and Home +#define HID_KEYPAD_8 96 // Keypad 8 and Up Arrow +#define HID_KEYPAD_9 97 // Keypad 9 and PageUp +#define HID_KEYPAD_0 98 // Keypad 0 and Insert +#define HID_KEYPAD_DOT 99 // Keypad . and Delete +#define HID_KEY_MUTE 127 // Keyboard Mute +#define HID_KEY_VOLUME_UP 128 // Keyboard Volume up +#define HID_KEY_VOLUME_DOWN 129 // Keyboard Volume down +#define HID_KEY_LEFT_CTRL 224 // Keyboard LeftContorl +#define HID_KEY_LEFT_SHIFT 225 // Keyboard LeftShift +#define HID_KEY_LEFT_ALT 226 // Keyboard LeftAlt +#define HID_KEY_LEFT_GUI 227 // Keyboard LeftGUI +#define HID_KEY_RIGHT_CTRL 228 // Keyboard LeftContorl +#define HID_KEY_RIGHT_SHIFT 229 // Keyboard LeftShift +#define HID_KEY_RIGHT_ALT 230 // Keyboard LeftAlt +#define HID_KEY_RIGHT_GUI 231 // Keyboard RightGUI +typedef uint8_t keyboard_cmd_t; + +#define HID_MOUSE_LEFT 253 +#define HID_MOUSE_MIDDLE 254 +#define HID_MOUSE_RIGHT 255 +typedef uint8_t mouse_cmd_t; + +// HID Consumer Usage IDs (subset of the codes available in the USB HID Usage Tables spec) +#define HID_CONSUMER_POWER 48 // Power +#define HID_CONSUMER_RESET 49 // Reset +#define HID_CONSUMER_SLEEP 50 // Sleep + +#define HID_CONSUMER_MENU 64 // Menu +#define HID_CONSUMER_SELECTION 128 // Selection +#define HID_CONSUMER_ASSIGN_SEL 129 // Assign Selection +#define HID_CONSUMER_MODE_STEP 130 // Mode Step +#define HID_CONSUMER_RECALL_LAST 131 // Recall Last +#define HID_CONSUMER_QUIT 148 // Quit +#define HID_CONSUMER_HELP 149 // Help +#define HID_CONSUMER_CHANNEL_UP 156 // Channel Increment +#define HID_CONSUMER_CHANNEL_DOWN 157 // Channel Decrement + +#define HID_CONSUMER_PLAY 176 // Play +#define HID_CONSUMER_PAUSE 177 // Pause +#define HID_CONSUMER_RECORD 178 // Record +#define HID_CONSUMER_FAST_FORWARD 179 // Fast Forward +#define HID_CONSUMER_REWIND 180 // Rewind +#define HID_CONSUMER_SCAN_NEXT_TRK 181 // Scan Next Track +#define HID_CONSUMER_SCAN_PREV_TRK 182 // Scan Previous Track +#define HID_CONSUMER_STOP 183 // Stop +#define HID_CONSUMER_EJECT 184 // Eject +#define HID_CONSUMER_RANDOM_PLAY 185 // Random Play +#define HID_CONSUMER_SELECT_DISC 186 // Select Disk +#define HID_CONSUMER_ENTER_DISC 187 // Enter Disc +#define HID_CONSUMER_REPEAT 188 // Repeat +#define HID_CONSUMER_STOP_EJECT 204 // Stop/Eject +#define HID_CONSUMER_PLAY_PAUSE 205 // Play/Pause +#define HID_CONSUMER_PLAY_SKIP 206 // Play/Skip + +#define HID_CONSUMER_VOLUME 224 // Volume +#define HID_CONSUMER_BALANCE 225 // Balance +#define HID_CONSUMER_MUTE 226 // Mute +#define HID_CONSUMER_BASS 227 // Bass +#define HID_CONSUMER_VOLUME_UP 233 // Volume Increment +#define HID_CONSUMER_VOLUME_DOWN 234 // Volume Decrement +typedef uint8_t consumer_cmd_t; + +#define HID_CC_RPT_MUTE 1 +#define HID_CC_RPT_POWER 2 +#define HID_CC_RPT_LAST 3 +#define HID_CC_RPT_ASSIGN_SEL 4 +#define HID_CC_RPT_PLAY 5 +#define HID_CC_RPT_PAUSE 6 +#define HID_CC_RPT_RECORD 7 +#define HID_CC_RPT_FAST_FWD 8 +#define HID_CC_RPT_REWIND 9 +#define HID_CC_RPT_SCAN_NEXT_TRK 10 +#define HID_CC_RPT_SCAN_PREV_TRK 11 +#define HID_CC_RPT_STOP 12 + +#define HID_CC_RPT_CHANNEL_UP 0x01 +#define HID_CC_RPT_CHANNEL_DOWN 0x03 +#define HID_CC_RPT_VOLUME_UP 0x40 +#define HID_CC_RPT_VOLUME_DOWN 0x80 + +// HID Consumer Control report bitmasks +#define HID_CC_RPT_NUMERIC_BITS 0xF0 +#define HID_CC_RPT_CHANNEL_BITS 0xCF +#define HID_CC_RPT_VOLUME_BITS 0x3F +#define HID_CC_RPT_BUTTON_BITS 0xF0 +#define HID_CC_RPT_SELECTION_BITS 0xCF + + +// Macros for the HID Consumer Control 2-byte report +#define HID_CC_RPT_SET_NUMERIC(s, x) (s)[0] &= HID_CC_RPT_NUMERIC_BITS; \ + (s)[0] = (x) +#define HID_CC_RPT_SET_CHANNEL(s, x) (s)[0] &= HID_CC_RPT_CHANNEL_BITS; \ + (s)[0] |= ((x) & 0x03) << 4 +#define HID_CC_RPT_SET_VOLUME_UP(s) (s)[0] &= HID_CC_RPT_VOLUME_BITS; \ + (s)[0] |= 0x40 +#define HID_CC_RPT_SET_VOLUME_DOWN(s) (s)[0] &= HID_CC_RPT_VOLUME_BITS; \ + (s)[0] |= 0x80 +#define HID_CC_RPT_SET_BUTTON(s, x) (s)[1] &= HID_CC_RPT_BUTTON_BITS; \ + (s)[1] |= (x) +#define HID_CC_RPT_SET_SELECTION(s, x) (s)[1] &= HID_CC_RPT_SELECTION_BITS; \ + (s)[1] |= ((x) & 0x03) << 4 + + +// HID report mapping table +typedef struct +{ + uint16_t handle; // Handle of report characteristic + uint16_t cccdHandle; // Handle of CCCD for report characteristic + uint8_t id; // Report ID + uint8_t type; // Report type + uint8_t mode; // Protocol mode (report or boot) +} hid_report_map_t; + +// HID dev configuration structure +typedef struct +{ + uint32_t idleTimeout; // Idle timeout in milliseconds + uint8_t hidFlags; // HID feature flags + +} hid_dev_cfg_t; + +void hid_dev_register_reports(uint8_t num_reports, hid_report_map_t *p_report); + +void hid_dev_send_report(esp_gatt_if_t gatts_if, uint16_t conn_id, + uint8_t id, uint8_t type, uint8_t length, uint8_t *data); + +void hid_consumer_build_report(uint8_t *buffer, consumer_cmd_t cmd); + +void hid_keyboard_build_report(uint8_t *buffer, keyboard_cmd_t cmd); + +void hid_mouse_build_report(uint8_t *buffer, mouse_cmd_t cmd); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif /* HID_DEV_H__ */ + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c new file mode 100644 index 00000000..8041e7b5 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hid_device_le_prf.c @@ -0,0 +1,816 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "hidd_le_prf_int.h" +#include +#include "esp_log.h" + +/// characteristic presentation information +struct prf_char_pres_fmt +{ + /// Unit (The Unit is a UUID) + uint16_t unit; + /// Description + uint16_t description; + /// Format + uint8_t format; + /// Exponent + uint8_t exponent; + /// Name space + uint8_t name_space; +}; + +// HID report mapping table +static hid_report_map_t hid_rpt_map[HID_NUM_REPORTS]; + +// HID Report Map characteristic value +// Keyboard report descriptor (using format for Boot interface descriptor) +static const uint8_t hidReportMap[] = { + 0x05, 0x01, // Usage Page (Generic Desktop) + 0x09, 0x02, // Usage (Mouse) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // Report Id (1) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Physical) + 0x05, 0x09, // Usage Page (Buttons) + 0x19, 0x01, // Usage Minimum (01) - Button 1 + 0x29, 0x03, // Usage Maximum (03) - Button 3 + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x03, // Report Count (3) + 0x81, 0x02, // Input (Data, Variable, Absolute) - Button states + 0x75, 0x05, // Report Size (5) + 0x95, 0x01, // Report Count (1) + 0x81, 0x01, // Input (Constant) - Padding or Reserved bits + 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, Variable, Relative) - X & Y coordinate + 0xC0, // End Collection + 0xC0, // End Collection + + 0x05, 0x01, // Usage Pg (Generic Desktop) + 0x09, 0x06, // Usage (Keyboard) + 0xA1, 0x01, // Collection: (Application) + 0x85, 0x02, // Report Id (2) + // + 0x05, 0x07, // Usage Pg (Key Codes) + 0x19, 0xE0, // Usage Min (224) + 0x29, 0xE7, // Usage Max (231) + 0x15, 0x00, // Log Min (0) + 0x25, 0x01, // Log Max (1) + // + // Modifier byte + 0x75, 0x01, // Report Size (1) + 0x95, 0x08, // Report Count (8) + 0x81, 0x02, // Input: (Data, Variable, Absolute) + // + // Reserved byte + 0x95, 0x01, // Report Count (1) + 0x75, 0x08, // Report Size (8) + 0x81, 0x01, // Input: (Constant) + // + // LED report + 0x95, 0x05, // Report Count (5) + 0x75, 0x01, // Report Size (1) + 0x05, 0x08, // Usage Pg (LEDs) + 0x19, 0x01, // Usage Min (1) + 0x29, 0x05, // Usage Max (5) + 0x91, 0x02, // Output: (Data, Variable, Absolute) + // + // LED report padding + 0x95, 0x01, // Report Count (1) + 0x75, 0x03, // Report Size (3) + 0x91, 0x01, // Output: (Constant) + // + // Key arrays (6 bytes) + 0x95, 0x06, // Report Count (6) + 0x75, 0x08, // Report Size (8) + 0x15, 0x00, // Log Min (0) + 0x25, 0x65, // Log Max (101) + 0x05, 0x07, // Usage Pg (Key Codes) + 0x19, 0x00, // Usage Min (0) + 0x29, 0x65, // Usage Max (101) + 0x81, 0x00, // Input: (Data, Array) + // + 0xC0, // End Collection + // + 0x05, 0x0C, // Usage Pg (Consumer Devices) + 0x09, 0x01, // Usage (Consumer Control) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x03, // Report Id (3) + 0x09, 0x02, // Usage (Numeric Key Pad) + 0xA1, 0x02, // Collection (Logical) + 0x05, 0x09, // Usage Pg (Button) + 0x19, 0x01, // Usage Min (Button 1) + 0x29, 0x0A, // Usage Max (Button 10) + 0x15, 0x01, // Logical Min (1) + 0x25, 0x0A, // Logical Max (10) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x00, // Input (Data, Ary, Abs) + 0xC0, // End Collection + 0x05, 0x0C, // Usage Pg (Consumer Devices) + 0x09, 0x86, // Usage (Channel) + 0x15, 0xFF, // Logical Min (-1) + 0x25, 0x01, // Logical Max (1) + 0x75, 0x02, // Report Size (2) + 0x95, 0x01, // Report Count (1) + 0x81, 0x46, // Input (Data, Var, Rel, Null) + 0x09, 0xE9, // Usage (Volume Up) + 0x09, 0xEA, // Usage (Volume Down) + 0x15, 0x00, // Logical Min (0) + 0x75, 0x01, // Report Size (1) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data, Var, Abs) + 0x09, 0xE2, // Usage (Mute) + 0x09, 0x30, // Usage (Power) + 0x09, 0x83, // Usage (Recall Last) + 0x09, 0x81, // Usage (Assign Selection) + 0x09, 0xB0, // Usage (Play) + 0x09, 0xB1, // Usage (Pause) + 0x09, 0xB2, // Usage (Record) + 0x09, 0xB3, // Usage (Fast Forward) + 0x09, 0xB4, // Usage (Rewind) + 0x09, 0xB5, // Usage (Scan Next) + 0x09, 0xB6, // Usage (Scan Prev) + 0x09, 0xB7, // Usage (Stop) + 0x15, 0x01, // Logical Min (1) + 0x25, 0x0C, // Logical Max (12) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x00, // Input (Data, Ary, Abs) + 0x09, 0x80, // Usage (Selection) + 0xA1, 0x02, // Collection (Logical) + 0x05, 0x09, // Usage Pg (Button) + 0x19, 0x01, // Usage Min (Button 1) + 0x29, 0x03, // Usage Max (Button 3) + 0x15, 0x01, // Logical Min (1) + 0x25, 0x03, // Logical Max (3) + 0x75, 0x02, // Report Size (2) + 0x81, 0x00, // Input (Data, Ary, Abs) + 0xC0, // End Collection + 0x81, 0x03, // Input (Const, Var, Abs) + 0xC0, // End Collectionq + +#if (SUPPORT_REPORT_VENDOR == true) + 0x06, 0xFF, 0xFF, // Usage Page(Vendor defined) + 0x09, 0xA5, // Usage(Vendor Defined) + 0xA1, 0x01, // Collection(Application) + 0x85, 0x04, // Report Id (4) + 0x09, 0xA6, // Usage(Vendor defined) + 0x09, 0xA9, // Usage(Vendor defined) + 0x75, 0x08, // Report Size + 0x95, 0x7F, // Report Count = 127 Btyes + 0x91, 0x02, // Output(Data, Variable, Absolute) + 0xC0, // End Collection +#endif + +}; + +/// Battery Service Attributes Indexes +enum +{ + BAS_IDX_SVC, + + BAS_IDX_BATT_LVL_CHAR, + BAS_IDX_BATT_LVL_VAL, + BAS_IDX_BATT_LVL_NTF_CFG, + BAS_IDX_BATT_LVL_PRES_FMT, + + BAS_IDX_NB, +}; + +#define HI_UINT16(a) (((a) >> 8) & 0xFF) +#define LO_UINT16(a) ((a) & 0xFF) +#define PROFILE_NUM 1 +#define PROFILE_APP_IDX 0 + +struct gatts_profile_inst { + esp_gatts_cb_t gatts_cb; + uint16_t gatts_if; + uint16_t app_id; + uint16_t conn_id; +}; + +hidd_le_env_t hidd_le_env; + +// HID report map length +uint8_t hidReportMapLen = sizeof(hidReportMap); +uint8_t hidProtocolMode = HID_PROTOCOL_MODE_REPORT; + +// HID report mapping table +//static hidRptMap_t hidRptMap[HID_NUM_REPORTS]; + +// HID Information characteristic value +static const uint8_t hidInfo[HID_INFORMATION_LEN] = { + LO_UINT16(0x0111), HI_UINT16(0x0111), // bcdHID (USB HID version) + 0x00, // bCountryCode + HID_KBD_FLAGS // Flags +}; + + +// HID External Report Reference Descriptor +static uint16_t hidExtReportRefDesc = ESP_GATT_UUID_BATTERY_LEVEL; + +// HID Report Reference characteristic descriptor, mouse input +static uint8_t hidReportRefMouseIn[HID_REPORT_REF_LEN] = + { HID_RPT_ID_MOUSE_IN, HID_REPORT_TYPE_INPUT }; + + +// HID Report Reference characteristic descriptor, key input +static uint8_t hidReportRefKeyIn[HID_REPORT_REF_LEN] = + { HID_RPT_ID_KEY_IN, HID_REPORT_TYPE_INPUT }; + +// HID Report Reference characteristic descriptor, LED output +static uint8_t hidReportRefLedOut[HID_REPORT_REF_LEN] = + { HID_RPT_ID_LED_OUT, HID_REPORT_TYPE_OUTPUT }; + +#if (SUPPORT_REPORT_VENDOR == true) + +static uint8_t hidReportRefVendorOut[HID_REPORT_REF_LEN] = + {HID_RPT_ID_VENDOR_OUT, HID_REPORT_TYPE_OUTPUT}; +#endif + +// HID Report Reference characteristic descriptor, Feature +static uint8_t hidReportRefFeature[HID_REPORT_REF_LEN] = + { HID_RPT_ID_FEATURE, HID_REPORT_TYPE_FEATURE }; + +// HID Report Reference characteristic descriptor, consumer control input +static uint8_t hidReportRefCCIn[HID_REPORT_REF_LEN] = + { HID_RPT_ID_CC_IN, HID_REPORT_TYPE_INPUT }; + + +/* + * Heart Rate PROFILE ATTRIBUTES + **************************************************************************************** + */ + +/// hid Service uuid +static uint16_t hid_le_svc = ATT_SVC_HID; +uint16_t hid_count = 0; +esp_gatts_incl_svc_desc_t incl_svc = {0}; + +#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) +///the uuid definition +static const uint16_t primary_service_uuid = ESP_GATT_UUID_PRI_SERVICE; +static const uint16_t include_service_uuid = ESP_GATT_UUID_INCLUDE_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 hid_info_char_uuid = ESP_GATT_UUID_HID_INFORMATION; +static const uint16_t hid_report_map_uuid = ESP_GATT_UUID_HID_REPORT_MAP; +static const uint16_t hid_control_point_uuid = ESP_GATT_UUID_HID_CONTROL_POINT; +static const uint16_t hid_report_uuid = ESP_GATT_UUID_HID_REPORT; +static const uint16_t hid_proto_mode_uuid = ESP_GATT_UUID_HID_PROTO_MODE; +static const uint16_t hid_kb_input_uuid = ESP_GATT_UUID_HID_BT_KB_INPUT; +static const uint16_t hid_kb_output_uuid = ESP_GATT_UUID_HID_BT_KB_OUTPUT; +static const uint16_t hid_mouse_input_uuid = ESP_GATT_UUID_HID_BT_MOUSE_INPUT; +static const uint16_t hid_repot_map_ext_desc_uuid = ESP_GATT_UUID_EXT_RPT_REF_DESCR; +static const uint16_t hid_report_ref_descr_uuid = ESP_GATT_UUID_RPT_REF_DESCR; +///the propoty definition +static const uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ; +static const uint8_t char_prop_write_nr = ESP_GATT_CHAR_PROP_BIT_WRITE_NR; +static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_READ; +static const uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_NOTIFY; + +/// battary Service +static const uint16_t battary_svc = ESP_GATT_UUID_BATTERY_SERVICE_SVC; + +static const uint16_t bat_lev_uuid = ESP_GATT_UUID_BATTERY_LEVEL; +static const uint8_t bat_lev_ccc[2] ={ 0x00, 0x00}; +static const uint16_t char_format_uuid = ESP_GATT_UUID_CHAR_PRESENT_FORMAT; + +static uint8_t battary_lev = 50; +/// Full HRS Database Description - Used to add attributes into the database +static const esp_gatts_attr_db_t bas_att_db[BAS_IDX_NB] = +{ + // Battary Service Declaration + [BAS_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, + sizeof(uint16_t), sizeof(battary_svc), (uint8_t *)&battary_svc}}, + + // Battary level Characteristic Declaration + [BAS_IDX_BATT_LVL_CHAR] = {{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_notify}}, + + // Battary level Characteristic Value + [BAS_IDX_BATT_LVL_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&bat_lev_uuid, ESP_GATT_PERM_READ, + sizeof(uint8_t),sizeof(uint8_t), &battary_lev}}, + + // Battary level Characteristic - Client Characteristic Configuration Descriptor + [BAS_IDX_BATT_LVL_NTF_CFG] = {{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(bat_lev_ccc), (uint8_t *)bat_lev_ccc}}, + + // Battary level report Characteristic Declaration + [BAS_IDX_BATT_LVL_PRES_FMT] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&char_format_uuid, ESP_GATT_PERM_READ, + sizeof(struct prf_char_pres_fmt), 0, NULL}}, +}; + + +/// Full Hid device Database Description - Used to add attributes into the database +static esp_gatts_attr_db_t hidd_le_gatt_db[HIDD_LE_IDX_NB] = +{ + // HID Service Declaration + [HIDD_LE_IDX_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, + ESP_GATT_PERM_READ_ENCRYPTED, sizeof(uint16_t), sizeof(hid_le_svc), + (uint8_t *)&hid_le_svc}}, + + // HID Service Declaration + [HIDD_LE_IDX_INCL_SVC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&include_service_uuid, + ESP_GATT_PERM_READ, + sizeof(esp_gatts_incl_svc_desc_t), sizeof(esp_gatts_incl_svc_desc_t), + (uint8_t *)&incl_svc}}, + + // HID Information Characteristic Declaration + [HIDD_LE_IDX_HID_INFO_CHAR] = {{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}}, + // HID Information Characteristic Value + [HIDD_LE_IDX_HID_INFO_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_info_char_uuid, + ESP_GATT_PERM_READ, + sizeof(hids_hid_info_t), sizeof(hidInfo), + (uint8_t *)&hidInfo}}, + + // HID Control Point Characteristic Declaration + [HIDD_LE_IDX_HID_CTNL_PT_CHAR] = {{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_write_nr}}, + // HID Control Point Characteristic Value + [HIDD_LE_IDX_HID_CTNL_PT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_control_point_uuid, + ESP_GATT_PERM_WRITE, + sizeof(uint8_t), 0, + NULL}}, + + // Report Map Characteristic Declaration + [HIDD_LE_IDX_REPORT_MAP_CHAR] = {{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}}, + // Report Map Characteristic Value + [HIDD_LE_IDX_REPORT_MAP_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_map_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_REPORT_MAP_MAX_LEN, sizeof(hidReportMap), + (uint8_t *)&hidReportMap}}, + + // Report Map Characteristic - External Report Reference Descriptor + [HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_repot_map_ext_desc_uuid, + ESP_GATT_PERM_READ, + sizeof(uint16_t), sizeof(uint16_t), + (uint8_t *)&hidExtReportRefDesc}}, + + // Protocol Mode Characteristic Declaration + [HIDD_LE_IDX_PROTO_MODE_CHAR] = {{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}}, + // Protocol Mode Characteristic Value + [HIDD_LE_IDX_PROTO_MODE_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_proto_mode_uuid, + (ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE), + sizeof(uint8_t), sizeof(hidProtocolMode), + (uint8_t *)&hidProtocolMode}}, + + [HIDD_LE_IDX_REPORT_MOUSE_IN_CHAR] = {{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_notify}}, + + [HIDD_LE_IDX_REPORT_MOUSE_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_REPORT_MAX_LEN, 0, + NULL}}, + + [HIDD_LE_IDX_REPORT_MOUSE_IN_CCC] = {{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), 0, + NULL}}, + + [HIDD_LE_IDX_REPORT_MOUSE_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid, + ESP_GATT_PERM_READ, + sizeof(hidReportRefMouseIn), sizeof(hidReportRefMouseIn), + hidReportRefMouseIn}}, + // Report Characteristic Declaration + [HIDD_LE_IDX_REPORT_KEY_IN_CHAR] = {{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_notify}}, + // Report Characteristic Value + [HIDD_LE_IDX_REPORT_KEY_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_REPORT_MAX_LEN, 0, + NULL}}, + // Report KEY INPUT Characteristic - Client Characteristic Configuration Descriptor + [HIDD_LE_IDX_REPORT_KEY_IN_CCC] = {{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), 0, + NULL}}, + // Report Characteristic - Report Reference Descriptor + [HIDD_LE_IDX_REPORT_KEY_IN_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid, + ESP_GATT_PERM_READ, + sizeof(hidReportRefKeyIn), sizeof(hidReportRefKeyIn), + hidReportRefKeyIn}}, + + // Report Characteristic Declaration + [HIDD_LE_IDX_REPORT_LED_OUT_CHAR] = {{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}}, + + [HIDD_LE_IDX_REPORT_LED_OUT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid, + ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + HIDD_LE_REPORT_MAX_LEN, 0, + NULL}}, + [HIDD_LE_IDX_REPORT_LED_OUT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid, + ESP_GATT_PERM_READ, + sizeof(hidReportRefLedOut), sizeof(hidReportRefLedOut), + hidReportRefLedOut}}, +#if (SUPPORT_REPORT_VENDOR == true) + // Report Characteristic Declaration + [HIDD_LE_IDX_REPORT_VENDOR_OUT_CHAR] = {{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_notify}}, + [HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid, + ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + HIDD_LE_REPORT_MAX_LEN, 0, + NULL}}, + [HIDD_LE_IDX_REPORT_VENDOR_OUT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid, + ESP_GATT_PERM_READ, + sizeof(hidReportRefVendorOut), sizeof(hidReportRefVendorOut), + hidReportRefVendorOut}}, +#endif + // Report Characteristic Declaration + [HIDD_LE_IDX_REPORT_CC_IN_CHAR] = {{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_notify}}, + // Report Characteristic Value + [HIDD_LE_IDX_REPORT_CC_IN_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_REPORT_MAX_LEN, 0, + NULL}}, + // Report KEY INPUT Characteristic - Client Characteristic Configuration Descriptor + [HIDD_LE_IDX_REPORT_CC_IN_CCC] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&character_client_config_uuid, + (ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE_ENCRYPTED), + sizeof(uint16_t), 0, + NULL}}, + // Report Characteristic - Report Reference Descriptor + [HIDD_LE_IDX_REPORT_CC_IN_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid, + ESP_GATT_PERM_READ, + sizeof(hidReportRefCCIn), sizeof(hidReportRefCCIn), + hidReportRefCCIn}}, + + // Boot Keyboard Input Report Characteristic Declaration + [HIDD_LE_IDX_BOOT_KB_IN_REPORT_CHAR] = {{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_notify}}, + // Boot Keyboard Input Report Characteristic Value + [HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_kb_input_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_BOOT_REPORT_MAX_LEN, 0, + NULL}}, + // Boot Keyboard Input Report Characteristic - Client Characteristic Configuration Descriptor + [HIDD_LE_IDX_BOOT_KB_IN_REPORT_NTF_CFG] = {{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), 0, + NULL}}, + + // Boot Keyboard Output Report Characteristic Declaration + [HIDD_LE_IDX_BOOT_KB_OUT_REPORT_CHAR] = {{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}}, + // Boot Keyboard Output Report Characteristic Value + [HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_kb_output_uuid, + (ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE), + HIDD_LE_BOOT_REPORT_MAX_LEN, 0, + NULL}}, + + // Boot Mouse Input Report Characteristic Declaration + [HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_CHAR] = {{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_notify}}, + // Boot Mouse Input Report Characteristic Value + [HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_mouse_input_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_BOOT_REPORT_MAX_LEN, 0, + NULL}}, + // Boot Mouse Input Report Characteristic - Client Characteristic Configuration Descriptor + [HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG] = {{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), 0, + NULL}}, + + // Report Characteristic Declaration + [HIDD_LE_IDX_REPORT_CHAR] = {{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}}, + // Report Characteristic Value + [HIDD_LE_IDX_REPORT_VAL] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_uuid, + ESP_GATT_PERM_READ, + HIDD_LE_REPORT_MAX_LEN, 0, + NULL}}, + // Report Characteristic - Report Reference Descriptor + [HIDD_LE_IDX_REPORT_REP_REF] = {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&hid_report_ref_descr_uuid, + ESP_GATT_PERM_READ, + sizeof(hidReportRefFeature), sizeof(hidReportRefFeature), + hidReportRefFeature}}, +}; + +static void hid_add_id_tbl(void); + +void esp_hidd_prf_cb_hdl(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_ble_gap_config_local_icon (ESP_BLE_APPEARANCE_GENERIC_HID); + esp_hidd_cb_param_t hidd_param; + hidd_param.init_finish.state = param->reg.status; + if(param->reg.app_id == HIDD_APP_ID) { + hidd_le_env.gatt_if = gatts_if; + if(hidd_le_env.hidd_cb != NULL) { + (hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_REG_FINISH, &hidd_param); + hidd_le_create_service(hidd_le_env.gatt_if); + } + } + if(param->reg.app_id == BATTRAY_APP_ID) { + hidd_param.init_finish.gatts_if = gatts_if; + if(hidd_le_env.hidd_cb != NULL) { + (hidd_le_env.hidd_cb)(ESP_BAT_EVENT_REG, &hidd_param); + } + + } + + break; + } + case ESP_GATTS_CONF_EVT: { + break; + } + case ESP_GATTS_CREATE_EVT: + break; + case ESP_GATTS_CONNECT_EVT: { + esp_hidd_cb_param_t cb_param = {0}; + ESP_LOGI(HID_LE_PRF_TAG, "HID connection establish, conn_id = %x",param->connect.conn_id); + memcpy(cb_param.connect.remote_bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + cb_param.connect.conn_id = param->connect.conn_id; + hidd_clcb_alloc(param->connect.conn_id, param->connect.remote_bda); + esp_ble_set_encryption(param->connect.remote_bda, ESP_BLE_SEC_ENCRYPT_NO_MITM); + if(hidd_le_env.hidd_cb != NULL) { + (hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_CONNECT, &cb_param); + } + break; + } + case ESP_GATTS_DISCONNECT_EVT: { + if(hidd_le_env.hidd_cb != NULL) { + (hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_DISCONNECT, NULL); + } + hidd_clcb_dealloc(param->disconnect.conn_id); + break; + } + case ESP_GATTS_CLOSE_EVT: + break; + case ESP_GATTS_WRITE_EVT: { +#if (SUPPORT_REPORT_VENDOR == true) + esp_hidd_cb_param_t cb_param = {0}; + if (param->write.handle == hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL] && + hidd_le_env.hidd_cb != NULL) { + cb_param.vendor_write.conn_id = param->write.conn_id; + cb_param.vendor_write.report_id = HID_RPT_ID_VENDOR_OUT; + cb_param.vendor_write.length = param->write.len; + cb_param.vendor_write.data = param->write.value; + (hidd_le_env.hidd_cb)(ESP_HIDD_EVENT_BLE_VENDOR_REPORT_WRITE_EVT, &cb_param); + } +#endif + break; + } + case ESP_GATTS_CREAT_ATTR_TAB_EVT: { + if (param->add_attr_tab.num_handle == BAS_IDX_NB && + param->add_attr_tab.svc_uuid.uuid.uuid16 == ESP_GATT_UUID_BATTERY_SERVICE_SVC && + param->add_attr_tab.status == ESP_GATT_OK) { + incl_svc.start_hdl = param->add_attr_tab.handles[BAS_IDX_SVC]; + incl_svc.end_hdl = incl_svc.start_hdl + BAS_IDX_NB -1; + ESP_LOGI(HID_LE_PRF_TAG, "%s(), start added the hid service to the stack database. incl_handle = %d", + __func__, incl_svc.start_hdl); + esp_ble_gatts_create_attr_tab(hidd_le_gatt_db, gatts_if, HIDD_LE_IDX_NB, 0); + } + if (param->add_attr_tab.num_handle == HIDD_LE_IDX_NB && + param->add_attr_tab.status == ESP_GATT_OK) { + memcpy(hidd_le_env.hidd_inst.att_tbl, param->add_attr_tab.handles, + HIDD_LE_IDX_NB*sizeof(uint16_t)); + ESP_LOGI(HID_LE_PRF_TAG, "hid svc handle = %x",hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]); + hid_add_id_tbl(); + esp_ble_gatts_start_service(hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_SVC]); + } else { + esp_ble_gatts_start_service(param->add_attr_tab.handles[0]); + } + break; + } + + default: + break; + } +} + +void hidd_le_create_service(esp_gatt_if_t gatts_if) +{ + /* Here should added the battery service first, because the hid service should include the battery service. + After finish to added the battery service then can added the hid service. */ + esp_ble_gatts_create_attr_tab(bas_att_db, gatts_if, BAS_IDX_NB, 0); + +} + +void hidd_le_init(void) +{ + + // Reset the hid device target environment + memset(&hidd_le_env, 0, sizeof(hidd_le_env_t)); +} + +void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda) +{ + uint8_t i_clcb = 0; + hidd_clcb_t *p_clcb = NULL; + + for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) { + if (!p_clcb->in_use) { + p_clcb->in_use = true; + p_clcb->conn_id = conn_id; + p_clcb->connected = true; + memcpy (p_clcb->remote_bda, bda, ESP_BD_ADDR_LEN); + break; + } + } + return; +} + +bool hidd_clcb_dealloc (uint16_t conn_id) +{ + uint8_t i_clcb = 0; + hidd_clcb_t *p_clcb = NULL; + + for (i_clcb = 0, p_clcb= hidd_le_env.hidd_clcb; i_clcb < HID_MAX_APPS; i_clcb++, p_clcb++) { + memset(p_clcb, 0, sizeof(hidd_clcb_t)); + return true; + } + + return false; +} + +static struct gatts_profile_inst heart_rate_profile_tab[PROFILE_NUM] = { + [PROFILE_APP_IDX] = { + .gatts_cb = esp_hidd_prf_cb_hdl, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + +}; + +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_LOGI(HID_LE_PRF_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + 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); +} + + +esp_err_t hidd_register_cb(void) +{ + esp_err_t status; + status = esp_ble_gatts_register_callback(gatts_event_handler); + return status; +} + +void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value) +{ + hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst; + if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle && + hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle) { + esp_ble_gatts_set_attr_value(handle, val_len, value); + } else { + ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.",__func__); + } + return; +} + +void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value) +{ + hidd_inst_t *hidd_inst = &hidd_le_env.hidd_inst; + if(hidd_inst->att_tbl[HIDD_LE_IDX_HID_INFO_VAL] <= handle && + hidd_inst->att_tbl[HIDD_LE_IDX_REPORT_REP_REF] >= handle){ + esp_ble_gatts_get_attr_value(handle, length, (const uint8_t **)value); + } else { + ESP_LOGE(HID_LE_PRF_TAG, "%s error:Invalid handle value.", __func__); + } + + return; +} + +static void hid_add_id_tbl(void) +{ + // Mouse input report + hid_rpt_map[0].id = hidReportRefMouseIn[0]; + hid_rpt_map[0].type = hidReportRefMouseIn[1]; + hid_rpt_map[0].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL]; + hid_rpt_map[0].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_MOUSE_IN_VAL]; + hid_rpt_map[0].mode = HID_PROTOCOL_MODE_REPORT; + + // Key input report + hid_rpt_map[1].id = hidReportRefKeyIn[0]; + hid_rpt_map[1].type = hidReportRefKeyIn[1]; + hid_rpt_map[1].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_KEY_IN_VAL]; + hid_rpt_map[1].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_KEY_IN_CCC]; + hid_rpt_map[1].mode = HID_PROTOCOL_MODE_REPORT; + + // Consumer Control input report + hid_rpt_map[2].id = hidReportRefCCIn[0]; + hid_rpt_map[2].type = hidReportRefCCIn[1]; + hid_rpt_map[2].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_CC_IN_VAL]; + hid_rpt_map[2].cccdHandle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_CC_IN_CCC]; + hid_rpt_map[2].mode = HID_PROTOCOL_MODE_REPORT; + + // LED output report + hid_rpt_map[3].id = hidReportRefLedOut[0]; + hid_rpt_map[3].type = hidReportRefLedOut[1]; + hid_rpt_map[3].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_LED_OUT_VAL]; + hid_rpt_map[3].cccdHandle = 0; + hid_rpt_map[3].mode = HID_PROTOCOL_MODE_REPORT; + + // Boot keyboard input report + // Use same ID and type as key input report + hid_rpt_map[4].id = hidReportRefKeyIn[0]; + hid_rpt_map[4].type = hidReportRefKeyIn[1]; + hid_rpt_map[4].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL]; + hid_rpt_map[4].cccdHandle = 0; + hid_rpt_map[4].mode = HID_PROTOCOL_MODE_BOOT; + + // Boot keyboard output report + // Use same ID and type as LED output report + hid_rpt_map[5].id = hidReportRefLedOut[0]; + hid_rpt_map[5].type = hidReportRefLedOut[1]; + hid_rpt_map[5].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL]; + hid_rpt_map[5].cccdHandle = 0; + hid_rpt_map[5].mode = HID_PROTOCOL_MODE_BOOT; + + // Boot mouse input report + // Use same ID and type as mouse input report + hid_rpt_map[6].id = hidReportRefMouseIn[0]; + hid_rpt_map[6].type = hidReportRefMouseIn[1]; + hid_rpt_map[6].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL]; + hid_rpt_map[6].cccdHandle = 0; + hid_rpt_map[6].mode = HID_PROTOCOL_MODE_BOOT; + + // Feature report + hid_rpt_map[7].id = hidReportRefFeature[0]; + hid_rpt_map[7].type = hidReportRefFeature[1]; + hid_rpt_map[7].handle = hidd_le_env.hidd_inst.att_tbl[HIDD_LE_IDX_REPORT_VAL]; + hid_rpt_map[7].cccdHandle = 0; + hid_rpt_map[7].mode = HID_PROTOCOL_MODE_REPORT; + + + // Setup report ID map + hid_dev_register_reports(HID_NUM_REPORTS, hid_rpt_map); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h new file mode 100644 index 00000000..89e7cc20 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/main/hidd_le_prf_int.h @@ -0,0 +1,344 @@ +// Copyright 2017-2018 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + + +#ifndef __HID_DEVICE_LE_PRF__ +#define __HID_DEVICE_LE_PRF__ +#include +#include "esp_gatts_api.h" +#include "esp_gatt_defs.h" +#include "esp_hidd_prf_api.h" +#include "esp_gap_ble_api.h" +#include "hid_dev.h" + +#define SUPPORT_REPORT_VENDOR false +//HID BLE profile log tag +#define HID_LE_PRF_TAG "HID_LE_PRF" + +/// Maximal number of HIDS that can be added in the DB +#ifndef USE_ONE_HIDS_INSTANCE +#define HIDD_LE_NB_HIDS_INST_MAX (2) +#else +#define HIDD_LE_NB_HIDS_INST_MAX (1) +#endif + +#define HIDD_GREAT_VER 0x01 //Version + Subversion +#define HIDD_SUB_VER 0x00 //Version + Subversion +#define HIDD_VERSION ((HIDD_GREAT_VER<<8)|HIDD_SUB_VER) //Version + Subversion + +#define HID_MAX_APPS 1 + +// Number of HID reports defined in the service +#define HID_NUM_REPORTS 9 + +// HID Report IDs for the service +#define HID_RPT_ID_MOUSE_IN 1 // Mouse input report ID +#define HID_RPT_ID_KEY_IN 2 // Keyboard input report ID +#define HID_RPT_ID_CC_IN 3 //Consumer Control input report ID +#define HID_RPT_ID_VENDOR_OUT 4 // Vendor output report ID +#define HID_RPT_ID_LED_OUT 0 // LED output report ID +#define HID_RPT_ID_FEATURE 0 // Feature report ID + +#define HIDD_APP_ID 0x1812//ATT_SVC_HID + +#define BATTRAY_APP_ID 0x180f + + +#define ATT_SVC_HID 0x1812 + +/// Maximal number of Report Char. that can be added in the DB for one HIDS - Up to 11 +#define HIDD_LE_NB_REPORT_INST_MAX (5) + +/// Maximal length of Report Char. Value +#define HIDD_LE_REPORT_MAX_LEN (255) +/// Maximal length of Report Map Char. Value +#define HIDD_LE_REPORT_MAP_MAX_LEN (512) + +/// Length of Boot Report Char. Value Maximal Length +#define HIDD_LE_BOOT_REPORT_MAX_LEN (8) + +/// Boot KB Input Report Notification Configuration Bit Mask +#define HIDD_LE_BOOT_KB_IN_NTF_CFG_MASK (0x40) +/// Boot KB Input Report Notification Configuration Bit Mask +#define HIDD_LE_BOOT_MOUSE_IN_NTF_CFG_MASK (0x80) +/// Boot Report Notification Configuration Bit Mask +#define HIDD_LE_REPORT_NTF_CFG_MASK (0x20) + + +/* HID information flags */ +#define HID_FLAGS_REMOTE_WAKE 0x01 // RemoteWake +#define HID_FLAGS_NORMALLY_CONNECTABLE 0x02 // NormallyConnectable + +/* Control point commands */ +#define HID_CMD_SUSPEND 0x00 // Suspend +#define HID_CMD_EXIT_SUSPEND 0x01 // Exit Suspend + +/* HID protocol mode values */ +#define HID_PROTOCOL_MODE_BOOT 0x00 // Boot Protocol Mode +#define HID_PROTOCOL_MODE_REPORT 0x01 // Report Protocol Mode + +/* Attribute value lengths */ +#define HID_PROTOCOL_MODE_LEN 1 // HID Protocol Mode +#define HID_INFORMATION_LEN 4 // HID Information +#define HID_REPORT_REF_LEN 2 // HID Report Reference Descriptor +#define HID_EXT_REPORT_REF_LEN 2 // External Report Reference Descriptor + +// HID feature flags +#define HID_KBD_FLAGS HID_FLAGS_REMOTE_WAKE + +/* HID Report type */ +#define HID_REPORT_TYPE_INPUT 1 +#define HID_REPORT_TYPE_OUTPUT 2 +#define HID_REPORT_TYPE_FEATURE 3 + + +/// HID Service Attributes Indexes +enum { + HIDD_LE_IDX_SVC, + + // Included Service + HIDD_LE_IDX_INCL_SVC, + + // HID Information + HIDD_LE_IDX_HID_INFO_CHAR, + HIDD_LE_IDX_HID_INFO_VAL, + + // HID Control Point + HIDD_LE_IDX_HID_CTNL_PT_CHAR, + HIDD_LE_IDX_HID_CTNL_PT_VAL, + + // Report Map + HIDD_LE_IDX_REPORT_MAP_CHAR, + HIDD_LE_IDX_REPORT_MAP_VAL, + HIDD_LE_IDX_REPORT_MAP_EXT_REP_REF, + + // Protocol Mode + HIDD_LE_IDX_PROTO_MODE_CHAR, + HIDD_LE_IDX_PROTO_MODE_VAL, + + // Report mouse input + HIDD_LE_IDX_REPORT_MOUSE_IN_CHAR, + HIDD_LE_IDX_REPORT_MOUSE_IN_VAL, + HIDD_LE_IDX_REPORT_MOUSE_IN_CCC, + HIDD_LE_IDX_REPORT_MOUSE_REP_REF, + //Report Key input + HIDD_LE_IDX_REPORT_KEY_IN_CHAR, + HIDD_LE_IDX_REPORT_KEY_IN_VAL, + HIDD_LE_IDX_REPORT_KEY_IN_CCC, + HIDD_LE_IDX_REPORT_KEY_IN_REP_REF, + ///Report Led output + HIDD_LE_IDX_REPORT_LED_OUT_CHAR, + HIDD_LE_IDX_REPORT_LED_OUT_VAL, + HIDD_LE_IDX_REPORT_LED_OUT_REP_REF, + +#if (SUPPORT_REPORT_VENDOR == true) + /// Report Vendor + HIDD_LE_IDX_REPORT_VENDOR_OUT_CHAR, + HIDD_LE_IDX_REPORT_VENDOR_OUT_VAL, + HIDD_LE_IDX_REPORT_VENDOR_OUT_REP_REF, +#endif + HIDD_LE_IDX_REPORT_CC_IN_CHAR, + HIDD_LE_IDX_REPORT_CC_IN_VAL, + HIDD_LE_IDX_REPORT_CC_IN_CCC, + HIDD_LE_IDX_REPORT_CC_IN_REP_REF, + + // Boot Keyboard Input Report + HIDD_LE_IDX_BOOT_KB_IN_REPORT_CHAR, + HIDD_LE_IDX_BOOT_KB_IN_REPORT_VAL, + HIDD_LE_IDX_BOOT_KB_IN_REPORT_NTF_CFG, + + // Boot Keyboard Output Report + HIDD_LE_IDX_BOOT_KB_OUT_REPORT_CHAR, + HIDD_LE_IDX_BOOT_KB_OUT_REPORT_VAL, + + // Boot Mouse Input Report + HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_CHAR, + HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_VAL, + HIDD_LE_IDX_BOOT_MOUSE_IN_REPORT_NTF_CFG, + + // Report + HIDD_LE_IDX_REPORT_CHAR, + HIDD_LE_IDX_REPORT_VAL, + HIDD_LE_IDX_REPORT_REP_REF, + //HIDD_LE_IDX_REPORT_NTF_CFG, + + HIDD_LE_IDX_NB, +}; + + +/// Attribute Table Indexes +enum { + HIDD_LE_INFO_CHAR, + HIDD_LE_CTNL_PT_CHAR, + HIDD_LE_REPORT_MAP_CHAR, + HIDD_LE_REPORT_CHAR, + HIDD_LE_PROTO_MODE_CHAR, + HIDD_LE_BOOT_KB_IN_REPORT_CHAR, + HIDD_LE_BOOT_KB_OUT_REPORT_CHAR, + HIDD_LE_BOOT_MOUSE_IN_REPORT_CHAR, + HIDD_LE_CHAR_MAX //= HIDD_LE_REPORT_CHAR + HIDD_LE_NB_REPORT_INST_MAX, +}; + +///att read event table Indexs +enum { + HIDD_LE_READ_INFO_EVT, + HIDD_LE_READ_CTNL_PT_EVT, + HIDD_LE_READ_REPORT_MAP_EVT, + HIDD_LE_READ_REPORT_EVT, + HIDD_LE_READ_PROTO_MODE_EVT, + HIDD_LE_BOOT_KB_IN_REPORT_EVT, + HIDD_LE_BOOT_KB_OUT_REPORT_EVT, + HIDD_LE_BOOT_MOUSE_IN_REPORT_EVT, + + HID_LE_EVT_MAX +}; + +/// Client Characteristic Configuration Codes +enum { + HIDD_LE_DESC_MASK = 0x10, + + HIDD_LE_BOOT_KB_IN_REPORT_CFG = HIDD_LE_BOOT_KB_IN_REPORT_CHAR | HIDD_LE_DESC_MASK, + HIDD_LE_BOOT_MOUSE_IN_REPORT_CFG = HIDD_LE_BOOT_MOUSE_IN_REPORT_CHAR | HIDD_LE_DESC_MASK, + HIDD_LE_REPORT_CFG = HIDD_LE_REPORT_CHAR | HIDD_LE_DESC_MASK, +}; + +/// Features Flag Values +enum { + HIDD_LE_CFG_KEYBOARD = 0x01, + HIDD_LE_CFG_MOUSE = 0x02, + HIDD_LE_CFG_PROTO_MODE = 0x04, + HIDD_LE_CFG_MAP_EXT_REF = 0x08, + HIDD_LE_CFG_BOOT_KB_WR = 0x10, + HIDD_LE_CFG_BOOT_MOUSE_WR = 0x20, +}; + +/// Report Char. Configuration Flag Values +enum { + HIDD_LE_CFG_REPORT_IN = 0x01, + HIDD_LE_CFG_REPORT_OUT = 0x02, + //HOGPD_CFG_REPORT_FEAT can be used as a mask to check Report type + HIDD_LE_CFG_REPORT_FEAT = 0x03, + HIDD_LE_CFG_REPORT_WR = 0x10, +}; + +/// Pointer to the connection clean-up function +#define HIDD_LE_CLEANUP_FNCT (NULL) + +/* + * TYPE DEFINITIONS + **************************************************************************************** + */ + +/// HIDD Features structure +typedef struct { + /// Service Features + uint8_t svc_features; + /// Number of Report Char. instances to add in the database + uint8_t report_nb; + /// Report Char. Configuration + uint8_t report_char_cfg[HIDD_LE_NB_REPORT_INST_MAX]; +} hidd_feature_t; + + +typedef struct { + bool in_use; + bool congest; + uint16_t conn_id; + bool connected; + esp_bd_addr_t remote_bda; + uint32_t trans_id; + uint8_t cur_srvc_id; + +} hidd_clcb_t; + +// HID report mapping table +typedef struct { + uint16_t handle; // Handle of report characteristic + uint16_t cccdHandle; // Handle of CCCD for report characteristic + uint8_t id; // Report ID + uint8_t type; // Report type + uint8_t mode; // Protocol mode (report or boot) +} hidRptMap_t; + + +typedef struct { + /// hidd profile id + uint8_t app_id; + /// Notified handle + uint16_t ntf_handle; + ///Attribute handle Table + uint16_t att_tbl[HIDD_LE_IDX_NB]; + /// Supported Features + hidd_feature_t hidd_feature[HIDD_LE_NB_HIDS_INST_MAX]; + /// Current Protocol Mode + uint8_t proto_mode[HIDD_LE_NB_HIDS_INST_MAX]; + /// Number of HIDS added in the database + uint8_t hids_nb; + uint8_t pending_evt; + uint16_t pending_hal; +} hidd_inst_t; + +/// Report Reference structure +typedef struct +{ + ///Report ID + uint8_t report_id; + ///Report Type + uint8_t report_type; +}hids_report_ref_t; + +/// HID Information structure +typedef struct +{ + /// bcdHID + uint16_t bcdHID; + /// bCountryCode + uint8_t bCountryCode; + /// Flags + uint8_t flags; +}hids_hid_info_t; + + +/* service engine control block */ +typedef struct { + hidd_clcb_t hidd_clcb[HID_MAX_APPS]; /* connection link*/ + esp_gatt_if_t gatt_if; + bool enabled; + bool is_take; + bool is_primery; + hidd_inst_t hidd_inst; + esp_hidd_event_cb_t hidd_cb; + uint8_t inst_id; +} hidd_le_env_t; + +extern hidd_le_env_t hidd_le_env; +extern uint8_t hidProtocolMode; + + +void hidd_clcb_alloc (uint16_t conn_id, esp_bd_addr_t bda); + +bool hidd_clcb_dealloc (uint16_t conn_id); + +void hidd_le_create_service(esp_gatt_if_t gatts_if); + +void hidd_set_attr_value(uint16_t handle, uint16_t val_len, const uint8_t *value); + +void hidd_get_attr_value(uint16_t handle, uint16_t *length, uint8_t **value); + +esp_err_t hidd_register_cb(void); + + +#endif ///__HID_DEVICE_LE_PRF__ + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults new file mode 100644 index 00000000..ee53a228 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_hid_device_demo/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/CMakeLists.txt new file mode 100644 index 00000000..dce22425 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_ibeacon_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/Makefile new file mode 100644 index 00000000..a104adcc --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_ibeacon_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/README.md new file mode 100644 index 00000000..2fc16a49 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/README.md @@ -0,0 +1,116 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# ESP-IDF iBeacon demo + +From welcoming people as they arrive at a sporting event to providing information about a nearby museum exhibit, iBeacon opens a new world of possibilities for location awareness, and countless opportunities for interactivity between iOS devices and iBeacon hardware. + +## Using Example + +iBeacon is a trademark of Apple Inc. + +Before building devices which use iBeacon technology, visit https://developer.apple.com/ibeacon/ to obtain a license. + +### iBeacon Mode + +This example demonstrates iBeacon-compatible BLE advertising, and scanning of iBeacons: + +- **IBEACON_SENDER**: demo to send iBeacon-compatible advertising data. + +- **IBEACON_RECEIVER**: demo to receive and resolve iBeacon advertising data. + +Which demo will be run depends on the menuconfig, developers can set it in `iBeacon Example Configuration`. + +The default mode is iBeacon Sender. + +### Menuconfig +Before compiling the demo,developers also need to configure the project: + +```c +idf.py menuconfig +``` +And then enter `Component config->Bluetooth->Bluedroid Enable` + +Because the number of peripherals may be very large, developers can enable the **BLE Scan Duplicate Options**, the maximum number of devices in scan duplicate filter depends on the free heap size, when the cache is full, it is cleared. + +### Event Processing +In the iBeacon receiver demo, the scan result will be posted to `ESP_GAP_SEARCH_INQ_RES_EVT` event: + +```c +switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + /* Search for BLE iBeacon Packet */ + ...... + break; + default: + break; +} + +``` +### Build and Flash + +Build each project and flash it to the board, then run monitor tool to view serial output: + +``` +idp.py -p PORT flash monitor +``` + +(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 iBeacon sender will broadcast iBeacon packet after initializing the Bluetooth protocol stack, and the iBeacon receiver will scan the iBeacon packet. + +### iBeacon Sender + +``` +I (384) boot: Loaded app from partition at offset 0x10000 +I (384) boot: Disabling RNG early entropy source... +I (386) cpu_start: Pro cpu up. +I (389) cpu_start: Starting app cpu, entry point is 0x40081010 +I (0) cpu_start: App cpu up. +I (400) heap_init: Initializing. RAM available for dynamic allocation: +I (406) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM +I (413) heap_init: At 3FFCCCA8 len 00013358 (76 KiB): DRAM +I (419) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM +I (425) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM +I (431) heap_init: At 40090E58 len 0000F1A8 (60 KiB): IRAM +I (438) cpu_start: Pro cpu start user code +I (120) cpu_start: Starting scheduler on PRO CPU +I (0) cpu_start: Starting scheduler on APP CPU +I (244) BTDM_INIT: BT controller compile version [44d04c1] + +I (244) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (624) phy: phy_version: 3910, c0c45a3, May 21 2018, 18:07:06, 0, 0 +I (654) IBEACON_DEMO: register callback +``` + +### iBeacon Receiver + +``` +I (384) boot: Loaded app from partition at offset 0x10000 +I (384) boot: Disabling RNG early entropy source... +I (385) cpu_start: Pro cpu up.\0x1b[0m +I (389) cpu_start: Starting app cpu, entry point is 0x40081010 +I (0) cpu_start: App cpu up. +I (400) heap_init: Initializing. RAM available for dynamic allocation: +I (406) heap_init: At 3FFAFF10 len 000000F0 (0 KiB): DRAM +I (412) heap_init: At 3FFCCC88 len 00013378 (76 KiB): DRAM +I (418) heap_init: At 3FFE0440 len 00003BC0 (14 KiB): D/IRAM +I (425) heap_init: At 3FFE4350 len 0001BCB0 (111 KiB): D/IRAM +I (431) heap_init: At 40090E58 len 0000F1A8 (60 KiB): IRAM +I (437) cpu_start: Pro cpu start user code\0x1b[0m +I (120) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +I (243) BTDM_INIT: BT controller compile version [44d04c1] + +I (243) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (633) phy: phy_version: 3910, c0c45a3, May 21 2018, 18:07:06, 0, 0 +I (663) IBEACON_DEMO: register callback +I (329203) IBEACON_DEMO: ----------iBeacon Found---------- +I (329203) IBEACON_DEMO: Device address:: 30 ae a4 00 42 82 +I (329203) IBEACON_DEMO: Proximity UUID:: fd a5 06 93 a4 e2 4f b1 af cf c6 eb 07 64 78 25 +``` + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/CMakeLists.txt new file mode 100644 index 00000000..50293e2a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "esp_ibeacon_api.c" + "ibeacon_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/Kconfig.projbuild b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/Kconfig.projbuild new file mode 100644 index 00000000..fd9df0c1 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/Kconfig.projbuild @@ -0,0 +1,26 @@ +menu "iBeacon Example Configuration" + + choice IBEACON_MODE + bool "iBeacon Mode" + default IBEACON_SENDER + help + Select the iBeacon Mode. + + config IBEACON_SENDER + bool "iBeacon Sender Mode" + help + Select the iBeacon Sender Mode. + + config IBEACON_RECEIVER + bool "iBeacon Receiver Mode" + help + Select the iBeacon Receiver Mode. + + endchoice + + config IBEACON_MODE + int + default 0 if IBEACON_SENDER + default 1 if IBEACON_RECEIVER + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/esp_ibeacon_api.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/esp_ibeacon_api.c new file mode 100644 index 00000000..248db09e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/esp_ibeacon_api.c @@ -0,0 +1,72 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This file is for iBeacon APIs. It supports both iBeacon encode and decode. +* +* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology, +* visit https://developer.apple.com/ibeacon/ to obtain a license. +* +****************************************************************************/ + +#include +#include +#include +#include + +#include "esp_gap_ble_api.h" +#include "esp_ibeacon_api.h" + + +const uint8_t uuid_zeros[ESP_UUID_LEN_128] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + +/* For iBeacon packet format, please refer to Apple "Proximity Beacon Specification" doc */ +/* Constant part of iBeacon data */ +esp_ble_ibeacon_head_t ibeacon_common_head = { + .flags = {0x02, 0x01, 0x06}, + .length = 0x1A, + .type = 0xFF, + .company_id = 0x004C, + .beacon_type = 0x1502 +}; + +/* Vendor part of iBeacon data*/ +esp_ble_ibeacon_vendor_t vendor_config = { + .proximity_uuid = ESP_UUID, + .major = ENDIAN_CHANGE_U16(ESP_MAJOR), //Major=ESP_MAJOR + .minor = ENDIAN_CHANGE_U16(ESP_MINOR), //Minor=ESP_MINOR + .measured_power = 0xC5 +}; + +bool esp_ble_is_ibeacon_packet (uint8_t *adv_data, uint8_t adv_data_len){ + bool result = false; + + if ((adv_data != NULL) && (adv_data_len == 0x1E)){ + if (!memcmp(adv_data, (uint8_t*)&ibeacon_common_head, sizeof(ibeacon_common_head))){ + result = true; + } + } + + return result; +} + +esp_err_t esp_ble_config_ibeacon_data (esp_ble_ibeacon_vendor_t *vendor_config, esp_ble_ibeacon_t *ibeacon_adv_data){ + if ((vendor_config == NULL) || (ibeacon_adv_data == NULL) || (!memcmp(vendor_config->proximity_uuid, uuid_zeros, sizeof(uuid_zeros)))){ + return ESP_ERR_INVALID_ARG; + } + + memcpy(&ibeacon_adv_data->ibeacon_head, &ibeacon_common_head, sizeof(esp_ble_ibeacon_head_t)); + memcpy(&ibeacon_adv_data->ibeacon_vendor, vendor_config, sizeof(esp_ble_ibeacon_vendor_t)); + + return ESP_OK; +} + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/esp_ibeacon_api.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/esp_ibeacon_api.h new file mode 100644 index 00000000..39529f22 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/esp_ibeacon_api.h @@ -0,0 +1,77 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This file is for iBeacon definitions. It supports both iBeacon sender and receiver +* which is distinguished by macros IBEACON_SENDER and IBEACON_RECEIVER, +* +* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology, +* visit https://developer.apple.com/ibeacon/ to obtain a license. +* +****************************************************************************/ + +#include +#include +#include +#include + +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" + + +/* Because current ESP IDF version doesn't support scan and adv simultaneously, + * so iBeacon sender and receiver should not run simultaneously */ +#define IBEACON_SENDER 0 +#define IBEACON_RECEIVER 1 +#define IBEACON_MODE CONFIG_IBEACON_MODE + +/* Major and Minor part are stored in big endian mode in iBeacon packet, + * need to use this macro to transfer while creating or processing + * iBeacon data */ +#define ENDIAN_CHANGE_U16(x) ((((x)&0xFF00)>>8) + (((x)&0xFF)<<8)) + +/* Espressif WeChat official account can be found using WeChat "Yao Yi Yao Zhou Bian", + * if device advertises using ESP defined UUID. + * Please refer to http://zb.weixin.qq.com for further information. */ +#define ESP_UUID {0xFD, 0xA5, 0x06, 0x93, 0xA4, 0xE2, 0x4F, 0xB1, 0xAF, 0xCF, 0xC6, 0xEB, 0x07, 0x64, 0x78, 0x25} +#define ESP_MAJOR 10167 +#define ESP_MINOR 61958 + + +typedef struct { + uint8_t flags[3]; + uint8_t length; + uint8_t type; + uint16_t company_id; + uint16_t beacon_type; +}__attribute__((packed)) esp_ble_ibeacon_head_t; + +typedef struct { + uint8_t proximity_uuid[16]; + uint16_t major; + uint16_t minor; + int8_t measured_power; +}__attribute__((packed)) esp_ble_ibeacon_vendor_t; + + +typedef struct { + esp_ble_ibeacon_head_t ibeacon_head; + esp_ble_ibeacon_vendor_t ibeacon_vendor; +}__attribute__((packed)) esp_ble_ibeacon_t; + + +/* For iBeacon packet format, please refer to Apple "Proximity Beacon Specification" doc */ +/* Constant part of iBeacon data */ +extern esp_ble_ibeacon_head_t ibeacon_common_head; + +bool esp_ble_is_ibeacon_packet (uint8_t *adv_data, uint8_t adv_data_len); + +esp_err_t esp_ble_config_ibeacon_data (esp_ble_ibeacon_vendor_t *vendor_config, esp_ble_ibeacon_t *ibeacon_adv_data); diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/ibeacon_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/ibeacon_demo.c new file mode 100644 index 00000000..7aa8ae26 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/main/ibeacon_demo.c @@ -0,0 +1,191 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This file is for iBeacon demo. It supports both iBeacon sender and receiver +* which is distinguished by macros IBEACON_SENDER and IBEACON_RECEIVER, +* +* iBeacon is a trademark of Apple Inc. Before building devices which use iBeacon technology, +* visit https://developer.apple.com/ibeacon/ to obtain a license. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs_flash.h" + +#include "esp_bt.h" +#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 "esp_ibeacon_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +static const char* DEMO_TAG = "IBEACON_DEMO"; +extern esp_ble_ibeacon_vendor_t vendor_config; + +///Declare static functions +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); + +#if (IBEACON_MODE == IBEACON_RECEIVER) +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +#elif (IBEACON_MODE == IBEACON_SENDER) +static esp_ble_adv_params_t ble_adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_NONCONN_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; +#endif + + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + esp_err_t err; + + switch (event) { + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:{ +#if (IBEACON_MODE == IBEACON_SENDER) + esp_ble_gap_start_advertising(&ble_adv_params); +#endif + break; + } + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { +#if (IBEACON_MODE == IBEACON_RECEIVER) + //the unit of the duration is second, 0 means scan permanently + uint32_t duration = 0; + esp_ble_gap_start_scanning(duration); +#endif + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(DEMO_TAG, "Scan start failed: %s", esp_err_to_name(err)); + } + break; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + //adv start complete event to indicate adv start successfully or failed + if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(DEMO_TAG, "Adv start failed: %s", esp_err_to_name(err)); + } + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + /* Search for BLE iBeacon Packet */ + if (esp_ble_is_ibeacon_packet(scan_result->scan_rst.ble_adv, scan_result->scan_rst.adv_data_len)){ + esp_ble_ibeacon_t *ibeacon_data = (esp_ble_ibeacon_t*)(scan_result->scan_rst.ble_adv); + ESP_LOGI(DEMO_TAG, "----------iBeacon Found----------"); + esp_log_buffer_hex("IBEACON_DEMO: Device address:", scan_result->scan_rst.bda, ESP_BD_ADDR_LEN ); + esp_log_buffer_hex("IBEACON_DEMO: Proximity UUID:", ibeacon_data->ibeacon_vendor.proximity_uuid, ESP_UUID_LEN_128); + + uint16_t major = ENDIAN_CHANGE_U16(ibeacon_data->ibeacon_vendor.major); + uint16_t minor = ENDIAN_CHANGE_U16(ibeacon_data->ibeacon_vendor.minor); + ESP_LOGI(DEMO_TAG, "Major: 0x%04x (%d)", major, major); + ESP_LOGI(DEMO_TAG, "Minor: 0x%04x (%d)", minor, minor); + ESP_LOGI(DEMO_TAG, "Measured power (RSSI at a 1m distance):%d dbm", ibeacon_data->ibeacon_vendor.measured_power); + ESP_LOGI(DEMO_TAG, "RSSI of packet:%d dbm", scan_result->scan_rst.rssi); + } + break; + default: + break; + } + break; + } + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(DEMO_TAG, "Scan stop failed: %s", esp_err_to_name(err)); + } + else { + ESP_LOGI(DEMO_TAG, "Stop scan successfully"); + } + break; + + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(DEMO_TAG, "Adv stop failed: %s", esp_err_to_name(err)); + } + else { + ESP_LOGI(DEMO_TAG, "Stop adv successfully"); + } + break; + + default: + break; + } +} + + +void ble_ibeacon_appRegister(void) +{ + esp_err_t status; + + ESP_LOGI(DEMO_TAG, "register callback"); + + //register the scan callback function to the gap module + if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) { + ESP_LOGE(DEMO_TAG, "gap register error: %s", esp_err_to_name(status)); + return; + } + +} + +void ble_ibeacon_init(void) +{ + esp_bluedroid_init(); + esp_bluedroid_enable(); + ble_ibeacon_appRegister(); +} + +void app_main(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + 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(); + esp_bt_controller_init(&bt_cfg); + esp_bt_controller_enable(ESP_BT_MODE_BLE); + + ble_ibeacon_init(); + + /* set scan parameters */ +#if (IBEACON_MODE == IBEACON_RECEIVER) + esp_ble_gap_set_scan_params(&ble_scan_params); + +#elif (IBEACON_MODE == IBEACON_SENDER) + esp_ble_ibeacon_t ibeacon_adv_data; + esp_err_t status = esp_ble_config_ibeacon_data (&vendor_config, &ibeacon_adv_data); + if (status == ESP_OK){ + esp_ble_gap_config_adv_data_raw((uint8_t*)&ibeacon_adv_data, sizeof(ibeacon_adv_data)); + } + else { + ESP_LOGE(DEMO_TAG, "Config iBeacon data failed: %s\n", esp_err_to_name(status)); + } +#endif +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults new file mode 100644 index 00000000..00fb5211 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_ibeacon/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/CMakeLists.txt new file mode 100644 index 00000000..301d8408 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(spp_client_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/Makefile new file mode 100644 index 00000000..9628803c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := spp_client_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/README.md new file mode 100644 index 00000000..bb9bf146 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/README.md @@ -0,0 +1,156 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# ESP-IDF SPP GATT CLIENT demo + + In Bluetooth classic (BR/EDR) systems, a Serial Port Profile (SPP) is an adopted profile defined by the Bluetooth Special Interest Group (SIG) used to emulate a serial port connection over a Bluetooth wireless connection. For BLE systems, an adopted SPP profile over BLE is not defined, thus emulation of a serial port must be implemented as a vendor-specific custom profile. + + This reference design consists of two Demos, the ble spp server and ble spp client that run on their respective endpoints. These devices connect and exchange data wirelessly with each other. This capability creates a virtual serial link over the air. Each byte input can be sent and received by both the server and client. The spp server is implemented as the [ble_spp_server](../ble_spp_server) demo while the spp client is implemented as the [ble_spp_client](../ble_spp_client) demo. Espressif designed the BLE SPP applications to use the UART transport layer but you could adapt this design to work with other serial protocols, such as SPI. + + This vendor-specific custom profile is implemented in [spp_client_demo.c](../ble_spp_client/main/spp_client_demo.c) and [spp_server_demo.c](../ble_spp_server/main/ble_spp_server_demo.c). + +## Using Examples + +### Initialization + + Both the server and client will first initialize the uart and ble. The server demo will set up the serial port service with standard GATT and GAP services in the attribute server. The client demo will scan the ble broadcast over the air to find the spp server. + +### Event Processing + + The spp server has two main event processing functions for BLE event: + +```c + void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param); + void gatts_profile_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t * param); +``` + + The spp client has two main event processing functions for BLE event: + +```c + esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t * param); + void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t * param); +``` + + These are some queues and tasks used by SPP application: + + Queues: + + * spp_uart_queue - Uart data messages received from the Uart + * cmd_cmd_queue - commands received from the client + * cmd_heartbeat_queue - heartbeat received, if supported + + Tasks: + + * `uart_task` - process Uart + * `spp_cmd_task` - process command messages, the commands and processing were defined by customer + * `spp_heartbeat_task` - if heartbeat is supported, the task will send a heatbeat packet to the remote device + +### Packet Structure + + After the Uart received data, the data will be posted to Uart task. Then, in the UART_DATA event, the raw data may be retrieved. The max length is 120 bytes every time. + If you run the ble spp demo with two ESP32 chips, the MTU size will be exchanged for 200 bytes after the ble connection is established, so every packet can be send directly. + If you only run the ble_spp_server demo, and it was connected by a phone, the MTU size may be less than 123 bytes. In such a case the data will be split into fragments and send in turn. + In every packet, we add 4 bytes to indicate that this is a fragment packet. The first two bytes contain "##" if this is a fragment packet, the third byte is the total number of the packets, the fourth byte is the current number of this packet. + The phone APP need to check the structure of the packet if it want to communicate with the ble_spp_server demo. + +### Sending Data Wirelessly + + The client will be sending WriteNoRsp packets to the server. The server side sends data through notifications. When the Uart receives data, the Uart task places it in the buffer. If the size of the data is larger than (MTU size - 3), the data will be split into packets and send in turn. + +### Receiving Data Wirelessly + + The server will receive this data in the ESP_GATTS_WRITE_EVT event and send data to the Uart terminal by `uart_wrire_bytes` function. For example: + + case ESP_GATTS_WRITE_EVT: + ... + if(res == SPP_IDX_SPP_DATA_RECV_VAL){ + uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len); + } + ... + break; + +### GATT Server Attribute Table + + charactertistic|UUID|Permissions + :-:|:-:|:-: + SPP_DATA_RECV_CHAR|0xABF1|READ&WRITE_NR + SPP_DATA_NOTIFY_CHAR|0xABF2|READ&NOTIFY + SPP_COMMAND_CHAR|0xABF3|READ&WRITE_NR + SPP_STATUS_CHAR|0xABF4|READ & NOTIFY + SPP_HEARTBEAT_CHAR|0xABF5|READ&WRITE_NR&NOTIFY + +### Build and Flash + +Build each project and flash it to the board, then run monitor tool to view serial output: + +``` +idf.py -p PORT flash monitor +``` + +(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 spp cilent will auto connect to the spp server, do service search, exchange MTU size and register notification. + +### Client + +``` +I (2894) GATTC_SPP_DEMO: ESP_GATTC_CONNECT_EVT: conn_id=0, gatt_if = 3 +I (2894) GATTC_SPP_DEMO: REMOTE BDA: +I (2904) GATTC_SPP_DEMO: 00 00 00 00 00 00 +I (2904) GATTC_SPP_DEMO: EVT 2, gattc if 3 +I (3414) GATTC_SPP_DEMO: EVT 7, gattc if 3 +I (3414) GATTC_SPP_DEMO: ESP_GATTC_SEARCH_RES_EVT: start_handle = 40, end_handle = 65535, UUID:0xabf0 +I (3424) GATTC_SPP_DEMO: EVT 6, gattc if 3 +I (3424) GATTC_SPP_DEMO: SEARCH_CMPL: conn_id = 0, status 0 +I (3464) GATTC_SPP_DEMO: EVT 18, gattc if 3 +I (3464) GATTC_SPP_DEMO: +MTU:200 + +I (3464) GATTC_SPP_DEMO: attr_type = PRIMARY_SERVICE,attribute_handle=40,start_handle=40,end_handle=65535,properties=0x0,uuid=0xabf0 +I (3474) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=42,start_handle=0,end_handle=0,properties=0x6,uuid=0xabf1 +I (3484) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=44,start_handle=0,end_handle=0,properties=0x12,uuid=0xabf2 +I (3494) GATTC_SPP_DEMO: attr_type = DESCRIPTOR,attribute_handle=45,start_handle=0,end_handle=0,properties=0x0,uuid=0x2902 +I (3504) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=47,start_handle=0,end_handle=0,properties=0x6,uuid=0xabf3 +I (3524) GATTC_SPP_DEMO: attr_type = CHARACTERISTIC,attribute_handle=49,start_handle=0,end_handle=0,properties=0x12,uuid=0xabf4 +I (3534) GATTC_SPP_DEMO: attr_type = DESCRIPTOR,attribute_handle=50,start_handle=0,end_handle=0,properties=0x0,uuid=0x2902 +I (3544) GATTC_SPP_DEMO: Index = 2,UUID = 0xabf2, handle = 44 +I (3554) GATTC_SPP_DEMO: EVT 38, gattc if 3 +I (3554) GATTC_SPP_DEMO: Index = 2,status = 0,handle = 44 +I (3594) GATTC_SPP_DEMO: EVT 9, gattc if 3 +I (3594) GATTC_SPP_DEMO: ESP_GATTC_WRITE_DESCR_EVT: status =0,handle = 45 +I (3654) GATTC_SPP_DEMO: Index = 5,UUID = 0xabf4, handle = 49 +I (3654) GATTC_SPP_DEMO: EVT 38, gattc if 3 +I (3654) GATTC_SPP_DEMO: Index = 5,status = 0,handle = 49 +I (3684) GATTC_SPP_DEMO: EVT 9, gattc if 3 +I (3684) GATTC_SPP_DEMO: ESP_GATTC_WRITE_DESCR_EVT: status =0,handle = 50 +I (16904) GATTC_SPP_DEMO: EVT 10, gattc if 3 +I (16904) GATTC_SPP_DEMO: ESP_GATTC_NOTIFY_EVT +I (16904) GATTC_SPP_DEMO: +NOTIFY:handle = 44,length = 22 +``` + +### Server + +``` +I (4452) GATTS_SPP_DEMO: EVT 14, gatts if 3 +I (4452) GATTS_SPP_DEMO: event = e +I (5022) GATTS_SPP_DEMO: EVT 4, gatts if 3 +I (5022) GATTS_SPP_DEMO: event = 4 +I (5152) GATTS_SPP_DEMO: EVT 2, gatts if 3 +I (5152) GATTS_SPP_DEMO: event = 2 +I (5152) GATTS_SPP_DEMO: ESP_GATTS_WRITE_EVT : handle = 5 +I (5242) GATTS_SPP_DEMO: EVT 2, gatts if 3 +I (5242) GATTS_SPP_DEMO: event = 2 +I (5242) GATTS_SPP_DEMO: ESP_GATTS_WRITE_EVT : handle = 10 +I (18462) GATTS_SPP_DEMO: EVT 5, gatts if 3 +I (18462) GATTS_SPP_DEMO: event = 5 +I (27652) GATTS_SPP_DEMO: EVT 2, gatts if 3 +I (27652) GATTS_SPP_DEMO: event = 2 +I (27652) GATTS_SPP_DEMO: ESP_GATTS_WRITE_EVT : handle = 2 + +``` +if you input data to the Uart terminal, it will be printed in the remote device Uart terminal. + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/CMakeLists.txt new file mode 100644 index 00000000..1b83a293 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "spp_client_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c new file mode 100644 index 00000000..6d83a31f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/main/spp_client_demo.c @@ -0,0 +1,643 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This file is for ble spp client demo. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "driver/uart.h" + +#include "esp_bt.h" +#include "nvs_flash.h" +#include "esp_bt_device.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_system.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +#define GATTC_TAG "GATTC_SPP_DEMO" +#define PROFILE_NUM 1 +#define PROFILE_APP_ID 0 +#define BT_BD_ADDR_STR "%02x:%02x:%02x:%02x:%02x:%02x" +#define BT_BD_ADDR_HEX(addr) addr[0],addr[1],addr[2],addr[3],addr[4],addr[5] +#define ESP_GATT_SPP_SERVICE_UUID 0xABF0 +#define SCAN_ALL_THE_TIME 0 + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; + +enum{ + SPP_IDX_SVC, + + SPP_IDX_SPP_DATA_RECV_VAL, + + SPP_IDX_SPP_DATA_NTY_VAL, + SPP_IDX_SPP_DATA_NTF_CFG, + + SPP_IDX_SPP_COMMAND_VAL, + + SPP_IDX_SPP_STATUS_VAL, + SPP_IDX_SPP_STATUS_CFG, + +#ifdef SUPPORT_HEARTBEAT + SPP_IDX_SPP_HEARTBEAT_VAL, + SPP_IDX_SPP_HEARTBEAT_CFG, +#endif + + SPP_IDX_NB, +}; + +///Declare static functions +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +static const char device_name[] = "ESP_SPP_SERVER"; +static bool is_connect = false; +static uint16_t spp_conn_id = 0; +static uint16_t spp_mtu_size = 23; +static uint16_t cmd = 0; +static uint16_t spp_srv_start_handle = 0; +static uint16_t spp_srv_end_handle = 0; +static uint16_t spp_gattc_if = 0xff; +static char * notify_value_p = NULL; +static int notify_value_offset = 0; +static int notify_value_count = 0; +static uint16_t count = SPP_IDX_NB; +static esp_gattc_db_elem_t *db = NULL; +static esp_ble_gap_cb_param_t scan_rst; +static xQueueHandle cmd_reg_queue = NULL; +QueueHandle_t spp_uart_queue = NULL; + +#ifdef SUPPORT_HEARTBEAT +static uint8_t heartbeat_s[9] = {'E','s','p','r','e','s','s','i','f'}; +static xQueueHandle cmd_heartbeat_queue = NULL; +#endif + +static esp_bt_uuid_t spp_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_SPP_SERVICE_UUID,}, +}; + +static void notify_event_handler(esp_ble_gattc_cb_param_t * p_data) +{ + uint8_t handle = 0; + + if(p_data->notify.is_notify == true){ + ESP_LOGI(GATTC_TAG,"+NOTIFY:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len); + }else{ + ESP_LOGI(GATTC_TAG,"+INDICATE:handle = %d,length = %d ", p_data->notify.handle, p_data->notify.value_len); + } + handle = p_data->notify.handle; + if(db == NULL) { + ESP_LOGE(GATTC_TAG, " %s db is NULL\n", __func__); + return; + } + if(handle == db[SPP_IDX_SPP_DATA_NTY_VAL].attribute_handle){ +#ifdef SPP_DEBUG_MODE + esp_log_buffer_char(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len); +#else + if((p_data->notify.value[0] == '#')&&(p_data->notify.value[1] == '#')){ + if((++notify_value_count) != p_data->notify.value[3]){ + if(notify_value_p != NULL){ + free(notify_value_p); + } + notify_value_count = 0; + notify_value_p = NULL; + notify_value_offset = 0; + ESP_LOGE(GATTC_TAG,"notify value count is not continuous,%s\n",__func__); + return; + } + if(p_data->notify.value[3] == 1){ + notify_value_p = (char *)malloc(((spp_mtu_size-7)*(p_data->notify.value[2]))*sizeof(char)); + if(notify_value_p == NULL){ + ESP_LOGE(GATTC_TAG, "malloc failed,%s L#%d\n",__func__,__LINE__); + notify_value_count = 0; + return; + } + memcpy((notify_value_p + notify_value_offset),(p_data->notify.value + 4),(p_data->notify.value_len - 4)); + if(p_data->notify.value[2] == p_data->notify.value[3]){ + uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset)); + free(notify_value_p); + notify_value_p = NULL; + notify_value_offset = 0; + return; + } + notify_value_offset += (p_data->notify.value_len - 4); + }else if(p_data->notify.value[3] <= p_data->notify.value[2]){ + memcpy((notify_value_p + notify_value_offset),(p_data->notify.value + 4),(p_data->notify.value_len - 4)); + if(p_data->notify.value[3] == p_data->notify.value[2]){ + uart_write_bytes(UART_NUM_0, (char *)(notify_value_p), (p_data->notify.value_len - 4 + notify_value_offset)); + free(notify_value_p); + notify_value_count = 0; + notify_value_p = NULL; + notify_value_offset = 0; + return; + } + notify_value_offset += (p_data->notify.value_len - 4); + } + }else{ + uart_write_bytes(UART_NUM_0, (char *)(p_data->notify.value), p_data->notify.value_len); + } +#endif + }else if(handle == ((db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle)){ + esp_log_buffer_char(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len); + //TODO:server notify status characteristic + }else{ + esp_log_buffer_char(GATTC_TAG, (char *)p_data->notify.value, p_data->notify.value_len); + } +} + +static void free_gattc_srv_db(void) +{ + is_connect = false; + spp_gattc_if = 0xff; + spp_conn_id = 0; + spp_mtu_size = 23; + cmd = 0; + spp_srv_start_handle = 0; + spp_srv_end_handle = 0; + notify_value_p = NULL; + notify_value_offset = 0; + notify_value_count = 0; + if(db){ + free(db); + db = NULL; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + uint8_t *adv_name = NULL; + uint8_t adv_name_len = 0; + esp_err_t err; + + switch(event){ + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + if((err = param->scan_param_cmpl.status) != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Scan param set failed: %s", esp_err_to_name(err)); + break; + } + //the unit of the duration is second + uint32_t duration = 0xFFFF; + ESP_LOGI(GATTC_TAG, "Enable Ble Scan:during time 0x%04X minutes.",duration); + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if ((err = param->scan_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTC_TAG, "Scan start failed: %s", esp_err_to_name(err)); + break; + } + ESP_LOGI(GATTC_TAG, "Scan start successed"); + break; + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if ((err = param->scan_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTC_TAG, "Scan stop failed: %s", esp_err_to_name(err)); + break; + } + ESP_LOGI(GATTC_TAG, "Scan stop successed"); + if (is_connect == false) { + ESP_LOGI(GATTC_TAG, "Connect to the remote device."); + esp_ble_gattc_open(gl_profile_tab[PROFILE_APP_ID].gattc_if, scan_rst.scan_rst.bda, scan_rst.scan_rst.ble_addr_type, true); + } + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + ESP_LOGI(GATTC_TAG, "\n"); + if (adv_name != NULL) { + if ( strncmp((char *)adv_name, device_name, adv_name_len) == 0) { + memcpy(&(scan_rst), scan_result, sizeof(esp_ble_gap_cb_param_t)); + esp_ble_gap_stop_scanning(); + } + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + break; + default: + break; + } + break; + } + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Adv stop failed: %s", esp_err_to_name(err)); + }else { + ESP_LOGI(GATTC_TAG, "Stop adv successfully"); + } + break; + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if); + + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d", param->reg.app_id, param->reg.status); + return; + } + } + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG EVT, set scan params"); + esp_ble_gap_set_scan_params(&ble_scan_params); + break; + case ESP_GATTC_CONNECT_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT: conn_id=%d, gatt_if = %d", spp_conn_id, gattc_if); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + spp_gattc_if = gattc_if; + is_connect = true; + spp_conn_id = p_data->connect.conn_id; + memcpy(gl_profile_tab[PROFILE_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); + esp_ble_gattc_search_service(spp_gattc_if, spp_conn_id, &spp_service_uuid); + break; + case ESP_GATTC_DISCONNECT_EVT: + ESP_LOGI(GATTC_TAG, "disconnect"); + free_gattc_srv_db(); + esp_ble_gap_start_scanning(SCAN_ALL_THE_TIME); + break; + case ESP_GATTC_SEARCH_RES_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_RES_EVT: start_handle = %d, end_handle = %d, UUID:0x%04x",p_data->search_res.start_handle,p_data->search_res.end_handle,p_data->search_res.srvc_id.uuid.uuid.uuid16); + spp_srv_start_handle = p_data->search_res.start_handle; + spp_srv_end_handle = p_data->search_res.end_handle; + break; + case ESP_GATTC_SEARCH_CMPL_EVT: + ESP_LOGI(GATTC_TAG, "SEARCH_CMPL: conn_id = %x, status %d", spp_conn_id, p_data->search_cmpl.status); + esp_ble_gattc_send_mtu_req(gattc_if, spp_conn_id); + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + ESP_LOGI(GATTC_TAG,"Index = %d,status = %d,handle = %d\n",cmd, p_data->reg_for_notify.status, p_data->reg_for_notify.handle); + if(p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT, status = %d", p_data->reg_for_notify.status); + break; + } + uint16_t notify_en = 1; + esp_ble_gattc_write_char_descr( + spp_gattc_if, + spp_conn_id, + (db+cmd+1)->attribute_handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + + break; + } + case ESP_GATTC_NOTIFY_EVT: + ESP_LOGI(GATTC_TAG,"ESP_GATTC_NOTIFY_EVT\n"); + notify_event_handler(p_data); + break; + case ESP_GATTC_READ_CHAR_EVT: + ESP_LOGI(GATTC_TAG,"ESP_GATTC_READ_CHAR_EVT\n"); + break; + case ESP_GATTC_WRITE_CHAR_EVT: + ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_CHAR_EVT:status = %d,handle = %d", param->write.status, param->write.handle); + if(param->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "ESP_GATTC_WRITE_CHAR_EVT, error status = %d", p_data->write.status); + break; + } + break; + case ESP_GATTC_PREP_WRITE_EVT: + break; + case ESP_GATTC_EXEC_EVT: + break; + case ESP_GATTC_WRITE_DESCR_EVT: + ESP_LOGI(GATTC_TAG,"ESP_GATTC_WRITE_DESCR_EVT: status =%d,handle = %d \n", p_data->write.status, p_data->write.handle); + if(p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "ESP_GATTC_WRITE_DESCR_EVT, error status = %d", p_data->write.status); + break; + } + switch(cmd){ + case SPP_IDX_SPP_DATA_NTY_VAL: + cmd = SPP_IDX_SPP_STATUS_VAL; + xQueueSend(cmd_reg_queue, &cmd,10/portTICK_PERIOD_MS); + break; + case SPP_IDX_SPP_STATUS_VAL: +#ifdef SUPPORT_HEARTBEAT + cmd = SPP_IDX_SPP_HEARTBEAT_VAL; + xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS); +#endif + break; +#ifdef SUPPORT_HEARTBEAT + case SPP_IDX_SPP_HEARTBEAT_VAL: + xQueueSend(cmd_heartbeat_queue, &cmd, 10/portTICK_PERIOD_MS); + break; +#endif + default: + break; + }; + break; + case ESP_GATTC_CFG_MTU_EVT: + if(p_data->cfg_mtu.status != ESP_OK){ + break; + } + ESP_LOGI(GATTC_TAG,"+MTU:%d\n", p_data->cfg_mtu.mtu); + spp_mtu_size = p_data->cfg_mtu.mtu; + + db = (esp_gattc_db_elem_t *)malloc(count*sizeof(esp_gattc_db_elem_t)); + if(db == NULL){ + ESP_LOGE(GATTC_TAG,"%s:malloc db falied\n",__func__); + break; + } + if(esp_ble_gattc_get_db(spp_gattc_if, spp_conn_id, spp_srv_start_handle, spp_srv_end_handle, db, &count) != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"%s:get db falied\n",__func__); + break; + } + if(count != SPP_IDX_NB){ + ESP_LOGE(GATTC_TAG,"%s:get db count != SPP_IDX_NB, count = %d, SPP_IDX_NB = %d\n",__func__,count,SPP_IDX_NB); + break; + } + for(int i = 0;i < SPP_IDX_NB;i++){ + switch((db+i)->type){ + case ESP_GATT_DB_PRIMARY_SERVICE: + ESP_LOGI(GATTC_TAG,"attr_type = PRIMARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\ + (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_SECONDARY_SERVICE: + ESP_LOGI(GATTC_TAG,"attr_type = SECONDARY_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\ + (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_CHARACTERISTIC: + ESP_LOGI(GATTC_TAG,"attr_type = CHARACTERISTIC,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\ + (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_DESCRIPTOR: + ESP_LOGI(GATTC_TAG,"attr_type = DESCRIPTOR,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\ + (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_INCLUDED_SERVICE: + ESP_LOGI(GATTC_TAG,"attr_type = INCLUDED_SERVICE,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\ + (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); + break; + case ESP_GATT_DB_ALL: + ESP_LOGI(GATTC_TAG,"attr_type = ESP_GATT_DB_ALL,attribute_handle=%d,start_handle=%d,end_handle=%d,properties=0x%x,uuid=0x%04x\n",\ + (db+i)->attribute_handle, (db+i)->start_handle, (db+i)->end_handle, (db+i)->properties, (db+i)->uuid.uuid.uuid16); + break; + default: + break; + } + } + cmd = SPP_IDX_SPP_DATA_NTY_VAL; + xQueueSend(cmd_reg_queue, &cmd, 10/portTICK_PERIOD_MS); + break; + case ESP_GATTC_SRVC_CHG_EVT: + break; + default: + break; + } +} + +void spp_client_reg_task(void* arg) +{ + uint16_t cmd_id; + for(;;) { + vTaskDelay(100 / portTICK_PERIOD_MS); + if(xQueueReceive(cmd_reg_queue, &cmd_id, portMAX_DELAY)) { + if(db != NULL) { + if(cmd_id == SPP_IDX_SPP_DATA_NTY_VAL){ + ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_DATA_NTY_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle); + esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_DATA_NTY_VAL)->attribute_handle); + }else if(cmd_id == SPP_IDX_SPP_STATUS_VAL){ + ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_STATUS_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle); + esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_STATUS_VAL)->attribute_handle); + } +#ifdef SUPPORT_HEARTBEAT + else if(cmd_id == SPP_IDX_SPP_HEARTBEAT_VAL){ + ESP_LOGI(GATTC_TAG,"Index = %d,UUID = 0x%04x, handle = %d \n", cmd_id, (db+SPP_IDX_SPP_HEARTBEAT_VAL)->uuid.uuid.uuid16, (db+SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle); + esp_ble_gattc_register_for_notify(spp_gattc_if, gl_profile_tab[PROFILE_APP_ID].remote_bda, (db+SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle); + } +#endif + } + } + } +} + +#ifdef SUPPORT_HEARTBEAT +void spp_heart_beat_task(void * arg) +{ + uint16_t cmd_id; + + for(;;) { + vTaskDelay(50 / portTICK_PERIOD_MS); + if(xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) { + while(1){ + if((is_connect == true) && (db != NULL) && ((db+SPP_IDX_SPP_HEARTBEAT_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE))){ + esp_ble_gattc_write_char( spp_gattc_if, + spp_conn_id, + (db+SPP_IDX_SPP_HEARTBEAT_VAL)->attribute_handle, + sizeof(heartbeat_s), + (uint8_t *)heartbeat_s, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + vTaskDelay(5000 / portTICK_PERIOD_MS); + }else{ + ESP_LOGI(GATTC_TAG,"disconnect\n"); + break; + } + } + } + } +} +#endif + +void ble_client_appRegister(void) +{ + esp_err_t status; + char err_msg[20]; + + ESP_LOGI(GATTC_TAG, "register callback"); + + //register the scan callback function to the gap module + if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) { + ESP_LOGE(GATTC_TAG, "gap register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg))); + return; + } + //register the callback function to the gattc module + if ((status = esp_ble_gattc_register_callback(esp_gattc_cb)) != ESP_OK) { + ESP_LOGE(GATTC_TAG, "gattc register error: %s", esp_err_to_name_r(status, err_msg, sizeof(err_msg))); + return; + } + esp_ble_gattc_app_register(PROFILE_APP_ID); + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(200); + if (local_mtu_ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed: %s", esp_err_to_name_r(local_mtu_ret, err_msg, sizeof(err_msg))); + } + + cmd_reg_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_client_reg_task, "spp_client_reg_task", 2048, NULL, 10, NULL); + +#ifdef SUPPORT_HEARTBEAT + cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_heart_beat_task, "spp_heart_beat_task", 2048, NULL, 10, NULL); +#endif +} + +void uart_task(void *pvParameters) +{ + uart_event_t event; + for (;;) { + //Waiting for UART event. + if (xQueueReceive(spp_uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { + switch (event.type) { + //Event of UART receving data + case UART_DATA: + if (event.size && (is_connect == true) && (db != NULL) && ((db+SPP_IDX_SPP_DATA_RECV_VAL)->properties & (ESP_GATT_CHAR_PROP_BIT_WRITE_NR | ESP_GATT_CHAR_PROP_BIT_WRITE))) { + uint8_t * temp = NULL; + temp = (uint8_t *)malloc(sizeof(uint8_t)*event.size); + if(temp == NULL){ + ESP_LOGE(GATTC_TAG, "malloc failed,%s L#%d\n", __func__, __LINE__); + break; + } + memset(temp, 0x0, event.size); + uart_read_bytes(UART_NUM_0,temp,event.size,portMAX_DELAY); + esp_ble_gattc_write_char( spp_gattc_if, + spp_conn_id, + (db+SPP_IDX_SPP_DATA_RECV_VAL)->attribute_handle, + event.size, + temp, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + free(temp); + } + break; + default: + break; + } + } + } + vTaskDelete(NULL); +} + +static void spp_uart_init(void) +{ + uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_RTS, + .rx_flow_ctrl_thresh = 122, + .source_clk = UART_SCLK_APB, + }; + + //Install UART driver, and get the queue. + uart_driver_install(UART_NUM_0, 4096, 8192, 10, &spp_uart_queue, 0); + //Set UART parameters + uart_param_config(UART_NUM_0, &uart_config); + //Set UART pins + uart_set_pin(UART_NUM_0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + xTaskCreate(uart_task, "uTask", 2048, (void*)UART_NUM_0, 8, NULL); +} + +void app_main(void) +{ + esp_err_t ret; + + 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(); + + nvs_flash_init(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ESP_LOGI(GATTC_TAG, "%s init bluetooth\n", __func__); + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ble_client_appRegister(); + spp_uart_init(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults new file mode 100644 index 00000000..00fb5211 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_client/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/CMakeLists.txt new file mode 100644 index 00000000..b4dcebe6 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_spp_server_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/Makefile new file mode 100644 index 00000000..0a272359 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_spp_server_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/README.md new file mode 100644 index 00000000..d9d8b7fb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/README.md @@ -0,0 +1,6 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +## ESP-IDF GATT SERVER SPP demo + +For description of this application please refer to [ESP-IDF GATT CLIENT SPP demo](../ble_spp_client/README.md) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/CMakeLists.txt new file mode 100644 index 00000000..be4f67ba --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ble_spp_server_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c new file mode 100644 index 00000000..ab74b896 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.c @@ -0,0 +1,701 @@ +/* + 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 "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 "driver/uart.h" +#include "string.h" + +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "ble_spp_server_demo.h" + +#define GATTS_TABLE_TAG "GATTS_SPP_DEMO" + +#define SPP_PROFILE_NUM 1 +#define SPP_PROFILE_APP_IDX 0 +#define ESP_SPP_APP_ID 0x56 +#define SAMPLE_DEVICE_NAME "ESP_SPP_SERVER" //The Device Name Characteristics in GAP +#define SPP_SVC_INST_ID 0 + +/// SPP Service +static const uint16_t spp_service_uuid = 0xABF0; +/// Characteristic UUID +#define ESP_GATT_UUID_SPP_DATA_RECEIVE 0xABF1 +#define ESP_GATT_UUID_SPP_DATA_NOTIFY 0xABF2 +#define ESP_GATT_UUID_SPP_COMMAND_RECEIVE 0xABF3 +#define ESP_GATT_UUID_SPP_COMMAND_NOTIFY 0xABF4 + +#ifdef SUPPORT_HEARTBEAT +#define ESP_GATT_UUID_SPP_HEARTBEAT 0xABF5 +#endif + +static const uint8_t spp_adv_data[23] = { + /* Flags */ + 0x02,0x01,0x06, + /* Complete List of 16-bit Service Class UUIDs */ + 0x03,0x03,0xF0,0xAB, + /* Complete Local Name in advertising */ + 0x0F,0x09, 'E', 'S', 'P', '_', 'S', 'P', 'P', '_', 'S', 'E', 'R','V', 'E', 'R' +}; + +static uint16_t spp_mtu_size = 23; +static uint16_t spp_conn_id = 0xffff; +static esp_gatt_if_t spp_gatts_if = 0xff; +QueueHandle_t spp_uart_queue = NULL; +static xQueueHandle cmd_cmd_queue = NULL; + +#ifdef SUPPORT_HEARTBEAT +static xQueueHandle cmd_heartbeat_queue = NULL; +static uint8_t heartbeat_s[9] = {'E','s','p','r','e','s','s','i','f'}; +static bool enable_heart_ntf = false; +static uint8_t heartbeat_count_num = 0; +#endif + +static bool enable_data_ntf = false; +static bool is_connected = false; +static esp_bd_addr_t spp_remote_bda = {0x0,}; + +static uint16_t spp_handle_table[SPP_IDX_NB]; + +static esp_ble_adv_params_t spp_adv_params = { + .adv_int_min = 0x20, + .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; +}; + +typedef struct spp_receive_data_node{ + int32_t len; + uint8_t * node_buff; + struct spp_receive_data_node * next_node; +}spp_receive_data_node_t; + +static spp_receive_data_node_t * temp_spp_recv_data_node_p1 = NULL; +static spp_receive_data_node_t * temp_spp_recv_data_node_p2 = NULL; + +typedef struct spp_receive_data_buff{ + int32_t node_num; + int32_t buff_size; + spp_receive_data_node_t * first_node; +}spp_receive_data_buff_t; + +static spp_receive_data_buff_t SppRecvDataBuff = { + .node_num = 0, + .buff_size = 0, + .first_node = NULL +}; + +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 spp_profile_tab[SPP_PROFILE_NUM] = { + [SPP_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 */ + }, +}; + +/* + * SPP PROFILE ATTRIBUTES + **************************************************************************************** + */ + +#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) +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 uint8_t char_prop_read_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE_NR|ESP_GATT_CHAR_PROP_BIT_READ; + +#ifdef SUPPORT_HEARTBEAT +static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_READ|ESP_GATT_CHAR_PROP_BIT_WRITE_NR|ESP_GATT_CHAR_PROP_BIT_NOTIFY; +#endif + +///SPP Service - data receive characteristic, read&write without response +static const uint16_t spp_data_receive_uuid = ESP_GATT_UUID_SPP_DATA_RECEIVE; +static const uint8_t spp_data_receive_val[20] = {0x00}; + +///SPP Service - data notify characteristic, notify&read +static const uint16_t spp_data_notify_uuid = ESP_GATT_UUID_SPP_DATA_NOTIFY; +static const uint8_t spp_data_notify_val[20] = {0x00}; +static const uint8_t spp_data_notify_ccc[2] = {0x00, 0x00}; + +///SPP Service - command characteristic, read&write without response +static const uint16_t spp_command_uuid = ESP_GATT_UUID_SPP_COMMAND_RECEIVE; +static const uint8_t spp_command_val[10] = {0x00}; + +///SPP Service - status characteristic, notify&read +static const uint16_t spp_status_uuid = ESP_GATT_UUID_SPP_COMMAND_NOTIFY; +static const uint8_t spp_status_val[10] = {0x00}; +static const uint8_t spp_status_ccc[2] = {0x00, 0x00}; + +#ifdef SUPPORT_HEARTBEAT +///SPP Server - Heart beat characteristic, notify&write&read +static const uint16_t spp_heart_beat_uuid = ESP_GATT_UUID_SPP_HEARTBEAT; +static const uint8_t spp_heart_beat_val[2] = {0x00, 0x00}; +static const uint8_t spp_heart_beat_ccc[2] = {0x00, 0x00}; +#endif + +///Full HRS Database Description - Used to add attributes into the database +static const esp_gatts_attr_db_t spp_gatt_db[SPP_IDX_NB] = +{ + //SPP - Service Declaration + [SPP_IDX_SVC] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, + sizeof(spp_service_uuid), sizeof(spp_service_uuid), (uint8_t *)&spp_service_uuid}}, + + //SPP - data receive characteristic Declaration + [SPP_IDX_SPP_DATA_RECV_CHAR] = + {{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}}, + + //SPP - data receive characteristic Value + [SPP_IDX_SPP_DATA_RECV_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_receive_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + SPP_DATA_MAX_LEN,sizeof(spp_data_receive_val), (uint8_t *)spp_data_receive_val}}, + + //SPP - data notify characteristic Declaration + [SPP_IDX_SPP_DATA_NOTIFY_CHAR] = + {{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_notify}}, + + //SPP - data notify characteristic Value + [SPP_IDX_SPP_DATA_NTY_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_data_notify_uuid, ESP_GATT_PERM_READ, + SPP_DATA_MAX_LEN, sizeof(spp_data_notify_val), (uint8_t *)spp_data_notify_val}}, + + //SPP - data notify characteristic - Client Characteristic Configuration Descriptor + [SPP_IDX_SPP_DATA_NTF_CFG] = + {{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(spp_data_notify_ccc), (uint8_t *)spp_data_notify_ccc}}, + + //SPP - command characteristic Declaration + [SPP_IDX_SPP_COMMAND_CHAR] = + {{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}}, + + //SPP - command characteristic Value + [SPP_IDX_SPP_COMMAND_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_command_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + SPP_CMD_MAX_LEN,sizeof(spp_command_val), (uint8_t *)spp_command_val}}, + + //SPP - status characteristic Declaration + [SPP_IDX_SPP_STATUS_CHAR] = + {{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_notify}}, + + //SPP - status characteristic Value + [SPP_IDX_SPP_STATUS_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_status_uuid, ESP_GATT_PERM_READ, + SPP_STATUS_MAX_LEN,sizeof(spp_status_val), (uint8_t *)spp_status_val}}, + + //SPP - status characteristic - Client Characteristic Configuration Descriptor + [SPP_IDX_SPP_STATUS_CFG] = + {{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(spp_status_ccc), (uint8_t *)spp_status_ccc}}, + +#ifdef SUPPORT_HEARTBEAT + //SPP - Heart beat characteristic Declaration + [SPP_IDX_SPP_HEARTBEAT_CHAR] = + {{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_notify}}, + + //SPP - Heart beat characteristic Value + [SPP_IDX_SPP_HEARTBEAT_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&spp_heart_beat_uuid, ESP_GATT_PERM_READ|ESP_GATT_PERM_WRITE, + sizeof(spp_heart_beat_val), sizeof(spp_heart_beat_val), (uint8_t *)spp_heart_beat_val}}, + + //SPP - Heart beat characteristic - Client Characteristic Configuration Descriptor + [SPP_IDX_SPP_HEARTBEAT_CFG] = + {{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(spp_data_notify_ccc), (uint8_t *)spp_heart_beat_ccc}}, +#endif +}; + +static uint8_t find_char_and_desr_index(uint16_t handle) +{ + uint8_t error = 0xff; + + for(int i = 0; i < SPP_IDX_NB ; i++){ + if(handle == spp_handle_table[i]){ + return i; + } + } + + return error; +} + +static bool store_wr_buffer(esp_ble_gatts_cb_param_t *p_data) +{ + temp_spp_recv_data_node_p1 = (spp_receive_data_node_t *)malloc(sizeof(spp_receive_data_node_t)); + + if(temp_spp_recv_data_node_p1 == NULL){ + ESP_LOGI(GATTS_TABLE_TAG, "malloc error %s %d\n", __func__, __LINE__); + return false; + } + if(temp_spp_recv_data_node_p2 != NULL){ + temp_spp_recv_data_node_p2->next_node = temp_spp_recv_data_node_p1; + } + temp_spp_recv_data_node_p1->len = p_data->write.len; + SppRecvDataBuff.buff_size += p_data->write.len; + temp_spp_recv_data_node_p1->next_node = NULL; + temp_spp_recv_data_node_p1->node_buff = (uint8_t *)malloc(p_data->write.len); + temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1; + memcpy(temp_spp_recv_data_node_p1->node_buff,p_data->write.value,p_data->write.len); + if(SppRecvDataBuff.node_num == 0){ + SppRecvDataBuff.first_node = temp_spp_recv_data_node_p1; + SppRecvDataBuff.node_num++; + }else{ + SppRecvDataBuff.node_num++; + } + + return true; +} + +static void free_write_buffer(void) +{ + temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node; + + while(temp_spp_recv_data_node_p1 != NULL){ + temp_spp_recv_data_node_p2 = temp_spp_recv_data_node_p1->next_node; + free(temp_spp_recv_data_node_p1->node_buff); + free(temp_spp_recv_data_node_p1); + temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p2; + } + + SppRecvDataBuff.node_num = 0; + SppRecvDataBuff.buff_size = 0; + SppRecvDataBuff.first_node = NULL; +} + +static void print_write_buffer(void) +{ + temp_spp_recv_data_node_p1 = SppRecvDataBuff.first_node; + + while(temp_spp_recv_data_node_p1 != NULL){ + uart_write_bytes(UART_NUM_0, (char *)(temp_spp_recv_data_node_p1->node_buff), temp_spp_recv_data_node_p1->len); + temp_spp_recv_data_node_p1 = temp_spp_recv_data_node_p1->next_node; + } +} + +void uart_task(void *pvParameters) +{ + uart_event_t event; + uint8_t total_num = 0; + uint8_t current_num = 0; + + for (;;) { + //Waiting for UART event. + if (xQueueReceive(spp_uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { + switch (event.type) { + //Event of UART receving data + case UART_DATA: + if ((event.size)&&(is_connected)) { + uint8_t * temp = NULL; + uint8_t * ntf_value_p = NULL; +#ifdef SUPPORT_HEARTBEAT + if(!enable_heart_ntf){ + ESP_LOGE(GATTS_TABLE_TAG, "%s do not enable heartbeat Notify\n", __func__); + break; + } +#endif + if(!enable_data_ntf){ + ESP_LOGE(GATTS_TABLE_TAG, "%s do not enable data Notify\n", __func__); + break; + } + temp = (uint8_t *)malloc(sizeof(uint8_t)*event.size); + if(temp == NULL){ + ESP_LOGE(GATTS_TABLE_TAG, "%s malloc.1 failed\n", __func__); + break; + } + memset(temp,0x0,event.size); + uart_read_bytes(UART_NUM_0,temp,event.size,portMAX_DELAY); + if(event.size <= (spp_mtu_size - 3)){ + esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL],event.size, temp, false); + }else if(event.size > (spp_mtu_size - 3)){ + if((event.size%(spp_mtu_size - 7)) == 0){ + total_num = event.size/(spp_mtu_size - 7); + }else{ + total_num = event.size/(spp_mtu_size - 7) + 1; + } + current_num = 1; + ntf_value_p = (uint8_t *)malloc((spp_mtu_size-3)*sizeof(uint8_t)); + if(ntf_value_p == NULL){ + ESP_LOGE(GATTS_TABLE_TAG, "%s malloc.2 failed\n", __func__); + free(temp); + break; + } + while(current_num <= total_num){ + if(current_num < total_num){ + ntf_value_p[0] = '#'; + ntf_value_p[1] = '#'; + ntf_value_p[2] = total_num; + ntf_value_p[3] = current_num; + memcpy(ntf_value_p + 4,temp + (current_num - 1)*(spp_mtu_size-7),(spp_mtu_size-7)); + esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL],(spp_mtu_size-3), ntf_value_p, false); + }else if(current_num == total_num){ + ntf_value_p[0] = '#'; + ntf_value_p[1] = '#'; + ntf_value_p[2] = total_num; + ntf_value_p[3] = current_num; + memcpy(ntf_value_p + 4,temp + (current_num - 1)*(spp_mtu_size-7),(event.size - (current_num - 1)*(spp_mtu_size - 7))); + esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_DATA_NTY_VAL],(event.size - (current_num - 1)*(spp_mtu_size - 7) + 4), ntf_value_p, false); + } + vTaskDelay(20 / portTICK_PERIOD_MS); + current_num++; + } + free(ntf_value_p); + } + free(temp); + } + break; + default: + break; + } + } + } + vTaskDelete(NULL); +} + +static void spp_uart_init(void) +{ + uart_config_t uart_config = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_RTS, + .rx_flow_ctrl_thresh = 122, + .source_clk = UART_SCLK_APB, + }; + + //Install UART driver, and get the queue. + uart_driver_install(UART_NUM_0, 4096, 8192, 10,&spp_uart_queue,0); + //Set UART parameters + uart_param_config(UART_NUM_0, &uart_config); + //Set UART pins + uart_set_pin(UART_NUM_0, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + xTaskCreate(uart_task, "uTask", 2048, (void*)UART_NUM_0, 8, NULL); +} + +#ifdef SUPPORT_HEARTBEAT +void spp_heartbeat_task(void * arg) +{ + uint16_t cmd_id; + + for(;;) { + vTaskDelay(50 / portTICK_PERIOD_MS); + if(xQueueReceive(cmd_heartbeat_queue, &cmd_id, portMAX_DELAY)) { + while(1){ + heartbeat_count_num++; + vTaskDelay(5000/ portTICK_PERIOD_MS); + if((heartbeat_count_num >3)&&(is_connected)){ + esp_ble_gap_disconnect(spp_remote_bda); + } + if(is_connected && enable_heart_ntf){ + esp_ble_gatts_send_indicate(spp_gatts_if, spp_conn_id, spp_handle_table[SPP_IDX_SPP_HEARTBEAT_VAL],sizeof(heartbeat_s), heartbeat_s, false); + }else if(!is_connected){ + break; + } + } + } + } + vTaskDelete(NULL); +} +#endif + +void spp_cmd_task(void * arg) +{ + uint8_t * cmd_id; + + for(;;){ + vTaskDelay(50 / portTICK_PERIOD_MS); + if(xQueueReceive(cmd_cmd_queue, &cmd_id, portMAX_DELAY)) { + esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(cmd_id),strlen((char *)cmd_id)); + free(cmd_id); + } + } + vTaskDelete(NULL); +} + +static void spp_task_init(void) +{ + spp_uart_init(); + +#ifdef SUPPORT_HEARTBEAT + cmd_heartbeat_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_heartbeat_task, "spp_heartbeat_task", 2048, NULL, 10, NULL); +#endif + + cmd_cmd_queue = xQueueCreate(10, sizeof(uint32_t)); + xTaskCreate(spp_cmd_task, "spp_cmd_task", 2048, NULL, 10, NULL); +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + esp_err_t err; + ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event); + + switch (event) { + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&spp_adv_params); + break; + case ESP_GAP_BLE_ADV_START_COMPLETE_EVT: + //advertising start complete event to indicate advertising start successfully or failed + if((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TABLE_TAG, "Advertising start failed: %s\n", esp_err_to_name(err)); + } + break; + default: + break; + } +} + +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) +{ + esp_ble_gatts_cb_param_t *p_data = (esp_ble_gatts_cb_param_t *) param; + uint8_t res = 0xff; + + ESP_LOGI(GATTS_TABLE_TAG, "event = %x\n",event); + switch (event) { + case ESP_GATTS_REG_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME); + + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gap_config_adv_data_raw((uint8_t *)spp_adv_data, sizeof(spp_adv_data)); + + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gatts_create_attr_tab(spp_gatt_db, gatts_if, SPP_IDX_NB, SPP_SVC_INST_ID); + break; + case ESP_GATTS_READ_EVT: + res = find_char_and_desr_index(p_data->read.handle); + if(res == SPP_IDX_SPP_STATUS_VAL){ + //TODO:client read the status characteristic + } + break; + case ESP_GATTS_WRITE_EVT: { + res = find_char_and_desr_index(p_data->write.handle); + if(p_data->write.is_prep == false){ + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT : handle = %d\n", res); + if(res == SPP_IDX_SPP_COMMAND_VAL){ + uint8_t * spp_cmd_buff = NULL; + spp_cmd_buff = (uint8_t *)malloc((spp_mtu_size - 3) * sizeof(uint8_t)); + if(spp_cmd_buff == NULL){ + ESP_LOGE(GATTS_TABLE_TAG, "%s malloc failed\n", __func__); + break; + } + memset(spp_cmd_buff,0x0,(spp_mtu_size - 3)); + memcpy(spp_cmd_buff,p_data->write.value,p_data->write.len); + xQueueSend(cmd_cmd_queue,&spp_cmd_buff,10/portTICK_PERIOD_MS); + }else if(res == SPP_IDX_SPP_DATA_NTF_CFG){ + if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){ + enable_data_ntf = true; + }else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){ + enable_data_ntf = false; + } + } +#ifdef SUPPORT_HEARTBEAT + else if(res == SPP_IDX_SPP_HEARTBEAT_CFG){ + if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x01)&&(p_data->write.value[1] == 0x00)){ + enable_heart_ntf = true; + }else if((p_data->write.len == 2)&&(p_data->write.value[0] == 0x00)&&(p_data->write.value[1] == 0x00)){ + enable_heart_ntf = false; + } + }else if(res == SPP_IDX_SPP_HEARTBEAT_VAL){ + if((p_data->write.len == sizeof(heartbeat_s))&&(memcmp(heartbeat_s,p_data->write.value,sizeof(heartbeat_s)) == 0)){ + heartbeat_count_num = 0; + } + } +#endif + else if(res == SPP_IDX_SPP_DATA_RECV_VAL){ +#ifdef SPP_DEBUG_MODE + esp_log_buffer_char(GATTS_TABLE_TAG,(char *)(p_data->write.value),p_data->write.len); +#else + uart_write_bytes(UART_NUM_0, (char *)(p_data->write.value), p_data->write.len); +#endif + }else{ + //TODO: + } + }else if((p_data->write.is_prep == true)&&(res == SPP_IDX_SPP_DATA_RECV_VAL)){ + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_PREP_WRITE_EVT : handle = %d\n", res); + store_wr_buffer(p_data); + } + break; + } + case ESP_GATTS_EXEC_WRITE_EVT:{ + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT\n"); + if(p_data->exec_write.exec_write_flag){ + print_write_buffer(); + free_write_buffer(); + } + break; + } + case ESP_GATTS_MTU_EVT: + spp_mtu_size = p_data->mtu.mtu; + break; + case ESP_GATTS_CONF_EVT: + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + spp_conn_id = p_data->connect.conn_id; + spp_gatts_if = gatts_if; + is_connected = true; + memcpy(&spp_remote_bda,&p_data->connect.remote_bda,sizeof(esp_bd_addr_t)); +#ifdef SUPPORT_HEARTBEAT + uint16_t cmd = 0; + xQueueSend(cmd_heartbeat_queue,&cmd,10/portTICK_PERIOD_MS); +#endif + break; + case ESP_GATTS_DISCONNECT_EVT: + is_connected = false; + enable_data_ntf = false; +#ifdef SUPPORT_HEARTBEAT + enable_heart_ntf = false; + heartbeat_count_num = 0; +#endif + esp_ble_gap_start_advertising(&spp_adv_params); + break; + case ESP_GATTS_OPEN_EVT: + break; + case ESP_GATTS_CANCEL_OPEN_EVT: + break; + case ESP_GATTS_CLOSE_EVT: + break; + case ESP_GATTS_LISTEN_EVT: + break; + case ESP_GATTS_CONGEST_EVT: + break; + case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ + ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle); + if (param->add_attr_tab.status != ESP_GATT_OK){ + ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table failed, error code=0x%x", param->add_attr_tab.status); + } + else if (param->add_attr_tab.num_handle != SPP_IDX_NB){ + ESP_LOGE(GATTS_TABLE_TAG, "Create attribute table abnormally, num_handle (%d) doesn't equal to HRS_IDX_NB(%d)", param->add_attr_tab.num_handle, SPP_IDX_NB); + } + else { + memcpy(spp_handle_table, param->add_attr_tab.handles, sizeof(spp_handle_table)); + esp_ble_gatts_start_service(spp_handle_table[SPP_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) +{ + ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if); + + /* 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) { + spp_profile_tab[SPP_PROFILE_APP_IDX].gatts_if = gatts_if; + } else { + ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n",param->reg.app_id, param->reg.status); + return; + } + } + + do { + int idx; + for (idx = 0; idx < SPP_PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gatts_if == spp_profile_tab[idx].gatts_if) { + if (spp_profile_tab[idx].gatts_cb) { + spp_profile_tab[idx].gatts_cb(event, gatts_if, param); + } + } + } + } while (0); +} + +void app_main(void) +{ + esp_err_t ret; + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + + // Initialize NVS + 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 ); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT)); + + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__); + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + esp_ble_gatts_register_callback(gatts_event_handler); + esp_ble_gap_register_callback(gap_event_handler); + esp_ble_gatts_app_register(ESP_SPP_APP_ID); + + spp_task_init(); + + return; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h new file mode 100644 index 00000000..15b3b67b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/ble_spp_server_demo.h @@ -0,0 +1,51 @@ +/* + 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 +#include +#include + +/* + * DEFINES + **************************************************************************************** + */ +//#define SUPPORT_HEARTBEAT +//#define SPP_DEBUG_MODE + +#define spp_sprintf(s,...) sprintf((char*)(s), ##__VA_ARGS__) +#define SPP_DATA_MAX_LEN (512) +#define SPP_CMD_MAX_LEN (20) +#define SPP_STATUS_MAX_LEN (20) +#define SPP_DATA_BUFF_MAX_LEN (2*1024) +///Attributes State Machine +enum{ + SPP_IDX_SVC, + + SPP_IDX_SPP_DATA_RECV_CHAR, + SPP_IDX_SPP_DATA_RECV_VAL, + + SPP_IDX_SPP_DATA_NOTIFY_CHAR, + SPP_IDX_SPP_DATA_NTY_VAL, + SPP_IDX_SPP_DATA_NTF_CFG, + + SPP_IDX_SPP_COMMAND_CHAR, + SPP_IDX_SPP_COMMAND_VAL, + + SPP_IDX_SPP_STATUS_CHAR, + SPP_IDX_SPP_STATUS_VAL, + SPP_IDX_SPP_STATUS_CFG, + +#ifdef SUPPORT_HEARTBEAT + SPP_IDX_SPP_HEARTBEAT_CHAR, + SPP_IDX_SPP_HEARTBEAT_VAL, + SPP_IDX_SPP_HEARTBEAT_CFG, +#endif + + SPP_IDX_NB, +}; diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults new file mode 100644 index 00000000..a6ee02f1 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_spp_server/sdkconfig.defaults @@ -0,0 +1,16 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +# +# ESP32-specific config +# +CONFIG_ESP32_ENABLE_STACK_BT=y +# CONFIG_ESP32_ENABLE_STACK_NONE is not set +CONFIG_MEMMAP_BT=y diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/CMakeLists.txt new file mode 100644 index 00000000..3a592ef4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(throughput_client_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/Makefile new file mode 100644 index 00000000..b08f8da8 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := throughput_client_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/README.md new file mode 100644 index 00000000..bd8f61ae --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/README.md @@ -0,0 +1,16 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF BLE throughput GATT CLIENT demo +======================== + +This is the demo used to test the BLE throughput, this demo should used with throughput server demo together. +The throughput of BLE can up to 720-767 Kbps between to ESP32 board. +Note: +1. In order to maximize throughput, we need to set the uart print baud rate at 921600 or more (idf.py menuconfig --> Component config --> ESP32-specific --> UART console baud rate --> 921600(or 1500000)) and don't print too much log. +2. We can only test notify or write throughput at the same time, this demo default to test the notify throughput, if want to test the write throughput, +please set: idf.py menuconfig --> Component config --> Example 'GATT CLIENT THROUGHPUT' Config ---> then select the 'test the gattc write throughput' option +3. This demo only test unidirectional throughput, if you want to test the bidirectional throughput please change the demo by yourself. +4. Should change the CPU frequency to 160MHZ or 240MHz in the idf.py menuconfig --> Component config ---> ESP32-specific ---> CPU frequency (240 MHz or 160 MHz). +5. Should change the bluetooth controller and Bluedroid run in different Core in the idf.py menuconfig --> Component config ---> Bluetooth ---> The cpu core which bluetooth controller run (Core 0 (PRO CPU)) & Bluedroid Enable ---> The cpu core which Bluedroid run (Core 1 (APP CPU)). +6. In order to maximize throughput, please test in a clean environment without many BLE devices working and both test devices are ESP32. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/CMakeLists.txt new file mode 100644 index 00000000..e4c37ee2 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "example_ble_client_throughput.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/Kconfig b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/Kconfig new file mode 100644 index 00000000..e3c97e08 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/Kconfig @@ -0,0 +1,14 @@ +menu "Example 'GATT CLIENT THROUGHPUT' Config" + + config GATTS_NOTIFY_THROUGHPUT + bool "test the gatts notify throughput" + help + If this config item is set, then the 'GATTC_WRITE_THROUGHPUT' config should be close, it can't test both + write or notify at the same time at this demo + + config GATTC_WRITE_THROUGHPUT + bool "test the gattc write throughput" + help + If this config item is set, then the 'GATTS_NOTIFY_THROUGHPUT' config should be close, it can't test both + write or notify at the same time at this demo +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c new file mode 100644 index 00000000..60a3774c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/main/example_ble_client_throughput.c @@ -0,0 +1,601 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This is the demo to test the BLE throughput. It should be used together with throughput_server demo. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" + +/********************************************************** + * Thread/Task reference + **********************************************************/ +#ifdef CONFIG_BLUEDROID_PINNED_TO_CORE +#define BLUETOOTH_TASK_PINNED_TO_CORE (CONFIG_BLUEDROID_PINNED_TO_CORE < portNUM_PROCESSORS ? CONFIG_BLUEDROID_PINNED_TO_CORE : tskNO_AFFINITY) +#else +#define BLUETOOTH_TASK_PINNED_TO_CORE (0) +#endif + +#define GATTC_TAG "GATTC_DEMO" +#define REMOTE_SERVICE_UUID 0x00FF +#define REMOTE_NOTIFY_CHAR_UUID 0xFF01 +#define PROFILE_NUM 1 +#define PROFILE_A_APP_ID 0 +#define INVALID_HANDLE 0 +#define SECOND_TO_USECOND 1000000 + +static const char remote_device_name[] = "THROUGHPUT_DEMO"; +static bool connect = false; +static bool get_server = false; +static esp_gattc_char_elem_t *char_elem_result = NULL; +static esp_gattc_descr_elem_t *descr_elem_result = NULL; +#if (CONFIG_GATTS_NOTIFY_THROUGHPUT) +static bool start = false; +static uint64_t notify_len = 0; +static uint64_t start_time = 0; +static uint64_t current_time = 0; +#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */ + +#if (CONFIG_GATTC_WRITE_THROUGHPUT) +#define GATTC_WRITE_LEN 490 + +static bool can_send_write = false; +static SemaphoreHandle_t gattc_semaphore; +uint8_t write_data[GATTC_WRITE_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0e, 0x0f}; +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ + +static bool is_connect = false; + +/* Declare static functions */ +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + + +static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, +}; + +static esp_bt_uuid_t remote_filter_char_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_NOTIFY_CHAR_UUID,}, +}; + +static esp_bt_uuid_t notify_descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, +}; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; + +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static uint8_t check_sum(uint8_t *addr, uint16_t count) +{ + uint32_t sum = 0; + + if (addr == NULL || count == 0) { + return 0; + } + + for(int i = 0; i < count; i++) { + sum = sum + addr[i]; + } + + while (sum >> 8) { + sum = (sum & 0xff) + (sum >> 8); + } + + return (uint8_t)~sum; +} + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret){ + ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); + } + break; + case ESP_GATTC_CONNECT_EVT: { + is_connect = true; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d", p_data->connect.conn_id, gattc_if); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id; + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->connect.conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + } + case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "open failed, status %d", p_data->open.status); + break; + } + ESP_LOGI(GATTC_TAG, "open success"); + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status); + } + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_RES_EVT"); + esp_gatt_srvc_id_t *srvc_id =(esp_gatt_srvc_id_t *)&p_data->search_res.srvc_id; + if (srvc_id->id.uuid.len == ESP_UUID_LEN_16 && srvc_id->id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(GATTC_TAG, "service found"); + get_server = true; + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle; + ESP_LOGI(GATTC_TAG, "UUID16: %x", srvc_id->id.uuid.uuid.uuid16); + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_CMPL_EVT"); + if (get_server){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + + if (count > 0){ + char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else{ + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service has only one char in our 'throughput_server' demo, so we use first 'char_elem_result' */ + if (count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, char_elem_result[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result); + }else{ + ESP_LOGE(GATTC_TAG, "no char found"); + } + } + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT"); + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status); + }else{ + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0){ + descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); + } + + /* Every char has only one descriptor in our 'throughput_server' demo, so we use first 'descr_elem_result' */ + if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result[0].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } + + /* free descr_elem_result */ + free(descr_elem_result); + } + } + else{ + ESP_LOGE(GATTC_TAG, "decsr not found"); + } + + } + break; + } + case ESP_GATTC_NOTIFY_EVT: { +#if (CONFIG_GATTS_NOTIFY_THROUGHPUT) + if (p_data->notify.is_notify && + (p_data->notify.value[p_data->notify.value_len - 1] == + check_sum(p_data->notify.value, p_data->notify.value_len - 1))){ + notify_len += p_data->notify.value_len; + } else { + ESP_LOGE(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value:"); + } + if (start == false) { + start_time = esp_timer_get_time(); + start = true; + break; + } + +#else /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */ + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); +#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */ + break; + } + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write descr success "); +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + can_send_write = true; + xSemaphoreGive(gattc_semaphore); +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:"); + esp_log_buffer_hex(GATTC_TAG, bda, sizeof(esp_bd_addr_t)); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK) { + ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write char success "); + break; + case ESP_GATTC_DISCONNECT_EVT: + is_connect = false; + get_server = false; +#if (CONFIG_GATTS_NOTIFY_THROUGHPUT) + start = false; + start_time = 0; + current_time = 0; + notify_len = 0; +#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */ + ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason); + break; + case ESP_GATTC_CONGEST_EVT: +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + if (param->congest.congested) { + can_send_write = false; + } else { + can_send_write = true; + xSemaphoreGive(gattc_semaphore); + } +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ + break; + default: + break; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + uint8_t *adv_name = NULL; + uint8_t adv_name_len = 0; + switch (event) { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + //the unit of the duration is second + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "scan start success"); + + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, + ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + ESP_LOGI(GATTC_TAG, "\n"); + if (adv_name != NULL) { + if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { + ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name); + if (connect == false) { + connect = true; + ESP_LOGI(GATTC_TAG, "connect to the remote device."); + esp_ble_gap_set_prefer_conn_params(scan_result->scan_rst.bda, 32, 32, 0, 600); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, BLE_ADDR_TYPE_PUBLIC, true); + } + } + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + break; + default: + break; + } + break; + } + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "stop scan successfully"); + break; + + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "adv stop failed, error status = %x", param->adv_stop_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "stop adv successfully"); + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTC_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(GATTC_TAG, "reg app failed, app_id %04x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +static void throughput_client_task(void *param) +{ + vTaskDelay(2000 / portTICK_PERIOD_MS); +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + uint8_t sum = check_sum(write_data, sizeof(write_data) - 1); + write_data[GATTC_WRITE_LEN - 1] = sum; +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ + + while(1) { +#if (CONFIG_GATTS_NOTIFY_THROUGHPUT) + vTaskDelay(2000 / portTICK_PERIOD_MS); + if(is_connect){ + uint32_t bit_rate = 0; + if (start_time) { + current_time = esp_timer_get_time(); + bit_rate = notify_len * SECOND_TO_USECOND / (current_time - start_time); + ESP_LOGI(GATTC_TAG, "Notify Bit rate = %d Byte/s, = %d bit/s, time = %ds", + bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND)); + } else { + ESP_LOGI(GATTC_TAG, "Notify Bit rate = 0 Byte/s, = 0 bit/s"); + } + } +#endif /* #if (CONFIG_GATTS_NOTIFY_THROUGHPUT) */ +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + if (!can_send_write) { + int res = xSemaphoreTake(gattc_semaphore, portMAX_DELAY); + assert(res == pdTRUE); + } else { + if (is_connect) { + int free_buff_num = esp_ble_get_cur_sendable_packets_num(gl_profile_tab[PROFILE_A_APP_ID].conn_id); + if(free_buff_num > 0) { + for( ; free_buff_num > 0; free_buff_num--) { + // the app data set to 490 just for divided into two packages to send in the low layer + // when the packet length set to 251. + esp_ble_gattc_write_char(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(write_data), write_data, + ESP_GATT_WRITE_TYPE_NO_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + } else { //Add the vTaskDelay to prevent this task from consuming the CPU all the time, causing low-priority tasks to not be executed at all. + vTaskDelay( 10 / portTICK_PERIOD_MS ); + } + } + } +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ + + } +} + +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); + + 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(GATTC_TAG, "%s initialize controller failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s init bluetooth failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed, error code = %x\n", __func__, ret); + return; + } + + //register the callback function to the gap module + ret = esp_ble_gap_register_callback(esp_gap_cb); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x\n", __func__, ret); + return; + } + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if(ret){ + ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret); + } + // set the maximum MTU for used. + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517); + if (local_mtu_ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + // The task is only created on the CPU core that Bluetooth is working on, + // preventing the sending task from using the un-updated Bluetooth state on another CPU. + xTaskCreatePinnedToCore(&throughput_client_task, "throughput_client_task", 4096, NULL, 10, NULL, BLUETOOTH_TASK_PINNED_TO_CORE); + +#if (CONFIG_GATTC_WRITE_THROUGHPUT) + gattc_semaphore = xSemaphoreCreateBinary(); + if (!gattc_semaphore) { + ESP_LOGE(GATTC_TAG, "%s, init fail, the gattc semaphore create fail.", __func__); + return; + } +#endif /* #if (CONFIG_GATTC_WRITE_THROUGHPUT) */ +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults new file mode 100644 index 00000000..00cfa32e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_client/sdkconfig.defaults @@ -0,0 +1,22 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BTDM_CTRL_BLE_MAX_CONN=9 +CONFIG_GATTS_NOTIFY_THROUGHPUT=y +CONFIG_BTDM_MODEM_SLEEP=n +CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=y +CONFIG_BTDM_CTRL_PINNED_TO_CORE=1 +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=921600 +# +# ESP32-specific +# +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_ESP_CONSOLE_UART_BAUDRATE=921600 diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/CMakeLists.txt new file mode 100644 index 00000000..adcc12d7 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(throughput_server_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/Makefile new file mode 100644 index 00000000..66f62011 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := throughput_server_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/README.md new file mode 100644 index 00000000..8d43423f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/README.md @@ -0,0 +1,17 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF BLE throughput GATT SERVER demo +======================== + +This is the demo used to test the BLE throughput, this demo should used with throughput client demo together. +The throughput of BLE can up to 720-767 Kbps between to ESP32 board. +Note: +1. In order to maximize throughput, we need to set the uart print baud rate at 921600 or more (idf.py menuconfig --> Component config --> ESP32-specific --> UART console baud rate --> 921600(or 1500000)) and don't print too much log; +2. We can only test notify or write throughput at the same time, this demo default to test the notify throughput, if want to test the write throughput, +please set: idf.py menuconfig --> Component config --> Example 'GATT SERVER THROUGHPUT' Config ---> then select the 'test the gattc write throughput' option +3. This demo only test unidirectional throughput, if you want to test the bidirectional throughput please change the demo by yourself. +4. Should change the CPU frequency to 160MHz or 240MHz in the idf.py menuconfig --> Component config ---> ESP32-specific ---> CPU frequency (160MHz or 240 MHz) +5. Should change the bluetooth controller and Bluedroid run in different Core in the idf.py menuconfig --> Component config ---> Bluetooth ---> The cpu core which bluetooth controller run (Core 0 (PRO CPU)) & Bluedroid Enable ---> The cpu core which Bluedroid run (Core 1 (APP CPU)) +6. In order to maximize throughput, please test in a clean environment without many BLE devices working and both test devices are ESP32. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/CMakeLists.txt new file mode 100644 index 00000000..c54e45c9 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "example_ble_server_throughput.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/Kconfig b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/Kconfig new file mode 100644 index 00000000..a5e9a858 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/Kconfig @@ -0,0 +1,27 @@ +menu "Example 'GATT SERVER THROUGHPUT' Config" + + config EXAMPLE_SET_RAW_ADV_DATA + bool "Use raw data for advertising packets and scan response data" + help + If this config item is set, raw binary data will be used to generate advertising & scan response data. + This option uses the esp_ble_gap_config_adv_data_raw() and esp_ble_gap_config_scan_rsp_data_raw() + functions. + + If this config item is unset, advertising & scan response data is provided via a higher-level + esp_ble_adv_data_t structure. The lower layer will generate the BLE packets. This option has higher + overhead at runtime. + + config EXAMPLE_GATTS_NOTIFY_THROUGHPUT + bool "test the gatts notify throughput" + help + If this config item is set, then the 'EXAMPLE_GATTC_WRITE_THROUGHPUT' config should be close, it can't test + both write or notify at the same time at this demo + + config EXAMPLE_GATTC_WRITE_THROUGHPUT + bool "test the gattc write throughput" + help + If this config item is set, then the 'EXAMPLE_GATTS_NOTIFY_THROUGHPUT' config should be close, it can't + test both write or notify at the same time at this demo +endmenu + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c new file mode 100644 index 00000000..5b75ca0f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/main/example_ble_server_throughput.c @@ -0,0 +1,725 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This is the demo to test the BLE throughput. It should be used together with throughput_client demo. +* +****************************************************************************/ + +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "freertos/semphr.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_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" + +#include "sdkconfig.h" + +/********************************************************** + * Thread/Task reference + **********************************************************/ +#ifdef CONFIG_BLUEDROID_PINNED_TO_CORE +#define BLUETOOTH_TASK_PINNED_TO_CORE (CONFIG_BLUEDROID_PINNED_TO_CORE < portNUM_PROCESSORS ? CONFIG_BLUEDROID_PINNED_TO_CORE : tskNO_AFFINITY) +#else +#define BLUETOOTH_TASK_PINNED_TO_CORE (0) +#endif + +#define SECOND_TO_USECOND 1000000 + +#define GATTS_TAG "GATTS_DEMO" + +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) +#define GATTS_NOTIFY_LEN 490 +static SemaphoreHandle_t gatts_semaphore; +static bool can_send_notify = false; +static uint8_t indicate_data[GATTS_NOTIFY_LEN] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a}; + +#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ + +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) +static bool start = false; +static uint64_t write_len = 0; +static uint64_t start_time = 0; +static uint64_t current_time = 0; +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ + +static bool is_connect = false; +///Declare the static function +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + +#define GATTS_SERVICE_UUID_TEST_A 0x00FF +#define GATTS_CHAR_UUID_TEST_A 0xFF01 +#define GATTS_DESCR_UUID_TEST_A 0x3333 +#define GATTS_NUM_HANDLE_TEST_A 4 + +#define GATTS_SERVICE_UUID_TEST_B 0x00EE +#define GATTS_CHAR_UUID_TEST_B 0xEE01 +#define GATTS_DESCR_UUID_TEST_B 0x2222 +#define GATTS_NUM_HANDLE_TEST_B 4 + +#define TEST_DEVICE_NAME "THROUGHPUT_DEMO" +#define TEST_MANUFACTURER_DATA_LEN 17 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 + +#define PREPARE_BUF_MAX_SIZE 1024 + +static uint8_t char1_str[] = {0x11,0x22,0x33}; +static esp_gatt_char_prop_t a_property = 0; + +static esp_attr_value_t gatts_demo_char1_val = +{ + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(char1_str), + .attr_value = char1_str, +}; + +static uint8_t adv_config_done = 0; +#define adv_config_flag (1 << 0) +#define scan_rsp_config_flag (1 << 1) + +#ifdef CONFIG_EXAMPLE_SET_RAW_ADV_DATA +static uint8_t raw_adv_data[] = { + 0x02, 0x01, 0x06, + 0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd +}; +static uint8_t raw_scan_rsp_data[] = { + 0x0f, 0x09, 0x45, 0x53, 0x50, 0x5f, 0x47, 0x41, 0x54, 0x54, 0x53, 0x5f, 0x44, + 0x45, 0x4d, 0x4f +}; +#else + +static uint8_t adv_service_uuid128[32] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, + //second uuid, 32bit, [12], [13], [14], [15] 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 uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56}; +//adv data +static esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x000C, //slave connection max interval, Time = max_interval * 1.25 msec + .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 = 32, + .p_service_uuid = adv_service_uuid128, + .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 = 0x0006, + .max_interval = 0x000C, + .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 = 32, + .p_service_uuid = adv_service_uuid128, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +#endif /* CONFIG_EXAMPLE_SET_RAW_ADV_DATA */ + +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +#define PROFILE_NUM 1 +#define PROFILE_A_APP_ID 0 + +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; +}; + +/* 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 gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gatts_cb = gatts_profile_a_event_handler, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +typedef struct { + uint8_t *prepare_buf; + int prepare_len; +} prepare_type_env_t; + +static prepare_type_env_t a_prepare_write_env; + +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); +void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); + +static uint8_t check_sum(uint8_t *addr, uint16_t count) +{ + uint32_t sum = 0; + + if (addr == NULL || count == 0) { + return 0; + } + + for(int i = 0; i < count; i++) { + sum = sum + addr[i]; + } + + while (sum >> 8) { + sum = (sum & 0xff) + (sum >> 8); + } + + return (uint8_t)~sum; +} + + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { +#ifdef CONFIG_EXAMPLE_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(GATTS_TAG, "Advertising start failed\n"); + } + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TAG, "Advertising stop failed\n"); + } + else { + ESP_LOGI(GATTS_TAG, "Stop adv successfully\n"); + } + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTS_TAG, "update connetion params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + default: + break; + } +} + +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ + esp_gatt_status_t status = ESP_GATT_OK; + if (param->write.need_rsp) { + if (param->write.is_prep) { + if (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(GATTS_TAG, "Gatt_server prep no mem\n"); + status = ESP_GATT_NO_RESOURCES; + } + } else { + if(param->write.offset > PREPARE_BUF_MAX_SIZE || + prepare_write_env->prepare_len > param->write.offset) { + status = ESP_GATT_INVALID_OFFSET; + } else if ((param->write.offset + param->write.len) > PREPARE_BUF_MAX_SIZE) { + status = ESP_GATT_INVALID_ATTR_LEN; + } + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + 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(GATTS_TAG, "Send response error\n"); + } + free(gatt_rsp); + 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; + + }else { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); + } + } +} + +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){ + esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + }else{ + ESP_LOGI(GATTS_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_a_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_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A; + gl_profile_tab[PROFILE_A_APP_ID].gatts_if = gatts_if; + esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(TEST_DEVICE_NAME); + if (set_dev_name_ret){ + ESP_LOGE(GATTS_TAG, "set device name failed, error code = %x", set_dev_name_ret); + } +#ifdef CONFIG_EXAMPLE_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(GATTS_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(GATTS_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(GATTS_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(GATTS_TAG, "config scan response data failed, error code = %x", ret); + } + adv_config_done |= scan_rsp_config_flag; + +#endif + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep){ + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); + if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ + uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ + + ESP_LOGI(GATTS_TAG, "notify enable"); + can_send_notify = true; + xSemaphoreGive(gatts_semaphore); + } + }else if (descr_value == 0x0002){ + if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ + ESP_LOGI(GATTS_TAG, "indicate enable"); + uint8_t indicate_data[600]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i%0xff; + } + + for (int j = 0; j < 1000; j++) { + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } + } + else if (descr_value == 0x0000){ + can_send_notify = false; + a_property = 0; + ESP_LOGI(GATTS_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(GATTS_TAG, "unknown descr value"); + esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); + } + + } + } +#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ + example_write_event_env(gatts_if, &a_prepare_write_env, param); +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + if (param->write.handle == gl_profile_tab[PROFILE_A_APP_ID].char_handle) { + // The last value byte is the checksum data, should used to check the data is received corrected or not. + if (param->write.value[param->write.len - 1] == + check_sum(param->write.value, param->write.len - 1)) { + write_len += param->write.len; + } + + if (start == false) { + start_time = esp_timer_get_time(); + start = true; + break; + } + } +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ + + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT"); +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_CANCEL) { + if (write_len > a_prepare_write_env.prepare_len) { + write_len -= a_prepare_write_env.prepare_len; + } else { + write_len = 0; + } + } +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&a_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A; + + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); + a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + a_property, + &gatts_demo_char1_val, NULL); + if (add_char_ret){ + ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret); + } + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: { + uint16_t length = 0; + const uint8_t *prf_char; + + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char); + if (get_attr_ret == ESP_FAIL){ + ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE"); + } + + ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length); + for(int i = 0; i < length; i++){ + ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x\n",i,prf_char[i]); + } + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL); + if (add_descr_ret){ + ESP_LOGE(GATTS_TAG, "add char descr failed, error code =%x", add_descr_ret); + } + break; + } + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + + gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: { + is_connect = true; + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + /* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */ + conn_params.latency = 0; + conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = 400; // timeout = 400*10ms = 4000ms + ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + //start sent the update connection parameters to the peer device. + //esp_ble_gap_update_conn_params(&conn_params); + break; + } + case ESP_GATTS_DISCONNECT_EVT: + is_connect = false; + ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT"); + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status %d", param->conf.status); +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + start_time = false; + current_time = 0; + write_len = 0; +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ + break; + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + break; + case ESP_GATTS_CONGEST_EVT: +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + if (param->congest.congested) { + can_send_notify = false; + } else { + can_send_notify = true; + xSemaphoreGive(gatts_semaphore); + } +#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ + 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) { + gl_profile_tab[param->reg.app_id].gatts_if = gatts_if; + } else { + ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gatts_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gatts_if == gl_profile_tab[idx].gatts_if) { + if (gl_profile_tab[idx].gatts_cb) { + gl_profile_tab[idx].gatts_cb(event, gatts_if, param); + } + } + } + } while (0); +} + +void throughput_server_task(void *param) +{ + vTaskDelay(2000 / portTICK_PERIOD_MS); +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + uint8_t sum = check_sum(indicate_data, sizeof(indicate_data) - 1); + // Added the check sum in the last data value. + indicate_data[GATTS_NOTIFY_LEN - 1] = sum; +#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ + + while(1) { +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + if (!can_send_notify) { + int res = xSemaphoreTake(gatts_semaphore, portMAX_DELAY); + assert(res == pdTRUE); + } else { + if (is_connect) { + int free_buff_num = esp_ble_get_cur_sendable_packets_num(gl_profile_tab[PROFILE_A_APP_ID].conn_id); + if(free_buff_num > 0) { + for( ; free_buff_num > 0; free_buff_num--) { + esp_ble_gatts_send_indicate(gl_profile_tab[PROFILE_A_APP_ID].gatts_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, false); + } + } else { //Add the vTaskDelay to prevent this task from consuming the CPU all the time, causing low-priority tasks to not be executed at all. + vTaskDelay( 10 / portTICK_PERIOD_MS ); + } + } + } +#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ + +#if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) + uint32_t bit_rate = 0; + vTaskDelay(2000 / portTICK_PERIOD_MS); + if (start_time) { + current_time = esp_timer_get_time(); + bit_rate = write_len * SECOND_TO_USECOND / (current_time - start_time); + ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = %d Byte/s, = %d bit/s, time = %ds", + bit_rate, bit_rate<<3, (int)((current_time - start_time) / SECOND_TO_USECOND)); + } else { + ESP_LOGI(GATTS_TAG, "GATTC write Bit rate = 0 Byte/s, = 0 bit/s"); + } +#endif /* #if (CONFIG_EXAMPLE_GATTC_WRITE_THROUGHPUT) */ + + } +} + +void app_main(void) +{ + esp_err_t ret; + + // Initialize NVS. + 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 ); + + 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(GATTS_TAG, "%s initialize controller failed\n", __func__); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s enable controller failed\n", __func__); + return; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s init bluetooth failed\n", __func__); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed\n", __func__); + return; + } + + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret); + return; + } + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret){ + ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret); + return; + } + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(517); + if (local_mtu_ret){ + ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + // The task is only created on the CPU core that Bluetooth is working on, + // preventing the sending task from using the un-updated Bluetooth state on another CPU. + xTaskCreatePinnedToCore(&throughput_server_task, "throughput_server_task", 4096, NULL, 15, NULL, BLUETOOTH_TASK_PINNED_TO_CORE); +#if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) + gatts_semaphore = xSemaphoreCreateBinary(); + if (!gatts_semaphore) { + ESP_LOGE(GATTS_TAG, "%s, init fail, the gatts semaphore create fail.", __func__); + return; + } +#endif /* #if (CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT) */ + return; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults new file mode 100644 index 00000000..3c869dae --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/ble_throughput/throughput_server/sdkconfig.defaults @@ -0,0 +1,22 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BTDM_CTRL_BLE_MAX_CONN=9 +CONFIG_EXAMPLE_GATTS_NOTIFY_THROUGHPUT=y +CONFIG_BTDM_MODEM_SLEEP=n +CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=y +CONFIG_BTDM_CTRL_PINNED_TO_CORE=1 +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_MONITOR_BAUD_921600B=y +CONFIG_ESPTOOLPY_MONITOR_BAUD=921600 +# +# ESP32-specific +# +CONFIG_ESP32_TRACEMEM_RESERVE_DRAM=0x0 +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_ESP_CONSOLE_UART_BAUDRATE=921600 diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/CMakeLists.txt new file mode 100644 index 00000000..b85d34bb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(blufi_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/Makefile new file mode 100644 index 00000000..9c80f26c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := blufi_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/README.md new file mode 100644 index 00000000..1da24f46 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/README.md @@ -0,0 +1,21 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Blufi demo +======================= + +This is the demo for bluetooth config wifi connection to ap. + +To test this demo, you need to prepare a mobile phone with blufi application installed. You can download the blufi application from [Android version](https://github.com/EspressifApp/EspBlufi) and [iOS version](https://itunes.apple.com/cn/app/espblufi/id1450614082?mt=8). + +Blufi is completely open source, here is the download link: + +* [blufi source code](https://github.com/espressif/esp-idf/tree/master/examples/bluetooth/bluedroid/ble/blufi) + +* [BluFi protocol](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/blufi.html?highlight=blufi#the-frame-formats-defined-in-blufi) + +* [iOS source code](https://github.com/EspressifApp/EspBlufiForiOS) + +* [Android source code](https://github.com/EspressifApp/EspBlufi) + +* [Bluetooth Network User Guide CN](https://www.espressif.com/sites/default/files/documentation/esp32_bluetooth_networking_user_guide_cn.pdf) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/CMakeLists.txt new file mode 100644 index 00000000..8e40bfbf --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "blufi_example_main.c" + "blufi_security.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_example.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_example.h new file mode 100644 index 00000000..955e238c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_example.h @@ -0,0 +1,13 @@ +#pragma once + +#define BLUFI_EXAMPLE_TAG "BLUFI_EXAMPLE" +#define BLUFI_INFO(fmt, ...) ESP_LOGI(BLUFI_EXAMPLE_TAG, fmt, ##__VA_ARGS__) +#define BLUFI_ERROR(fmt, ...) ESP_LOGE(BLUFI_EXAMPLE_TAG, fmt, ##__VA_ARGS__) + +void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free); +int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len); +int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len); +uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len); + +int blufi_security_init(void); +void blufi_security_deinit(void); diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_example_main.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_example_main.c new file mode 100644 index 00000000..c25e5c2f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_example_main.c @@ -0,0 +1,472 @@ +/* + 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. +*/ + + +/**************************************************************************** +* This is a demo for bluetooth config wifi connection to ap. You can config ESP32 to connect a softap +* or config ESP32 as a softap to be connected by other device. APP can be downloaded from github +* android source code: https://github.com/EspressifApp/EspBlufi +* iOS source code: https://github.com/EspressifApp/EspBlufiForiOS +****************************************************************************/ + +#include +#include +#include +#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 "esp_bt.h" + +#include "esp_blufi_api.h" +#include "esp_bt_defs.h" +#include "esp_gap_ble_api.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "blufi_example.h" + +static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param); + +#define BLUFI_DEVICE_NAME "BLUFI_DEVICE" +static uint8_t example_service_uuid128[32] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, +}; + +//static uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56}; +static esp_ble_adv_data_t example_adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec + .appearance = 0x00, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 16, + .p_service_uuid = example_service_uuid128, + .flag = 0x6, +}; + +static esp_ble_adv_params_t example_adv_params = { + .adv_int_min = 0x100, + .adv_int_max = 0x100, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +#define WIFI_LIST_NUM 10 + +static wifi_config_t sta_config; +static wifi_config_t ap_config; + +/* FreeRTOS event group to signal when we are connected & ready to make a request */ +static EventGroupHandle_t wifi_event_group; + +/* The event group allows multiple bits for each event, + but we only care about one event - are we connected + to the AP with an IP? */ +const int CONNECTED_BIT = BIT0; + +/* store the station info for send back to phone */ +static bool gl_sta_connected = false; +static bool ble_is_connected = false; +static uint8_t gl_sta_bssid[6]; +static uint8_t gl_sta_ssid[32]; +static int gl_sta_ssid_len; + +/* connect infor*/ +static uint8_t server_if; +static uint16_t conn_id; + +static void ip_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + wifi_mode_t mode; + + switch (event_id) { + case IP_EVENT_STA_GOT_IP: { + esp_blufi_extra_info_t info; + + xEventGroupSetBits(wifi_event_group, CONNECTED_BIT); + esp_wifi_get_mode(&mode); + + memset(&info, 0, sizeof(esp_blufi_extra_info_t)); + memcpy(info.sta_bssid, gl_sta_bssid, 6); + info.sta_bssid_set = true; + info.sta_ssid = gl_sta_ssid; + info.sta_ssid_len = gl_sta_ssid_len; + if (ble_is_connected == true) { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info); + } else { + BLUFI_INFO("BLUFI BLE is not connected yet\n"); + } + break; + } + default: + break; + } + return; +} + +static void wifi_event_handler(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) +{ + wifi_event_sta_connected_t *event; + wifi_mode_t mode; + + switch (event_id) { + case WIFI_EVENT_STA_START: + esp_wifi_connect(); + break; + case WIFI_EVENT_STA_CONNECTED: + gl_sta_connected = true; + event = (wifi_event_sta_connected_t*) event_data; + memcpy(gl_sta_bssid, event->bssid, 6); + memcpy(gl_sta_ssid, event->ssid, event->ssid_len); + gl_sta_ssid_len = event->ssid_len; + break; + case WIFI_EVENT_STA_DISCONNECTED: + /* This is a workaround as ESP32 WiFi libs don't currently + auto-reassociate. */ + gl_sta_connected = false; + memset(gl_sta_ssid, 0, 32); + memset(gl_sta_bssid, 0, 6); + gl_sta_ssid_len = 0; + esp_wifi_connect(); + xEventGroupClearBits(wifi_event_group, CONNECTED_BIT); + break; + case WIFI_EVENT_AP_START: + esp_wifi_get_mode(&mode); + + /* TODO: get config or information of softap, then set to report extra_info */ + if (ble_is_connected == true) { + if (gl_sta_connected) { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, NULL); + } else { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL); + } + } else { + BLUFI_INFO("BLUFI BLE is not connected yet\n"); + } + break; + case WIFI_EVENT_SCAN_DONE: { + uint16_t apCount = 0; + esp_wifi_scan_get_ap_num(&apCount); + if (apCount == 0) { + BLUFI_INFO("Nothing AP found"); + break; + } + wifi_ap_record_t *ap_list = (wifi_ap_record_t *)malloc(sizeof(wifi_ap_record_t) * apCount); + if (!ap_list) { + BLUFI_ERROR("malloc error, ap_list is NULL"); + break; + } + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&apCount, ap_list)); + esp_blufi_ap_record_t * blufi_ap_list = (esp_blufi_ap_record_t *)malloc(apCount * sizeof(esp_blufi_ap_record_t)); + if (!blufi_ap_list) { + if (ap_list) { + free(ap_list); + } + BLUFI_ERROR("malloc error, blufi_ap_list is NULL"); + break; + } + for (int i = 0; i < apCount; ++i) + { + blufi_ap_list[i].rssi = ap_list[i].rssi; + memcpy(blufi_ap_list[i].ssid, ap_list[i].ssid, sizeof(ap_list[i].ssid)); + } + + if (ble_is_connected == true) { + esp_blufi_send_wifi_list(apCount, blufi_ap_list); + } else { + BLUFI_INFO("BLUFI BLE is not connected yet\n"); + } + + esp_wifi_scan_stop(); + free(ap_list); + free(blufi_ap_list); + break; + } + default: + break; + } + return; +} + +static void initialise_wifi(void) +{ + ESP_ERROR_CHECK(esp_netif_init()); + wifi_event_group = xEventGroupCreate(); + ESP_ERROR_CHECK(esp_event_loop_create_default()); + esp_netif_t *sta_netif = esp_netif_create_default_wifi_sta(); + assert(sta_netif); + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL)); + + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK( esp_wifi_init(&cfg) ); + ESP_ERROR_CHECK( esp_wifi_set_mode(WIFI_MODE_STA) ); + ESP_ERROR_CHECK( esp_wifi_start() ); +} + +static esp_blufi_callbacks_t example_callbacks = { + .event_cb = example_event_callback, + .negotiate_data_handler = blufi_dh_negotiate_data_handler, + .encrypt_func = blufi_aes_encrypt, + .decrypt_func = blufi_aes_decrypt, + .checksum_func = blufi_crc_checksum, +}; + +static void example_event_callback(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param) +{ + /* actually, should post to blufi_task handle the procedure, + * now, as a example, we do it more simply */ + switch (event) { + case ESP_BLUFI_EVENT_INIT_FINISH: + BLUFI_INFO("BLUFI init finish\n"); + + esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME); + esp_ble_gap_config_adv_data(&example_adv_data); + break; + case ESP_BLUFI_EVENT_DEINIT_FINISH: + BLUFI_INFO("BLUFI deinit finish\n"); + break; + case ESP_BLUFI_EVENT_BLE_CONNECT: + BLUFI_INFO("BLUFI ble connect\n"); + ble_is_connected = true; + server_if = param->connect.server_if; + conn_id = param->connect.conn_id; + esp_ble_gap_stop_advertising(); + blufi_security_init(); + break; + case ESP_BLUFI_EVENT_BLE_DISCONNECT: + BLUFI_INFO("BLUFI ble disconnect\n"); + ble_is_connected = false; + blufi_security_deinit(); + esp_ble_gap_start_advertising(&example_adv_params); + break; + case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: + BLUFI_INFO("BLUFI Set WIFI opmode %d\n", param->wifi_mode.op_mode); + ESP_ERROR_CHECK( esp_wifi_set_mode(param->wifi_mode.op_mode) ); + break; + case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: + BLUFI_INFO("BLUFI requset wifi connect to AP\n"); + /* there is no wifi callback when the device has already connected to this wifi + so disconnect wifi before connection. + */ + esp_wifi_disconnect(); + esp_wifi_connect(); + break; + case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP: + BLUFI_INFO("BLUFI requset wifi disconnect from AP\n"); + esp_wifi_disconnect(); + break; + case ESP_BLUFI_EVENT_REPORT_ERROR: + BLUFI_ERROR("BLUFI report error, error code %d\n", param->report_error.state); + esp_blufi_send_error_info(param->report_error.state); + break; + case ESP_BLUFI_EVENT_GET_WIFI_STATUS: { + wifi_mode_t mode; + esp_blufi_extra_info_t info; + + esp_wifi_get_mode(&mode); + + if (gl_sta_connected) { + memset(&info, 0, sizeof(esp_blufi_extra_info_t)); + memcpy(info.sta_bssid, gl_sta_bssid, 6); + info.sta_bssid_set = true; + info.sta_ssid = gl_sta_ssid; + info.sta_ssid_len = gl_sta_ssid_len; + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, 0, &info); + } else { + esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, 0, NULL); + } + BLUFI_INFO("BLUFI get wifi status from AP\n"); + + break; + } + case ESP_BLUFI_EVENT_RECV_SLAVE_DISCONNECT_BLE: + BLUFI_INFO("blufi close a gatt connection"); + esp_blufi_close(server_if, conn_id); + break; + case ESP_BLUFI_EVENT_DEAUTHENTICATE_STA: + /* TODO */ + break; + case ESP_BLUFI_EVENT_RECV_STA_BSSID: + memcpy(sta_config.sta.bssid, param->sta_bssid.bssid, 6); + sta_config.sta.bssid_set = 1; + esp_wifi_set_config(WIFI_IF_STA, &sta_config); + BLUFI_INFO("Recv STA BSSID %s\n", sta_config.sta.ssid); + break; + case ESP_BLUFI_EVENT_RECV_STA_SSID: + strncpy((char *)sta_config.sta.ssid, (char *)param->sta_ssid.ssid, param->sta_ssid.ssid_len); + sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0'; + esp_wifi_set_config(WIFI_IF_STA, &sta_config); + BLUFI_INFO("Recv STA SSID %s\n", sta_config.sta.ssid); + break; + case ESP_BLUFI_EVENT_RECV_STA_PASSWD: + strncpy((char *)sta_config.sta.password, (char *)param->sta_passwd.passwd, param->sta_passwd.passwd_len); + sta_config.sta.password[param->sta_passwd.passwd_len] = '\0'; + esp_wifi_set_config(WIFI_IF_STA, &sta_config); + BLUFI_INFO("Recv STA PASSWORD %s\n", sta_config.sta.password); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_SSID: + strncpy((char *)ap_config.ap.ssid, (char *)param->softap_ssid.ssid, param->softap_ssid.ssid_len); + ap_config.ap.ssid[param->softap_ssid.ssid_len] = '\0'; + ap_config.ap.ssid_len = param->softap_ssid.ssid_len; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP SSID %s, ssid len %d\n", ap_config.ap.ssid, ap_config.ap.ssid_len); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_PASSWD: + strncpy((char *)ap_config.ap.password, (char *)param->softap_passwd.passwd, param->softap_passwd.passwd_len); + ap_config.ap.password[param->softap_passwd.passwd_len] = '\0'; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP PASSWORD %s len = %d\n", ap_config.ap.password, param->softap_passwd.passwd_len); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_MAX_CONN_NUM: + if (param->softap_max_conn_num.max_conn_num > 4) { + return; + } + ap_config.ap.max_connection = param->softap_max_conn_num.max_conn_num; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP MAX CONN NUM %d\n", ap_config.ap.max_connection); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_AUTH_MODE: + if (param->softap_auth_mode.auth_mode >= WIFI_AUTH_MAX) { + return; + } + ap_config.ap.authmode = param->softap_auth_mode.auth_mode; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP AUTH MODE %d\n", ap_config.ap.authmode); + break; + case ESP_BLUFI_EVENT_RECV_SOFTAP_CHANNEL: + if (param->softap_channel.channel > 13) { + return; + } + ap_config.ap.channel = param->softap_channel.channel; + esp_wifi_set_config(WIFI_IF_AP, &ap_config); + BLUFI_INFO("Recv SOFTAP CHANNEL %d\n", ap_config.ap.channel); + break; + case ESP_BLUFI_EVENT_GET_WIFI_LIST:{ + wifi_scan_config_t scanConf = { + .ssid = NULL, + .bssid = NULL, + .channel = 0, + .show_hidden = false + }; + ESP_ERROR_CHECK(esp_wifi_scan_start(&scanConf, true)); + break; + } + case ESP_BLUFI_EVENT_RECV_CUSTOM_DATA: + BLUFI_INFO("Recv Custom Data %d\n", param->custom_data.data_len); + esp_log_buffer_hex("Custom Data", param->custom_data.data, param->custom_data.data_len); + break; + case ESP_BLUFI_EVENT_RECV_USERNAME: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_CA_CERT: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_CERT: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_SERVER_CERT: + /* Not handle currently */ + break; + case ESP_BLUFI_EVENT_RECV_CLIENT_PRIV_KEY: + /* Not handle currently */ + break;; + case ESP_BLUFI_EVENT_RECV_SERVER_PRIV_KEY: + /* Not handle currently */ + break; + default: + break; + } +} + +static void example_gap_event_handler(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_ble_gap_start_advertising(&example_adv_params); + break; + default: + break; + } +} + +void app_main(void) +{ + esp_err_t ret; + + // Initialize NVS + 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(); + + 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) { + BLUFI_ERROR("%s initialize bt controller failed: %s\n", __func__, esp_err_to_name(ret)); + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + BLUFI_ERROR("%s enable bt controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + BLUFI_ERROR("%s init bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + BLUFI_ERROR("%s init bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + BLUFI_INFO("BD ADDR: "ESP_BD_ADDR_STR"\n", ESP_BD_ADDR_HEX(esp_bt_dev_get_address())); + + BLUFI_INFO("BLUFI VERSION %04x\n", esp_blufi_get_version()); + + ret = esp_ble_gap_register_callback(example_gap_event_handler); + if(ret){ + BLUFI_ERROR("%s gap register failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_blufi_register_callbacks(&example_callbacks); + if(ret){ + BLUFI_ERROR("%s blufi register failed, error code = %x\n", __func__, ret); + return; + } + + esp_blufi_profile_init(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_security.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_security.c new file mode 100644 index 00000000..635d5f00 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/blufi_security.c @@ -0,0 +1,216 @@ +/* + 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 +#include +#include +#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 "esp_bt.h" + +#include "esp_blufi_api.h" +#include "esp_bt_defs.h" +#include "esp_gap_ble_api.h" +#include "esp_bt_main.h" +#include "blufi_example.h" + +#include "mbedtls/aes.h" +#include "mbedtls/dhm.h" +#include "mbedtls/md5.h" +#include "esp_crc.h" + +/* + The SEC_TYPE_xxx is for self-defined packet data type in the procedure of "BLUFI negotiate key" + If user use other negotiation procedure to exchange(or generate) key, should redefine the type by yourself. + */ +#define SEC_TYPE_DH_PARAM_LEN 0x00 +#define SEC_TYPE_DH_PARAM_DATA 0x01 +#define SEC_TYPE_DH_P 0x02 +#define SEC_TYPE_DH_G 0x03 +#define SEC_TYPE_DH_PUBLIC 0x04 + + +struct blufi_security { +#define DH_SELF_PUB_KEY_LEN 128 +#define DH_SELF_PUB_KEY_BIT_LEN (DH_SELF_PUB_KEY_LEN * 8) + uint8_t self_public_key[DH_SELF_PUB_KEY_LEN]; +#define SHARE_KEY_LEN 128 +#define SHARE_KEY_BIT_LEN (SHARE_KEY_LEN * 8) + uint8_t share_key[SHARE_KEY_LEN]; + size_t share_len; +#define PSK_LEN 16 + uint8_t psk[PSK_LEN]; + uint8_t *dh_param; + int dh_param_len; + uint8_t iv[16]; + mbedtls_dhm_context dhm; + mbedtls_aes_context aes; +}; +static struct blufi_security *blufi_sec; + +static int myrand( void *rng_state, unsigned char *output, size_t len ) +{ + esp_fill_random(output, len); + return( 0 ); +} + +extern void btc_blufi_report_error(esp_blufi_error_state_t state); + +void blufi_dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len, bool *need_free) +{ + int ret; + uint8_t type = data[0]; + + if (blufi_sec == NULL) { + BLUFI_ERROR("BLUFI Security is not initialized"); + btc_blufi_report_error(ESP_BLUFI_INIT_SECURITY_ERROR); + return; + } + + switch (type) { + case SEC_TYPE_DH_PARAM_LEN: + blufi_sec->dh_param_len = ((data[1]<<8)|data[2]); + if (blufi_sec->dh_param) { + free(blufi_sec->dh_param); + blufi_sec->dh_param = NULL; + } + blufi_sec->dh_param = (uint8_t *)malloc(blufi_sec->dh_param_len); + if (blufi_sec->dh_param == NULL) { + btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR); + BLUFI_ERROR("%s, malloc failed\n", __func__); + return; + } + break; + case SEC_TYPE_DH_PARAM_DATA:{ + if (blufi_sec->dh_param == NULL) { + BLUFI_ERROR("%s, blufi_sec->dh_param == NULL\n", __func__); + btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR); + return; + } + uint8_t *param = blufi_sec->dh_param; + memcpy(blufi_sec->dh_param, &data[1], blufi_sec->dh_param_len); + ret = mbedtls_dhm_read_params(&blufi_sec->dhm, ¶m, ¶m[blufi_sec->dh_param_len]); + if (ret) { + BLUFI_ERROR("%s read param failed %d\n", __func__, ret); + btc_blufi_report_error(ESP_BLUFI_READ_PARAM_ERROR); + return; + } + free(blufi_sec->dh_param); + blufi_sec->dh_param = NULL; + ret = mbedtls_dhm_make_public(&blufi_sec->dhm, (int) mbedtls_mpi_size( &blufi_sec->dhm.P ), blufi_sec->self_public_key, blufi_sec->dhm.len, myrand, NULL); + if (ret) { + BLUFI_ERROR("%s make public failed %d\n", __func__, ret); + btc_blufi_report_error(ESP_BLUFI_MAKE_PUBLIC_ERROR); + return; + } + + mbedtls_dhm_calc_secret( &blufi_sec->dhm, + blufi_sec->share_key, + SHARE_KEY_BIT_LEN, + &blufi_sec->share_len, + NULL, NULL); + + mbedtls_md5(blufi_sec->share_key, blufi_sec->share_len, blufi_sec->psk); + + mbedtls_aes_setkey_enc(&blufi_sec->aes, blufi_sec->psk, 128); + + /* alloc output data */ + *output_data = &blufi_sec->self_public_key[0]; + *output_len = blufi_sec->dhm.len; + *need_free = false; + + } + break; + case SEC_TYPE_DH_P: + break; + case SEC_TYPE_DH_G: + break; + case SEC_TYPE_DH_PUBLIC: + break; + } +} + +int blufi_aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) +{ + int ret; + size_t iv_offset = 0; + uint8_t iv0[16]; + + memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv)); + iv0[0] = iv8; /* set iv8 as the iv0[0] */ + + ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data); + if (ret) { + return -1; + } + + return crypt_len; +} + +int blufi_aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len) +{ + int ret; + size_t iv_offset = 0; + uint8_t iv0[16]; + + memcpy(iv0, blufi_sec->iv, sizeof(blufi_sec->iv)); + iv0[0] = iv8; /* set iv8 as the iv0[0] */ + + ret = mbedtls_aes_crypt_cfb128(&blufi_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0, crypt_data, crypt_data); + if (ret) { + return -1; + } + + return crypt_len; +} + +uint16_t blufi_crc_checksum(uint8_t iv8, uint8_t *data, int len) +{ + /* This iv8 ignore, not used */ + return esp_crc16_be(0, data, len); +} + +esp_err_t blufi_security_init(void) +{ + blufi_sec = (struct blufi_security *)malloc(sizeof(struct blufi_security)); + if (blufi_sec == NULL) { + return ESP_FAIL; + } + + memset(blufi_sec, 0x0, sizeof(struct blufi_security)); + + mbedtls_dhm_init(&blufi_sec->dhm); + mbedtls_aes_init(&blufi_sec->aes); + + memset(blufi_sec->iv, 0x0, 16); + return 0; +} + +void blufi_security_deinit(void) +{ + if (blufi_sec == NULL) { + return; + } + if (blufi_sec->dh_param){ + free(blufi_sec->dh_param); + blufi_sec->dh_param = NULL; + } + mbedtls_dhm_free(&blufi_sec->dhm); + mbedtls_aes_free(&blufi_sec->aes); + + memset(blufi_sec, 0x0, sizeof(struct blufi_security)); + + free(blufi_sec); + blufi_sec = NULL; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/sdkconfig.defaults new file mode 100644 index 00000000..dcadf58e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/blufi/sdkconfig.defaults @@ -0,0 +1,31 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y +CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=n +CONFIG_BTDM_CTRL_PINNED_TO_CORE=0 +CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y +CONFIG_BTDM_CTRL_HCI_MODE_UART_H4=n +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y +CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1=n +CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0 +CONFIG_BT_BTC_TASK_STACK_SIZE=3072 +CONFIG_BT_BLUEDROID_MEM_DEBUG=n +CONFIG_BT_CLASSIC_ENABLED=n +CONFIG_BT_GATTS_ENABLE=y +CONFIG_BT_GATTC_ENABLE=n +CONFIG_BT_BLE_SMP_ENABLE=n +CONFIG_BL_ENABLE_SRVCHG_REG=y +CONFIG_BT_STACK_NO_LOG=n +CONFIG_BT_ACL_CONNECTIONS=4 +CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=n +CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=n +CONFIG_BT_SMP_ENABLE=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/CMakeLists.txt new file mode 100644 index 00000000..d32eb6be --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gatt_client_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/Makefile new file mode 100644 index 00000000..700ffd7a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := gatt_client_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/README.md new file mode 100644 index 00000000..309feff4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/README.md @@ -0,0 +1,13 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gatt Client Demo +======================== + +This is the demo for users to use ESP_APIs to create a GATT Client. + +To test this demo, you can run the [gatt_server_demo](../gatt_server), which creates services and starts advertising. `Gatt_client_demo` will start scanning and connect to the `gatt_server_demo` automatically. + +This demo will enable gatt server's notification function once the connection is established and then the devices start exchanging data. + +Please check the [tutorial](tutorial/Gatt_Client_Example_Walkthrough.md) for more information about this example. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/CMakeLists.txt new file mode 100644 index 00000000..82a30b2f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "gattc_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/Kconfig.projbuild b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/Kconfig.projbuild new file mode 100644 index 00000000..15378b1c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/Kconfig.projbuild @@ -0,0 +1,7 @@ +menu "Example Configuration" + + config EXAMPLE_DUMP_ADV_DATA_AND_SCAN_RESP + bool "Dump whole adv data and scan response data in example" + default n + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c new file mode 100644 index 00000000..e53d85d3 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/main/gattc_demo.c @@ -0,0 +1,500 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This demo showcases BLE GATT client. It can scan BLE devices and connect to one device. +* Run the gatt_server demo, the client demo will automatically connect to the gatt_server demo. +* Client demo will enable gatt_server's notify after connection. The two devices will then exchange +* data. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +#define GATTC_TAG "GATTC_DEMO" +#define REMOTE_SERVICE_UUID 0x00FF +#define REMOTE_NOTIFY_CHAR_UUID 0xFF01 +#define PROFILE_NUM 1 +#define PROFILE_A_APP_ID 0 +#define INVALID_HANDLE 0 + +static const char remote_device_name[] = "ESP_GATTS_DEMO"; +static bool connect = false; +static bool get_server = false; +static esp_gattc_char_elem_t *char_elem_result = NULL; +static esp_gattc_descr_elem_t *descr_elem_result = NULL; + +/* Declare static functions */ +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + + +static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, +}; + +static esp_bt_uuid_t remote_filter_char_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_NOTIFY_CHAR_UUID,}, +}; + +static esp_bt_uuid_t notify_descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, +}; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; + +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret){ + ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); + } + break; + case ESP_GATTC_CONNECT_EVT:{ + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d", p_data->connect.conn_id, gattc_if); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id; + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->connect.conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + } + case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "open failed, status %d", p_data->open.status); + break; + } + ESP_LOGI(GATTC_TAG, "open success"); + break; + case ESP_GATTC_DIS_SRVC_CMPL_EVT: + if (param->dis_srvc_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "discover service failed, status %d", param->dis_srvc_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "discover service complete conn_id %d", param->dis_srvc_cmpl.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status); + } + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary); + ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id); + if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(GATTC_TAG, "service found"); + get_server = true; + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle; + ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16); + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { + ESP_LOGI(GATTC_TAG, "Get service information from remote device"); + } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { + ESP_LOGI(GATTC_TAG, "Get service information from flash"); + } else { + ESP_LOGI(GATTC_TAG, "unknown service source"); + } + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SEARCH_CMPL_EVT"); + if (get_server){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + + if (count > 0){ + char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else{ + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, char_elem_result[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result); + }else{ + ESP_LOGE(GATTC_TAG, "no char found"); + } + } + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT"); + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status); + }else{ + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0){ + descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); + } + /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ + if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result[0].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } + + /* free descr_elem_result */ + free(descr_elem_result); + } + } + else{ + ESP_LOGE(GATTC_TAG, "decsr not found"); + } + + } + break; + } + case ESP_GATTC_NOTIFY_EVT: + if (p_data->notify.is_notify){ + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value:"); + }else{ + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value:"); + } + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write descr success "); + uint8_t write_char_data[35]; + for (int i = 0; i < sizeof(write_char_data); ++i) + { + write_char_data[i] = i % 256; + } + esp_ble_gattc_write_char( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(write_char_data), + write_char_data, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:"); + esp_log_buffer_hex(GATTC_TAG, bda, sizeof(esp_bd_addr_t)); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write char success "); + break; + case ESP_GATTC_DISCONNECT_EVT: + connect = false; + get_server = false; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d", p_data->disconnect.reason); + break; + default: + break; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + uint8_t *adv_name = NULL; + uint8_t adv_name_len = 0; + switch (event) { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + //the unit of the duration is second + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "scan start success"); + + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, + ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + +#if CONFIG_EXAMPLE_DUMP_ADV_DATA_AND_SCAN_RESP + if (scan_result->scan_rst.adv_data_len > 0) { + ESP_LOGI(GATTC_TAG, "adv data:"); + esp_log_buffer_hex(GATTC_TAG, &scan_result->scan_rst.ble_adv[0], scan_result->scan_rst.adv_data_len); + } + if (scan_result->scan_rst.scan_rsp_len > 0) { + ESP_LOGI(GATTC_TAG, "scan resp:"); + esp_log_buffer_hex(GATTC_TAG, &scan_result->scan_rst.ble_adv[scan_result->scan_rst.adv_data_len], scan_result->scan_rst.scan_rsp_len); + } +#endif + ESP_LOGI(GATTC_TAG, "\n"); + + if (adv_name != NULL) { + if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { + ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name); + if (connect == false) { + connect = true; + ESP_LOGI(GATTC_TAG, "connect to the remote device."); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + } + } + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + break; + default: + break; + } + break; + } + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "stop scan successfully"); + break; + + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "adv stop failed, error status = %x", param->adv_stop_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "stop adv successfully"); + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTC_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(GATTC_TAG, "reg app failed, app_id %04x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +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 ); + + 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(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + //register the callback function to the gap module + ret = esp_ble_gap_register_callback(esp_gap_cb); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x\n", __func__, ret); + return; + } + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if(ret){ + ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret); + } + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults new file mode 100644 index 00000000..ee53a228 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md new file mode 100644 index 00000000..26a639b8 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md @@ -0,0 +1,723 @@ +# Gatt Client Example Walkthrough + +## Introduction + +In this tutorial, the GATT client example code for the ESP32 is reviewed. The code implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) client, which scans for nearby peripheral servers and connects to a predefined service. The client then searches for available characteristics and subscribes to a known characteristic in order to receive notifications or indications. The example can register an Application Profile and initializes a sequence of events, which can be used to configure Generic Access Profile (GAP) parameters and to handle events such as scanning, connecting to peripherals and reading and writing characteristics. + +# Includes + +This example is located in the examples folder of the ESP-IDF under the [bluetooth/bluedroid/ble/gatt_client/main](../main). The [gattc_demo.c](../main/gattc_demo.c) file located in the main folder contains all the functionality that we are going to review. The header files contained in [gattc_demo.c](../main/gattc_demo.c) are: + +```c +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "controller.h" + +#include "bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +``` + +These `includes` are required for the FreeRTOS and underlaying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `“bt.h”`, `“esp_bt_main.h”`, `"esp_gap_ble_api.h"` and `“esp_gattc_api.h”`, which expose the BLE APIs required to implement this example. + +* `bt.h`: configures the BT controller and VHCI from the host side. +* `esp_bt_main.h`: initializes and enables the Bluedroid stack. +* `esp_gap_ble_api.h`: implements the GAP configuration, such as advertising and connection parameters. +* `esp_gattc_api.h`: implements the GATT Client configuration, such as connecting to peripherals and searching for services. + +## Main Entry Point + +The program’s entry point is the app_main() function: + +```c +void app_main() +{ + // 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 ); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s initialize controller failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s init bluetooth failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed, error code = %x\n", __func__, ret); + return; + } + + //register the callback function to the gap module + ret = esp_ble_gap_register_callback(esp_gap_cb); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gap register failed, error code = %x\n", __func__, ret); + return; + } + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if(ret){ + ESP_LOGE(GATTC_TAG, "%s gattc register failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret); + } + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + +} +``` + +The main function starts by initializing the non-volatile storage library. This library allows to save key-value pairs in flash memory and is used by some components such as the Wi-Fi library to save the SSID and password: + +```c +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 ); +``` + +## BT Controller and Stack Initialization + +The main function also initializes the BT controller by first creating a BT controller configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. The BT controller implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL) and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` function: + +```c +esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&bt_cfg); +``` + +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +>The controller should be enabled in `ESP_BT_MODE_BTDM`, if you want to use the dual mode (BLE + BT). + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the Bluedroid stack, which includes the common definitions and APIs for both BT Classic and BLE, is initialized and enabled by using: + +```c +ret = esp_bluedroid_init(); +ret = esp_bluedroid_enable(); +``` +The main function ends by registering the GAP and GATT event handlers, as well as the Application Profile and set the maximum supported MTU size. + +```c + //register the callback function to the gap module + ret = esp_ble_gap_register_callback(esp_gap_cb); + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } +``` + +The GAP and GATT event handlers are the functions used to catch the events generated by the BLE stack and execute functions to configure parameters of the application. Moreover, the event handlers are also used to handle read and write events coming from the central. The GAP event handler takes care of scanning and connecting to servers and the GATT handler manages events that happen after the client has connected to a server, such as searching for services and writing and reading data. The GAP and GATT event handlers are registered by using: + +```c +esp_ble_gap_register_callback(); +esp_ble_gattc_register_callback(); +``` +The functions `esp_gap_cb()` and `esp_gattc_cb()` handle all the events generated by the BLE stack. + +## Application Profiles + +The Application Profiles are a way to group functionalities that are designed for one or more server applications. For example, you can have an Application Profile connected to the Heart Rate Sensors, and another one connected to the Temperature Sensors. Each Application Profile creates a GATT interface to connect to other devices. The Application Profiles in the code are instances of the `gattc_profile_inst` structure, which is defined as: + +```c +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; +``` + +The Application Profile structure contains: + +* `gattc_cb`: GATT client callback function +* `gattc_if`: GATT client interface number for this profile +* `app_id`: Application Profile ID number +* `conn_id`: Connection ID +* `service_start_handle`: Service start handle +* `service_end_handle`: Service end handle +* `char_handle`: Char handle +* `remote_bda`: Remote device address connected to this client. + +In this example there is one Application Profile and its ID is defined as: + +```c +#define PROFILE_NUM 1 +#define PROFILE_A_APP_ID 0 +``` +The Application Profile are stored in the `gl_profile_tab` array, which is initialized as: + +```c +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = {.gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; +``` + +The initialization of the Application Profile table array includes defining the callback functions for each Profile. These are `gattc_profile_a_event_handler()` and `gattc_profile_a_event_handler()` respectively. In addition, the GATT interface is initialized to the default value of `ESP_GATT_IF_NONE`. Later on, when the Application Profile is registered, the BLE stack returns a GATT interface instance to use with that Application Profile. + +The profile registration triggers an `ESP_GATTC_REG_EVT` event, which is handled by the `esp_gattc_cb()` event handler. The handler takes the GATT interface returned by the event and stores it in the profile table: + +```c +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if); + + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(GATTC_TAG, "reg app failed, app_id %04x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } +… +``` + +Finally, the callback function invokes the corresponding event handler for each profile in the `gl_profile_tab` table. + +```c +… +/* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} +``` +## Setting Scan Parameters + +The GATT client normally scans for nearby servers and tries connect to them, if interested. However, in order to perform the scanning, first the configuration parameters need to be set. This is done after the registration of the Application Profiles, because the registration, once completed, triggers an `ESP_GATTC_REG_EVT` event. The first time this event is triggered, the GATT event handler captures it and assigns a GATT interface to Profile A, then the event is forwarded to the GATT event handler of Profile A. One in this event handler, the event is used to call the `esp_ble_gap_set_scan_params()` function, which takes a `ble_scan_params` structure instance as parameter. This structure is defined as: + +```c +/// Ble scan parameters +typedef struct { + esp_ble_scan_type_t scan_type; /*!< Scan type */ + esp_ble_addr_type_t own_addr_type; /*!< Owner address type */ + esp_ble_scan_filter_t scan_filter_policy; /*!< Scan filter policy */ + uint16_t scan_interval; /*!< Scan interval. This is defined as the time interval from when the Controller started its last LE scan until it begins the subsequent LE scan.*/ + //Range: 0x0004 to 0x4000 + //Default: 0x0010 (10 ms) + //Time = N * 0.625 msec + //Time Range: 2.5 msec to 10.24 seconds + uint16_t scan_window; /*!< Scan window. The duration of the LE scan. LE_Scan_Window shall be less than or equal to LE_Scan_Interval*/ + //Range: 0x0004 to 0x4000 //Default: 0x0010 (10 ms) + //Time = N * 0.625 msec + //Time Range: 2.5 msec to 10240 msec +} esp_ble_scan_params_t; +``` +An it is initialized as: + +```c +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30 +}; +``` + +The BLE scan parameters are configured so that the type of scanning is active (includes reading the scanning response), it is of public type, allows any advertising device to be read and has a scanning interval of 100 ms (1.25 ms * 0x50) and a scanning window of 60 ms (1.25 ms * 0x30). + +The scan values are set using the `esp_ble_gap_set_scan_params()` function: + +```c +case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret){ + ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); + } + break; +``` + +## Start Scanning + +Once the scanning parameters are set, an `ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT` event is triggered, which is handled by the GAP event handler `esp_gap_cb()`. This event is used to start the scanning of nearby GATT servers: + +```c + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + //the unit of the duration is second + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); + break; + } +``` + +The scanning is started using the `esp_ble_gap_start_scanning()` function which takes a parameter representing the duration of the continuous scanning (in seconds). Once the scanning period is ended, an `ESP_GAP_SEARCH_INQ_CMPL_EVT` event is triggered. + +## Getting Scan Results + +The results of the scanning are displayed as soon as they arrive with the `ESP_GAP_BLE_SCAN_RESULT_EVT` event, which includes the following parameters: + +```c + /** + * @brief ESP_GAP_BLE_SCAN_RESULT_EVT + */ + struct ble_scan_result_evt_param { + esp_gap_search_evt_t search_evt; /*!< Search event type */ + esp_bd_addr_t bda; /*!< Bluetooth device address which has been searched */ + esp_bt_dev_type_t dev_type; /*!< Device type */ + esp_ble_addr_type_t ble_addr_type; /*!< Ble device address type */ + esp_ble_evt_type_t ble_evt_type; /*!< Ble scan result event type */ + int rssi; /*!< Searched device's RSSI */ + uint8_t ble_adv[ESP_BLE_ADV_DATA_LEN_MAX + ESP_BLE_SCAN_RSP_DATA_LEN_MAX]; /*!< Received EIR */ + int flag; /*!< Advertising data flag bit */ + int num_resps; /*!< Scan result number */ + uint8_t adv_data_len; /*!< Adv data length */ + uint8_t scan_rsp_len; /*!< Scan response length */ + } scan_rst; /*!< Event parameter of ESP_GAP_BLE_SCAN_RESULT_EVT */ +``` + +This event also includes a list of sub events as shown below: + +```c +/// Sub Event of ESP_GAP_BLE_SCAN_RESULT_EVT +typedef enum { + ESP_GAP_SEARCH_INQ_RES_EVT = 0, /*!< Inquiry result for a peer device. */ + ESP_GAP_SEARCH_INQ_CMPL_EVT = 1, /*!< Inquiry complete. */ + ESP_GAP_SEARCH_DISC_RES_EVT = 2, /*!< Discovery result for a peer device. */ + ESP_GAP_SEARCH_DISC_BLE_RES_EVT = 3, /*!< Discovery result for BLE GATT based service on a peer device. */ + ESP_GAP_SEARCH_DISC_CMPL_EVT = 4, /*!< Discovery complete. */ + ESP_GAP_SEARCH_DI_DISC_CMPL_EVT = 5, /*!< Discovery complete. */ + ESP_GAP_SEARCH_SEARCH_CANCEL_CMPL_EVT = 6, /*!< Search cancelled */ +} esp_gap_search_evt_t; +``` +We are interested in the `ESP_GAP_SEARCH_INQ_RES_EVT` event, which is called every time a new device is found. We are also interested in the `ESP_GAP_SEARCH_INQ_CMPL_EVT`, which is triggered when the duration of the scanning is completed and can be used to restart the scanning procedure: + +```c + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + ESP_LOGI(GATTC_TAG, "\n"); + if (adv_name != NULL) { + if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { + ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name); + if (connect == false) { + connect = true; + ESP_LOGI(GATTC_TAG, "connect to the remote device."); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + } + } + } + break; +``` + +First the device name is resolved and compared to the one defined in `remote_device_name`. If it equals to the device name of the GATT Server we are interested in, then the scanning is stopped. + + +## Connecting to A GATT Server + +Every time we receive a result from the `ESP_GAP_SEARCH_INQ_RES_EVT` event, the code first prints the address of the remote device: + +```c +case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); +``` + +The client then prints the advertised data length and the scan response length: + +```c +ESP_LOGI(GATTC_TAG, "searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); +``` + +In order to get the device name, we use the `esp_ble_resolve_adv_data()` function, which takes the advertised data stored in `scan_result->scan_rst.ble_adv`, the type of advertising data and the length, in order to extract the value from the advertising packet frame. Then the device name is printed. + +```c +adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); +ESP_LOGI(GATTC_TAG, "searched Device Name Len %d", adv_name_len); +esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); +``` + +Finally if the remote device name is the same as we have defined above, the local device stops scanning and tries to open a connection to the remote device using the `esp_ble_gattc_open()` function. This function takes as parameters the Application Profile GATT interface, the remote server address and a boolean value. The boolean value is used to indicate if the connection is done directly or if it’s done in the background (auto-connection), at the moment this boolean value must be set to true in order to establish the connection. Notice that the client opens a virtual connection to the server. The virtual connection returns a connection ID. The virtual connection is the connection between the Application Profile and the remote server. Since many Application Profiles can run on one ESP32, there could be many virtual connection opened to the same remote server. There is also the physical connection which is the actual BLE link between the client and the server. Therefore, if the physical connection is disconnected with the `esp_ble_gap_disconnect()` function, all other virtual connections are closed as well. In this example, each Application Profile creates a virtual connection to the same server with the `esp_ble_gattc_open()` function, so when the close function is called, only that connection from the Application Profile is closed, while if the gap disconnect function is called, both connections will be closed. In addition, connect events are propagated to all profiles because it relates to the physical connection, while open events are propagated only to the profile that creates the virtual connection. + +## Configuring the MTU Size + +ATT_MTU is defined as the maximum size of any packet sent between a client and a server. When the client connects to the server, it informs the server which MTU size to use by exchanging MTU Request and Response protocol data units (PDUs). This is done after the opening of a connection. After opening the connection, an `ESP_GATTC_CONNECT_EVT` event is triggered: + +```c + case ESP_GATTC_CONNECT_EVT: + //p_data->connect.status always be ESP_GATT_OK + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d, status %d", conn_id, gattc_if, p_data->connect.status); + conn_id = p_data->connect.conn_id; + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id; + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; +``` + +The connection ID and the address of the remote device (server) are stored in the Application Profile table and printed to the console: + +```c +conn_id = p_data->connect.conn_id; +gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->connect.conn_id; +memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->connect.remote_bda, + sizeof(esp_bd_addr_t)); +ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); +esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + sizeof(esp_bd_addr_t)); +``` + +The typical MTU size for a Bluetooth 4.0 connection is 23 bytes. A client can change the size of MUT, using `esp_ble_gattc_send_mtu_req()` function, which takes the GATT interface and the connection ID. The size of the requested MTU is defined by `esp_ble_gatt_set_local_mtu()`. The server can then accept or reject the request. The ESP32 supports a MTU size of up to 517 bytes, which is defined by the `ESP_GATT_MAX_MTU_SIZE` in `esp_gattc_api.h`. In this example, the MTU size is set to 500 bytes. In case the configuration fails, the returned error is printed: + +```c +esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, conn_id); +if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); +} +break; +``` + +The connection opening also triggers an `ESP_GATTC_OPEN_EVT`, which is used to check that the opening of the connection was done successfully, otherwise print an error and exit. + +```c +case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "open failed, status %d", p_data->open.status); + break; + } +ESP_LOGI(GATTC_TAG, "open success"); +``` + +When the MTU is exchanged, an `ESP_GATTC_CFG_MTU_EVT` is triggered, which in this example is used to print the new MTU size. + +```c +case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status); + } + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); +… +``` + +## Discovering Services + +The MTU configuration event is also used to start discovering the services available in the server that the client just connected to. To discover the services, the function `esp_ble_gattc_search_service()` is used. The parameters of the function are the GATT interface, the Application Profile connection ID and the UUID of the service application that the client is interested in. The service we are looking for is defined as: + +```c +static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, +}; +``` +Where, + +```c +#define REMOTE_SERVICE_UUID 0x00FF +``` +If UUID of the service application the user is interested in is 128-bit, then there is one note below for the user which is relevant with the little-endian storage mode of the processor architecture. +The struct of UUID is defined as: + +```c +typedef struct { +#define ESP_UUID_LEN_16 2 +#define ESP_UUID_LEN_32 4 +#define ESP_UUID_LEN_128 16 + uint16_t len; /*!< UUID length, 16bit, 32bit or 128bit */ + union { + uint16_t uuid16; /*!< 16bit UUID */ + uint32_t uuid32; /*!< 32bit UUID */ + uint8_t uuid128[ESP_UUID_LEN_128]; /*!< 128bit UUID */ + } uuid; /*!< UUID */ +} __attribute__((packed)) esp_bt_uuid_t; +``` + +Note: In little-endian storage mode, you can define service UUID directly in the normal order if it's a 16-bit or a 32-bit UUID, but if service UUID is 128-bit, there is minor difference. For example, if the UUID of the service application that the user is interested in is 12345678-a1b2-c3d4-e5f6-9fafd205e457, `REMOTE_SERVICE_UUID` should be defined as {0x57,0xE4,0x05,0xD2,0xAF,0x9F,0xF6,0xE5,0xD4,0xC3,0xB2,0xA1,0x78,0x56,0x34,0x12}. + +The services are then discovered as follows: + +```c +esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; +``` + +The resulting service found, if there is any, will be returned from an `ESP_GATTC_SEARCH_RES_EVT`. For each service found, the event is triggered to print information about the service discovered, depending on the size of the UUID: + +```c + case ESP_GATTC_SEARCH_RES_EVT: { + esp_gatt_srvc_id_t *srvc_id = &p_data->search_res.srvc_id; + conn_id = p_data->search_res.conn_id; + if (srvc_id->id.uuid.len == ESP_UUID_LEN_16 && srvc_id->id.uuid.uuid.uuid16 == +REMOTE_SERVICE_UUID) { + get_server = true; + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle; + ESP_LOGI(GATTC_TAG, "UUID16: %x", srvc_id->id.uuid.uuid.uuid16); + } + break; +``` + +In case that the client finds the service that it is looking for, the flag get_server is set to true, and the start handle value and end handle value, which will be used later to get all the characteristics of that service, are saved. After all service results are returned, the search is completed and an `ESP_GATTC_SEARCH_CMPL_EVT` event is triggered. + +## Getting Characteristics + +This example implements getting characteristic data from a predefined service. The service that we want the characteristics from has an UUID of 0x00FF, and the characteristic we are interested in has an UUID of 0xFF01: + +```c +#define REMOTE_NOTIFY_CHAR_UUID 0xFF01 +``` +A service is defined using the `esp_gatt_srvc_id_t` structure as: + +```c +/** + * @brief Gatt id, include uuid and instance id + */ +typedef struct { + esp_bt_uuid_t uuid; /*!< UUID */ + uint8_t inst_id; /*!< Instance id */ +} __attribute__((packed)) esp_gatt_id_t; +``` + +In this example, we define the service that we want to get the characteristics from as: + +```c +static esp_gatt_srvc_id_t remote_service_id = { + .id = { + .uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, + }, + .inst_id = 0, + }, + .is_primary = true, +}; +``` + +Once defined, we can get the characteristics from that service using the `esp_ble_gattc_get_characteristic()` function, which is called in the `ESP_GATTC_SEARCH_CMPL_EVT` event after the search for services is completed and the client has found the service that it was looking for. + +```c +case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + conn_id = p_data->search_cmpl.conn_id; + if (get_server){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id,ESP_GATT_DB_CHARACTERISTIC, gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + + if (count > 0){ + char_elem_result = (esp_gattc_char_elem_t*)malloc + (sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else{ + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, + so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result[0].properties + &ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = + char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result); + }else{ + ESP_LOGE(GATTC_TAG, "no char found"); + } } + break; +``` + +`esp_ble_gattc_get_attr_count()` gets the attribute count with the given service or characteristic in the gattc cache. The parameters of `esp_ble_gattc_get_attr_count()` function are the GATT interface, the connection ID, the attribute type defined in `esp_gatt_db_attr_type_t`, the attribute start handle, the attribute end handle, the characteristic handle (this parameter is only valid when the type is set to `ESP_GATT_DB_DESCRIPTOR`.) and output the number of attribute has been found in the gattc cache with the given attribute type. Then we allocate a buffer to save the char information for `esp_ble_gattc_get_char_by_uuid()` function. The function finds the characteristic with the given characteristic UUID in the gattc cache. It just gets characteristic from local cache, instead of the remote devices. In a server, there might be more than one chars sharing the same UUID. However, in our gatt_server demo, every char has an unique UUID and that’s why we only use the first char in `char_elem_result`, which is the pointer to the characteristic of the service. Count initially stores the number of the characteristics that the client wants to find, and will be updated with the number of the characteristics that have been actually found in the gattc cache with `esp_ble_gattc_get_char_by_uuid`. + +## Registering for Notifications + +The client can register to receive notifications from the server every time the characteristic value changes. In this example, we want to register for notifications of the characteristic identified with an UUID of 0xff01. After getting all the characteristics, we check the properties of the received characteristic, then use the `esp_ble_gattc_register_for_notify()` function to register notifications. The function arguments are the GATT interface, the address of the remote server, and the handle we want to register for notifications. + +```c +… +/* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ + if(count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[0].char_handle); + } +… +``` + +This procedure registers notifications to the BLE stack, and triggers an `ESP_GATTC_REG_FOR_NOTIFY_EVT`. This event is used to write to the server Client Configuration Descriptor: + +```c + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + ESP_LOGI(GATTC_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT"); + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "REG FOR NOTIFY failed: error status = %d", p_data->reg_for_notify.status); + }else{ + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0){ + descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_descr_by_char_handle( + gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result,&count); + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle + error"); + } + + /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ + if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result[0].handle, + sizeof(notify_en), + (Uint8 *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } + + /* free descr_elem_result */ + free(descr_elem_result); + } + } + else{ + ESP_LOGE(GATTC_TAG, "decsr not found"); + } + + } + break; + } +``` + +The event is used to first print the notification register status and the service and characteristic UUIDs of the just registered notifications. The client then writes to the Client Configuration Descriptor by using the `esp_ble_gattc_write_char_descr()` function. There are many characteristic descriptors defined in the Bluetooth specification. However, in this case we are interested in writing to the descriptor that deals with enabling notifications, which is the Client Configuration descriptor. In order to pass this descriptor as parameter, we first define it as: + +```c +static esp_gatt_id_t notify_descr_id = { + .uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, + }, + .inst_id = 0, +}; +``` +Where `ESP_GATT_UUID_CHAR_CLIENT_CONFIG` is defined with the UUID to identify the Characteristic Client Configuration: + +```c +#define ESP_GATT_UUID_CHAR_CLIENT_CONFIG 0x2902 /* Client Characteristic Configuration */ +``` +The value to write is “1” to enable notifications. We also pass `ESP_GATT_WRITE_TYPE_RSP` to request that the server responds to the request of enabling notifications and `ESP_GATT_AUTH_REQ_NONE` to indicate that the Write request does not need authorization. + + + +## Conclusion + +We have reviewed the GATT Client example code for the ESP32. This example scans for nearby devices and searches for services and characteristics of servers of interest. When the server of interest is found, a connection is made with that server and a search for services is performed. Finally, the client looks for a specific characteristic in the services found, if found, gets the characteristic value and registers for notifications to that characteristic. This is done by registering one Application Profile and following a sequence of events to configure the GAP and GATT parameters required. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/CMakeLists.txt new file mode 100644 index 00000000..8f468d76 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(sec_gattc_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/Makefile new file mode 100644 index 00000000..551e25a0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := sec_gattc_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/README.md new file mode 100644 index 00000000..7f05ed40 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/README.md @@ -0,0 +1,16 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gatt Security Client Demo +======================== + +This is the demo for users to use ESP BLE security APIs to connect to and encrypt with peer devices. + +To test this demo, you can run the [gatt_security_server_demo](../gatt_security_server), which starts advertising and can be connected to this demo automatically. + +There are some important points for this demo: +1.`esp_ble_gap_set_security_param` should be used to set the security parameters in the initial stage; +2.`esp_ble_set_encryption` should be used to start encryption with peer device. If the peer device initiates the encryption, `esp_ble_gap_security_rsp` should be used to send security response to the peer device when `ESP_GAP_BLE_SEC_REQ_EVT` is received. +3.The `gatt_security_client_demo` will receive a `ESP_GAP_BLE_AUTH_CMPL_EVT` once the encryption procedure has completed. + +Please check the [tutorial](tutorial/Gatt_Security_Client_Example_Walkthrough.md) for more information about this example. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/CMakeLists.txt new file mode 100644 index 00000000..691e1edd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "example_ble_sec_gattc_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c new file mode 100644 index 00000000..365fb40d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/main/example_ble_sec_gattc_demo.c @@ -0,0 +1,592 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This file is for gatt_security_client demo. It can scan ble device, connect one device that needs to be encrypted. +* run gatt_security_server demo, the gatt_security_client demo will automatically connect the gatt_security_server, +* then paring and bonding. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +#define GATTC_TAG "SEC_GATTC_DEMO" +#define REMOTE_SERVICE_UUID ESP_GATT_UUID_HEART_RATE_SVC +#define REMOTE_NOTIFY_UUID 0x2A37 + +static esp_gattc_char_elem_t *char_elem_result = NULL; +static esp_gattc_descr_elem_t *descr_elem_result = NULL; + +///Declare static functions +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + + +static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, +}; + +static bool connect = false; +static bool get_service = false; +static const char remote_device_name[] = "ESP_BLE_SECURITY"; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + + +#define PROFILE_NUM 1 +#define PROFILE_A_APP_ID 0 +#define INVALID_HANDLE 0 + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t notify_char_handle; + esp_bd_addr_t remote_bda; +}; + +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static const char *esp_key_type_to_str(esp_ble_key_type_t key_type) +{ + const char *key_str = NULL; + switch(key_type) { + case ESP_LE_KEY_NONE: + key_str = "ESP_LE_KEY_NONE"; + break; + case ESP_LE_KEY_PENC: + key_str = "ESP_LE_KEY_PENC"; + break; + case ESP_LE_KEY_PID: + key_str = "ESP_LE_KEY_PID"; + break; + case ESP_LE_KEY_PCSRK: + key_str = "ESP_LE_KEY_PCSRK"; + break; + case ESP_LE_KEY_PLK: + key_str = "ESP_LE_KEY_PLK"; + break; + case ESP_LE_KEY_LLK: + key_str = "ESP_LE_KEY_LLK"; + break; + case ESP_LE_KEY_LENC: + key_str = "ESP_LE_KEY_LENC"; + break; + case ESP_LE_KEY_LID: + key_str = "ESP_LE_KEY_LID"; + break; + case ESP_LE_KEY_LCSRK: + key_str = "ESP_LE_KEY_LCSRK"; + break; + default: + key_str = "INVALID BLE KEY TYPE"; + break; + + } + return key_str; +} + +static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req) +{ + char *auth_str = NULL; + switch(auth_req) { + case ESP_LE_AUTH_NO_BOND: + auth_str = "ESP_LE_AUTH_NO_BOND"; + break; + case ESP_LE_AUTH_BOND: + auth_str = "ESP_LE_AUTH_BOND"; + break; + case ESP_LE_AUTH_REQ_MITM: + auth_str = "ESP_LE_AUTH_REQ_MITM"; + break; + case ESP_LE_AUTH_REQ_BOND_MITM: + auth_str = "ESP_LE_AUTH_REQ_BOND_MITM"; + break; + case ESP_LE_AUTH_REQ_SC_ONLY: + auth_str = "ESP_LE_AUTH_REQ_SC_ONLY"; + break; + case ESP_LE_AUTH_REQ_SC_BOND: + auth_str = "ESP_LE_AUTH_REQ_SC_BOND"; + break; + case ESP_LE_AUTH_REQ_SC_MITM: + auth_str = "ESP_LE_AUTH_REQ_SC_MITM"; + break; + case ESP_LE_AUTH_REQ_SC_MITM_BOND: + auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND"; + break; + default: + auth_str = "INVALID BLE AUTH REQ"; + break; + } + + return auth_str; +} + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_ble_gap_config_local_privacy(true); + break; + case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "open failed, error status = %x", p_data->open.status); + break; + } + ESP_LOGI(GATTC_TAG, "open success"); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id; + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"config mtu failed, error status = %x", param->cfg_mtu.status); + } + ESP_LOGI(GATTC_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary); + ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id); + if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16); + get_service = true; + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { + ESP_LOGI(GATTC_TAG, "Get service information from remote device"); + } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { + ESP_LOGI(GATTC_TAG, "Get service information from flash"); + } else { + ESP_LOGI(GATTC_TAG, "unknown service source"); + } + if (get_service){ + uint16_t count = 0; + uint16_t offset = 0; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__); + } + if (count > 0){ + char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_all_char(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + char_elem_result, + &count, + offset); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_all_char error, %d", __LINE__); + } + if (count > 0){ + + for (int i = 0; i < count; ++i) + { + if (char_elem_result[i].uuid.len == ESP_UUID_LEN_16 && char_elem_result[i].uuid.uuid.uuid16 == REMOTE_NOTIFY_UUID && (char_elem_result[i].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)) + { + gl_profile_tab[PROFILE_A_APP_ID].notify_char_handle = char_elem_result[i].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].remote_bda, + char_elem_result[i].char_handle); + break; + } + } + } + } + free(char_elem_result); + } + } + + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "reg for notify failed, error status = %x", p_data->reg_for_notify.status); + break; + } + + uint16_t count = 0; + uint16_t offset = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + p_data->reg_for_notify.handle, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error, %d", __LINE__); + } + if (count > 0){ + descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_all_descr(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + p_data->reg_for_notify.handle, + descr_elem_result, + &count, + offset); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_all_descr error, %d", __LINE__); + } + + for (int i = 0; i < count; ++i) + { + if (descr_elem_result[i].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[i].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) + { + esp_ble_gattc_write_char_descr (gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result[i].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + + break; + } + } + } + free(descr_elem_result); + } + + break; + } + case ESP_GATTC_NOTIFY_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value:"); + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write descr success"); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:"); + esp_log_buffer_hex(GATTC_TAG, bda, sizeof(esp_bd_addr_t)); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "Write char success "); + break; + case ESP_GATTC_DISCONNECT_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = 0x%x", p_data->disconnect.reason); + connect = false; + get_service = false; + break; + default: + break; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + uint8_t *adv_name = NULL; + uint8_t adv_name_len = 0; + switch (event) { + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "config local privacy failed, error code =%x", param->local_privacy_cmpl.status); + break; + } + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret){ + ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); + } + break; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + //the unit of the duration is second + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "Scan start success"); + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + /* Call the following function to input the passkey which is displayed on the remote device */ + //esp_ble_passkey_reply(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true, 0x00); + ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT"); + break; + case ESP_GAP_BLE_OOB_REQ_EVT: { + ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk + esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk)); + break; + } + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + 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_NC_REQ_EVT: + /* The app will receive this evt 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_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true); + ESP_LOGI(GATTC_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", param->ble_security.key_notif.passkey); + 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(GATTC_TAG, "The passkey Notify number:%06d", 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. + ESP_LOGI(GATTC_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)); + ESP_LOGI(GATTC_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]); + ESP_LOGI(GATTC_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type); + ESP_LOGI(GATTC_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); + if (!param->ble_security.auth_cmpl.success) { + ESP_LOGI(GATTC_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason); + } else { + ESP_LOGI(GATTC_TAG, "auth mode = %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode)); + } + break; + } + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, + ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + ESP_LOGI(GATTC_TAG, "\n"); + if (adv_name != NULL) { + if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { + ESP_LOGI(GATTC_TAG, "searched device %s\n", remote_device_name); + if (connect == false) { + connect = true; + ESP_LOGI(GATTC_TAG, "connect to the remote device."); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + } + } + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + break; + default: + break; + } + break; + } + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Scan stop failed, error status = %x", param->scan_stop_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "Stop scan successfully"); + break; + + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d", event, gattc_if); + + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +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 ); + + 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(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + //register the callback function to the gap module + ret = esp_ble_gap_register_callback(esp_gap_cb); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gap register error, error code = %x\n", __func__, ret); + return; + } + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if(ret){ + ESP_LOGE(GATTC_TAG, "%s gattc register error, error code = %x\n", __func__, ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "%s gattc app register error, error code = %x\n", __func__, ret); + } + + ret = esp_ble_gatt_set_local_mtu(200); + if (ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", 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_NONE; //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; + uint8_t oob_support = ESP_BLE_OOB_DISABLE; + 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)); + esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, 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)); + +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults new file mode 100644 index 00000000..00fb5211 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md new file mode 100644 index 00000000..e5fabaca --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md @@ -0,0 +1,119 @@ +# GATT Security Client Example Walkthrough + + + +## Introduction + +This document presents a review of the GATT Security Client code example for the ESP32. The GATT Client is capable of scanning for nearby devices and once it has found a device of interest, it requests a secure connection. The GATT client behaves as a master device that initiates a connection to a slave by sending a *Pairing Request* as specified by the [Bluetooth Core Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification). The remote slave device is normally a GATT Server that exposes Services and Characteristics. The slave replies with a *Pairing Response* followed by authentication and exchange of keys. If the bonding process is also executed, the Long Term Keys are stored for subsequent connections. Finally an encrypted channel is established which can support protection against Man-In-The-Middle (MITM) attacks depending on the security configuration. The code is implemented using an Application Profile that upon registration, allows to set the local privacy configuration as events are triggered during the lifetime of the program. + +This document only includes a description of the security aspects of the GATT Client implementation, for a review of how to define the functionality of the GATT client such as scanning parameters and opening connections please refer to [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +## Configuring Local Privacy of the Security Client + +The example registers one Application Profile defined as: + +```c +#define PROFILE_NUM 1 +#define PROFILE_A_APP_ID 0 +``` + +The registration takes place in the ``app_main()`` function by using the ``esp_ble_gattc_app_register()`` function: + +```c +… +ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); +if (ret){ + ESP_LOGE(GATTC_TAG, "%s gattc app register error, error code = %x\n", __func__, ret); +} +… +``` + +The Application Profile registration triggers an ``ESP_GATTC_REG_EVT`` event which is managed by the ``esp_gattc_cb()`` callback function and forwarded to the Profile A event handler ``gattc_profile_event_handler()``. Here, the event is used to configure the local privacy of the slave device by using the ``esp_ble_gap_config_local_privacy()`` function. + +```c +case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_ble_gap_config_local_privacy(true); + break; +``` + +This function is a Bluedroid API call for configuring default privacy settings on the local device. Once the privacy is set, an ``ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT`` is triggered which is used to set scan parameters and start scanning for nearby peripherals: + +```c + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "config local privacy failed, error code =%x", param->local_privacy_cmpl.status); + break; + } + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret){ + ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); + } + break; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + //the unit of the duration is second + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTC_TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); + break; + } + ESP_LOGI(GATTC_TAG, "Scan start success"); + break; +``` + +## Configuring and Bonding to a Slave Device + +The rest of the configuration for the GATT Client is performed normally in the same way as the regular GATT Client example. That is, the client finds a device of interest and opens a connection. At this point the GATT client, which is usually the master, initiates the pairing process by sending a Pairing Request to the slave device. This request should be acknowledged with a Pairing Response. The Pairing process is implemented automatically by the stack and no extra user configuration is needed. However, depending on the I/O capabilities of both devices, a passkey might be generated on the ESP32 which is presented to the user with the ``ESP_GAP_BLE_PASSKEY_NOTIF_EVT``: + +```c + 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_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey); + break; + +``` +The combination of input and output capabilities that determine which algorithm is used are: + +| | Display Only | Display Yes/No | Keyboard Only | No Input No Output | Keyboard Display| +| :-- | :------------- | :------------- | :------------- | :----------------- | :-------------- | +| **Display Only** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Display Yes/No** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Keyboard Only** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | +| **No Input No Output** | Just Works | Just Works | Just Works | Just Works | Just Works | +| **Keyboard Display** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | + +In the Just Works method, the Temporary Key is set to 0. This is a practical way to authenticate devices when no display or keyboards are attached to them, so that there is no way to show or enter a passkey. However, if the ESP32 GATT Client has an LCD, it can present the passkey generated locally so that the user can input it on the other peer device, or if the GATT Client has a keyboard, it can input the passkey generated by the other peer device. Additionally, a numeric comparison can be performed if both devices have a display and yes/no confirm buttons and LE Secure Connections are used, that way an independently generated passkey is displayed on both devices and the user manually checks that both 6-digit confirmation values match. + +## Exchanging Keys + +When the client connects to a remote device and the pairing is done successfully, the initiator and responder keys are exchanged. For each key exchange message, an ``ESP_GAP_BLE_KEY_EVT`` event is triggered which can be used to print the type of key received: + +```c +case ESP_GAP_BLE_KEY_EVT: + //shows the ble key info share with peer device to the user. + ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; +``` + +When the keys are exchanged successfully, the pairing process is completed and encryption of payload data can be started using the AES-128 engine. This triggers an ``ESP_GAP_BLE_AUTH_CMPL_EVT`` event which is used to print information: + +```c +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)); + ESP_LOGI(GATTS_TABLE_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]); + ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type); + ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); + break; +``` + +## Conclusion + +In this document, a review of the security aspects of the GATT Client has been realized. BLE security encompasses Pairing, Bonding and Encryption. In order to establish a secure link between a master and a slave device, the local privacy of the GATT client is set, which allows the BLE stack to set necessary security parameters automatically without the need of additional user configuration. The combination of features and capabilities of the peer devices results in the selection of the appropriate pairing method which the BLE stack then executes. Immediately, the required keys are generated and exchanged and the encryption of subsequent messages is started using the AES-128 engine. These steps trigger different events that are managed by the GATT and GAP event handlers which can be used to print useful information such as the types of keys exchanged and the pairing status. The rest of the security GATT client functionality such as registering for notifications of characteristics is implemented in the same way as in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/CMakeLists.txt new file mode 100644 index 00000000..8e9d61ff --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(sec_gatts_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/Makefile new file mode 100644 index 00000000..bb3f8e07 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := sec_gatts_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/README.md new file mode 100644 index 00000000..516b697d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/README.md @@ -0,0 +1,17 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gatt Security Server Demo +======================== + +This is the demo of APIs to connect to and encrypt with peer devices. + +To test this demo, you can run [gatt_security_client_demo](../gatt_security_client), which starts scanning, connects to and starts encryption with `gatt_security_server_demo` automatically. + +There are some important points for this demo: +1.`esp_ble_gap_set_security_param` should be used to set the security parameters in the initial stage; +2.`esp_ble_set_encryption` should be used to start encryption with peer device. If the peer device initiates the encryption, `esp_ble_gap_security_rsp` should be used to send security response to the peer device when `ESP_GAP_BLE_SEC_REQ_EVT` is received. +3.The `gatt_security_client_demo` will receive a `ESP_GAP_BLE_AUTH_CMPL_EVT` once the encryption procedure has completed. + +Please check the [tutorial](tutorial/Gatt_Security_Server_Example_Walkthrough.md) for more information about this example. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/CMakeLists.txt new file mode 100644 index 00000000..65145911 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "example_ble_sec_gatts_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/component.mk new file mode 100644 index 00000000..f2f38c36 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/component.mk @@ -0,0 +1,9 @@ +# +# Main Makefile. This is basically the same as a component makefile. +# +# This Makefile should, at the very least, just include $(SDK_PATH)/make/component_common.mk. By default, +# this will take the sources in the src/ directory, compile them and link them into +# lib(subdirectory_name).a in the build directory. This behaviour is entirely configurable, +# please read the ESP-IDF documents if you need to do this. +# + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c new file mode 100644 index 00000000..6cbfb718 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.c @@ -0,0 +1,586 @@ +/* + 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 "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_defs.h" +#include "esp_bt_main.h" +#include "example_ble_sec_gatts_demo.h" + +#define GATTS_TABLE_TAG "SEC_GATTS_DEMO" + +#define HEART_PROFILE_NUM 1 +#define HEART_PROFILE_APP_IDX 0 +#define ESP_HEART_RATE_APP_ID 0x55 +#define EXAMPLE_DEVICE_NAME "ESP_BLE_SECURITY" +#define HEART_RATE_SVC_INST_ID 0 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 + +#define ADV_CONFIG_FLAG (1 << 0) +#define SCAN_RSP_CONFIG_FLAG (1 << 1) + +static uint8_t adv_config_done = 0; + +static uint16_t heart_rate_handle_table[HRS_IDX_NB]; + +static uint8_t test_manufacturer[3]={'E', 'S', 'P'}; + +static uint8_t sec_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, 0x18, 0x0D, 0x00, 0x00, +}; + +// config adv data +static esp_ble_adv_data_t heart_rate_adv_config = { + .set_scan_rsp = false, + .include_txpower = true, + .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec + .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 = sizeof(sec_service_uuid), + .p_service_uuid = sec_service_uuid, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; +// config scan response data +static esp_ble_adv_data_t heart_rate_scan_rsp_config = { + .set_scan_rsp = true, + .include_name = true, + .manufacturer_len = sizeof(test_manufacturer), + .p_manufacturer_data = test_manufacturer, +}; + +static esp_ble_adv_params_t heart_rate_adv_params = { + .adv_int_min = 0x100, + .adv_int_max = 0x100, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .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[HEART_PROFILE_NUM] = { + [HEART_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 */ + }, + +}; + +/* + * Heart Rate PROFILE ATTRIBUTES + **************************************************************************************** + */ + +/// Heart Rate Sensor Service +static const uint16_t heart_rate_svc = ESP_GATT_UUID_HEART_RATE_SVC; + +#define CHAR_DECLARATION_SIZE (sizeof(uint8_t)) +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 uint8_t char_prop_notify = ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ; +static const uint8_t char_prop_read_write = ESP_GATT_CHAR_PROP_BIT_WRITE|ESP_GATT_CHAR_PROP_BIT_READ; + +/// Heart Rate Sensor Service - Heart Rate Measurement Characteristic, notify +static const uint16_t heart_rate_meas_uuid = ESP_GATT_HEART_RATE_MEAS; +static const uint8_t heart_measurement_ccc[2] ={ 0x00, 0x00}; + + +/// Heart Rate Sensor Service -Body Sensor Location characteristic, read +static const uint16_t body_sensor_location_uuid = ESP_GATT_BODY_SENSOR_LOCATION; +static const uint8_t body_sensor_loc_val[1] = {0x00}; + + +/// Heart Rate Sensor Service - Heart Rate Control Point characteristic, write&read +static const uint16_t heart_rate_ctrl_point = ESP_GATT_HEART_RATE_CNTL_POINT; +static const uint8_t heart_ctrl_point[1] = {0x00}; + +/// Full HRS Database Description - Used to add attributes into the database +static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = +{ + // Heart Rate Service Declaration + [HRS_IDX_SVC] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, + sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}}, + + // Heart Rate Measurement Characteristic Declaration + [HRS_IDX_HR_MEAS_CHAR] = + {{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}}, + + // Heart Rate Measurement Characteristic Value + [HRS_IDX_HR_MEAS_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ, + HRPS_HT_MEAS_MAX_LEN,0, NULL}}, + + // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor + [HRS_IDX_HR_MEAS_NTF_CFG] = + {{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(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}}, + + // Body Sensor Location Characteristic Declaration + [HRS_IDX_BOBY_SENSOR_LOC_CHAR] = + {{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}}, + + // Body Sensor Location Characteristic Value + [HRS_IDX_BOBY_SENSOR_LOC_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ_ENCRYPTED, + sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}}, + + // Heart Rate Control Point Characteristic Declaration + [HRS_IDX_HR_CTNL_PT_CHAR] = + {{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}}, + + // Heart Rate Control Point Characteristic Value + [HRS_IDX_HR_CTNL_PT_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE_ENCRYPTED|ESP_GATT_PERM_READ_ENCRYPTED, + sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}}, +}; + +static char *esp_key_type_to_str(esp_ble_key_type_t key_type) +{ + char *key_str = NULL; + switch(key_type) { + case ESP_LE_KEY_NONE: + key_str = "ESP_LE_KEY_NONE"; + break; + case ESP_LE_KEY_PENC: + key_str = "ESP_LE_KEY_PENC"; + break; + case ESP_LE_KEY_PID: + key_str = "ESP_LE_KEY_PID"; + break; + case ESP_LE_KEY_PCSRK: + key_str = "ESP_LE_KEY_PCSRK"; + break; + case ESP_LE_KEY_PLK: + key_str = "ESP_LE_KEY_PLK"; + break; + case ESP_LE_KEY_LLK: + key_str = "ESP_LE_KEY_LLK"; + break; + case ESP_LE_KEY_LENC: + key_str = "ESP_LE_KEY_LENC"; + break; + case ESP_LE_KEY_LID: + key_str = "ESP_LE_KEY_LID"; + break; + case ESP_LE_KEY_LCSRK: + key_str = "ESP_LE_KEY_LCSRK"; + break; + default: + key_str = "INVALID BLE KEY TYPE"; + break; + + } + + return key_str; +} + +static char *esp_auth_req_to_str(esp_ble_auth_req_t auth_req) +{ + char *auth_str = NULL; + switch(auth_req) { + case ESP_LE_AUTH_NO_BOND: + auth_str = "ESP_LE_AUTH_NO_BOND"; + break; + case ESP_LE_AUTH_BOND: + auth_str = "ESP_LE_AUTH_BOND"; + break; + case ESP_LE_AUTH_REQ_MITM: + auth_str = "ESP_LE_AUTH_REQ_MITM"; + break; + case ESP_LE_AUTH_REQ_BOND_MITM: + auth_str = "ESP_LE_AUTH_REQ_BOND_MITM"; + break; + case ESP_LE_AUTH_REQ_SC_ONLY: + auth_str = "ESP_LE_AUTH_REQ_SC_ONLY"; + break; + case ESP_LE_AUTH_REQ_SC_BOND: + auth_str = "ESP_LE_AUTH_REQ_SC_BOND"; + break; + case ESP_LE_AUTH_REQ_SC_MITM: + auth_str = "ESP_LE_AUTH_REQ_SC_MITM"; + break; + case ESP_LE_AUTH_REQ_SC_MITM_BOND: + auth_str = "ESP_LE_AUTH_REQ_SC_MITM_BOND"; + break; + default: + auth_str = "INVALID BLE AUTH REQ"; + break; + } + + return auth_str; +} + +static void show_bonded_devices(void) +{ + int dev_num = esp_ble_get_bond_device_num(); + + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + esp_ble_get_bond_device_list(&dev_num, dev_list); + ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices number : %d\n", dev_num); + + ESP_LOGI(GATTS_TABLE_TAG, "Bonded devices list : %d\n", dev_num); + for (int i = 0; i < dev_num; i++) { + esp_log_buffer_hex(GATTS_TABLE_TAG, (void *)dev_list[i].bd_addr, sizeof(esp_bd_addr_t)); + } + + free(dev_list); +} + +static void __attribute__((unused)) remove_all_bonded_devices(void) +{ + int dev_num = esp_ble_get_bond_device_num(); + + esp_ble_bond_dev_t *dev_list = (esp_ble_bond_dev_t *)malloc(sizeof(esp_ble_bond_dev_t) * dev_num); + 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) +{ + ESP_LOGV(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event); + + switch (event) { + 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(&heart_rate_adv_params); + } + break; + 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(&heart_rate_adv_params); + } + break; + 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(GATTS_TABLE_TAG, "advertising start failed, error status = %x", param->adv_start_cmpl.status); + break; + } + ESP_LOGI(GATTS_TABLE_TAG, "advertising start success"); + break; + case ESP_GAP_BLE_PASSKEY_REQ_EVT: /* passkey request event */ + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_PASSKEY_REQ_EVT"); + /* Call the following function to input the passkey which is displayed on the remote device */ + //esp_ble_passkey_reply(heart_rate_profile_tab[HEART_PROFILE_APP_IDX].remote_bda, true, 0x00); + break; + case ESP_GAP_BLE_OOB_REQ_EVT: { + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_OOB_REQ_EVT"); + uint8_t tk[16] = {1}; //If you paired with OOB, both devices need to use the same tk + esp_ble_oob_req_reply(param->ble_security.ble_req.bd_addr, tk, sizeof(tk)); + break; + } + case ESP_GAP_BLE_LOCAL_IR_EVT: /* BLE local IR event */ + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_LOCAL_IR_EVT"); + break; + case ESP_GAP_BLE_LOCAL_ER_EVT: /* BLE local ER event */ + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_LOCAL_ER_EVT"); + break; + case ESP_GAP_BLE_NC_REQ_EVT: + /* The app will receive this evt 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_ble_confirm_reply(param->ble_security.ble_req.bd_addr, true); + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_NC_REQ_EVT, the passkey Notify number:%d", 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(GATTS_TABLE_TAG, "The passkey Notify number:%06d", 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. + ESP_LOGI(GATTS_TABLE_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)); + ESP_LOGI(GATTS_TABLE_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]); + ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", param->ble_security.auth_cmpl.addr_type); + ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s",param->ble_security.auth_cmpl.success ? "success" : "fail"); + if(!param->ble_security.auth_cmpl.success) { + ESP_LOGI(GATTS_TABLE_TAG, "fail reason = 0x%x",param->ble_security.auth_cmpl.fail_reason); + } else { + ESP_LOGI(GATTS_TABLE_TAG, "auth mode = %s",esp_auth_req_to_str(param->ble_security.auth_cmpl.auth_mode)); + } + show_bonded_devices(); + break; + } + case ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT: { + ESP_LOGD(GATTS_TABLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV_COMPLETE_EVT status = %d", param->remove_bond_dev_cmpl.status); + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GAP_BLE_REMOVE_BOND_DEV"); + ESP_LOGI(GATTS_TABLE_TAG, "-----ESP_GAP_BLE_REMOVE_BOND_DEV----"); + esp_log_buffer_hex(GATTS_TABLE_TAG, (void *)param->remove_bond_dev_cmpl.bd_addr, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTS_TABLE_TAG, "------------------------------------"); + break; + } + case ESP_GAP_BLE_SET_LOCAL_PRIVACY_COMPLETE_EVT: + if (param->local_privacy_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTS_TABLE_TAG, "config local privacy failed, error status = %x", param->local_privacy_cmpl.status); + break; + } + + esp_err_t ret = esp_ble_gap_config_adv_data(&heart_rate_adv_config); + if (ret){ + ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret); + }else{ + adv_config_done |= ADV_CONFIG_FLAG; + } + + ret = esp_ble_gap_config_adv_data(&heart_rate_scan_rsp_config); + if (ret){ + ESP_LOGE(GATTS_TABLE_TAG, "config adv data failed, error code = %x", ret); + }else{ + adv_config_done |= SCAN_RSP_CONFIG_FLAG; + } + + break; + default: + break; + } +} + +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) +{ + ESP_LOGV(GATTS_TABLE_TAG, "event = %x\n",event); + switch (event) { + case ESP_GATTS_REG_EVT: + esp_ble_gap_set_device_name(EXAMPLE_DEVICE_NAME); + //generate a resolvable random address + esp_ble_gap_config_local_privacy(true); + esp_ble_gatts_create_attr_tab(heart_rate_gatt_db, gatts_if, + HRS_IDX_NB, HEART_RATE_SVC_INST_ID); + break; + case ESP_GATTS_READ_EVT: + break; + case ESP_GATTS_WRITE_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_WRITE_EVT, write value:"); + esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len); + break; + case ESP_GATTS_EXEC_WRITE_EVT: + break; + case ESP_GATTS_MTU_EVT: + break; + case ESP_GATTS_CONF_EVT: + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT"); + /* 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(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason); + /* start advertising again when missing the connect */ + esp_ble_gap_start_advertising(&heart_rate_adv_params); + break; + case ESP_GATTS_OPEN_EVT: + break; + case ESP_GATTS_CANCEL_OPEN_EVT: + break; + case ESP_GATTS_CLOSE_EVT: + break; + case ESP_GATTS_LISTEN_EVT: + break; + case ESP_GATTS_CONGEST_EVT: + break; + case ESP_GATTS_CREAT_ATTR_TAB_EVT: { + ESP_LOGI(GATTS_TABLE_TAG, "The number handle = %x",param->add_attr_tab.num_handle); + if (param->create.status == ESP_GATT_OK){ + if(param->add_attr_tab.num_handle == HRS_IDX_NB) { + memcpy(heart_rate_handle_table, param->add_attr_tab.handles, + sizeof(heart_rate_handle_table)); + esp_ble_gatts_start_service(heart_rate_handle_table[HRS_IDX_SVC]); + }else{ + ESP_LOGE(GATTS_TABLE_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_LOGE(GATTS_TABLE_TAG, " Create attribute table failed, error code = %x", param->create.status); + } + 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[HEART_PROFILE_APP_IDX].gatts_if = gatts_if; + } else { + ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + do { + int idx; + for (idx = 0; idx < HEART_PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + 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 || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + 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(GATTS_TABLE_TAG, "%s init controller failed: %s", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret)); + return; + } + + ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth", __func__); + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TABLE_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(GATTS_TABLE_TAG, "gatts register error, error code = %x", ret); + return; + } + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret){ + ESP_LOGE(GATTS_TABLE_TAG, "gap register error, error code = %x", ret); + return; + } + ret = esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TABLE_TAG, "gatts app register error, error code = %x", ret); + return; + } + + /* 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_NONE; //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; + //set static passkey + uint32_t passkey = 123456; + uint8_t auth_option = ESP_BLE_ONLY_ACCEPT_SPECIFIED_AUTH_DISABLE; + uint8_t oob_support = ESP_BLE_OOB_DISABLE; + 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)); + esp_ble_gap_set_security_param(ESP_BLE_SM_ONLY_ACCEPT_SPECIFIED_SEC_AUTH, &auth_option, sizeof(uint8_t)); + esp_ble_gap_set_security_param(ESP_BLE_SM_OOB_SUPPORT, &oob_support, sizeof(uint8_t)); + /* If your BLE device acts 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 acts 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)); + + /* Just show how to clear all the bonded devices + * Delay 30s, clear all the bonded devices + * + * vTaskDelay(30000 / portTICK_PERIOD_MS); + * remove_all_bonded_devices(); + */ +} + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.h new file mode 100644 index 00000000..f8f7c83c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/main/example_ble_sec_gatts_demo.h @@ -0,0 +1,42 @@ +/* + 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 +#include +#include + +/* + * DEFINES + **************************************************************************************** + */ + +#define HRPS_HT_MEAS_MAX_LEN (13) + +#define HRPS_MANDATORY_MASK (0x0F) +#define HRPS_BODY_SENSOR_LOC_MASK (0x30) +#define HRPS_HR_CTNL_PT_MASK (0xC0) + + +///Attributes State Machine +enum +{ + HRS_IDX_SVC, + + HRS_IDX_HR_MEAS_CHAR, + HRS_IDX_HR_MEAS_VAL, + HRS_IDX_HR_MEAS_NTF_CFG, + + HRS_IDX_BOBY_SENSOR_LOC_CHAR, + HRS_IDX_BOBY_SENSOR_LOC_VAL, + + HRS_IDX_HR_CTNL_PT_CHAR, + HRS_IDX_HR_CTNL_PT_VAL, + + HRS_IDX_NB, +}; diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults new file mode 100644 index 00000000..ee53a228 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md new file mode 100644 index 00000000..6db37dd0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_security_server/tutorial/Gatt_Security_Server_Example_Walkthrough.md @@ -0,0 +1,235 @@ +# Gatt Security Server Example Walkthrough + +## Introduction + +In this document, a description of the security GATT Server BLE example for the ESP32 is presented. The security configuration enables a GATT Server acting as a slave device to bond with a master and establish an encrypted link between them. This functionality is defined by the [Bluetooth Specification version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification) and implemented on the ESP-IDF BLE stack, specifically on the Security Manager Protocol (SMP) API. + +BLE security involves three interrelated concepts: pairing, bonding and encryption. Pairing concerns with the exchange of security features and types of keys needed. In addition, the pairing procedure takes care of the generation and exchange of shared keys. The core specification defines the legacy pairing and Secure Connections pairing (introduced in Bluetooth 4.2), which are both supported by ESP32. Once the exchange of shared keys is completed, a temporary encrypted link is established to exchange short term and long term keys. Bonding refers to storing the exchanged keys for subsequent connections so that they do not have to be transmitted again. Finally, encryption pertains to the ciphering of plain text data using the AES-128 engine and the shared keys. Server attributes may also be defined to allow only encrypted write and read messages. At any point of the communication, a slave device can always ask to start encryption by issuing a security request to the other peer device, which returns a security response by calling an API. + +This document only describes the security configuration. The rest of the GATT server functionalities, such as defining the service table, are explained in the GATT Server example walkthrough documentation. For a better understanding of this example workflow, it is recommended that the reader is familiar with the pairing feature exchange and key generation defined in the section 3.5 of the [Bluetooth Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification) [Vol 3, Part H]. + +## Setting Security Parameters + +The ESP32 requires a series of security parameters in order to define how the pairing request and response are going to be built. The Pairing Response packet built by the GATT Server includes fields such as the input/output capabilities, Secure Connections pairing, authenticated Man-In-The-Middle (MITM) protection or no security requirements (see Section 2.3.1 of the [Bluetooth Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification) [Vol 3, Part H]). In this example, this procedure is done in the `app_main()` function. The pairing request is sent by the initiator which in this case is a remote GATT client. The ESP32 server implemented in this example receives this request and replies with a pairing response, which contains the same security parameters in order for both devices to agree on the resources available and the applicable pairing algorithm (*Just Works* or *Passkey Entry*). Both the pairing request and response commands have the following parameters: + +* *IO Capability*: describes if the device has input/output capabilities such as a display or a keyboard. +* *OOB Flag*: describes if the device supports Out of Band passkey exchange, for example using NFC or Wi-Fi to exchange keys as TKs. +* *Authorization Request*: indicates the requested security properties such as Bonding, Secure Connections (SC), MITM protection or none that will be present in the Pairing Request and Response packets. +* *Maximum Encryption Key Size*: maximum encryption key size in octets. +* *Initiator Key Distribution/Generation*: indicates which keys the initiator is requesting to distribute/generate or use during the Transport Specific Key Distribution phase. In the pairing request, these keys are requested, while in the pairing response, these keys are confirmed to be distributed. +* *Responder Key Distribution/Generation*: indicates which keys the initiator is requesting the responder to distribute/generate or use during the Transport Specific Key Distribution phase. In the pairing request, these keys are requested, while in the pairing response, these keys are confirmed to be distributed. + +In code, these parameters are defined as follows: + +* *IO Capability*: + + ```c + esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;//set the IO capability to No Input No Output + ``` + + The possible values for *IO Capabilities* are: + + ```c + ESP_IO_CAP_OUT 0 /*!< DisplayOnly */ + ESP_IO_CAP_IO 1 /*!< DisplayYesNo */ + ESP_IO_CAP_IN 2 /*!< KeyboardOnly */ + ESP_IO_CAP_NONE 3 /*!< NoInputNoOutput */ + ESP_IO_CAP_KBDISP 4 /*!< Keyboard display */ + ``` + +* *Authorization Request*: + + ```c + esp_ble_auth_req_t auth_req = ESP_LE_AUTH_BOND; //bonding with peer device after authentication + ``` + + The possible values for *Authorization Request* are a combination of Bonding, MITM protection and Secure Connections requests: + + ```c + ESP_LE_AUTH_NO_BOND: No bonding. + ESP_LE_AUTH_BOND: Bonding is performed. + ESP_LE_AUTH_REQ_MITM: MITM Protection is enabled. + ESP_LE_AUTH_REQ_SC_ONLY: Secure Connections without bonding enabled. + ESP_LE_AUTH_REQ_SC_BOND: Secure Connections with bonding enabled. + ESP_LE_AUTH_REQ_SC_MITM: Secure Connections with MITM Protection and no bonding enabled. + ESP_LE_AUTH_REQ_SC_MITM_BOND: Secure Connections with MITM Protection and bonding enabled. + ``` + +* *Maximum Encryption Key Size*: + + ```c + uint8_t key_size = 16; //the key size should be 7~16 bytes + ``` + +* *Initiator Key Distribution/Generation*: + + ```c + uint8_t init_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + ``` + + The initiator distributes the LTK and IRK keys by the setting the EncKey and IdKey masks. + +* *Responder Key Distribution/Generation*: + + ```c + uint8_t rsp_key = ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK; + ``` + + The responder distributes the LTK and IRK keys by the setting the *EncKey* and *IdKey* masks. + + Once defined, the parameters are set using the `esp_ble_gap_set_security_param()` function. This function sets the parameter type, the parameter value and the parameter length: + + ```c + 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)); + 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)); + ``` + + This information is enough for the BLE stack to perform the pairing process, including pairing confirmation and key generation. The procedure is invisible to the user and executed automatically by the stack. + +## Connecting and Bonding to a Peer Device + +The security parameters set previously are stored locally to be used later when the master device connects to the slave. Every time a remote device connects to the local GATT server, the connection event `ESP_GATTS_CONNECT_EVT` is triggered. This event is employed to perform the pairing and bonding process by invoking the `esp_ble_set_encryption()` function which takes as parameters the remote device address and the type of encryption that will be performed. The BLE stack then executes the actual pairing process in the background. In this example, the encryption includes MITM protection. + +```c +case ESP_GATTS_CONNECT_EVT: + //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; +``` +The types of encryptions available are: + +* `ESP_BLE_SEC_NONE` +* `ESP_BLE_SEC_ENCRYPT` +* `ESP_BLE_SEC_ENCRYPT_NO_MITM` +* `ESP_BLE_SEC_ENCRYPT_MITM` + +The difference between `ESP_BLE_SEC_ENCRYPT` and `ESP_BLE_SEC_ENCRYPT_NO_MITM` lies in the fact that a previous connection might have a security level that needs to be upgraded, therefore requires to exchange keys again. + +In this example, the I/O capabilities are set to *No Input No Output*, therefore the *Just Works* pairing method, which doesn't not require the generation of a random 6-digit passkey, is used (For details, please refer to the the table below). The user may modify the example to set the I/O capabilities to use other than *No Input No Output*. Therefore, depending on the I/O capabilities of the remote device, a passkey might be generated on the ESP32 which is presented to the user with the `ESP_GAP_BLE_PASSKEY_NOTIF_EVT`: + +```c +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_LOGE(GATTS_TABLE_TAG, "The passkey Notify number:%d", param->ble_security.key_notif.passkey); + break; +``` + +The combination of input and output capabilities that determine which algorithm is used are: + +| | Display Only | Display Yes/No | Keyboard Only | No Input No Output | Keyboard Display| +| :-- | :------------- | :------------- | :------------- | :----------------- | :-------------- | +| **Display Only** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Display Yes/No** | Just Works | Just Works | Passkey Entry | Just Works | Passkey Entry | +| **Keyboard Only** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | +| **No Input No Output** | Just Works | Just Works | Just Works | Just Works | Just Works | +| **Keyboard Display** | Passkey Entry | Passkey Entry | Passkey Entry | Just Works | Passkey Entry | + + +## Exchanging Keys + +When the client connects to the server and the pairing is done successfully, the keys indicated by the `init_key` and `rsp_key` security parameters are exchanged. In this example the following keys are generated and distributed: + +* Local device LTK +* Local device IRK +* Local device CSRK +* Peer device LTK +* Peer device IRK + +Note that for this example only, the peer device CSRK is not exchanged. For each key exchange message, an `ESP_GAP_BLE_KEY_EVT` event is triggered, which can be used to print the type of key received: + +```c +case ESP_GAP_BLE_KEY_EVT: + //shows the ble key info share with peer device to the user. + ESP_LOGI(GATTS_TABLE_TAG, "key type = %s", esp_key_type_to_str(param->ble_security.ble_key.key_type)); + break; +``` + +When the keys are exchanged successfully, the pairing process is done. This triggers an `ESP_GAP_BLE_AUTH_CMPL_EVT` event, which is used to print information such as remote device, address type and pair status: + +```c +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)); + ESP_LOGI(GATTS_TABLE_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]); + ESP_LOGI(GATTS_TABLE_TAG, "address type = %d", + param->ble_security.auth_cmpl.addr_type); + ESP_LOGI(GATTS_TABLE_TAG, "pair status = %s", + param->ble_security.auth_cmpl.success ? "success" : "fail"); + break; +} +``` + +## Security Permission for Attributes + +When defining the attributes of the server, different permissions can be set to the write and read events. Attributes permissions can be: + +* Permission to read +* Permission to read with encryption +* Permission to read with encryption and MITM protection +* Permission to write +* Permission to write with encryption +* Permission to write with encryption and MITM protection +* Permission to signed write +* Permission to signed write with MITM protection + +These permissions are defined in the API as: + +* `ESP_GATT_PERM_READ` +* `ESP_GATT_PERM_READ_ENCRYPTED` +* `ESP_GATT_PERM_READ_ENC_MITM` +* `ESP_GATT_PERM_WRITE` +* `ESP_GATT_PERM_WRITE_ENCRYPTED` +* `ESP_GATT_PERM_WRITE_ENC_MITM` +* `ESP_GATT_PERM_WRITE_SIGNED` +* `ESP_GATT_PERM_WRITE_SIGNED_MITM` + +When creating the services table, each attribute can have permissions to read or write, with or without encryption. When an attribute has encrypted permissions and a peer device that does not have the required security clearance tries to read or write to that attribute, the local host sends an Insufficient Authorization Error. In the example, the following attributes are defined with permissions with encryption: + +```c +… +// Body Sensor Location Characteristic Value + [HRS_IDX_BOBY_SENSOR_LOC_VAL] = { + {ESP_GATT_AUTO_RSP}, + {ESP_UUID_LEN_16, + (uint8_t *)&body_sensor_location_uuid, + ESP_GATT_PERM_READ_ENCRYPTED, + sizeof(uint8_t), + sizeof(body_sensor_loc_val), + (uint8_t *)body_sensor_loc_val} + }, +… +// Heart Rate Control Point Characteristic Value + [HRS_IDX_HR_CTNL_PT_VAL] = { + {ESP_GATT_AUTO_RSP}, + {ESP_UUID_LEN_16, + (uint8_t *)&heart_rate_ctrl_point, + ESP_GATT_PERM_WRITE_ENCRYPTED|ESP_GATT_PERM_READ_ENCRYPTED, + sizeof(uint8_t), + sizeof(heart_ctrl_point), + (uint8_t *)heart_ctrl_point} + }, +… +``` + +## Security Requests + +During the communication between a master and a slave device, the slave might request to start encryption at any moment by issuing a security request command. This command will trigger an `ESP_GAP_BLE_SEC_REQ_EVT` event on the master, which will reply a positive (true) security response to the peer device to accept the request or a negative (false) one to reject the request. In this example, this event is used to reply a start encryption response by using the `esp_ble_gap_security_rsp()` API call. + +```c +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; +``` +## Conclusion + +In this document, a review of the security aspects of the GATT Server has been realized. BLE security encompasses Pairing, Bonding and Encryption. In order to establish a secured link between a master and a slave device, security parameters that define the capabilities and features each device possess are set. The combination of features and capabilities of the peer devices results in the selection of the appropriate pairing method which the BLE stack then executes. Immediately, the required keys are generated and exchanged, and the encryption of subsequent messages is started using the AES-128 engine and these keys. These steps trigger different events that are managed by the GATT and GAP event handlers which can be used to print useful information such as the types of keys exchanged and the pairing status. In addition, attribute permissions are appointed to allow only encrypted read and write events when needed. The rest of the Security GATT server functionalities such as defining services and characteristics are implemented in the same way as presented in the GATT Server example. \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/CMakeLists.txt new file mode 100644 index 00000000..fb1760c1 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gatt_server_demos) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/Makefile new file mode 100644 index 00000000..2f76e60b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := gatt_server_demos + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/README.md new file mode 100644 index 00000000..3a677eda --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/README.md @@ -0,0 +1,16 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gatt Server Demo +======================== + +This is the demo of APIs to create a GATT service by adding attributes one by one. However, this method is defined by Bluedroid and is difficult for users to use. + +Hence, we also allow users to create a GATT service with an attribute table, which releases the user from adding attributes one by one. And it is recommended for users to use. For more information about this method, please refer to [gatt_server_service_table_demo](../gatt_server_service_table). + +This demo creates GATT a service and then starts advertising, waiting to be connected to a GATT client. + +To test this demo, we can run the [gatt_client_demo](../gatt_client), which can scan for and connect to this demo automatically. They will start exchanging data once the GATT client has enabled the notification function of the GATT server. + +Please check the [tutorial](tutorial/Gatt_Server_Example_Walkthrough.md) for more information about this example. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/CMakeLists.txt new file mode 100644 index 00000000..e950c9ec --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "gatts_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/Kconfig b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/Kconfig new file mode 100644 index 00000000..6ecc4bc7 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/Kconfig @@ -0,0 +1,14 @@ +menu "Example 'GATT SERVER' Config" + + config SET_RAW_ADV_DATA + bool "Use raw data for advertising packets and scan response data" + help + If this config item is set, raw binary data will be used to generate advertising & scan response data. + This option uses the esp_ble_gap_config_adv_data_raw() and esp_ble_gap_config_scan_rsp_data_raw() + functions. + + If this config item is unset, advertising & scan response data is provided via a higher-level + esp_ble_adv_data_t structure. The lower layer will generate the BLE packets. This option has higher + overhead at runtime. + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c new file mode 100644 index 00000000..ea140e8c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/main/gatts_demo.c @@ -0,0 +1,737 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This demo showcases BLE GATT server. It can send adv data, be connected by client. +* Run the gatt_client demo, the client demo will automatically connect to the gatt_server demo. +* Client demo will enable gatt_server's notify after connection. The two devices will then exchange +* data. +* +****************************************************************************/ + + +#include +#include +#include +#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_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" + +#include "sdkconfig.h" + +#define GATTS_TAG "GATTS_DEMO" + +///Declare the static function +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); + +#define GATTS_SERVICE_UUID_TEST_A 0x00FF +#define GATTS_CHAR_UUID_TEST_A 0xFF01 +#define GATTS_DESCR_UUID_TEST_A 0x3333 +#define GATTS_NUM_HANDLE_TEST_A 4 + +#define GATTS_SERVICE_UUID_TEST_B 0x00EE +#define GATTS_CHAR_UUID_TEST_B 0xEE01 +#define GATTS_DESCR_UUID_TEST_B 0x2222 +#define GATTS_NUM_HANDLE_TEST_B 4 + +#define TEST_DEVICE_NAME "ESP_GATTS_DEMO" +#define TEST_MANUFACTURER_DATA_LEN 17 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 + +#define PREPARE_BUF_MAX_SIZE 1024 + +static uint8_t char1_str[] = {0x11,0x22,0x33}; +static esp_gatt_char_prop_t a_property = 0; +static esp_gatt_char_prop_t b_property = 0; + +static esp_attr_value_t gatts_demo_char1_val = +{ + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(char1_str), + .attr_value = char1_str, +}; + +static uint8_t adv_config_done = 0; +#define adv_config_flag (1 << 0) +#define scan_rsp_config_flag (1 << 1) + +#ifdef CONFIG_SET_RAW_ADV_DATA +static uint8_t raw_adv_data[] = { + 0x02, 0x01, 0x06, + 0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd +}; +static uint8_t raw_scan_rsp_data[] = { + 0x0f, 0x09, 0x45, 0x53, 0x50, 0x5f, 0x47, 0x41, 0x54, 0x54, 0x53, 0x5f, 0x44, + 0x45, 0x4d, 0x4f +}; +#else + +static uint8_t adv_service_uuid128[32] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, + //second uuid, 32bit, [12], [13], [14], [15] 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 uint8_t test_manufacturer[TEST_MANUFACTURER_DATA_LEN] = {0x12, 0x23, 0x45, 0x56}; +//adv data +static esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = false, + .min_interval = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec + .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 = sizeof(adv_service_uuid128), + .p_service_uuid = adv_service_uuid128, + .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 = 0x0006, + //.max_interval = 0x0010, + .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 = sizeof(adv_service_uuid128), + .p_service_uuid = adv_service_uuid128, + .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 = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; + +#define PROFILE_NUM 2 +#define PROFILE_A_APP_ID 0 +#define PROFILE_B_APP_ID 1 + +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; +}; + +/* 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 gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gatts_cb = gatts_profile_a_event_handler, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [PROFILE_B_APP_ID] = { + .gatts_cb = gatts_profile_b_event_handler, /* This demo does not implement, similar as profile A */ + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +typedef struct { + uint8_t *prepare_buf; + int prepare_len; +} prepare_type_env_t; + +static prepare_type_env_t a_prepare_write_env; +static prepare_type_env_t b_prepare_write_env; + +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); +void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); + +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(GATTS_TAG, "Advertising start failed\n"); + } + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TAG, "Advertising stop failed\n"); + } else { + ESP_LOGI(GATTS_TAG, "Stop adv successfully\n"); + } + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTS_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + default: + break; + } +} + +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ + esp_gatt_status_t status = ESP_GATT_OK; + if (param->write.need_rsp){ + if (param->write.is_prep){ + if (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(GATTS_TAG, "Gatt_server prep no mem\n"); + status = ESP_GATT_NO_RESOURCES; + } + } else { + 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; + } + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + 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(GATTS_TAG, "Send response error\n"); + } + free(gatt_rsp); + 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; + + }else{ + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); + } + } +} + +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){ + esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + }else{ + ESP_LOGI(GATTS_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_a_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_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A; + + esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(TEST_DEVICE_NAME); + if (set_dev_name_ret){ + ESP_LOGE(GATTS_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(GATTS_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(GATTS_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(GATTS_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(GATTS_TAG, "config scan response data failed, error code = %x", ret); + } + adv_config_done |= scan_rsp_config_flag; + +#endif + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep){ + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); + if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ + uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ + ESP_LOGI(GATTS_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) + { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(notify_data), notify_data, false); + } + }else if (descr_value == 0x0002){ + if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ + ESP_LOGI(GATTS_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i%0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } + else if (descr_value == 0x0000){ + ESP_LOGI(GATTS_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(GATTS_TAG, "unknown descr value"); + esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); + } + + } + } + example_write_event_env(gatts_if, &a_prepare_write_env, param); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&a_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A; + + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); + a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + a_property, + &gatts_demo_char1_val, NULL); + if (add_char_ret){ + ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret); + } + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: { + uint16_t length = 0; + const uint8_t *prf_char; + + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char); + if (get_attr_ret == ESP_FAIL){ + ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE"); + } + + ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length); + for(int i = 0; i < length; i++){ + ESP_LOGI(GATTS_TAG, "prf_char[%x] =%x\n",i,prf_char[i]); + } + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL); + if (add_descr_ret){ + ESP_LOGE(GATTS_TAG, "add char descr failed, error code =%x", add_descr_ret); + } + break; + } + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: { + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + /* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */ + conn_params.latency = 0; + conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = 400; // timeout = 400*10ms = 4000ms + ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + //start sent the update connection parameters to the peer device. + esp_ble_gap_update_conn_params(&conn_params); + break; + } + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x", param->disconnect.reason); + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d", param->conf.status, param->conf.handle); + if (param->conf.status != ESP_GATT_OK){ + esp_log_buffer_hex(GATTS_TAG, param->conf.value, param->conf.len); + } + break; + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + default: + break; + } +} + +static void gatts_profile_b_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_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_B_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B; + + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep){ + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); + if (gl_profile_tab[PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ + uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ + ESP_LOGI(GATTS_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) + { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(notify_data), notify_data, false); + } + }else if (descr_value == 0x0002){ + if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ + ESP_LOGI(GATTS_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i%0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } + else if (descr_value == 0x0000){ + ESP_LOGI(GATTS_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(GATTS_TAG, "unknown value"); + } + + } + } + example_write_event_env(gatts_if, &b_prepare_write_env, param); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&b_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gl_profile_tab[PROFILE_B_APP_ID].service_handle = param->create.service_handle; + gl_profile_tab[PROFILE_B_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_B; + + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_B_APP_ID].service_handle); + b_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret =esp_ble_gatts_add_char( gl_profile_tab[PROFILE_B_APP_ID].service_handle, &gl_profile_tab[PROFILE_B_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + b_property, + NULL, NULL); + if (add_char_ret){ + ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret); + } + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + + gl_profile_tab[PROFILE_B_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_B_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_B_APP_ID].service_handle, &gl_profile_tab[PROFILE_B_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + NULL, NULL); + break; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + gl_profile_tab[PROFILE_B_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(GATTS_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(GATTS_TAG, "CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]); + gl_profile_tab[PROFILE_B_APP_ID].conn_id = param->connect.conn_id; + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONF_EVT status %d attr_handle %d", param->conf.status, param->conf.handle); + if (param->conf.status != ESP_GATT_OK){ + esp_log_buffer_hex(GATTS_TAG, param->conf.value, param->conf.len); + } + break; + case ESP_GATTS_DISCONNECT_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + 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) { + gl_profile_tab[param->reg.app_id].gatts_if = gatts_if; + } else { + ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gatts_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gatts_if == gl_profile_tab[idx].gatts_if) { + if (gl_profile_tab[idx].gatts_cb) { + gl_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 || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + 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(GATTS_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret); + return; + } + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret){ + ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_B_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret); + return; + } + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret){ + ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + + return; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults new file mode 100644 index 00000000..ee53a228 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md new file mode 100644 index 00000000..89dea521 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md @@ -0,0 +1,954 @@ +# Gatt Server Example Walkthrough + +## Introduction + +In this document, we review the GATT SERVER example code which implements a Bluetooth Low Energy (BLE) Generic Attribute Profile (GATT) Server on the ESP32. This example is designed around two Application Profiles and a series of events that are handled in order to execute a sequence of configuration steps, such as defining advertising parameters, updating connection parameters and creating services and characteristics. In addition, this example handles read and write events, including a Write Long Characteristic request, which divides the incoming data into chunks so that the data can fit in the Attribute Protocol (ATT) message. This document follows the program workflow and breaks down the code in order to make sense of every section and reasoning behind the implementation. + +## Includes + +First, let’s take a look at the includes: + +```c +#include +#include +#include +#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 "bt.h" +#include "bta_api.h" +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "sdkconfig.h" +``` +These includes are required for the FreeRTOS and underlaying system components to run, including the logging functionality and a library to store data in non-volatile flash memory. We are interested in `"bt.h"`, `"esp_bt_main.h"`, `"esp_gap_ble_api.h"` and `"esp_gatts_api.h"`, which expose the BLE APIs required to implement this example. + +* `bt.h`: implements BT controller and VHCI configuration procedures from the host side. +* `esp_bt_main.h`: implements initialization and enabling of the Bluedroid stack. +* `esp_gap_ble_api.h`: implements GAP configuration, such as advertising and connection parameters. +* `esp_gatts_api.h`: implements GATT configuration, such as creating services and characteristics. + +## Main Entry Point + +The entry point to this example is the app_main() function: + +```c + void app_main() +{ + esp_err_t ret; + + // Initialize NVS. + 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); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s initialize controller failed\n", __func__); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s enable controller failed\n", __func__); + return; + } + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s init bluetooth failed\n", __func__); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TAG, "%s enable bluetooth failed\n", __func__); + return; + } + + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts register error, error code = %x", ret); + return; + } + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret){ + ESP_LOGE(GATTS_TAG, "gap register error, error code = %x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_B_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TAG, "gatts app register error, error code = %x", ret); + return; + } + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(512); + if (local_mtu_ret){ + ESP_LOGE(GATTS_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + return; +} +``` +The main function starts by initializing the non-volatile storage library. This library allows to save key-value pairs in flash memory and is used by some components such as the Wi-Fi library to save the SSID and password: + +```c +ret = nvs_flash_init(); +``` +## BT Controller and Stack Initialization + +The main function also initializes the BT controller by first creating a BT controller configuration structure named `esp_bt_controller_config_t` with default settings generated by the `BT_CONTROLLER_INIT_CONFIG_DEFAULT()` macro. The BT controller implements the Host Controller Interface (HCI) on the controller side, the Link Layer (LL) and the Physical Layer (PHY). The BT Controller is invisible to the user applications and deals with the lower layers of the BLE stack. The controller configuration includes setting the BT controller stack size, priority and HCI baud rate. With the settings created, the BT controller is initialized and enabled with the `esp_bt_controller_init()` function: + +```c +esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); +ret = esp_bt_controller_init(&bt_cfg); +``` + +Next, the controller is enabled in BLE Mode. + +```c +ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); +``` +>The controller should be enabled in `ESP_BT_MODE_BTDM`, if you want to use the dual mode (BLE + BT). + +There are four Bluetooth modes supported: + +1. `ESP_BT_MODE_IDLE`: Bluetooth not running +2. `ESP_BT_MODE_BLE`: BLE mode +3. `ESP_BT_MODE_CLASSIC_BT`: BT Classic mode +4. `ESP_BT_MODE_BTDM`: Dual mode (BLE + BT Classic) + +After the initialization of the BT controller, the Bluedroid stack, which includes the common definitions and APIs for both BT Classic and BLE, is initialized and enabled by using: + +```c +ret = esp_bluedroid_init(); +ret = esp_bluedroid_enable(); +``` +The Bluetooth stack is up and running at this point in the program flow, however the functionality of the application has not been defined yet. The functionality is defined by reacting to events such as what happens when another device tries to read or write parameters and establish a connection. The two main managers of events are the GAP and GATT event handlers. The application needs to register a callback function for each event handler in order to let the application know which functions are going to handle the GAP and GATT events: + +```c +esp_ble_gatts_register_callback(gatts_event_handler); +esp_ble_gap_register_callback(gap_event_handler); +``` +The functions `gatts_event_handler()` and `gap_event_handler()` handle all the events that are pushed to the application from the BLE stack. + +## APPLICATION PROFILES + +The GATT Server example application is organized by using Application Profiles as shown in the figure below. Each Application Profile describes a way to group functionalities that are designed for one client application, such as a mobile app running on a smartphone or tablet. In this way, a single design, enabled by different Application Profiles, can behave differently when used by different smartphone apps, allowing the server to react differently according to the client app that is being used. In practice, each profile is seen by the client as an independent BLE service. It is up to the client to discriminate the services that it is interested in. + +
Application Profiles are used to organize a BLE application in order to implement different functionality for different clients.
+ + +Each profile is defined as a struct where the struct members depend on the services and characteristics that are implemented in that Application Profile. The members also include a GATT interface, Application ID, Connection ID and a callback function to handle profile events. In this example, each profile is composed by: + +* GATT interface +* Application ID +* Connection ID +* Service handle +* Service ID +* Characteristic handle +* Characteristic UUID +* Attribute permissions +* Characteristic properties +* Client Characteristic Configuration descriptor handle +* Client Characteristic Configuration descriptor UUID + +It can be observed from this structure that this profile was designed to have one service and one characteristic, and that the characteristic has one descriptor. The service has a handle and an ID, in the same manner that each characteristic has a handle, an UUID, attribute permissions and properties. In addition, if the characteristic supports notifications or indications, it must implement a Client Characteristic Configuration descriptor (CCCD), which is an additional attribute that describes if the notifications or indications are enabled and defines how the characteristic may be configured by a specific client. This descriptor also has a handle and an UUID. + +The structure implementation is: + +```c +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; +}; +``` +The Application Profiles are stored in an array and corresponding callback functions `gatts_profile_a_event_handler()` and `gatts_profile_b_event_handler()` are assigned. Different applications on the GATT client use different interfaces, represented by the gatts_if parameter. For initialization, this parameter is set to `ESP_GATT_IF_NONE`, which means that the Application Profile is not linked to any client yet. + +```c +static struct gatts_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gatts_cb = gatts_profile_a_event_handler, + .gatts_if = ESP_GATT_IF_NONE, + [PROFILE_B_APP_ID] = { + .gatts_cb = gatts_profile_b_event_handler, + .gatts_if = ESP_GATT_IF_NONE, + }, +}; +``` +Finally, the Application Profiles are registered using the Application ID, which is an user-assigned number to identify each profile. In this way, multiple Application Profiles can run in one server. + +```c +esp_ble_gatts_app_register(PROFILE_A_APP_ID); +esp_ble_gatts_app_register(PROFILE_B_APP_ID); +``` + +## Setting GAP Parameters + +The register application event is the first one that is triggered during the lifetime of the program, this example uses the Profile A GATT event handle to configure the advertising parameters upon registration. This example has the option to use both standard Bluetooth Core Specification advertising parameters or a customized raw buffer. The option can be selected with the `CONFIG_SET_RAW_ADV_DATA` define. The raw advertising data can be used to implement iBeacons, Eddystone or other proprietaries, and custom frame types such as the ones used for Indoor Location Services that are different from the standard specifications. + +The function used to configure standard Bluetooth Specification advertisement parameters is `esp_ble_gap_config_adv_data()`, which takes a pointer to an `esp_ble_adv_data_t` structure. The `esp_ble_adv_data_t` data structure for advertising data has the following definition: + +```c +typedef struct { + bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/ + bool include_name; /*!< Advertising data include device name or not */ + bool include_txpower; /*!< Advertising data include TX power */ + int min_interval; /*!< Advertising data show slave preferred connection min interval */ + int max_interval; /*!< Advertising data show slave preferred connection max interval */ + int appearance; /*!< External appearance of device */ + uint16_t manufacturer_len; /*!< Manufacturer data length */ + uint8_t *p_manufacturer_data; /*!< Manufacturer data point */ + uint16_t service_data_len; /*!< Service data length */ + uint8_t *p_service_data; /*!< Service data point */ + uint16_t service_uuid_len; /*!< Service uuid length */ + uint8_t *p_service_uuid; /*!< Service uuid array point */ + uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */ +} esp_ble_adv_data_t; +``` +In this example, the structure is initialized as follows: + +```c +static esp_ble_adv_data_t adv_data = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, + .max_interval = 0x0010, + .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 = 32, + .p_service_uuid = test_service_uuid128, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; +``` +The minimum and maximum slave preferred connection intervals are set in units of 1.25 ms. In this example, the minimum slave preferred connection interval is defined as 0x0006 * 1.25 ms = 7.5 ms, and the maximum slave preferred connection interval is initialized as 0x0010 * 1.25 ms = 20 ms. + +An advertising payload can be up to 31 bytes of data. It is possible the parameter data is large enough to surpass the 31-byte advertisement packet limit, which causes the stack to cut the advertisement packet and leave some of the parameters out. This behavior can be demonstrated in this example if the manufacturer length and data are uncommented, which makes the services to not be advertised after recompiling and testing. + +It is possible to also advertise customized raw data using the `esp_ble_gap_config_adv_data_raw()` +and `esp_ble_gap_config_scan_rsp_data_raw()` functions, which require to create and pass a buffer for both advertising data and scanning response data. In this example, the raw data is represented by the `raw_adv_data[]` and `raw_scan_rsp_data[]` arrays. + +Finally, to set the device name, the `esp_ble_gap_set_device_name()` function is used. The registering event handler is shown as follows: + +```c +static void gatts_profile_a_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_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A; + + esp_ble_gap_set_device_name(TEST_DEVICE_NAME); +#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(GATTS_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(GATTS_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(GATTS_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(GATTS_TAG, "config scan response data failed, error code = %x", ret); + } + adv_config_done |= scan_rsp_config_flag; +#endif +``` +## GAP Event Handler + +Once the advertising data have been set, the GAP event `ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT` is triggered. For the case of raw advertising data set, the event triggered is `ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT`. Additionally when the raw scan response data is set, `ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT` event is triggered. + +```c +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 +… +``` +In any case, the server can start advertising using the `esp_ble_gap_start_advertising()` function, which takes a structure of type `esp_ble_adv_params_t` with the advertising parameters required by the stack to operate: + +```c +/// Advertising parameters +typedef struct { + uint16_t adv_int_min; + /*!< Minimum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 + Default: N = 0x0800 (1.28 second) + Time = N * 0.625 msec + Time Range: 20 ms to 10.24 sec */ + uint16_t adv_int_max; + /*!< Maximum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 + Default: N = 0x0800 (1.28 second) + Time = N * 0.625 msec + Time Range: 20 ms to 10.24 sec */ + esp_ble_adv_type_t adv_type; /*!< Advertising type */ + esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */ + esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */ + esp_ble_addr_type_t peer_addr_type; /*!< Peer device bluetooth device address type */ + esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */ + esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */ +} +esp_ble_adv_params_t; +``` + +Note that `esp_ble_gap_config_adv_data()` configures the data that will be advertised to the client and takes an `esp_ble_adv_data_t` structure, while `esp_ble_gap_start_advertising()` makes the server to actually start advertising and takes an `esp_ble_adv_params_t` structure. The advertising data is the information that is shown to the client, while the advertising parameters are the configuration required by the GAP to execute. + +For this example, the advertisement parameters are initialized as follows: + +```c +static esp_ble_adv_params_t test_adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; +``` + +These parameters configure the advertising interval between 40 ms to 80 ms. The advertisement is `ADV_IND`, which is a generic, not directed to a particular central device and connectable type. The address type is public, uses all channels and allows both scan and connection requests from any central. + +If the advertising started successfully, an `ESP_GAP_BLE_ADV_START_COMPLETE_EVT` event is generated, which in this example is used to check if the advertising status is indeed advertising. Otherwise, an error message is printed. + +```c +… + 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(GATTS_TAG, "Advertising start failed\n"); + } + break; +… +``` + +## GATT Event Handlers + +When an Application Profile is registered, an `ESP_GATTS_REG_EVT` event is triggered. The parameters of the `ESP_GATTS_REG_EVT` are: + +```c +esp_gatt_status_t status; /*!< Operation status */` +uint16_t app_id; /*!< Application id which input in register API */` +``` + +In addition to the previous parameters, the event also contains the GATT interface assigned by the BLE stack. The event is captured by the `gatts_event_handler()`, which used to store the generated interface in the profile table, and then the event is forwarded to the corresponding profile event handler. + +```c +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) { + gl_profile_tab[param->reg.app_id].gatts_if = gatts_if; + } else { + ESP_LOGI(GATTS_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + +/* If the gatts_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE||gatts_if == gl_profile_tab[idx].gatts_if) { + if (gl_profile_tab[idx].gatts_cb) { + gl_profile_tab[idx].gatts_cb(event, gatts_if, param); + } + } + } + } while (0); +} +``` + +## Creating Services + +The register event is also used to create a profile attributes table by using the `esp_ble_gatts_create_attr_tab()` function, which takes an `esp_gatts_attr_db_t` type structure that has all the information of the service table. The way to create this table is: + +```c +… +esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A); +break; +… +``` +The number of handles is defined as 4: + +```c +#define GATTS_NUM_HANDLE_TEST_A 4 +``` + +The handles are: + +1. Service handle +2. Characteristic handle +3. Characteristic value handle +4. Characteristic descriptor handle + +The service is defined as a primary service with a UUID length of 16 bits. The service ID is initialized with instance ID = 0 and UUID defined by `GATTS_SERVICE_UUID_TEST_A`. + +The service instance ID can be used to differentiate multiple services with the same UUID. In this example, since there is only one service for each Application Profiles and the services have different UUIDs, the service instance ID can be defined as 0 in both profile A and B. However if there was only one Application Profile with two services using the same UUID, then it would be necessary to use different instance IDs to refer to one service or the other. + +Application Profile B creates the service in the same way as Profile A: + +```c +static void gatts_profile_b_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_LOGI(GATTS_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_B_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B; + + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B); + break; +… +} +``` + +## Starting Services and Creating Characteristics + +When a service is created successfully, an `ESP_GATTS_CREATE_EVT` event managed by the profile GATT handler is triggered, and can be used to start the service and add characteristics to the service. For the case of Profile A, the service is started and the characteristics are added as follows: + +```c +case ESP_GATTS_CREATE_EVT: + ESP_LOGI(GATTS_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A; + + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); + a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret = + esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, + &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + a_property, + &gatts_demo_char1_val, + NULL); + if (add_char_ret){ + ESP_LOGE(GATTS_TAG, "add char failed, error code =%x",add_char_ret); + } + break; +``` + +First, the service handle generated by the BLE stack is stored in the profile table, which will be used later by the application layer to refer to this service. Then, the UUID of the characteristic and its UUID length are set. The length of the characteristic UUID is again 16 bits. The service is started using the `esp_ble_gatts_start_service()` function with the service handle previously generated. An `ESP_GATTS_START_EVT` event, which is used to print information, is triggered. The characteristic is added to the service by the `esp_ble_gatts_start_service()` function in conjunction with the characteristics permissions and properties. In this example, the characteristics in both profiles are set up as follows: + +Permissions: + +* `ESP_GATT_PERM_READ`: To read characteristic value is permitted +* `ESP_GATT_PERM_WRITE`: To write characteristic value is permitted + +Properties: + +* `ESP_GATT_CHAR_PROP_BIT_READ`: Characteristic can be read +* `ESP_GATT_CHAR_PROP_BIT_WRITE`: Characteristic can be written +* `ESP_GATT_CHAR_PROP_BIT_NOTIFY`: Characteristic can notify value changes + +It might seem redundant to have both permissions and properties for read and write. However, the read and write properties of an attribute are information that is shown to the client in order to let the client know if the server accepts read and write requests. In that sense, the properties serve as a hint to the client to properly access the server resources. On the other hand, permissions are the authorization that is given to the client to either read or write that attribute. For example, if a client tries to write an attribute for which it does not have the write permission, the server will reject that request, even if the write property is set. + +In addition, this example gives an initial value to the characteristic represented by `gatts_demo_char1_val`. The initial value is defined as follows: + +```c +esp_attr_value_t gatts_demo_char1_val = +{ + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(char1_str), + .attr_value = char1_str, +}; +``` +Where `char1_str` is dummy data: + +```c +uint8_t char1_str[] = {0x11,0x22,0x33}; +``` + +and the characteristic length is defined as: + +```c +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 +``` + +The characteristic initial value must be a non-null object and the characteristic length must always be greater than zero, otherwise the stack will return an error. + +Finally, the characteristic is configured in a way that it is required to send a response manually every time the characteristic is read or written, instead of letting the stack auto respond. This is configured by setting the last parameter of the `esp_ble_gatts_add_char()` function, representing the attribute response control parameter, to `ESP_GATT_RSP_BY_APP` or NULL. + +## Creating the Characteristic Descriptor + +Adding a characteristic to a service triggers an `ESP_GATTS_ADD_CHAR_EVT` event, which returns the handle generated by the stack for the characteristic just added. The event includes the following parameters: + +```c +esp_gatt_status_t status; /*!< Operation status */ +uint16_t attr_handle; /*!< Characteristic attribute handle */ +uint16_t service_handle; /*!< Service attribute handle */ +esp_bt_uuid_t char_uuid; /*!< Characteristic uuid */ +``` + +The attribute handle returned by the event is stored in the profile table and the characteristic descriptor length and UUID are set as well. The characteristic length and value are read using the `esp_ble_gatts_get_attr_value()` function, and then printed for information purposes. Finally, the characteristic description is added using the `esp_ble_gatts_add_char_descr()` function. The parameters used are the service handle, the descriptor UUID, write and read permissions, an initial value and the auto response setting. The initial value for the characteristic descriptor can be a NULL pointer and the auto response parameter is set to NULL as well, which means that requests that require responses have to be replied manually. + +```c + case ESP_GATTS_ADD_CHAR_EVT: { + uint16_t length = 0; + const uint8_t *prf_char; + + ESP_LOGI(GATTS_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_err_t get_attr_ret = esp_ble_gatts_get_attr_value(param->add_char.attr_handle, &length, &prf_char); + if (get_attr_ret == ESP_FAIL){ + ESP_LOGE(GATTS_TAG, "ILLEGAL HANDLE"); + } + ESP_LOGI(GATTS_TAG, "the gatts demo char length = %x\n", length); + for(int i = 0; i < length; i++){ + ESP_LOGI(GATTS_TAG, "prf_char[%x] = %x\n",i,prf_char[i]); + } + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr( + gl_profile_tab[PROFILE_A_APP_ID].service_handle, + &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + NULL,NULL); + if (add_descr_ret){ + ESP_LOGE(GATTS_TAG, "add char descr failed, error code = %x", add_descr_ret); + } + break; + } +``` + +Once the descriptor is added, the `ESP_GATTS_ADD_CHAR_DESCR_EVT` event is triggered, which in this example is used to print an information message. + +```c + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + ESP_LOGI(GATTS_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, + param->add_char.service_handle); + break; +``` +This procedure is repeated in the Profile B event handler, in order to create the services and characteristics for that profile. + +## Connection Event + +An `ESP_GATTS_CONNECT_EVT` is triggered when a client has connected to the GATT server. This event is used to update the connection parameters, such as latency, minimum connection interval, maximum connection interval and time out. The connection parameters are stored into an `esp_ble_conn_update_params_t` structure, and then passed to the `esp_ble_gap_update_conn_params()` function. The update connection parameters procedure needs to be done only once, therefore the Profile B connection event handler does not include the `esp_ble_gap_update_conn_params()` function. Finally, the connection ID returned by the event is stored in the profile table. + +Profile A connection event: + +```c +case ESP_GATTS_CONNECT_EVT: { + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + /* For the IOS system, please reference the apple official documents about the ble connection parameters restrictions. */ + conn_params.latency = 0; + conn_params.max_int = 0x30; // max_int = 0x30*1.25ms = 40ms + conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = 400; // timeout = 400*10ms = 4000ms + ESP_LOGI(GATTS_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d", + param->connect.conn_id, + param->connect.remote_bda[0], + param->connect.remote_bda[1], + param->connect.remote_bda[2], + param->connect.remote_bda[3], + param->connect.remote_bda[4], + param->connect.remote_bda[5], + param->connect.is_connected); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + //start sent the update connection parameters to the peer device. + esp_ble_gap_update_conn_params(&conn_params); + break; + } +``` + +Profile B connection event: + +```c +case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(GATTS_TAG, "CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:, is_conn %d\n", + param->connect.conn_id, + param->connect.remote_bda[0], + param->connect.remote_bda[1], + param->connect.remote_bda[2], + param->connect.remote_bda[3], + param->connect.remote_bda[4], + param->connect.remote_bda[5], + param->connect.is_connected); + gl_profile_tab[PROFILE_B_APP_ID].conn_id = param->connect.conn_id; + break; +``` + +The `esp_ble_gap_update_conn_params()` function triggers a GAP event `ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT`, which is used to print the connection information: + +```c + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTS_TAG, "update connection params status = %d, min_int = %d, max_int = %d, + conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; +``` + +## Managing Read Events + +Now that the services and characteristics are created and started, the program can receive read and write events. The read operations are represented by the `ESP_GATTS_READ_EVT` event, which has the following parameters: + +```c +uint16_t conn_id; /*!< Connection id */ +uint32_t trans_id; /*!< Transfer id */ +esp_bd_addr_t bda; /*!< The bluetooth device address which been read */ +uint16_t handle; /*!< The attribute handle */ +uint16_t offset; /*!< Offset of the value, if the value is too long */ +bool is_long; /*!< The value is too long or not */ +bool need_rsp; /*!< The read operation need to do response */ +``` + +In this example, a response is constructed with dummy data and sent back to the host using the same handle given by the event. In addition to the response, the GATT interface, the connection ID and the transfer ID are also included as parameters in the `esp_ble_gatts_send_response()` function. This function is necessary if the auto response byte is set to NULL when creating the characteristic or descriptor. + +```c +case ESP_GATTS_READ_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", + param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, + param->read.conn_id, + param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } +``` + +## Managing Write Events + +The write events are represented by the `ESP_GATTS_WRITE_EVT` event, which has the following parameters: + +```c +uint16_t conn_id; /*!< Connection id */ +uint32_t trans_id; /*!< Transfer id */ +esp_bd_addr_t bda; /*!< The bluetooth device address which been written */ +uint16_t handle; /*!< The attribute handle */ +uint16_t offset; /*!< Offset of the value, if the value is too long */ +bool need_rsp; /*!< The write operation need to do response */ +bool is_prep; /*!< This write operation is prepare write */ +uint16_t len; /*!< The write attribute value length */ +uint8_t *value; /*!< The write attribute value */ +``` + +There are two types of write events implemented in this example, write characteristic value and write long characteristic value. The first type of write is used when the characteristic value can fit in one Attribute Protocol Maximum Transmission Unit (ATT MTU), which is usually 23 bytes long. The second type is used when the attribute to write is longer than what can be sent in one single ATT message by dividing the data into multiple chunks using Prepare Write Responses, after which an Executive Write Request is used to confirm or cancel the complete write request. This behavior is defined in the [Bluetooth Specification Version 4.2](https://www.bluetooth.com/specifications/bluetooth-core-specification), Vol 3, Part G, section 4.9. The write long characteristic message flow is shown in the figure below. + +When a write event is triggered, this example prints logging messages, and then executes the `example_write_event_env()` function. + +```c +case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep){ + ESP_LOGI(GATTS_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(GATTS_TAG, param->write.value, param->write.len); + if (gl_profile_tab[PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ + uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ + ESP_LOGI(GATTS_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) + { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, + gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(notify_data), + notify_data, false); + } + }else if (descr_value == 0x0002){ + if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ + ESP_LOGI(GATTS_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i % 0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, + gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(indicate_data), + indicate_data, true); + } + } + else if (descr_value == 0x0000){ + ESP_LOGI(GATTS_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(GATTS_TAG, "unknown value"); + } + } + } + example_write_event_env(gatts_if, &a_prepare_write_env, param); + break; +} +``` + +
Message flow for Write Long Characteristic
+ +The `example_write_event_env()` function contains the logic for the write long characteristic procedure: + +```c +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ + esp_gatt_status_t status = ESP_GATT_OK; + if (param->write.need_rsp){ + if (param->write.is_prep){ + if (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(GATTS_TAG, "Gatt_server prep no mem\n"); + status = ESP_GATT_NO_RESOURCES; + } + } else { + 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; + } + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + 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(GATTS_TAG, "Send response error\n"); + } + free(gatt_rsp); + 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; + + }else{ + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); + } + } +} +``` + +When the client sends a Write Request or a Prepare Write Request, the server shall respond. However, if the client sends a Write Without Response command, the server does not need to reply back a response. This is checked in the write procedure by examining the value of the `write.need_rsp parameter`. If a response is needed, the procedure continues doing the response preparation, if not present, the client does not need a response and therefore the procedure is ended. + +```c +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, + esp_ble_gatts_cb_param_t *param){ + esp_gatt_status_t status = ESP_GATT_OK; + if (param->write.need_rsp){ +… +``` + +The function then checks if the Prepare Write Request parameter represented by the `write.is_prep` is set, which means that the client is requesting a Write Long Characteristic. If present, the procedure continues with the preparation of multiple write responses, if not present, then the server simply sends a single write response back. + +```c +… +if (param->write.is_prep){ +… +}else{ + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); +} +… +``` + +To handle long characteristic write, a prepare buffer structure is defined and instantiated: + +```c +typedef struct { + uint8_t *prepare_buf; + int prepare_len; +} prepare_type_env_t; + +static prepare_type_env_t a_prepare_write_env; +static prepare_type_env_t b_prepare_write_env; +``` +In order to use the prepare buffer, some memory space is allocated for it. In case the allocation fails due to a lack of memory, an error is printed: + +```c +if (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(GATTS_TAG, "Gatt_server prep no mem\n"); + status = ESP_GATT_NO_RESOURCES; + } +} +``` + +If the buffer is not NULL, which means initialization completed, the procedure then checks if the offset and message length of the incoming write fit the buffer. + +```c +else { + 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; + } +} +``` + +The procedure now prepares the response of type `esp_gatt_rsp_t` to be sent back to the client. The response it constructed using the same parameters of the write request, such as length, handle and offset. In addition, the GATT authentication type needed to write to this characteristic is set with `ESP_GATT_AUTH_REQ_NONE`, which means that the client can write to this characteristic without needing to authenticate first. Once the response is sent, the memory allocated for its use is freed. + +```c +esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); +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(GATTS_TAG, "Send response error\n"); +} +free(gatt_rsp); +if (status != ESP_GATT_OK){ + return; +} +``` +Finally, the incoming data is copied to the buffer created and its length is incremented by the offset: + +```c +memcpy(prepare_write_env->prepare_buf + param->write.offset, + param->write.value, + param->write.len); +prepare_write_env->prepare_len += param->write.len; +``` +The client finishes the long write sequence by sending an Executive Write Request. This command triggers an `ESP_GATTS_EXEC_WRITE_EVT` event. The server handles this event by sending a response and executing the `example_exec_write_event_env()` function: + +```c +case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(GATTS_TAG,"ESP_GATTS_EXEC_WRITE_EVT"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&a_prepare_write_env, param); + break; +``` +Let’s take a look at the Executive Write function: + +```c +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){ + esp_log_buffer_hex(GATTS_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + } + else{ + ESP_LOGI(GATTS_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; +} +``` + +The executive write is used to either confirm or cancel the write procedure done before, by the Long Characteristic Write procedure. In order to do this, the function checks for the `exec_write_flag` in the parameters received with the event. If the flag equals the execute flag represented by `exec_write_flag`, the write is confirmed and the buffer is printed in the log; if not, then it means the write is canceled and all the data that has been written is deleted. + +```c +if (param->exec_write.exec_write_flag == ESP_GATT_PREP_WRITE_EXEC){ + esp_log_buffer_hex(GATTS_TAG, + prepare_write_env->prepare_buf, + prepare_write_env->prepare_len); + } +else{ + ESP_LOGI(GATTS_TAG,"ESP_GATT_PREP_WRITE_CANCEL"); + } +``` +Finally, the buffer structure that was created to store the chunks of data from the long write operation is freed and its pointer is set to NULL to leave it ready for the next long write procedure. + +```c +if (prepare_write_env->prepare_buf) { + free(prepare_write_env->prepare_buf); + prepare_write_env->prepare_buf = NULL; +} +prepare_write_env->prepare_len = 0; +``` + +## Conclusion +In this document, we have gone through the GATT SERVER example code describing each section. The application is designed around the concept of Application Profiles. In addition, the procedures that this example uses to register event handlers are explained. The events follow a sequence of configuration steps, such as defining advertising parameters, updating connection parameters and creating services and characteristics. Finally, the way in which read and write events are handled, including Write Long characteristics by dividing the write into chunks that can fit the Attribute Protocol message is explained. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/image/GATT_Server_Figure_1.png b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/image/GATT_Server_Figure_1.png new file mode 100644 index 00000000..a5838cfc Binary files /dev/null and b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/image/GATT_Server_Figure_1.png differ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/image/GATT_Server_Figure_2.png b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/image/GATT_Server_Figure_2.png new file mode 100644 index 00000000..65fa0ecc Binary files /dev/null and b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server/tutorial/image/GATT_Server_Figure_2.png differ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/CMakeLists.txt new file mode 100644 index 00000000..d6dc6a41 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gatt_server_service_table_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/Makefile new file mode 100644 index 00000000..2ff9c6ea --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/Makefile @@ -0,0 +1,11 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := gatt_server_service_table_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/README.md new file mode 100644 index 00000000..7467053c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/README.md @@ -0,0 +1,9 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gatt Server Service Table Demo +=============================================== + +This demo shows how to create a GATT service with an attribute table defined in one place. Provided API releases the user from adding attributes one by one as implemented in BLUEDROID. A demo of the other method to create the attribute table is presented in [gatt_server_demo](../gatt_server). + +Please check the [tutorial](tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md) for more information about this example. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/CMakeLists.txt new file mode 100644 index 00000000..fbe55357 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "gatts_table_creat_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c new file mode 100644 index 00000000..b6a9abff --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.c @@ -0,0 +1,576 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This demo showcases creating a GATT database using a predefined attribute table. +* It acts as a GATT server and can send adv data, be connected by client. +* Run the gatt_client demo, the client demo will automatically connect to the gatt_server_service_table demo. +* Client demo will enable GATT server's notify after connection. The two devices will then exchange +* data. +* +****************************************************************************/ + + + #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 "gatts_table_creat_demo.h" +#include "esp_gatt_common_api.h" + +#define GATTS_TABLE_TAG "GATTS_TABLE_DEMO" + +#define PROFILE_NUM 1 +#define PROFILE_APP_IDX 0 +#define ESP_APP_ID 0x55 +#define SAMPLE_DEVICE_NAME "ESP_GATTS_DEMO" +#define SVC_INST_ID 0 + +/* The max length of characteristic value. When the GATT client performs a write or prepare write operation, +* the data length must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX. +*/ +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 500 +#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 heart_rate_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, 0x01, 0x06, + /* tx power*/ + 0x02, 0x0a, 0xeb, + /* service uuid */ + 0x03, 0x03, 0xFF, 0x00, + /* device name */ + 0x0f, 0x09, 'E', 'S', 'P', '_', 'G', 'A', 'T', 'T', 'S', '_', 'D','E', 'M', 'O' +}; +static uint8_t raw_scan_rsp_data[] = { + /* flags */ + 0x02, 0x01, 0x06, + /* tx power */ + 0x02, 0x0a, 0xeb, + /* service uuid */ + 0x03, 0x03, 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 = 0x0006, //slave connection min interval, Time = min_interval * 1.25 msec + .max_interval = 0x0010, //slave connection max interval, Time = max_interval * 1.25 msec + .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 = 0x0006, + .max_interval = 0x0010, + .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 = sizeof(service_uuid), + .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 = 0x20, + .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 GATTS_CHAR_UUID_TEST_A = 0xFF01; +static const uint16_t GATTS_CHAR_UUID_TEST_B = 0xFF02; +static const uint16_t GATTS_CHAR_UUID_TEST_C = 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 uint8_t char_prop_read = ESP_GATT_CHAR_PROP_BIT_READ; +static const uint8_t char_prop_write = ESP_GATT_CHAR_PROP_BIT_WRITE; +static const uint8_t char_prop_read_write_notify = ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_NOTIFY; +static const uint8_t heart_measurement_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_notify}}, + + /* Characteristic Value */ + [IDX_CHAR_VAL_A] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_A, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}}, + + /* Client Characteristic Configuration Descriptor */ + [IDX_CHAR_CFG_A] = + {{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(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}}, + + /* 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}}, + + /* Characteristic Value */ + [IDX_CHAR_VAL_B] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_B, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}}, + + /* 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_write}}, + + /* Characteristic Value */ + [IDX_CHAR_VAL_C] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&GATTS_CHAR_UUID_TEST_C, ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + GATTS_DEMO_CHAR_VAL_LEN_MAX, sizeof(char_value), (uint8_t *)char_value}}, + +}; + +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(GATTS_TABLE_TAG, "advertising start failed"); + }else{ + ESP_LOGI(GATTS_TABLE_TAG, "advertising start successfully"); + } + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(GATTS_TABLE_TAG, "Advertising stop failed"); + } + else { + ESP_LOGI(GATTS_TABLE_TAG, "Stop adv successfully\n"); + } + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + 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) +{ + ESP_LOGI(GATTS_TABLE_TAG, "prepare write, handle = %d, value len = %d", param->write.handle, param->write.len); + esp_gatt_status_t status = ESP_GATT_OK; + if (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(GATTS_TABLE_TAG, "%s, Gatt_server prep no mem", __func__); + status = ESP_GATT_NO_RESOURCES; + } + } else { + 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; + } + } + /*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(GATTS_TABLE_TAG, "Send response error"); + } + free(gatt_rsp); + }else{ + ESP_LOGE(GATTS_TABLE_TAG, "%s, malloc failed", __func__); + } + } + 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; + +} + +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){ + esp_log_buffer_hex(GATTS_TABLE_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + }else{ + ESP_LOGI(GATTS_TABLE_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(GATTS_TABLE_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(GATTS_TABLE_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(GATTS_TABLE_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(GATTS_TABLE_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(GATTS_TABLE_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(GATTS_TABLE_TAG, "create attr table failed, error code = %x", create_attr_ret); + } + } + break; + case ESP_GATTS_READ_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_READ_EVT"); + break; + case ESP_GATTS_WRITE_EVT: + if (!param->write.is_prep){ + // the data length of gattc write must be less than GATTS_DEMO_CHAR_VAL_LEN_MAX. + ESP_LOGI(GATTS_TABLE_TAG, "GATT_WRITE_EVT, handle = %d, value len = %d, value :", param->write.handle, param->write.len); + esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len); + if (heart_rate_handle_table[IDX_CHAR_CFG_A] == param->write.handle && param->write.len == 2){ + uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + ESP_LOGI(GATTS_TABLE_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) + { + notify_data[i] = i % 0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A], + sizeof(notify_data), notify_data, false); + }else if (descr_value == 0x0002){ + ESP_LOGI(GATTS_TABLE_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i % 0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, heart_rate_handle_table[IDX_CHAR_VAL_A], + sizeof(indicate_data), indicate_data, true); + } + else if (descr_value == 0x0000){ + ESP_LOGI(GATTS_TABLE_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(GATTS_TABLE_TAG, "unknown descr value"); + esp_log_buffer_hex(GATTS_TABLE_TAG, param->write.value, param->write.len); + } + + } + /* 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_DEMO_CHAR_VAL_LEN_MAX. + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_EXEC_WRITE_EVT"); + example_exec_write_event_env(&prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONF_EVT, status = %d, attr_handle %d", param->conf.status, param->conf.handle); + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "SERVICE_START_EVT, status %d, service_handle %d", param->start.status, param->start.service_handle); + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_CONNECT_EVT, conn_id = %d", param->connect.conn_id); + esp_log_buffer_hex(GATTS_TABLE_TAG, param->connect.remote_bda, 6); + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + /* For the iOS system, please refer to Apple official documents about the BLE connection parameters restrictions. */ + conn_params.latency = 0; + conn_params.max_int = 0x20; // max_int = 0x20*1.25ms = 40ms + conn_params.min_int = 0x10; // min_int = 0x10*1.25ms = 20ms + conn_params.timeout = 400; // timeout = 400*10ms = 4000ms + //start sent the update connection parameters to the peer device. + esp_ble_gap_update_conn_params(&conn_params); + break; + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "ESP_GATTS_DISCONNECT_EVT, reason = 0x%x", 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(GATTS_TABLE_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(GATTS_TABLE_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(GATTS_TABLE_TAG, "create attribute table successfully, the number handle = %d\n",param->add_attr_tab.num_handle); + memcpy(heart_rate_handle_table, param->add_attr_tab.handles, sizeof(heart_rate_handle_table)); + esp_ble_gatts_start_service(heart_rate_handle_table[IDX_SVC]); + } + break; + } + case ESP_GATTS_STOP_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + case ESP_GATTS_UNREG_EVT: + case ESP_GATTS_DELETE_EVT: + 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(GATTS_TABLE_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 || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + 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(GATTS_TABLE_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(GATTS_TABLE_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed: %s", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TABLE_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(GATTS_TABLE_TAG, "gatts register error, error code = %x", ret); + return; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret){ + ESP_LOGE(GATTS_TABLE_TAG, "gap register error, error code = %x", ret); + return; + } + + ret = esp_ble_gatts_app_register(ESP_APP_ID); + if (ret){ + ESP_LOGE(GATTS_TABLE_TAG, "gatts app register error, error code = %x", ret); + return; + } + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret){ + ESP_LOGE(GATTS_TABLE_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.h new file mode 100644 index 00000000..20379cf0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/main/gatts_table_creat_demo.h @@ -0,0 +1,30 @@ +/* + 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 +#include +#include + + +/* 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_C, + IDX_CHAR_VAL_C, + + HRS_IDX_NB, +}; diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults new file mode 100644 index 00000000..97761eb9 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/sdkconfig.defaults @@ -0,0 +1,17 @@ +# Override some defaults so BT stack is enabled +# in this example + +# +# BT config +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n + +# +# ESP32-specific config +# +CONFIG_ESP32_ENABLE_STACK_BT=y +# CONFIG_ESP32_ENABLE_STACK_NONE is not set +CONFIG_MEMMAP_BT=y diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md new file mode 100644 index 00000000..6c47d918 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/tutorial/Gatt_Server_Service_Table_Example_Walkthrough.md @@ -0,0 +1,496 @@ +# GATT Server Service Table Example Walkthrough + +## Introduction + +This document presents a walkthrough of the GATT Server Service Table example code for the ESP32. This example implements a Bluetooth Low Energy (BLE) Generic Attribute (GATT) Server using a table-like data structure to define the server services and characteristics such as the one shown in the figure below Therefore, it demonstrates a practical way to define the server functionality in one place instead of adding services and characteristics one by one. + +This example implements the *Heart Rate Profile* as defined by the [Traditional Profile Specifications](https://www.bluetooth.com/specifications/profiles-overview). + +
Table-like data structure representing the Heart Rate Service
+ +## Includes + +Let’s start by taking a look at the included headers in the [gatts_table_creat_demo.c](../main/gatts_table_creat_demo.c) file: + +```c +#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 "bt.h" +#include "bta_api.h" + +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_bt_main.h" +#include “gatts_table_creat_demo.h" +``` +These includes are required for the *FreeRTOS* and underlaying system components to run, including logging functionality and a library to store data in non-volatile flash memory. We are interested in ``bt.h``, ``esp_bt_main.h``, ``esp_gap_ble_api.h`` and ``esp_gatts_api.h`` which expose the BLE APIs required to implement this example. + +* ``bt.h``: implements BT controller and VHCI configuration procedures from the host side. +* ``esp_bt_main.h``: implements initialization and enabling of the Bluedroid stack. +* ``esp_gap_ble_api.h``: implements GAP configuration such as advertising and connection parameters. +* ``esp_gatts_api.h``: implements GATT Server configuration such as creating services and characteristics. + +## Service Table + +The header file [gatts_table_creat_demo.h](../main/gatts_table_creat_demo.h) is where an enumeration of the services and characteristics is created: + +```c +enum +{ + HRS_IDX_SVC, + + HRS_IDX_HR_MEAS_CHAR, + HRS_IDX_HR_MEAS_VAL, + HRS_IDX_HR_MEAS_NTF_CFG, + + HRS_IDX_BOBY_SENSOR_LOC_CHAR, + HRS_IDX_BOBY_SENSOR_LOC_VAL, + + HRS_IDX_HR_CTNL_PT_CHAR, + HRS_IDX_HR_CTNL_PT_VAL, + + HRS_IDX_NB, +}; +``` +The enumeration elements are set up in the same order as the Heart Rate Profile attributes, starting with the service followed by the characteristics of that service. In addition, the Heart Rate Measurement characteristic has a Client Characteristic Configuration (CCC) descriptor which is an additional attribute that describes if the characteristic has notifications enabled. The enumeration index can be used to identify each element later when creating the actual attributes table. In summary, the elements are described as follows: + +* ``HRS_IDX_SVC``: Heart Rate Service index +* ``HRS_IDX_HR_MEAS_CHAR``: Heart Rate Measurement characteristic index +* ``HRS_IDX_HR_MEAS_VAL``: Heart Rate Measurement characteristic value index +* ``HRS_IDX_HR_MEAS_NTF_CFG``: Heart Rate Measurement notifications configuration (CCC) index +* ``HRS_IDX_BOBY_SENSOR_LOC_CHAR``: Heart Rate Body Sensor Location characteristic index +* ``HRS_IDX_BOBY_SENSOR_LOC_VAL``: Heart Rate Body Sensor Location characteristic value index +* ``HRS_IDX_HR_CTNL_PT_CHAR``: Heart Rate Control Point characteristic index +* ``HRS_IDX_HR_CTNL_PT_VAL``: Heart Rate Control Point characteristic value index +* ``HRS_IDX_NB``: Number of table elements. + +## Main Entry Point + +The entry point to this example is the ``app_main()`` function: + +```c +void app_main() +{ + esp_err_t ret; + + // Initialize NVS. + 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 ); + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + ret = esp_bt_controller_init(&bt_cfg); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable controller failed\n", __func__); + return; + } + + ESP_LOGI(GATTS_TABLE_TAG, "%s init bluetooth\n", __func__); + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s init bluetooth failed\n", __func__); + return; + } + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTS_TABLE_TAG, "%s enable bluetooth failed\n", __func__); + return; + } + + esp_ble_gatts_register_callback(gatts_event_handler); + esp_ble_gap_register_callback(gap_event_handler); + esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID); + return; +} +``` + +The main function starts by initializing the non-volatile storage library in order to be able to save parameters in flash memory. + +```c +ret = nvs_flash_init(); +``` + +## BT Controller and Stack Initialization + +See this section in [GATT Server Example Walkthrough](../../gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md). + + +## Application Profiles + +This example implements one Application Profile for the Heart Rate Service. An Application Profile is a way to group functionality which is designed to be used by one client application, for example one smartphone mobile app. In this way, different types of profiles can be accommodated in one server. The Application Profile ID, which is an user-assigned number to identify each profile, is used to register the profile in the stack, in this example the ID is 0x55. + +```c +#define HEART_PROFILE_NUM 1 +#define HEART_PROFILE_APP_IDX 0 +#define ESP_HEART_RATE_APP_ID 0x55 +``` + +The profiles are stored in the ``heart_rate_profile_tab`` array. Since there is only one profile in this example, one element is stored in the array with index zero as defined by the ``HEART_PROFILE_APP_IDX``. Additionally, the profile event handler callback function is initialized. Each application on the GATT server uses a different interface, represented by the gatts_if parameter. For initialization, this parameter is set to ``ESP_GATT_IF_NONE``, later when the application is registered, the gatts_if parameter is updated with the corresponding interface generated by the stack. + +```c +/* 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[HEART_PROFILE_NUM] = { + [HEART_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 */ + }, + +}; +``` + +The application registration takes place inside ``app_main()`` using the ``esp_ble_gatts_app_register()`` function: + +```c +esp_ble_gatts_app_register(ESP_HEART_RATE_APP_ID); +``` + +## Setting GAP Parameters + +The register application event is the first one that is triggered during the lifetime of the program. This example uses this event to configure advertising parameters upon registration in the profile event handler. The functions used to achieve this are: + +* ``esp_ble_gap_set_device_name()``: used to set the advertised device name. +* ``esp_ble_gap_config_adv_data()``: used to configure standard advertising data. + +The function used to configure standard Bluetooth Specification advertisement parameters is ``esp_ble_gap_config_adv_data()`` which takes a pointer to an ``esp_ble_adv_data_t`` structure. The ``esp_ble_adv_data_t`` data structure for advertising data has the following definition: + +```c +typedef struct { + bool set_scan_rsp; /*!< Set this advertising data as scan response or not*/ + bool include_name; /*!< Advertising data include device name or not */ + bool include_txpower; /*!< Advertising data include TX power */ + int min_interval; /*!< Advertising data show slave preferred connection min interval */ + int max_interval; /*!< Advertising data show slave preferred connection max interval */ + int appearance; /*!< External appearance of device */ + uint16_t manufacturer_len; /*!< Manufacturer data length */ + uint8_t *p_manufacturer_data; /*!< Manufacturer data point */ + uint16_t service_data_len; /*!< Service data length */ + uint8_t *p_service_data; /*!< Service data point */ + uint16_t service_uuid_len; /*!< Service uuid length */ + uint8_t *p_service_uuid; /*!< Service uuid array point */ + uint8_t flag; /*!< Advertising flag of discovery mode, see BLE_ADV_DATA_FLAG detail */ +} esp_ble_adv_data_t; +``` + +In this example, the structure is initialized as follows: + +```c +static esp_ble_adv_data_t heart_rate_adv_config = { + .set_scan_rsp = false, + .include_name = true, + .include_txpower = true, + .min_interval = 0x0006, + .max_interval = 0x0010, + .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 = sizeof(heart_rate_service_uuid), + .p_service_uuid = heart_rate_service_uuid, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; +``` + +The minimum and maximum slave preferred connection intervals are set in units of 1.25 ms. In this example, the minimum slave preferred connection interval is defined as 0x0006 * 1.25 ms = 7.5 ms and the maximum slave preferred connection interval is initialized as 0x0010 * 1.25 ms = 20 ms. + +An advertising payload can be up to 31 bytes of data. It is possible that some of the parameters surpass the 31-byte advertisement packet limit which causes the stack to cut the message and leave some of the parameters out. To solve this, usually the longer parameters are stored in the scan response, which can be configured using the same ``esp_ble_gap_config_adv_data()`` function and an additional esp_ble_adv_data_t type structure with the .set_scan_rsp parameter is set to true. Finally, to set the device name the ``esp_ble_gap_set_device_name()`` function is used. The registering event handler is shown as follows: + +```c +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) +{ + ESP_LOGE(GATTS_TABLE_TAG, "event = %x\n",event); + switch (event) { + case ESP_GATTS_REG_EVT: + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gap_set_device_name(SAMPLE_DEVICE_NAME); + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); + esp_ble_gap_config_adv_data(&heart_rate_adv_config); + ESP_LOGI(GATTS_TABLE_TAG, "%s %d\n", __func__, __LINE__); +… +``` + +## GAP Event Handler + +Once the advertising data have been set, the ``ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT`` is triggered and managed by the GAP event handler. Moreover, an ``ESP_GAP_BLE_SCAN_RSP_DATA_SET_COMPLETE_EVT`` is triggered as well if the scan response is also set. Once the configuration of the advertising and scan response data has been set, the handler can use any of these events to start advertising, which is done using the ``esp_ble_gap_start_advertising()`` function: + +```c +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + ESP_LOGE(GATTS_TABLE_TAG, "GAP_EVT, event %d\n", event); + + switch (event) { + case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&heart_rate_adv_params); + break; + 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(GATTS_TABLE_TAG, "Advertising start failed\n"); + } + break; + default: + break; + } +} +``` + +The function to start advertising takes a structure of type ``esp_ble_adv_params_t`` with the advertising parameters required. + +```c +/// Advertising parameters +typedef struct { + uint16_t adv_int_min; /*!< Minimum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 + Default: N = 0x0800 (1.28 second) + Time = N * 0.625 msec + Time Range: 20 ms to 10.24 sec */ + uint16_t adv_int_max; /*!< Maximum advertising interval for undirected and low duty cycle directed advertising. + Range: 0x0020 to 0x4000 + Default: N = 0x0800 (1.28 second) + Time = N * 0.625 msec + Time Range: 20 ms to 10.24 sec */ + esp_ble_adv_type_t adv_type; /*!< Advertising type */ + esp_ble_addr_type_t own_addr_type; /*!< Owner bluetooth device address type */ + esp_bd_addr_t peer_addr; /*!< Peer device bluetooth device address */ + esp_ble_addr_type_t peer_addr_type; /*!< Peer device bluetooth device address type */ + esp_ble_adv_channel_t channel_map; /*!< Advertising channel map */ + esp_ble_adv_filter_t adv_filter_policy; /*!< Advertising filter policy */ +} esp_ble_adv_params_t; +``` + +Note that ``esp_ble_gap_config_adv_data()`` configures the data that is advertised to the client and takes an ``esp_ble_adv_data_t structure``, while ``esp_ble_gap_start_advertising()`` makes the server to actually start advertising and takes an ``esp_ble_adv_params_t`` structure. The advertising data is the information that is shown to the client, while the advertising parameters are the configuration required by the BLE stack to execute. + +For this example, the advertisement parameters are initialized as follows: + +```c +static esp_ble_adv_params_t heart_rate_adv_params = { + .adv_int_min = 0x20, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + //.peer_addr = + //.peer_addr_type = + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, +}; +``` + +These parameters configure the advertising interval between 20 ms to 40 ms. The advertisement is of type ADV_IND, which is generic, not directed to a particular central device and advertises the server as connectable. The address type is public, uses all channels and allows both scan and connection requests from any central. + +If the advertising started successfully, an ``ESP_GAP_BLE_ADV_START_COMPLETE_EVT`` event is generated which in this example is used to check if the advertising status is indeed advertising or otherwise print an error message. + +```c +… + 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(GATTS_TABLE_TAG, "Advertising start failed\n"); + } + break; +… +``` + +## GATT Event Handlers + +When an Application Profile is registered, an ``ESP_GATTS_REG_EVT`` event is triggered. The parameters of the ``ESP_GATTS_REG_EVT`` are: + +```c +esp_gatt_status_t status; /*!< Operation status */ +uint16_t app_id; /*!< Application id which input in register API */ +``` + +In addition to the previous parameters, the event also contains the GATT interface assigned by the BLE stack. The event is captured by the ``gatts_event_handler()`` which stores the generated interface in the profile table and then forwards it to the corresponding profile event handler. + +```c +static void gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param) +{ + ESP_LOGI(GATTS_TABLE_TAG, "EVT %d, gatts if %d\n", event, gatts_if); + + /* 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[HEART_PROFILE_APP_IDX].gatts_if = gatts_if; + } else { + ESP_LOGI(GATTS_TABLE_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + do { + int idx; + for (idx = 0; idx < HEART_PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + 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); +} +``` + +## Creating Services and Characteristics with the Attribute Table + +The register event is used to create a table of profile attributes by employing the ``esp_ble_gatts_create_attr_tab()`` function. This function takes an argument of type ``esp_gatts_attr_db_t`` which corresponds to a look up table keyed by the enumeration values defined in the header file. + +The ``esp_gatts_attr_db_t`` structure has two members: + +```c +esp_attr_control_t attr_control; /*!< The attribute control type*/ +esp_attr_desc_t att_desc; /*!< The attribute type*/ +``` + +The attr_control is the auto-respond parameter which can be set as ``ESP_GATT_AUTO_RSP`` to allow the BLE stack to take care of responding messages when read or write events arrive. The other option is ``ESP_GATT_RSP_BY_APP`` which allows to manually respond to messages using the ``esp_ble_gatts_send_response()`` function. + +The ``att_desc`` is the attribute description which is made of: + +```c +uint16_t uuid_length; /*!< UUID length */ +uint8_t *uuid_p; /*!< UUID value */ +uint16_t perm; /*!< Attribute permission */ +uint16_t max_length; /*!< Maximum length of the element*/ +uint16_t length; /*!< Current length of the element*/ +uint8_t *value; /*!< Element value array*/ +``` + +For example, the first element of the table in this example is the service attribute: + +```c +[HRS_IDX_SVC] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, + sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}}, +``` + +The initialization values are: + +* ``[HRS_IDX_SVC]``: Named or designated initializer in the enum table. +* ``ESP_GATT_AUTO_RSP``: Auto respond configuration, set to respond automatically by the stack. +* ``ESP_UUID_LEN_16``: UUID length set to 16 bits. +* ``(uint8_t *)&primary_service_uuid``: UUID to identify the service as a primary one (0x2800). +* ``ESP_GATT_PERM_READ``: Read Permission for the service. +* ``sizeof(uint16_t)``: Maximum length of the service UUID (16 bits). +* ``sizeof(heart_rate_svc)``: Current service length set to the size of the variable *heart_rate_svc*, which is 16 bits. +* ``(uint8_t *)&heart_rate_svc``: Service attribute value set to the variable *heart_rate_svc* which contains the Heart Rate Service UUID (0x180D). + +The rest of the attributes is initialized in the same way. Some attributes also have the *NOTIFY* property which is set by ``&char_prop_notify``. The complete table structure is initialized as follows: + +```c +/// Full HRS Database Description - Used to add attributes into the database +static const esp_gatts_attr_db_t heart_rate_gatt_db[HRS_IDX_NB] = +{ + // Heart Rate Service Declaration + [HRS_IDX_SVC] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&primary_service_uuid, ESP_GATT_PERM_READ, + sizeof(uint16_t), sizeof(heart_rate_svc), (uint8_t *)&heart_rate_svc}}, + + // Heart Rate Measurement Characteristic Declaration + [HRS_IDX_HR_MEAS_CHAR] = + {{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}}, + + // Heart Rate Measurement Characteristic Value + [HRS_IDX_HR_MEAS_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_meas_uuid, ESP_GATT_PERM_READ, + HRPS_HT_MEAS_MAX_LEN,0, NULL}}, + + // Heart Rate Measurement Characteristic - Client Characteristic Configuration Descriptor + [HRS_IDX_HR_MEAS_NTF_CFG] = + {{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(heart_measurement_ccc), (uint8_t *)heart_measurement_ccc}}, + + // Body Sensor Location Characteristic Declaration + [HRS_IDX_BOBY_SENSOR_LOC_CHAR] = + {{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}}, + + // Body Sensor Location Characteristic Value + [HRS_IDX_BOBY_SENSOR_LOC_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&body_sensor_location_uuid, ESP_GATT_PERM_READ, + sizeof(uint8_t), sizeof(body_sensor_loc_val), (uint8_t *)body_sensor_loc_val}}, + + // Heart Rate Control Point Characteristic Declaration + [HRS_IDX_HR_CTNL_PT_CHAR] = + {{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}}, + + // Heart Rate Control Point Characteristic Value + [HRS_IDX_HR_CTNL_PT_VAL] = + {{ESP_GATT_AUTO_RSP}, {ESP_UUID_LEN_16, (uint8_t *)&heart_rate_ctrl_point, ESP_GATT_PERM_WRITE|ESP_GATT_PERM_READ, + sizeof(uint8_t), sizeof(heart_ctrl_point), (uint8_t *)heart_ctrl_point}}, +}; +``` + +## Starting the Service +When the attribute table is created, an ``ESP_GATTS_CREAT_ATTR_TAB_EVT`` event is triggered. This event has the following parameters: + +```c +esp_gatt_status_t status; /*!< Operation status */ +esp_bt_uuid_t svc_uuid; /*!< Service uuid type */ +uint16_t num_handle; /*!< The number of the attribute handle to be added to the gatts database */ +uint16_t *handles; /*!< The number to the handles */ +``` + +This example uses this event to print information and to check that the size of the created table equals the number of elements in the enumeration HRS_IDX_NB. If the table is correctly created, the attribute handles are copied into the handle table heart_rate_handle_table and the service is started using the ``esp_ble_gatts_start_service()`` function: + +```c +case ESP_GATTS_CREAT_ATTR_TAB_EVT:{ + ESP_LOGI(GATTS_TABLE_TAG, "The number handle =%x\n",param->add_attr_tab.num_handle); + if (param->add_attr_tab.status != ESP_GATT_OK){ + ESP_LOGE(GATTS_TABLE_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(GATTS_TABLE_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 { + memcpy(heart_rate_handle_table, param->add_attr_tab.handles, sizeof(heart_rate_handle_table)); + esp_ble_gatts_start_service(heart_rate_handle_table[HRS_IDX_SVC]); + } + break; +``` + +The handles stored in the handles pointer of the event parameters are numbers that identify each attribute. The handles can be used to know which characteristic is being read or written to, therefore they can be passed around and to upper layers of the application to handle different actions. + +Finally, the heart_rate_handle_table contains the Application Profile in the form of a structure with information about the attribute parameters as well as GATT interface, connection ID, permissions and application ID. The profile structure is shown as follows, note that not all members are used in this example: + +```c +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; +}; +``` + +## Conclusion +This document explains the work flow of the GATT Server Service Table example code that implements a Heart Rate Profile. This example begins by defining a table of attributes which include all the services and characteristics of the server, then it registers the Application Profile which triggers events that are used to configure GAP parameters and to create the service table. A service table is initialized with all the parameters required for each attribute and the service is started. This example shows a practical way of defining the server attributes by using a table instead of adding characteristic one by one. + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/tutorial/image/Heart_Rate_Service.png b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/tutorial/image/Heart_Rate_Service.png new file mode 100644 index 00000000..108362a3 Binary files /dev/null and b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gatt_server_service_table/tutorial/image/Heart_Rate_Service.png differ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/CMakeLists.txt new file mode 100644 index 00000000..6a7a6b3b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gatt_multi_connect) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/Makefile new file mode 100644 index 00000000..512ac329 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := gatt_multi_connect + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/README.md new file mode 100644 index 00000000..5a65af9a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/README.md @@ -0,0 +1,13 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gatt Client Multi Connection Demo +======================== + +This is the demo of APIs to create a GATT multi-connection client. It can be used to connect to three GATT servers at the same time. + +To test this demo, please run [gatt_server_demo](../gatt_server) to create three GATT server devices, namely ESP_GATTS_DEMO_a, ESP_GATTS_DEMO_b and ESP_GATTS_DEMO_c, `Gatt_client_multi_connection_demo` will connect to these three gatt server demos, and then exchange data. + +The code can be modified to connect to more devices (up to 4 devices by default). If you need to connect to more devices (more than 4 devices), you need to change `BT/BLE MAX ACL CONNECTIONS` in menuconfig. + +Please check the [tutorial](tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md) for more information about this example. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/CMakeLists.txt new file mode 100644 index 00000000..826febe7 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "gattc_multi_connect.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c new file mode 100644 index 00000000..175c0f98 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/main/gattc_multi_connect.c @@ -0,0 +1,960 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This file is for gatt client. It can scan ble device, connect multiple devices, +* The gattc_multi_connect demo can connect three ble slaves at the same time. +* Modify the name of gatt_server demo named ESP_GATTS_DEMO_a, +* ESP_GATTS_DEMO_b and ESP_GATTS_DEMO_c,then run three demos,the gattc_multi_connect demo will connect +* the three gatt_server demos, and then exchange data. +* Of course you can also modify the code to connect more devices, we default to connect +* up to 4 devices, more than 4 you need to modify menuconfig. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" + +#define GATTC_TAG "GATTC_MULTIPLE_DEMO" +#define REMOTE_SERVICE_UUID 0x00FF +#define REMOTE_NOTIFY_CHAR_UUID 0xFF01 + +/* register three profiles, each profile corresponds to one connection, + which makes it easy to handle each connection event */ +#define PROFILE_NUM 3 +#define PROFILE_A_APP_ID 0 +#define PROFILE_B_APP_ID 1 +#define PROFILE_C_APP_ID 2 +#define INVALID_HANDLE 0 + +/* Declare static functions */ +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param); +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); + +static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, +}; + +static esp_bt_uuid_t remote_filter_char_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_NOTIFY_CHAR_UUID,}, +}; + +static esp_bt_uuid_t notify_descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, +}; + +static bool conn_device_a = false; +static bool conn_device_b = false; +static bool conn_device_c = false; + +static bool get_service_a = false; +static bool get_service_b = false; +static bool get_service_c = false; + +static bool Isconnecting = false; +static bool stop_scan_done = false; + +static esp_gattc_char_elem_t *char_elem_result_a = NULL; +static esp_gattc_descr_elem_t *descr_elem_result_a = NULL; +static esp_gattc_char_elem_t *char_elem_result_b = NULL; +static esp_gattc_descr_elem_t *descr_elem_result_b = NULL; +static esp_gattc_char_elem_t *char_elem_result_c = NULL; +static esp_gattc_descr_elem_t *descr_elem_result_c = NULL; + +static const char remote_device_name[3][20] = {"ESP_GATTS_DEMO_a", "ESP_GATTS_DEMO_b", "ESP_GATTS_DEMO_c"}; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; + +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gattc_cb = gattc_profile_a_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [PROFILE_B_APP_ID] = { + .gattc_cb = gattc_profile_b_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [PROFILE_C_APP_ID] = { + .gattc_cb = gattc_profile_c_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + +}; + +static void start_scan(void) +{ + stop_scan_done = false; + Isconnecting = false; + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); +} +static void gattc_profile_a_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret){ + ESP_LOGE(GATTC_TAG, "set scan params error, error code = %x", scan_ret); + } + break; + /* one device connect successfully, all profiles callback function will get the ESP_GATTC_CONNECT_EVT, + so must compare the mac address to check which device is connected, so it is a good choice to use ESP_GATTC_OPEN_EVT. */ + case ESP_GATTC_CONNECT_EVT: + break; + case ESP_GATTC_OPEN_EVT: + if (p_data->open.status != ESP_GATT_OK){ + //open failed, ignore the first device, connect the second device + ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); + conn_device_a = false; + //start_scan(); + break; + } + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, p_data->open.remote_bda, 6); + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"Config mtu failed"); + } + ESP_LOGI(GATTC_TAG, "Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary); + ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id); + if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16); + get_service_a = true; + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle = p_data->search_res.end_handle; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + if (get_service_a){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0) { + char_elem_result_a = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result_a){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else { + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result_a, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result_a[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_A_APP_ID].char_handle = char_elem_result_a[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, char_elem_result_a[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result_a); + }else { + ESP_LOGE(GATTC_TAG, "no char found"); + } + } + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "reg notify failed, error status =%x", p_data->reg_for_notify.status); + break; + } + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_A_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_A_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0){ + descr_elem_result_a = (esp_gattc_descr_elem_t *)malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result_a){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result_a, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); + } + + /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ + if (count > 0 && descr_elem_result_a[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result_a[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + descr_elem_result_a[0].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } + + /* free descr_elem_result */ + free(descr_elem_result_a); + } + } + else{ + ESP_LOGE(GATTC_TAG, "decsr not found"); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, Receive notify value:"); + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write descr success"); + uint8_t write_char_data[35]; + for (int i = 0; i < sizeof(write_char_data); ++i) + { + write_char_data[i] = i % 256; + } + esp_ble_gattc_write_char( gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(write_char_data), + write_char_data, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status); + }else{ + ESP_LOGI(GATTC_TAG, "write char success"); + } + start_scan(); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3], + (bda[4] << 8) + bda[5]); + break; + } + case ESP_GATTC_DISCONNECT_EVT: + //Start scanning again + start_scan(); + if (memcmp(p_data->disconnect.remote_bda, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, 6) == 0){ + ESP_LOGI(GATTC_TAG, "device a disconnect"); + conn_device_a = false; + get_service_a = false; + } + break; + default: + break; + } +} + +static void gattc_profile_b_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + break; + case ESP_GATTC_CONNECT_EVT: + break; + case ESP_GATTC_OPEN_EVT: + if (p_data->open.status != ESP_GATT_OK){ + //open failed, ignore the second device, connect the third device + ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); + conn_device_b = false; + //start_scan(); + break; + } + memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, p_data->open.remote_bda, 6); + gl_profile_tab[PROFILE_B_APP_ID].conn_id = p_data->open.conn_id; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"Config mtu failed"); + } + ESP_LOGI(GATTC_TAG, "Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary); + ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id); + if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16); + get_service_b = true; + gl_profile_tab[PROFILE_B_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_B_APP_ID].service_end_handle = p_data->search_res.end_handle; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + if (get_service_b){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_B_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_B_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + + if (count > 0){ + char_elem_result_b = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result_b){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else{ + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_B_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_B_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result_b, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result_b[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_B_APP_ID].char_handle = char_elem_result_b[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, char_elem_result_b[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result_b); + }else{ + ESP_LOGE(GATTC_TAG, "no char found"); + } + } + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "reg notify failed, error status =%x", p_data->reg_for_notify.status); + break; + } + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, + gl_profile_tab[PROFILE_B_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_B_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_B_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_B_APP_ID].char_handle, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0){ + descr_elem_result_b = (esp_gattc_descr_elem_t *)malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result_b){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, + gl_profile_tab[PROFILE_B_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result_b, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); + } + + /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ + if (count > 0 && descr_elem_result_b[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result_b[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_B_APP_ID].conn_id, + descr_elem_result_b[0].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } + + /* free descr_elem_result */ + free(descr_elem_result_b); + } + } + else{ + ESP_LOGE(GATTC_TAG, "decsr not found"); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, Receive notify value:"); + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write descr success"); + uint8_t write_char_data[35]; + for (int i = 0; i < sizeof(write_char_data); ++i) + { + write_char_data[i] = i % 256; + } + esp_ble_gattc_write_char( gattc_if, + gl_profile_tab[PROFILE_B_APP_ID].conn_id, + gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(write_char_data), + write_char_data, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "Write char failed, error status = %x", p_data->write.status); + }else{ + ESP_LOGI(GATTC_TAG, "Write char success"); + } + start_scan(); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3], + (bda[4] << 8) + bda[5]); + break; + } + case ESP_GATTC_DISCONNECT_EVT: + if (memcmp(p_data->disconnect.remote_bda, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, 6) == 0){ + ESP_LOGI(GATTC_TAG, "device b disconnect"); + conn_device_b = false; + get_service_b = false; + } + break; + default: + break; + } +} + +static void gattc_profile_c_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(GATTC_TAG, "REG_EVT"); + break; + case ESP_GATTC_CONNECT_EVT: + break; + case ESP_GATTC_OPEN_EVT: + if (p_data->open.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); + conn_device_c = false; + //start_scan(); + break; + } + memcpy(gl_profile_tab[PROFILE_C_APP_ID].remote_bda, p_data->open.remote_bda, 6); + gl_profile_tab[PROFILE_C_APP_ID].conn_id = p_data->open.conn_id; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->open.conn_id); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"Config mtu failed"); + } + ESP_LOGI(GATTC_TAG, "Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(GATTC_TAG, "SEARCH RES: conn_id = %x is primary service %d", p_data->search_res.conn_id, p_data->search_res.is_primary); + ESP_LOGI(GATTC_TAG, "start handle %d end handle %d current handle value %d", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id); + if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(GATTC_TAG, "UUID16: %x", p_data->search_res.srvc_id.uuid.uuid.uuid16); + get_service_c = true; + gl_profile_tab[PROFILE_C_APP_ID].service_start_handle = p_data->search_res.start_handle; + gl_profile_tab[PROFILE_C_APP_ID].service_end_handle = p_data->search_res.end_handle; + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + if (get_service_c){ + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gl_profile_tab[PROFILE_C_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_C_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + + if (count > 0){ + char_elem_result_c = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result_c){ + ESP_LOGE(GATTC_TAG, "gattc no mem"); + }else{ + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gl_profile_tab[PROFILE_C_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_C_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result_c, + &count); + if (status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_char_by_uuid error"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result_c[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)){ + gl_profile_tab[PROFILE_C_APP_ID].char_handle = char_elem_result_c[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gl_profile_tab[PROFILE_C_APP_ID].remote_bda, char_elem_result_c[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result_c); + }else{ + ESP_LOGE(GATTC_TAG, "no char found"); + } + } + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "reg notify failed, error status =%x", p_data->reg_for_notify.status); + break; + } + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, + gl_profile_tab[PROFILE_C_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gl_profile_tab[PROFILE_C_APP_ID].service_start_handle, + gl_profile_tab[PROFILE_C_APP_ID].service_end_handle, + gl_profile_tab[PROFILE_C_APP_ID].char_handle, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_attr_count error"); + } + if (count > 0){ + descr_elem_result_c = (esp_gattc_descr_elem_t *)malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result_c){ + ESP_LOGE(GATTC_TAG, "malloc error, gattc no mem"); + }else{ + ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, + gl_profile_tab[PROFILE_C_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result_c, + &count); + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_get_descr_by_char_handle error"); + } + + /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ + if (count > 0 && descr_elem_result_c[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result_c[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG){ + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gl_profile_tab[PROFILE_C_APP_ID].conn_id, + descr_elem_result_c[0].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "esp_ble_gattc_write_char_descr error"); + } + + /* free descr_elem_result */ + free(descr_elem_result_c); + } + } + else{ + ESP_LOGE(GATTC_TAG, "decsr not found"); + } + break; + } + case ESP_GATTC_NOTIFY_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, Receive notify value:"); + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write descr failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "write descr success"); + uint8_t write_char_data[35]; + for (int i = 0; i < sizeof(write_char_data); ++i) + { + write_char_data[i] = i % 256; + } + esp_ble_gattc_write_char( gattc_if, + gl_profile_tab[PROFILE_C_APP_ID].conn_id, + gl_profile_tab[PROFILE_C_APP_ID].char_handle, + sizeof(write_char_data), + write_char_data, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "Write char failed, error status = %x", p_data->write.status); + break; + } + ESP_LOGI(GATTC_TAG, "Write char success"); + start_scan(); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(GATTC_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:%08x%04x",(bda[0] << 24) + (bda[1] << 16) + (bda[2] << 8) + bda[3], + (bda[4] << 8) + bda[5]); + break; + } + case ESP_GATTC_DISCONNECT_EVT: + if (memcmp(p_data->disconnect.remote_bda, gl_profile_tab[PROFILE_C_APP_ID].remote_bda, 6) == 0){ + ESP_LOGI(GATTC_TAG, "device c disconnect"); + conn_device_c = false; + get_service_c = false; + } + break; + default: + break; + } +} + +static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + uint8_t *adv_name = NULL; + uint8_t adv_name_len = 0; + switch (event) { + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(GATTC_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + //the unit of the duration is second + uint32_t duration = 30; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + //scan start complete event to indicate scan start successfully or failed + if (param->scan_start_cmpl.status == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(GATTC_TAG, "Scan start success"); + }else{ + ESP_LOGE(GATTC_TAG, "Scan start failed"); + } + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + esp_log_buffer_hex(GATTC_TAG, scan_result->scan_rst.bda, 6); + ESP_LOGI(GATTC_TAG, "Searched Adv Data Len %d, Scan Response Len %d", scan_result->scan_rst.adv_data_len, scan_result->scan_rst.scan_rsp_len); + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, + ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ESP_LOGI(GATTC_TAG, "Searched Device Name Len %d", adv_name_len); + esp_log_buffer_char(GATTC_TAG, adv_name, adv_name_len); + ESP_LOGI(GATTC_TAG, "\n"); + if (Isconnecting){ + break; + } + if (conn_device_a && conn_device_b && conn_device_c && !stop_scan_done){ + stop_scan_done = true; + esp_ble_gap_stop_scanning(); + ESP_LOGI(GATTC_TAG, "all devices are connected"); + break; + } + if (adv_name != NULL) { + + if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[0], adv_name_len) == 0) { + if (conn_device_a == false) { + conn_device_a = true; + ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[0]); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + Isconnecting = true; + } + break; + } + else if (strlen(remote_device_name[1]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[1], adv_name_len) == 0) { + if (conn_device_b == false) { + conn_device_b = true; + ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[1]); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_B_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + Isconnecting = true; + + } + } + else if (strlen(remote_device_name[2]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[2], adv_name_len) == 0) { + if (conn_device_c == false) { + conn_device_c = true; + ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[2]); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gl_profile_tab[PROFILE_C_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + Isconnecting = true; + } + break; + } + + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + break; + default: + break; + } + break; + } + + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Scan stop failed"); + break; + } + ESP_LOGI(GATTC_TAG, "Stop scan successfully"); + + break; + + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Adv stop failed"); + break; + } + ESP_LOGI(GATTC_TAG, "Stop adv successfully"); + break; + + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + //ESP_LOGI(GATTC_TAG, "EVT %d, gattc if %d, app_id %d", event, gattc_if, param->reg.app_id); + + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gl_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(GATTC_TAG, "Reg app failed, app_id %04x, status %d", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gl_profile_tab[idx].gattc_if) { + if (gl_profile_tab[idx].gattc_cb) { + gl_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +void app_main(void) +{ + 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 ); + + 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(GATTC_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(GATTC_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + //register the callback function to the gap module + ret = esp_ble_gap_register_callback(esp_gap_cb); + if (ret){ + ESP_LOGE(GATTC_TAG, "gap register error, error code = %x", ret); + return; + } + + //register the callback function to the gattc module + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if(ret){ + ESP_LOGE(GATTC_TAG, "gattc register error, error code = %x", ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "gattc app register error, error code = %x", ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_B_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "gattc app register error, error code = %x", ret); + return; + } + + ret = esp_ble_gattc_app_register(PROFILE_C_APP_ID); + if (ret){ + ESP_LOGE(GATTC_TAG, "gattc app register error, error code = %x", ret); + return; + } + ret = esp_ble_gatt_set_local_mtu(200); + if (ret){ + ESP_LOGE(GATTC_TAG, "set local MTU failed, error code = %x", ret); + } + + +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults new file mode 100644 index 00000000..249325cc --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/sdkconfig.defaults @@ -0,0 +1,7 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BTDM_CTRL_BLE_MAX_CONN=9 diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md new file mode 100644 index 00000000..17614d03 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/Gatt_Client_Multi_Connection_Example_Walkthrough.md @@ -0,0 +1,319 @@ +# GATT Client Multi-connection Example Walkthrough + +## Introduction +This document presents a description of the multi-connection BLE GATT client example for the ESP32. In this implementation, a single ESP32 working as a GATT client connects to three different GATT servers at the same time. This set up illustrates the use case of an ESP32 device acting in a way so that it receives data from different BLE sensors. The unique combination of ESP32’s BLE + Wi-Fi capabilities in addition to connection to multiple peripherals makes it a great candidate to serve as an IoT gateway. + +This example’s workflow is similar to the [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md) and is shown in the figure below. However, in the multi-connection implementation, a GATT client searches for three specific server names and once that it has found them it opens a connection to all three of them one after the other. In code, each connection is handled separately with one Application Profile. + +Four ESP32 devices are needed in order to demonstrate this example, among which: + +* one would be employed as a GATT Client flashed with the [gattc_multi_connect](../../gattc_multi_connect) demo, and, +* the rest run as GATT servers flashed with the [gatt_server](../../gatt_server) demo of the ESP-IDF examples/bluetooth/bluedroid/ble folder. + +
Multi-Connection GATT Client Flowchart
+ +## Includes +The multi-connection example’s main source file is [gattc_multi_connect.c](../main/gattc_multi_connect.c). For details, see Section [Includes](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#includes) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +## Main Entry Point +See Section [Main Entry Point](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#main-entry-point) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +## Implementation Procedure +The GATT Client implementation includes the following steps: + +* system initialization, +* scanning configuration, +* scanning of nearby devices, +* connection to devices of interest, +* registering for notifications. + +### Initializing +See Section [Main Entry Point](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#main-entry-point) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +#### Application Profiles +Application Profiles are a way to group functionality. They are designed so that each Application Profile connects to one peer device, that way the same ESP32 can connect to multiple devices by assigning one Application Profile to each one, as figure below shows. Each Application Profile creates a GATT interface to connect to other devices. The Application Profiles are defined by an ID number, there are three profiles in this example: + +```c +#define PROFILE_NUM 3 +#define PROFILE_A_APP_ID 0 +#define PROFILE_B_APP_ID 1 +#define PROFILE_C_APP_ID 2 +``` + +
Multi-Connection GATT Client Flowchart
+ + +The ``esp_ble_gattc_app_register()`` function is used to register each Application Profile to the BLE stack. The registration operation generates a GATT interface that is returned as a parameter in a registration event. In addition, each Application Profile is also defined by a structure that can be used to keep the state of the application and update its parameters when new data is propagated by the stack. + +The Application Profiles in code are instances of a ``gattc_profile_inst`` structure. For details, see Section [Application Profiles](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#application-profiles) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +### Scanning + +#### Setting Scan Parameters +See Section [Setting Scan Parameters](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#setting-scan-parameters) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +#### Starting to Scan +See Section [Start Scanning](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#start-scanning) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +#### Getting Scan Results +See Section [Getting Scan Results](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md#start-scanning) in [GATT Client Example Walkthrough](../../gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md). + +#### Name Comparison + +* First, the name of the device is extracted from the advertised data and stored in the ``adv_name`` variable: + + ```c + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + ``` + +* Then, the device name found is compared to the server names that the client wants to connect to. The server names are defined in the ``remote_device_name`` array: + + ```c + static const char remote_device_name[3][20] = {"ESP_GATTS_DEMO_1", "ESP_GATTS_DEMO_2", “ESP_GATTS_DEMO_3"}; + ``` + The name comparison takes places as follows: + + ```c + if (strlen(remote_device_name[0]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[0], adv_name_len) == 0) { + if (find_device_1 == false) { + find_device_1 = true; + ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[0]); + memcpy(gl_profile_tab[PROFILE_A_APP_ID].remote_bda, scan_result->scan_rst.bda, 6); + } + break; + } + else if (strlen(remote_device_name[1]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[1], adv_name_len) == 0) { + if (find_device_2 == false) { + find_device_2 = true; + ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[1]); + memcpy(gl_profile_tab[PROFILE_B_APP_ID].remote_bda, scan_result->scan_rst.bda, 6); + } + } + else if (strlen(remote_device_name[2]) == adv_name_len && strncmp((char *)adv_name, remote_device_name[2], adv_name_len) == 0) { + if (find_device_3 == false) { + find_device_3 = true; + ESP_LOGI(GATTC_TAG, "Searched device %s", remote_device_name[2]); + memcpy(gl_profile_tab[PROFILE_C_APP_ID].remote_bda, scan_result->scan_rst.bda, 6); + } + break; + } + ``` + +* If any of the device names found corresponds to a remote device name, the ``find_device_X`` flag is set and the address of the remote device is stored in the ``gl_profile_tab`` table. When all flags are set, the client stops scanning and connects to the remote devices. + +### Connecting to Remote Devices + +#### Connecting to the First Remote Device +Once all devices have been found, the client stops scanning: + +```c +if (find_device_1 && find_device_2 && find_device_3 && stop_scan == false { + stop_scan = true; + esp_ble_gap_stop_scanning(); + } +``` + +The scan stop triggers an ``ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT`` event which is used to open a connection to the first remote device. The second and third devices get connected once the client searches for services, gets characteristics and registers for notifications on the first device. This workflow is designed to test that the communication between each remote device is working correctly before trying to connect to the next device or in case of error, skip to the next device. + +```c +case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS){ + ESP_LOGE(GATTC_TAG, "Scan stop failed"); + break; + } + ESP_LOGI(GATTC_TAG, "Stop scan successfully"); + if (!stop_scan){ + ESP_LOGE(GATTC_TAG, "Did not find all devices"); + } + if (find_device_1){ + esp_ble_gattc_open(gl_profile_tab[PROFILE_A_APP_ID].gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, true); + } + break; +``` + +* The connection is opened with the ``esp_ble_gattc_open()`` function which takes the GATT interface, the remote device address and a boolean value set to true for direct connection or false for background auto connection. To disconnect the physical connection, the GAP API function ``esp_ble_gap_disconnect()`` is used. + + When connecting to the first device, an ``ESP_GATTC_CONNECT_EVT`` event is generated which is forwarded to all profiles. It also triggers an ``ESP_GATTC_OPEN_EVT`` event that is forwarded to the Profile A event handler only, or ``gattc_profile_a_event_handler()`` function. The event checks that the connection is opened successfully, if not, the device is ignored and the client tries to open a connection to the second device: + + ```c + case ESP_GATTC_OPEN_EVT: + if (p_data->open.status != ESP_GATT_OK){ + //open failed, ignore the first device, connect the second device + ESP_LOGE(GATTC_TAG, "connect device failed, status %d", p_data->open.status); + if (find_device_2){ + esp_ble_gattc_open(gl_profile_tab[PROFILE_B_APP_ID].gattc_if, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, true); + } + break; + } + ``` + If the connection is successful the client saves the connection ID, prints the remote device information and configures the MTU size to 200 bytes. + + ```c + gl_profile_tab[PROFILE_A_APP_ID].conn_id = p_data->open.conn_id; + ESP_LOGI(GATTC_TAG, "ESP_GATTC_OPEN_EVT conn_id %d, if %d, status %d, mtu %d", p_data->open.conn_id, gattc_if, p_data->open.status, p_data->open.mtu); + ESP_LOGI(GATTC_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(GATTC_TAG, p_data->open.remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_config_mtu (gattc_if, p_data->open.conn_id, 200); + if (mtu_ret){ + ESP_LOGE(GATTC_TAG, "config MTU error, error code = %x", mtu_ret); + } + break; + ``` +* After configuration of the MTU size, an ``ESP_GATTC_CFG_MTU_EVT`` is generated. This event is used to search for available known services on the remote device. The search is performed by using the ``esp_ble_gattc_search_service()`` function and a service ID defined by: + + ```c + static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, + }; + ``` +* The handler then searches for the service: + + ```c + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG,"Config mtu failed"); + } + ESP_LOGI(GATTC_TAG, "Status %d, MTU %d, conn_id %d", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + ``` + If the service is found, an ``ESP_GATTC_SEARCH_RES_EVT`` event is triggered which allows to set the ``get_service_1 flag`` to true. This flag is used to print information and later get the characteristic that the client is interested in. + +* Once the search for all services is completed, an ``ESP_GATTC_SEARCH_CMPL_EVT`` event is generated which is used to get the characteristics of the service just discovered. This is done with the ``esp_ble_gattc_get_characteristic()`` function: + + ```c + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "search service failed, error status = %x", p_data->search_cmpl.status); + break; + } + if (get_service_1){ + esp_ble_gattc_get_characteristic(gattc_if, p_data->search_cmpl.conn_id, &remote_service_id, NULL); + } + break; + ``` + The ``esp_ble_gattc_get_characteristic()`` function takes the GATT interface, the connection ID and the remote service ID as parameters. In addition, a NULL value is passed to indicate that we want all the characteristics starting from the first one. If the client is interested in a specific characteristic it could pass the characteristic ID in this field to specify that. + An ``ESP_GATTC_GET_CHAR_EVT`` event is triggered when a characteristic is discovered. This event is used to print information about the characteristic. + +* If the characteristic ID is the same as the one defined by ``REMOTE_NOTIFY_CHAR_UUID``, the client registers for notifications on that characteristic value. +* Finally, the next characteristic is requested using the same ``esp_ble_gattc_get_characteristic()`` function, this time, the last parameter is set to the current characteristic. This triggers another ``ESP_GATTC_GET_CHAR_EVT`` and the process is repeated until all characteristics are obtained. + + ```c + case ESP_GATTC_GET_CHAR_EVT: + if (p_data->get_char.status != ESP_GATT_OK) { + break; + } + ESP_LOGI(GATTC_TAG, "GET CHAR: conn_id = %x, status %d", p_data->get_char.conn_id, p_data->get_char.status); + ESP_LOGI(GATTC_TAG, "GET CHAR: srvc_id = %04x, char_id = %04x", p_data->get_char.srvc_id.id.uuid.uuid.uuid16, p_data->get_char.char_id.uuid.uuid.uuid16); + + if (p_data->get_char.char_id.uuid.uuid.uuid16 == REMOTE_NOTIFY_CHAR_UUID) { + ESP_LOGI(GATTC_TAG, "register notify"); + esp_ble_gattc_register_for_notify(gattc_if, gl_profile_tab[PROFILE_A_APP_ID].remote_bda, &remote_service_id, &p_data->get_char.char_id); + } + + esp_ble_gattc_get_characteristic(gattc_if, p_data->get_char.conn_id, &remote_service_id, &p_data->get_char.char_id); + break; + ``` + +At this point the client has acquired all characteristics from the remote device and has subscribed for notifications on the characteristics of interest. Every time a client registers for notifications, an ``ESP_GATTC_REG_FOR_NOTIFY_EVT`` event is triggered. In this example, this event is set to write to the remote device Client Configuration Characteristic (CCC) using the ``esp_ble_gattc_write_char_descr()`` function. In turn, this function is used to write to characteristic descriptors. There are many characteristic descriptors defined by the Bluetooth specification, however, for this example, the descriptor of interest is the one that deals with enabling notifications, that is the Client Configuration descriptor. + +#### Connecting to the Next Remote Device +* In order to pass this descriptor as a parameter we first define it as: + + ```c + static esp_gatt_id_t notify_descr_id = { + .uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, + }, + .inst_id = 0, + }; + ``` + + Where ``ESP_GATT_UUID_CHAR_CLIENT_CONFIG`` is defined as the UUID to identify the CCC: + + ```c + #define ESP_GATT_UUID_CHAR_CLIENT_CONFIG 0x2902 /* Client Characteristic Configuration */ + ``` + The value to write is “1” to enable notifications. The parameter ``ESP_GATT_WRITE_TYPE_RSP`` is also passed to request that the server responds to the write request, as well as the ``ESP_GATT_AUTH_REQ_NONE`` parameter to indicate that the write request does not need authorization: + + ```c + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + if (p_data->reg_for_notify.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "reg notify failed, error status =%x", p_data->reg_for_notify.status); + break; + } + uint16_t notify_en = 1; + ESP_LOGI(GATTC_TAG, "REG FOR NOTIFY: status %d, srvc_id = %04x, char_id = %04x", + p_data->reg_for_notify.status, + p_data->reg_for_notify.srvc_id.id.uuid.uuid.uuid16, + p_data->reg_for_notify.char_id.uuid.uuid.uuid16); + + esp_ble_gattc_write_char_descr(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + &remote_service_id, + &p_data->reg_for_notify.char_id, + ¬ify_descr_id, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + } + ``` + +* Once notifications are enabled, the remote device sends a notification which triggers the ``ESP_GATTC_NOTIFY_EVT`` event on the client. This event is handled to write back to the characteristic using the ``esp_ble_gattc_write_char()`` function: + + ```c + case ESP_GATTC_NOTIFY_EVT: + ESP_LOGI(GATTC_TAG, "ESP_GATTC_NOTIFY_EVT, Receive notify value:"); + esp_log_buffer_hex(GATTC_TAG, p_data->notify.value, p_data->notify.value_len); + //write back + esp_ble_gattc_write_char(gattc_if, + gl_profile_tab[PROFILE_A_APP_ID].conn_id, + &remote_service_id, + &p_data->notify.char_id, + p_data->notify.value_len, + p_data->notify.value, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + ``` + +* If the writing procedure is acknowledged then the remote device has connected successfully and communication is established without error. Immediately, the write procedure generates an ``ESP_GATTC_WRITE_CHAR_EVT`` event which in this example is used to print information and connect to the second remote device: + + ```c + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "write char failed, error status = %x", p_data->write.status); + }else{ + ESP_LOGI(GATTC_TAG, "write char success"); + } + //connect the second device + if (find_device_2){ + esp_ble_gattc_open(gl_profile_tab[PROFILE_B_APP_ID].gattc_if, gl_profile_tab[PROFILE_B_APP_ID].remote_bda, true); + } + break; + ``` + +* This triggers an open event which is handled by the Profile B event handler. This handler follows the same steps to search for services, get characteristics, register for notifications and write to the characteristic as the first device. The sequence for the second device also ends with an ``ESP_GATTC_WRITE_CHAR_EVT`` event which in turn is used to connect to the third device: + + ```c + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK){ + ESP_LOGE(GATTC_TAG, "Write char failed, error status = %x", p_data->write.status); + }else{ + ESP_LOGI(GATTC_TAG, "Write char success"); + } + //connect the third device + if (find_device_3){ + esp_ble_gattc_open(gl_profile_tab[PROFILE_C_APP_ID].gattc_if, gl_profile_tab[PROFILE_C_APP_ID].remote_bda, true); + } + break; + ``` + +* The third devices also performs the same configuration and communication steps in identical form as the first and second devices. Upon successful completion, all three remote devices are simultaneously connected appropriately and receiving notifications without error. + +## Conclusion +In this example we have reviewed the example code for the multi-connection GATT client. The client connects to three remote BLE peripherals and searches for services of interest. If the services are found, the characteristics of those services are discovered and subscribed to. The connections to the remote devices are done in order, starting from the first one and making sure that remote device has connected successfully and is notifying before trying to connect to the next device. This example shows a practical way to use the ESP32 as a central device that can read multiple BLE sensors at the same time. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/image/ESP32_GATT_Multi_Connect_Client_Application_Profiles.png b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/image/ESP32_GATT_Multi_Connect_Client_Application_Profiles.png new file mode 100644 index 00000000..dbc61b96 Binary files /dev/null and b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/image/ESP32_GATT_Multi_Connect_Client_Application_Profiles.png differ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/image/Multi_Connection_GATT_Client_Flowchart.png b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/image/Multi_Connection_GATT_Client_Flowchart.png new file mode 100644 index 00000000..eb4b4591 Binary files /dev/null and b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/ble/gattc_multi_connect/tutorial/image/Multi_Connection_GATT_Client_Flowchart.png differ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/CMakeLists.txt new file mode 100644 index 00000000..cc8e4502 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(a2dp_sink) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/Makefile new file mode 100644 index 00000000..2db03bb7 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := a2dp_sink + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md new file mode 100644 index 00000000..5a6274be --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/README.md @@ -0,0 +1,76 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF A2DP-SINK demo +====================== + +Demo of A2DP audio sink role + +This is the demo of API implementing Advanced Audio Distribution Profile to receive an audio stream. + +This example involves the use of Bluetooth legacy profile A2DP for audio stream reception, AVRCP for media information notifications, and I2S for audio stream output interface. + +Applications such as bluetooth speakers can take advantage of this example as a reference of basic functionalities. + +## How to use this example + +### Hardware Required + +To play the sound, there is a need of loudspeaker and possibly an external I2S codec. Otherwise the example will only show a count of audio data packets received silently. Internal DAC can be selected and in this case external I2S codec may not be needed. + +For the I2S codec, pick whatever chip or board works for you; this code was written using a PCM5102 chip, but other I2S boards and chips will probably work as well. The default I2S connections are shown below, but these can be changed in menuconfig: + +| ESP pin | I2S signal | +| :-------- | :----------- | +| GPIO22 | LRCK | +| GPIO25 | DATA | +| GPIO26 | BCK | + +If the internal DAC is selected, analog audio will be available on GPIO25 and GPIO26. The output resolution on these pins will always be limited to 8 bit because of the internal structure of the DACs. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Set the use of external I2S codec or internal DAC for audio output, and configure the output PINs under A2DP Example Configuration + +* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable + +### 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 +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +After the program is started, the example starts inquiry scan and page scan, awaiting being discovered and connected. Other bluetooth devices such as smart phones can discover a device named "ESP_SPEAKER". A smartphone or another ESP-IDF example of A2DP source can be used to connect to the local device. + +Once A2DP connection is set up, there will be a notification message with the remote device's bluetooth MAC address like the following: + +``` +I (106427) BT_AV: A2DP connection state: Connected, [64:a2:f9:69:57:a4] +``` + +If a smartphone is used to connect to local device, starting to play music with an APP will result in the transmission of audio stream. The transmitting of audio stream will be visible in the application log including a count of audio data packets, like this: + +``` +I (120627) BT_AV: A2DP audio state: Started +I (122697) BT_AV: Audio packet count 100 +I (124697) BT_AV: Audio packet count 200 +I (126697) BT_AV: Audio packet count 300 +I (128697) BT_AV: Audio packet count 400 + +``` + +Also, the sound will be heard if a loudspeaker is connected and possible external I2S codec is correctly configured. For ESP32 A2DP source example, the sound is noise as the audio source generates the samples with a random sequence. + +## Troubleshooting +* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC data stream is transmitted to A2DP sink and then decoded into PCM samples as output. The PCM data format is normally of 44.1kHz sampling rate, two-channel 16-bit sample stream. Other decoder configurations in ESP32 A2DP sink is supported but need additional modifications of protocol stack settings. +* As a usage limitation, ESP32 A2DP sink can support at most one connection with remote A2DP source devices. Also, A2DP sink cannot be used together with A2DP source at the same time, but can be used with other profiles such as SPP and HFP. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt new file mode 100644 index 00000000..55f2d445 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/CMakeLists.txt @@ -0,0 +1,4 @@ +idf_component_register(SRCS "bt_app_av.c" + "bt_app_core.c" + "main.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild new file mode 100644 index 00000000..73f6d669 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/Kconfig.projbuild @@ -0,0 +1,42 @@ +menu "A2DP Example Configuration" + + choice EXAMPLE_A2DP_SINK_OUTPUT + prompt "A2DP Sink Output" + default EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + Select to use Internal DAC or external I2S driver + + config EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC + bool "Internal DAC" + help + Select this to use Internal DAC sink output + + config EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + bool "External I2S Codec" + help + Select this to use External I2S sink output + + endchoice + + config EXAMPLE_I2S_LRCK_PIN + int "I2S LRCK (WS) GPIO" + default 22 + depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + GPIO number to use for I2S LRCK(WS) Driver. + + config EXAMPLE_I2S_BCK_PIN + int "I2S BCK GPIO" + default 26 + depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + GPIO number to use for I2S BCK Driver. + + config EXAMPLE_I2S_DATA_PIN + int "I2S DATA GPIO" + default 25 + depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + GPIO number to use for I2S Data Driver. + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c new file mode 100644 index 00000000..17c2edbb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.c @@ -0,0 +1,361 @@ + +/* + 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 +#include +#include +#include +#include "esp_log.h" + +#include "bt_app_core.h" +#include "bt_app_av.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s.h" + +#include "sys/lock.h" + +// AVRCP used transaction label +#define APP_RC_CT_TL_GET_CAPS (0) +#define APP_RC_CT_TL_GET_META_DATA (1) +#define APP_RC_CT_TL_RN_TRACK_CHANGE (2) +#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3) +#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4) + +/* a2dp event handler */ +static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param); +/* avrc CT event handler */ +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); +/* avrc TG event handler */ +static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param); + +static uint32_t s_pkt_cnt = 0; +static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED; +static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"}; +static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; +static _lock_t s_volume_lock; +static xTaskHandle s_vcs_task_hdl = NULL; +static uint8_t s_volume = 0; +static bool s_volume_notify; + +/* callback for A2DP sink */ +void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: { + bt_app_work_dispatch(bt_av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) +{ + write_ringbuf(data, len); + if (++s_pkt_cnt % 100 == 0) { + ESP_LOGI(BT_AV_TAG, "Audio packet count %u", s_pkt_cnt); + } +} + +void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param) +{ + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); + uint8_t *attr_text = (uint8_t *) malloc (rc->meta_rsp.attr_length + 1); + memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length); + attr_text[rc->meta_rsp.attr_length] = 0; + + rc->meta_rsp.attr_text = attr_text; +} + +void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: + bt_app_alloc_meta_buffer(param); + /* fall through */ + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: + bt_app_work_dispatch(bt_av_hdl_avrc_tg_evt, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); + break; + default: + ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + bt_i2s_task_shut_down(); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){ + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + bt_i2s_task_start_up(); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); + s_audio_state = a2d->audio_stat.state; + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type); + // for now only SBC stream is supported + if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) { + int sample_rate = 16000; + char oct0 = a2d->audio_cfg.mcc.cie.sbc[0]; + if (oct0 & (0x01 << 6)) { + sample_rate = 32000; + } else if (oct0 & (0x01 << 5)) { + sample_rate = 44100; + } else if (oct0 & (0x01 << 4)) { + sample_rate = 48000; + } + i2s_set_clk(0, sample_rate, 16, 2); + + ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x", + a2d->audio_cfg.mcc.cie.sbc[0], + a2d->audio_cfg.mcc.cie.sbc[1], + a2d->audio_cfg.mcc.cie.sbc[2], + a2d->audio_cfg.mcc.cie.sbc[3]); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate); + } + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_av_new_track(void) +{ + // request metadata + uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE; + esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask); + + // register notification if peer support the event_id + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_TRACK_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_TRACK_CHANGE, ESP_AVRC_RN_TRACK_CHANGE, 0); + } +} + +static void bt_av_playback_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_PLAY_STATUS_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAYBACK_CHANGE, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0); + } +} + +static void bt_av_play_pos_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_PLAY_POS_CHANGED)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAY_POS_CHANGE, ESP_AVRC_RN_PLAY_POS_CHANGED, 10); + } +} + +void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + case ESP_AVRC_RN_TRACK_CHANGE: + bt_av_new_track(); + break; + case ESP_AVRC_RN_PLAY_STATUS_CHANGE: + ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback); + bt_av_playback_changed(); + break; + case ESP_AVRC_RN_PLAY_POS_CHANGED: + ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos); + bt_av_play_pos_changed(); + break; + } +} + +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event); + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + // get remote supported event_ids of peer AVRCP Target + esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); + } else { + // clear peer notification capability record + s_avrc_peer_rn_cap.bits = 0; + } + break; + } + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); + break; + } + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + free(rc->meta_rsp.attr_text); + break; + } + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; + bt_av_new_track(); + bt_av_playback_changed(); + bt_av_play_pos_changed(); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void volume_set_by_controller(uint8_t volume) +{ + ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f); + _lock_acquire(&s_volume_lock); + s_volume = volume; + _lock_release(&s_volume_lock); +} + +static void volume_set_by_local_host(uint8_t volume) +{ + ESP_LOGI(BT_RC_TG_TAG, "Volume is set locally to: %d%%", (uint32_t)volume * 100 / 0x7f); + _lock_acquire(&s_volume_lock); + s_volume = volume; + _lock_release(&s_volume_lock); + + if (s_volume_notify) { + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param); + s_volume_notify = false; + } +} + +static void volume_change_simulation(void *arg) +{ + ESP_LOGI(BT_RC_TG_TAG, "start volume change simulation"); + + for (;;) { + vTaskDelay(10000 / portTICK_RATE_MS); + + uint8_t volume = (s_volume + 5) & 0x7f; + volume_set_by_local_host(volume); + } +} + +static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_TG_TAG, "%s evt %d", __func__, event); + esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (rc->conn_stat.connected) { + // create task to simulate volume change + xTaskCreate(volume_change_simulation, "vcsT", 2048, NULL, 5, &s_vcs_task_hdl); + } else { + vTaskDelete(s_vcs_task_hdl); + ESP_LOGI(BT_RC_TG_TAG, "Stop volume change simulation"); + } + break; + } + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state); + break; + } + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100/ 0x7f); + volume_set_by_controller(rc->set_abs_vol.volume); + break; + } + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%x", rc->reg_ntf.event_id, rc->reg_ntf.event_parameter); + if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { + s_volume_notify = true; + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param); + } + break; + } + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag); + break; + } + default: + ESP_LOGE(BT_RC_TG_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h new file mode 100644 index 00000000..46342daa --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_av.h @@ -0,0 +1,40 @@ +/* + 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. +*/ + +#ifndef __BT_APP_AV_H__ +#define __BT_APP_AV_H__ + +#include +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#define BT_AV_TAG "BT_AV" +#define BT_RC_TG_TAG "RCTG" +#define BT_RC_CT_TAG "RCCT" + +/** + * @brief callback function for A2DP sink + */ +void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/** + * @brief callback function for A2DP sink audio data stream + */ +void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len); + +/** + * @brief callback function for AVRCP controller + */ +void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +/** + * @brief callback function for AVRCP target + */ +void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param); + +#endif /* __BT_APP_AV_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c new file mode 100644 index 00000000..7dee9e7f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.c @@ -0,0 +1,166 @@ +/* + 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 +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" +#include "driver/i2s.h" +#include "freertos/ringbuf.h" + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle s_bt_app_task_queue = NULL; +static xTaskHandle s_bt_app_task_handle = NULL; +static xTaskHandle s_bt_i2s_task_handle = NULL; +static RingbufHandle_t s_ringbuf_i2s = NULL;; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } // switch (msg.sig) + + if (msg.param) { + free(msg.param); + } + } + } +} + +void bt_app_task_start_up(void) +{ + s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppT", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); + return; +} + +void bt_app_task_shut_down(void) +{ + if (s_bt_app_task_handle) { + vTaskDelete(s_bt_app_task_handle); + s_bt_app_task_handle = NULL; + } + if (s_bt_app_task_queue) { + vQueueDelete(s_bt_app_task_queue); + s_bt_app_task_queue = NULL; + } +} + +static void bt_i2s_task_handler(void *arg) +{ + uint8_t *data = NULL; + size_t item_size = 0; + size_t bytes_written = 0; + + for (;;) { + data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (portTickType)portMAX_DELAY); + if (item_size != 0){ + i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY); + vRingbufferReturnItem(s_ringbuf_i2s,(void *)data); + } + } +} + +void bt_i2s_task_start_up(void) +{ + s_ringbuf_i2s = xRingbufferCreate(8 * 1024, RINGBUF_TYPE_BYTEBUF); + if(s_ringbuf_i2s == NULL){ + return; + } + + xTaskCreate(bt_i2s_task_handler, "BtI2ST", 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle); + return; +} + +void bt_i2s_task_shut_down(void) +{ + if (s_bt_i2s_task_handle) { + vTaskDelete(s_bt_i2s_task_handle); + s_bt_i2s_task_handle = NULL; + } + + if (s_ringbuf_i2s) { + vRingbufferDelete(s_ringbuf_i2s); + s_ringbuf_i2s = NULL; + } +} + +size_t write_ringbuf(const uint8_t *data, size_t size) +{ + BaseType_t done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (portTickType)portMAX_DELAY); + if(done){ + return size; + } else { + return 0; + } +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.h new file mode 100644 index 00000000..eaa390de --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/bt_app_core.h @@ -0,0 +1,53 @@ +/* + 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. +*/ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +#define BT_APP_CORE_TAG "BT_APP_CORE" + +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback); + +void bt_app_task_start_up(void); + +void bt_app_task_shut_down(void); + +void bt_i2s_task_start_up(void); + +void bt_i2s_task_shut_down(void); + +size_t write_ringbuf(const uint8_t *data, size_t size); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c new file mode 100644 index 00000000..ae09f467 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/main/main.c @@ -0,0 +1,206 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include +#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.h" +#include "bt_app_core.h" +#include "bt_app_av.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" +#include "driver/i2s.h" + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + + +void app_main(void) +{ + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + i2s_config_t i2s_config = { +#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC + .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN, +#else + .mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX +#endif + .sample_rate = 44100, + .bits_per_sample = 16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .communication_format = I2S_COMM_FORMAT_STAND_MSB, + .dma_buf_count = 6, + .dma_buf_len = 60, + .intr_alloc_flags = 0, //Default interrupt priority + .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow + }; + + + i2s_driver_install(0, &i2s_config, 0, NULL); +#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC + i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); + i2s_set_pin(0, NULL); +#else + i2s_pin_config_t pin_config = { + .bck_io_num = CONFIG_EXAMPLE_I2S_BCK_PIN, + .ws_io_num = CONFIG_EXAMPLE_I2S_LRCK_PIN, + .data_out_num = CONFIG_EXAMPLE_I2S_DATA_PIN, + .data_in_num = -1 //Not used + }; + + i2s_set_pin(0, &pin_config); +#endif + + + 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 ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + +#if (CONFIG_BT_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_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); +#endif + + /* + * Set default parameters for Legacy Pairing + * Use fixed pin code + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; + 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_set_pin(pin_type, 4, pin_code); + +} + +void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + +#if (CONFIG_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + ESP_LOGI(BT_AV_TAG, "event: %d", event); + break; + } + } + return; +} +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + switch (event) { + case BT_APP_EVT_STACK_UP: { + /* set up device name */ + char *dev_name = "ESP_SPEAKER"; + esp_bt_dev_set_device_name(dev_name); + + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize AVRCP controller */ + esp_avrc_ct_init(); + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + /* initialize AVRCP target */ + assert (esp_avrc_tg_init() == ESP_OK); + esp_avrc_tg_register_callback(bt_app_rc_tg_cb); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); + + /* initialize A2DP sink */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_sink_init(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults new file mode 100644 index 00000000..52869c8b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_sink/sdkconfig.defaults @@ -0,0 +1,11 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_SPP_ENABLED=n +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/CMakeLists.txt new file mode 100644 index 00000000..7c4e2152 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(a2dp_source) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/Makefile new file mode 100644 index 00000000..62e30c03 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := a2dp_source + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/README.md new file mode 100644 index 00000000..f5a61c5a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/README.md @@ -0,0 +1,86 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF A2DP-SOURCE demo +======================== + +Demo of A2DP audio source role + +This is the demo of using Advanced Audio Distribution Profile APIs to transmit audio stream. Application can take advantage of this example to implement portable audio players or microphones to transmit audio stream to A2DP sink devices. + +## How to use this example + +### Hardware Required + +This example is able to run on any commonly available ESP32 development board. And is supposed to connect to A2DP sink example in ESP-IDF. + +### Configure the project + +``` +idf.py menuconfig +``` + +* Enable Classic Bluetooth and A2DP under Component config --> Bluetooth --> Bluedroid Enable + +### 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 +``` + +(To exit the serial monitor, type ``Ctrl-]``.) + +## Example Output + +For the first step, this example performs device discovery to search for a target device(A2DP sink) whose device name is "ESP_SPEAKER" and whose "Rendering" bit of its Service Class field is set in its Class of Device. If a candidate target is found, the local device will initiate connection with it. + +After connection with A2DP sink is established, the example performs the following running loop 1-2-3-4-1: +1. audio transmission starts and lasts for a while +2. audio transmission stops +3. disconnect with target device +4. reconnect to target device + +The example implements an event loop triggered by a periodic "heart beat" timer and events from Bluetooth protocol stack callback functions. + +After the local device discovers the target device and initiates connection, there will be logging message like this: + +``` +I (4090) BT_AV: Found a target device, address 24:0a:c4:02:0e:ee, name ESP_SPEAKER +I (4090) BT_AV: Cancel device discovery ... +I (4100) BT_AV: Device discovery stopped. +I (4100) BT_AV: a2dp connecting to peer: ESP_SPEAKER +``` + +If connection is set up successfully, there will be such message: + +``` +I (5100) BT_AV: a2dp connected +``` + +Start of audio transmission has the following notification message: + +``` +I (10880) BT_AV: a2dp media ready checking ... +... +I (10880) BT_AV: a2dp media ready, starting ... +... +I (11400) BT_AV: a2dp media start successfully. +``` + +Stop of audio transmission, and disconnection with remote device generate the following notification message: + +``` +I (110880) BT_AV: a2dp media stopping... +... +I (110920) BT_AV: a2dp media stopped successfully, disconnecting... +... +I (111040) BT_AV: a2dp disconnected +``` + +## Troubleshooting +* For current stage, the supported audio codec in ESP32 A2DP is SBC. SBC audio stream is encoded from PCM data normally formatted as 44.1kHz sampling rate, two-channel 16-bit sample data. Other SBC configurations can be supported but there is a need for additional modifications to the protocol stack. +* The raw PCM media stream in the example is generated by a sequence of random number, so the sound played on the sink side will be piercing noise. +* As a usage limitation, ESP32 A2DP source can support at most one connection with remote A2DP sink devices. Also, A2DP source cannot be used together with A2DP sink at the same time, but can be used with other profiles such as SPP and HFP. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/CMakeLists.txt new file mode 100644 index 00000000..d0a3885d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "bt_app_core.c" + "main.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c new file mode 100644 index 00000000..528b34fc --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.c @@ -0,0 +1,113 @@ +/* + 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 +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle s_bt_app_task_queue = NULL; +static xTaskHandle s_bt_app_task_handle = NULL; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } // switch (msg.sig) + + if (msg.param) { + free(msg.param); + } + } + } +} + +void bt_app_task_start_up(void) +{ + s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); + return; +} + +void bt_app_task_shut_down(void) +{ + if (s_bt_app_task_handle) { + vTaskDelete(s_bt_app_task_handle); + s_bt_app_task_handle = NULL; + } + if (s_bt_app_task_queue) { + vQueueDelete(s_bt_app_task_queue); + s_bt_app_task_queue = NULL; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.h new file mode 100644 index 00000000..4415058a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/bt_app_core.h @@ -0,0 +1,47 @@ +/* + 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. +*/ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +#define BT_APP_CORE_TAG "BT_APP_CORE" + +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback); + +void bt_app_task_start_up(void); + +void bt_app_task_shut_down(void); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c new file mode 100644 index 00000000..25f66c90 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/main/main.c @@ -0,0 +1,694 @@ +/* + 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 +#include +#include +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/timers.h" +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_system.h" +#include "esp_log.h" + +#include "esp_bt.h" +#include "bt_app_core.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#define BT_AV_TAG "BT_AV" +#define BT_RC_CT_TAG "RCCT" + +// AVRCP used transaction label +#define APP_RC_CT_TL_GET_CAPS (0) +#define APP_RC_CT_TL_RN_VOLUME_CHANGE (1) + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/* A2DP global state */ +enum { + APP_AV_STATE_IDLE, + APP_AV_STATE_DISCOVERING, + APP_AV_STATE_DISCOVERED, + APP_AV_STATE_UNCONNECTED, + APP_AV_STATE_CONNECTING, + APP_AV_STATE_CONNECTED, + APP_AV_STATE_DISCONNECTING, +}; + +/* sub states of APP_AV_STATE_CONNECTED */ +enum { + APP_AV_MEDIA_STATE_IDLE, + APP_AV_MEDIA_STATE_STARTING, + APP_AV_MEDIA_STATE_STARTED, + APP_AV_MEDIA_STATE_STOPPING, +}; + +#define BT_APP_HEART_BEAT_EVT (0xff00) + +/// handler for bluetooth stack enabled events +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param); + +/// callback function for A2DP source +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/// callback function for A2DP source audio data stream +static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len); + +/// callback function for AVRCP controller +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +static void a2d_app_heart_beat(void *arg); + +/// A2DP application state machine +static void bt_app_av_sm_hdlr(uint16_t event, void *param); + +/// avrc CT event handler +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); + +/* A2DP application state machine handler for each state */ +static void bt_app_av_state_unconnected(uint16_t event, void *param); +static void bt_app_av_state_connecting(uint16_t event, void *param); +static void bt_app_av_state_connected(uint16_t event, void *param); +static void bt_app_av_state_disconnecting(uint16_t event, void *param); + +static esp_bd_addr_t s_peer_bda = {0}; +static uint8_t s_peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +static int s_a2d_state = APP_AV_STATE_IDLE; +static int s_media_state = APP_AV_MEDIA_STATE_IDLE; +static int s_intv_cnt = 0; +static int s_connecting_intv = 0; +static uint32_t s_pkt_cnt = 0; +static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; +static TimerHandle_t s_tmr; + +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; +} + +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 ); + + 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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize controller failed\n", __func__); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable controller failed\n", __func__); + return; + } + + if (esp_bluedroid_init() != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s initialize bluedroid failed\n", __func__); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + ESP_LOGE(BT_AV_TAG, "%s enable bluedroid failed\n", __func__); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + +#if (CONFIG_BT_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_IO; + 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); +} + +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 filter_inquiry_scan_result(esp_bt_gap_cb_param_t *param) +{ + char bda_str[18]; + uint32_t cod = 0; + int32_t rssi = -129; /* invalid value */ + uint8_t *eir = NULL; + esp_bt_gap_dev_prop_t *p; + + ESP_LOGI(BT_AV_TAG, "Scanned device: %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(BT_AV_TAG, "--Class of Device: 0x%x", cod); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + rssi = *(int8_t *)(p->val); + ESP_LOGI(BT_AV_TAG, "--RSSI: %d", rssi); + break; + case ESP_BT_GAP_DEV_PROP_EIR: + eir = (uint8_t *)(p->val); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: + default: + break; + } + } + + /* search for device with MAJOR service class as "rendering" in COD */ + if (!esp_bt_gap_is_valid_cod(cod) || + !(esp_bt_gap_get_cod_srvc(cod) & ESP_BT_COD_SRVC_RENDERING)) { + return; + } + + /* search for device named "ESP_SPEAKER" in its extended inqury response */ + if (eir) { + get_name_from_eir(eir, s_peer_bdname, NULL); + if (strcmp((char *)s_peer_bdname, "ESP_SPEAKER") != 0) { + return; + } + + ESP_LOGI(BT_AV_TAG, "Found a target device, address %s, name %s", bda_str, s_peer_bdname); + s_a2d_state = APP_AV_STATE_DISCOVERED; + memcpy(s_peer_bda, param->disc_res.bda, ESP_BD_ADDR_LEN); + ESP_LOGI(BT_AV_TAG, "Cancel device discovery ..."); + esp_bt_gap_cancel_discovery(); + } +} + + +void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_DISC_RES_EVT: { + filter_inquiry_scan_result(param); + break; + } + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: { + if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STOPPED) { + if (s_a2d_state == APP_AV_STATE_DISCOVERED) { + s_a2d_state = APP_AV_STATE_CONNECTING; + ESP_LOGI(BT_AV_TAG, "Device discovery stopped."); + ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %s", s_peer_bdname); + esp_a2d_source_connect(s_peer_bda); + } else { + // not discovered, continue to discover + ESP_LOGI(BT_AV_TAG, "Device discovery failed, continue to discover..."); + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + } + } else if (param->disc_st_chg.state == ESP_BT_GAP_DISCOVERY_STARTED) { + ESP_LOGI(BT_AV_TAG, "Discovery started."); + } + break; + } + case ESP_BT_GAP_RMT_SRVCS_EVT: + case ESP_BT_GAP_RMT_SRVC_REC_EVT: + break; + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(BT_AV_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(BT_AV_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT: { + ESP_LOGI(BT_AV_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(BT_AV_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(BT_AV_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_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(BT_AV_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(BT_AV_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + ESP_LOGI(BT_AV_TAG, "event: %d", event); + break; + } + } + return; +} + +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + switch (event) { + case BT_APP_EVT_STACK_UP: { + /* set up device name */ + char *dev_name = "ESP_A2DP_SRC"; + esp_bt_dev_set_device_name(dev_name); + + /* register GAP callback function */ + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize AVRCP controller */ + esp_avrc_ct_init(); + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); + + /* initialize A2DP source */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_source_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_source_init(); + + /* set discoverable and connectable mode */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + + /* start device discovery */ + ESP_LOGI(BT_AV_TAG, "Starting device discovery..."); + s_a2d_state = APP_AV_STATE_DISCOVERING; + esp_bt_gap_start_discovery(ESP_BT_INQ_MODE_GENERAL_INQUIRY, 10, 0); + + /* create and start heart beat timer */ + do { + int tmr_id = 0; + s_tmr = xTimerCreate("connTmr", (10000 / portTICK_RATE_MS), + pdTRUE, (void *)tmr_id, a2d_app_heart_beat); + xTimerStart(s_tmr, portMAX_DELAY); + } while (0); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, event, param, sizeof(esp_a2d_cb_param_t), NULL); +} + +static int32_t bt_app_a2d_data_cb(uint8_t *data, int32_t len) +{ + if (len < 0 || data == NULL) { + return 0; + } + + // generate random sequence + int val = rand() % (1 << 16); + for (int i = 0; i < (len >> 1); i++) { + data[(i << 1)] = val & 0xff; + data[(i << 1) + 1] = (val >> 8) & 0xff; + } + + return len; +} + +static void a2d_app_heart_beat(void *arg) +{ + bt_app_work_dispatch(bt_app_av_sm_hdlr, BT_APP_HEART_BEAT_EVT, NULL, 0, NULL); +} + +static void bt_app_av_sm_hdlr(uint16_t event, void *param) +{ + ESP_LOGI(BT_AV_TAG, "%s state %d, evt 0x%x", __func__, s_a2d_state, event); + switch (s_a2d_state) { + case APP_AV_STATE_DISCOVERING: + case APP_AV_STATE_DISCOVERED: + break; + case APP_AV_STATE_UNCONNECTED: + bt_app_av_state_unconnected(event, param); + break; + case APP_AV_STATE_CONNECTING: + bt_app_av_state_connecting(event, param); + break; + case APP_AV_STATE_CONNECTED: + bt_app_av_state_connected(event, param); + break; + case APP_AV_STATE_DISCONNECTING: + bt_app_av_state_disconnecting(event, param); + break; + default: + ESP_LOGE(BT_AV_TAG, "%s invalid state %d", __func__, s_a2d_state); + break; + } +} + +static void bt_app_av_state_unconnected(uint16_t event, void *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + break; + case BT_APP_HEART_BEAT_EVT: { + uint8_t *p = s_peer_bda; + ESP_LOGI(BT_AV_TAG, "a2dp connecting to peer: %02x:%02x:%02x:%02x:%02x:%02x", + p[0], p[1], p[2], p[3], p[4], p[5]); + esp_a2d_source_connect(s_peer_bda); + s_a2d_state = APP_AV_STATE_CONNECTING; + s_connecting_intv = 0; + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_connecting(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp connected"); + s_a2d_state = APP_AV_STATE_CONNECTED; + s_media_state = APP_AV_MEDIA_STATE_IDLE; + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + s_a2d_state = APP_AV_STATE_UNCONNECTED; + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + break; + case BT_APP_HEART_BEAT_EVT: + if (++s_connecting_intv >= 2) { + s_a2d_state = APP_AV_STATE_UNCONNECTED; + s_connecting_intv = 0; + } + break; + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_media_proc(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (s_media_state) { + case APP_AV_MEDIA_STATE_IDLE: { + if (event == BT_APP_HEART_BEAT_EVT) { + ESP_LOGI(BT_AV_TAG, "a2dp media ready checking ..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY); + } else if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_CHECK_SRC_RDY && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media ready, starting ..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_START); + s_media_state = APP_AV_MEDIA_STATE_STARTING; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_START && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media start successfully."); + s_intv_cnt = 0; + s_media_state = APP_AV_MEDIA_STATE_STARTED; + } else { + // not started succesfully, transfer to idle state + ESP_LOGI(BT_AV_TAG, "a2dp media start failed."); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + } + } + break; + } + case APP_AV_MEDIA_STATE_STARTED: { + if (event == BT_APP_HEART_BEAT_EVT) { + if (++s_intv_cnt >= 10) { + ESP_LOGI(BT_AV_TAG, "a2dp media stopping..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + s_media_state = APP_AV_MEDIA_STATE_STOPPING; + s_intv_cnt = 0; + } + } + break; + } + case APP_AV_MEDIA_STATE_STOPPING: { + if (event == ESP_A2D_MEDIA_CTRL_ACK_EVT) { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->media_ctrl_stat.cmd == ESP_A2D_MEDIA_CTRL_STOP && + a2d->media_ctrl_stat.status == ESP_A2D_MEDIA_CTRL_ACK_SUCCESS) { + ESP_LOGI(BT_AV_TAG, "a2dp media stopped successfully, disconnecting..."); + s_media_state = APP_AV_MEDIA_STATE_IDLE; + esp_a2d_source_disconnect(s_peer_bda); + s_a2d_state = APP_AV_STATE_DISCONNECTING; + } else { + ESP_LOGI(BT_AV_TAG, "a2dp media stopping..."); + esp_a2d_media_ctrl(ESP_A2D_MEDIA_CTRL_STOP); + } + } + break; + } + } +} + +static void bt_app_av_state_connected(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp disconnected"); + s_a2d_state = APP_AV_STATE_UNCONNECTED; + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: + // not suppposed to occur for A2DP source + break; + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + case BT_APP_HEART_BEAT_EVT: { + bt_app_av_media_proc(event, param); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_av_state_disconnecting(uint16_t event, void *param) +{ + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(param); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + ESP_LOGI(BT_AV_TAG, "a2dp disconnected"); + s_a2d_state = APP_AV_STATE_UNCONNECTED; + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: + case ESP_A2D_MEDIA_CTRL_ACK_EVT: + case BT_APP_HEART_BEAT_EVT: + break; + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: + case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: { + bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_av_volume_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_VOLUME_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, ESP_AVRC_RN_VOLUME_CHANGE, 0); + } +} + +void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + case ESP_AVRC_RN_VOLUME_CHANGE: + ESP_LOGI(BT_RC_CT_TAG, "Volume changed: %d", event_parameter->volume); + ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume: volume %d", event_parameter->volume + 5); + esp_avrc_ct_send_set_absolute_volume_cmd(APP_RC_CT_TL_RN_VOLUME_CHANGE, event_parameter->volume + 5); + bt_av_volume_changed(); + break; + } +} + +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event); + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + // get remote supported event_ids of peer AVRCP Target + esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); + } else { + // clear peer notification capability record + s_avrc_peer_rn_cap.bits = 0; + } + break; + } + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); + break; + } + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + free(rc->meta_rsp.attr_text); + break; + } + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; + + bt_av_volume_changed(); + break; + } + case ESP_AVRC_CT_SET_ABSOLUTE_VOLUME_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "Set absolute volume rsp: volume %d", rc->set_volume_rsp.volume); + break; + } + + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/sdkconfig.defaults new file mode 100644 index 00000000..e87de5c3 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/a2dp_source/sdkconfig.defaults @@ -0,0 +1,11 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_BT_SPP_ENABLED=n +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/CMakeLists.txt new file mode 100644 index 00000000..6b73adc3 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bt_discovery) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/Makefile new file mode 100644 index 00000000..37e3abec --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := bt_discovery + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/README.rst b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/README.rst new file mode 100644 index 00000000..8c5e1f0c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/README.rst @@ -0,0 +1,18 @@ +================= ===== +Supported Targets ESP32 +================= ===== + +ESP-IDF BT-INQUIRY demo +====================== + +Demo of Classic Bluetooth Device and Service Discovery + +This is the demo for user to use ESP_APIs to perform inquiry to search for a target device and then performs service search via SDP. + +Options choose step: + 1. idf.py menuconfig. + 2. enter menuconfig "Component config", choose "Bluetooth" + 3. enter menu Bluetooth, choose "Classic Bluetooth" and do not choose "Release DRAM from Classic BT controller" + 4. choose your options. + +After the program started, the device will start inquiry to search for a device with Major device type "PHONE" in the Class of Device Field. Then it will cancel the inquiry and started to perform service discovering on this remote device. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/CMakeLists.txt new file mode 100644 index 00000000..4bc23a96 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "bt_discovery.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/bt_discovery.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/bt_discovery.c new file mode 100644 index 00000000..cb8a687f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/bt_discovery.c @@ -0,0 +1,306 @@ +/* + 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. +*/ + + + +/**************************************************************************** +* +* This file is for Classic Bluetooth device and service discovery Demo. +* +****************************************************************************/ + +#include +#include +#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.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.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, "%08x", 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 */ + 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%x", cod); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + rssi = *(int8_t *)(p->val); + ESP_LOGI(GAP_TAG, "--RSSI: %d", rssi); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: + default: + break; + } + } + + /* search for device with MAJOR service class as "rendering" in COD */ + app_gap_cb_t *p_dev = &m_dev_info; + if (p_dev->dev_found && 0 != memcmp(param->disc_res.bda, p_dev->bda, ESP_BD_ADDR_LEN)) { + return; + } + + if (!esp_bt_gap_is_valid_cod(cod) || + !(esp_bt_gap_get_cod_major_dev(cod) == ESP_BT_COD_MAJOR_DEV_PHONE)) { + return; + } + + memcpy(p_dev->bda, param->disc_res.bda, ESP_BD_ADDR_LEN); + p_dev->dev_found = true; + 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: + p_dev->cod = *(uint32_t *)(p->val); + break; + case ESP_BT_GAP_DEV_PROP_RSSI: + p_dev->rssi = *(int8_t *)(p->val); + break; + case ESP_BT_GAP_DEV_PROP_BDNAME: { + uint8_t len = (p->len > ESP_BT_GAP_MAX_BDNAME_LEN) ? ESP_BT_GAP_MAX_BDNAME_LEN : + (uint8_t)p->len; + memcpy(p_dev->bdname, (uint8_t *)(p->val), len); + p_dev->bdname[len] = '\0'; + p_dev->bdname_len = len; + break; + } + case ESP_BT_GAP_DEV_PROP_EIR: { + memcpy(p_dev->eir, (uint8_t *)(p->val), p->len); + p_dev->eir_len = p->len; + break; + } + default: + break; + } + } + + if (p_dev->eir && 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(); + } +} + +void bt_app_gap_init(void) +{ + app_gap_cb_t *p_dev = &m_dev_info; + memset(p_dev, 0, sizeof(app_gap_cb_t)); +} + +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)); + // ESP_LOGI(GAP_TAG, "--%d", u->len); + } + } 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; +} + +void bt_app_gap_start_up(void) +{ + char *dev_name = "ESP_GAP_INQRUIY"; + esp_bt_dev_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); + + /* register GAP callback function */ + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* inititialize device information and status */ + app_gap_cb_t *p_dev = &m_dev_info; + memset(p_dev, 0, sizeof(app_gap_cb_t)); + + /* start to discover nearby Bluetooth devices */ + 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 */ + 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 ); + + 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\n", __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\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(GAP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(GAP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + bt_app_gap_start_up(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/sdkconfig.defaults new file mode 100644 index 00000000..356b30e9 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_discovery/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=n +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/CMakeLists.txt new file mode 100644 index 00000000..8b26ee3f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bt_spp_acceptor_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/Makefile new file mode 100644 index 00000000..a2666a75 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := bt_spp_acceptor_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/README.rst b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/README.rst new file mode 100644 index 00000000..4fbc8830 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/README.rst @@ -0,0 +1,20 @@ +================= ===== +Supported Targets ESP32 +================= ===== + +ESP-IDF BT-SPP-ACCEPTOR demo +====================== + +Demo of SPP acceptor role + +This is the demo for user to use ESP_APIs to create a SPP acceptor. + +Options choose step: + 1. idf.py menuconfig. + 2. enter menuconfig "Component config", choose "Bluetooth" + 3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile" + 4. choose your options. + +Then set SPP_SHOW_MODE as SPP_SHOW_DATA or SPP_SHOW_SPEED in code(should be same with bt_spp_initator). + +After the program started, bt_spp_initator will connect it and send data. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/CMakeLists.txt new file mode 100644 index 00000000..a71f85ba --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "example_spp_acceptor_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/example_spp_acceptor_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/example_spp_acceptor_demo.c new file mode 100644 index 00000000..f8710342 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/main/example_spp_acceptor_demo.c @@ -0,0 +1,225 @@ +/* + 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 +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_bt_device.h" +#include "esp_spp_api.h" + +#include "time.h" +#include "sys/time.h" + +#define SPP_TAG "SPP_ACCEPTOR_DEMO" +#define SPP_SERVER_NAME "SPP_SERVER" +#define EXAMPLE_DEVICE_NAME "ESP_SPP_ACCEPTOR" +#define SPP_SHOW_DATA 0 +#define SPP_SHOW_SPEED 1 +#define SPP_SHOW_MODE SPP_SHOW_SPEED /*Choose show mode: show data or speed*/ + +static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB; + +static struct timeval time_new, time_old; +static long data_num = 0; + +static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; +static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE; + +static void print_speed(void) +{ + float time_old_s = time_old.tv_sec + time_old.tv_usec / 1000000.0; + float time_new_s = time_new.tv_sec + time_new.tv_usec / 1000000.0; + float time_interval = time_new_s - time_old_s; + float speed = data_num * 8 / time_interval / 1000.0; + ESP_LOGI(SPP_TAG, "speed(%fs ~ %fs): %f kbit/s" , time_old_s, time_new_s, speed); + data_num = 0; + time_old.tv_sec = time_new.tv_sec; + time_old.tv_usec = time_new.tv_usec; +} + +static void esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) +{ + switch (event) { + case ESP_SPP_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT"); + esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + esp_spp_start_srv(sec_mask,role_slave, 0, SPP_SERVER_NAME); + break; + case ESP_SPP_DISCOVERY_COMP_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT"); + break; + case ESP_SPP_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT"); + break; + case ESP_SPP_CLOSE_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT"); + break; + case ESP_SPP_START_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT"); + break; + case ESP_SPP_CL_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT"); + break; + case ESP_SPP_DATA_IND_EVT: +#if (SPP_SHOW_MODE == SPP_SHOW_DATA) + ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT len=%d handle=%d", + param->data_ind.len, param->data_ind.handle); + esp_log_buffer_hex("",param->data_ind.data,param->data_ind.len); +#else + gettimeofday(&time_new, NULL); + data_num += param->data_ind.len; + if (time_new.tv_sec - time_old.tv_sec >= 3) { + print_speed(); + } +#endif + break; + case ESP_SPP_CONG_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT"); + break; + case ESP_SPP_WRITE_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT"); + break; + case ESP_SPP_SRV_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT"); + gettimeofday(&time_old, NULL); + break; + case ESP_SPP_SRV_STOP_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_STOP_EVT"); + break; + case ESP_SPP_UNINIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_UNINIT_EVT"); + break; + default: + break; + } +} + +void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_AUTH_CMPL_EVT:{ + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT:{ + ESP_LOGI(SPP_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(SPP_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(SPP_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_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + ESP_LOGI(SPP_TAG, "event: %d", event); + break; + } + } + return; +} + +void app_main(void) +{ + 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 ); + + 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(SPP_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp register failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp init failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + +#if (CONFIG_BT_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_IO; + 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); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/sdkconfig.defaults new file mode 100644 index 00000000..a3035995 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_acceptor/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_WIFI_ENABLED=n +CONFIG_BT_SPP_ENABLED=y +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/CMakeLists.txt new file mode 100644 index 00000000..4ee5f077 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bt_spp_initiator_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/Makefile new file mode 100644 index 00000000..d8630415 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := bt_spp_initiator_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/README.rst b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/README.rst new file mode 100644 index 00000000..543f43fa --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/README.rst @@ -0,0 +1,20 @@ +================= ===== +Supported Targets ESP32 +================= ===== + +ESP-IDF BT-SPP-INITATOR demo +====================== + +Demo of SPP initator role + +This is the demo for user to use ESP_APIs to create a SPP initator. + +Options choose step: + 1. idf.py menuconfig. + 2. enter menuconfig "Component config", choose "Bluetooth" + 3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile" + 4. choose your options. + +Then set SPP_SHOW_MODE as SPP_SHOW_DATA or SPP_SHOW_SPEED in code(should be same with bt_spp_acceptor). + +After the program started, It will connect to bt_spp_acceptor and send data. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/CMakeLists.txt new file mode 100644 index 00000000..09a976ad --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "example_spp_initiator_demo.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/example_spp_initiator_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/example_spp_initiator_demo.c new file mode 100644 index 00000000..e3dbf36b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/main/example_spp_initiator_demo.c @@ -0,0 +1,307 @@ +/* + 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 +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_bt_device.h" +#include "esp_spp_api.h" + +#include "time.h" +#include "sys/time.h" + +#define SPP_TAG "SPP_INITIATOR_DEMO" +#define EXAMPLE_DEVICE_NAME "ESP_SPP_INITIATOR" + +#define SPP_SHOW_DATA 0 +#define SPP_SHOW_SPEED 1 +#define SPP_SHOW_MODE SPP_SHOW_SPEED /*Choose show mode: show data or speed*/ + +static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_CB; + +static struct timeval time_new, time_old; +static long data_num = 0; + +static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; +static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER; + +static esp_bd_addr_t peer_bd_addr; +static uint8_t peer_bdname_len; +static char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +static const char remote_device_name[] = "ESP_SPP_ACCEPTOR"; +static const esp_bt_inq_mode_t inq_mode = ESP_BT_INQ_MODE_GENERAL_INQUIRY; +static const uint8_t inq_len = 30; +static const uint8_t inq_num_rsps = 0; + +#if (SPP_SHOW_MODE == SPP_SHOW_DATA) +#define SPP_DATA_LEN 20 +#else +#define SPP_DATA_LEN ESP_SPP_MAX_MTU +#endif +static uint8_t spp_data[SPP_DATA_LEN]; + +static void print_speed(void) +{ + float time_old_s = time_old.tv_sec + time_old.tv_usec / 1000000.0; + float time_new_s = time_new.tv_sec + time_new.tv_usec / 1000000.0; + float time_interval = time_new_s - time_old_s; + float speed = data_num * 8 / time_interval / 1000.0; + ESP_LOGI(SPP_TAG, "speed(%fs ~ %fs): %f kbit/s" , time_old_s, time_new_s, speed); + data_num = 0; + time_old.tv_sec = time_new.tv_sec; + time_old.tv_usec = time_new.tv_usec; +} + +static bool get_name_from_eir(uint8_t *eir, char *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 esp_spp_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) +{ + switch (event) { + case ESP_SPP_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT"); + esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps); + + break; + case ESP_SPP_DISCOVERY_COMP_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT status=%d scn_num=%d",param->disc_comp.status, param->disc_comp.scn_num); + if (param->disc_comp.status == ESP_SPP_SUCCESS) { + esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], peer_bd_addr); + } + break; + case ESP_SPP_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT"); + esp_spp_write(param->open.handle, SPP_DATA_LEN, spp_data); + gettimeofday(&time_old, NULL); + break; + case ESP_SPP_CLOSE_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT"); + break; + case ESP_SPP_START_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT"); + break; + case ESP_SPP_CL_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT"); + break; + case ESP_SPP_DATA_IND_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_DATA_IND_EVT"); + break; + case ESP_SPP_CONG_EVT: +#if (SPP_SHOW_MODE == SPP_SHOW_DATA) + ESP_LOGI(SPP_TAG, "ESP_SPP_CONG_EVT cong=%d", param->cong.cong); +#endif + if (param->cong.cong == 0) { + esp_spp_write(param->cong.handle, SPP_DATA_LEN, spp_data); + } + break; + case ESP_SPP_WRITE_EVT: +#if (SPP_SHOW_MODE == SPP_SHOW_DATA) + ESP_LOGI(SPP_TAG, "ESP_SPP_WRITE_EVT len=%d cong=%d", param->write.len , param->write.cong); + esp_log_buffer_hex("",spp_data,SPP_DATA_LEN); +#else + gettimeofday(&time_new, NULL); + data_num += param->write.len; + if (time_new.tv_sec - time_old.tv_sec >= 3) { + print_speed(); + } +#endif + if (param->write.cong == 0) { + esp_spp_write(param->write.handle, SPP_DATA_LEN, spp_data); + } + break; + case ESP_SPP_SRV_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT"); + break; + case ESP_SPP_UNINIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_UNINIT_EVT"); + break; + default: + break; + } +} + +static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch(event){ + case ESP_BT_GAP_DISC_RES_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_RES_EVT"); + esp_log_buffer_hex(SPP_TAG, param->disc_res.bda, ESP_BD_ADDR_LEN); + for (int i = 0; i < param->disc_res.num_prop; i++){ + if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR + && get_name_from_eir(param->disc_res.prop[i].val, peer_bdname, &peer_bdname_len)){ + esp_log_buffer_char(SPP_TAG, peer_bdname, peer_bdname_len); + if (strlen(remote_device_name) == peer_bdname_len + && strncmp(peer_bdname, remote_device_name, peer_bdname_len) == 0) { + memcpy(peer_bd_addr, param->disc_res.bda, ESP_BD_ADDR_LEN); + esp_spp_start_discovery(peer_bd_addr); + esp_bt_gap_cancel_discovery(); + } + } + } + break; + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_STATE_CHANGED_EVT"); + break; + case ESP_BT_GAP_RMT_SRVCS_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVCS_EVT"); + break; + case ESP_BT_GAP_RMT_SRVC_REC_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVC_REC_EVT"); + break; + case ESP_BT_GAP_AUTH_CMPL_EVT:{ + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT:{ + ESP_LOGI(SPP_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(SPP_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(SPP_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_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: + break; + } +} + +void app_main(void) +{ + for (int i = 0; i < SPP_DATA_LEN; ++i) { + spp_data[i] = i; + } + + 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 ); + + 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(SPP_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_bt_gap_register_callback(esp_bt_gap_cb)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_spp_register_callback(esp_spp_cb)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp register failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((ret = esp_spp_init(esp_spp_mode)) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp init failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + +#if (CONFIG_BT_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_IO; + 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); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/sdkconfig.defaults new file mode 100644 index 00000000..a3035995 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_initiator/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_WIFI_ENABLED=n +CONFIG_BT_SPP_ENABLED=y +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/CMakeLists.txt new file mode 100644 index 00000000..46030210 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bt_spp_vfs_acceptor_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/Makefile new file mode 100644 index 00000000..ea7d1e83 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := bt_spp_vfs_acceptor_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/README.rst b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/README.rst new file mode 100644 index 00000000..f4ecf95d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/README.rst @@ -0,0 +1,18 @@ +================= ===== +Supported Targets ESP32 +================= ===== + +ESP-IDF BT-SPP-VFS-ACCEPTOR demo +====================== + +Demo of SPP acceptor role + +This is the demo for user to use ESP_APIs to create a SPP acceptor. + +Options choose step: + 1. idf.py menuconfig. + 2. enter menuconfig "Component config", choose "Bluetooth" + 3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile" + 4. choose your options. + +After the program started, bt_spp_vfs_initator will connect it and send data. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/CMakeLists.txt new file mode 100644 index 00000000..369e404a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "example_spp_vfs_acceptor_demo.c" + "spp_task.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/example_spp_vfs_acceptor_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/example_spp_vfs_acceptor_demo.c new file mode 100644 index 00000000..8b9e947b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/example_spp_vfs_acceptor_demo.c @@ -0,0 +1,229 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This file is for bt_spp_vfs_acceptor demo. It can create servers, wait for connected and receive data. +* run bt_spp_vfs_acceptor demo, the bt_spp_vfs_initiator demo will automatically connect the bt_spp_vfs_acceptor demo, +* then receive data. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_bt_device.h" +#include "esp_spp_api.h" +#include "spp_task.h" + +#include "time.h" +#include "sys/time.h" + +#include "esp_vfs.h" +#include "sys/unistd.h" + +#define SPP_TAG "SPP_ACCEPTOR_DEMO" +#define SPP_SERVER_NAME "SPP_SERVER" +#define EXAMPLE_DEVICE_NAME "ESP_SPP_ACCEPTOR" + +static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_VFS; + +static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; +static const esp_spp_role_t role_slave = ESP_SPP_ROLE_SLAVE; + +#define SPP_DATA_LEN 100 +static uint8_t spp_data[SPP_DATA_LEN]; + +static void spp_read_handle(void * param) +{ + int size = 0; + int fd = (int)param; + do { + /* controll the log frequency, retry after 1s */ + vTaskDelay(1000 / portTICK_PERIOD_MS); + + size = read (fd, spp_data, SPP_DATA_LEN); + ESP_LOGI(SPP_TAG, "fd = %d data_len = %d", fd, size); + if (size == -1) { + break; + } + esp_log_buffer_hex(SPP_TAG, spp_data, size); + if (size == 0) { + /*read fail due to there is no data, retry after 1s*/ + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } while (1); + spp_wr_task_shut_down(); +} + +static void esp_spp_cb(uint16_t e, void *p) +{ + esp_spp_cb_event_t event = e; + esp_spp_cb_param_t *param = p; + + switch (event) { + case ESP_SPP_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT"); + esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + esp_spp_start_srv(sec_mask,role_slave, 0, SPP_SERVER_NAME); + break; + case ESP_SPP_DISCOVERY_COMP_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT"); + break; + case ESP_SPP_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT"); + break; + case ESP_SPP_CLOSE_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT"); + break; + case ESP_SPP_START_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT"); + break; + case ESP_SPP_CL_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT"); + break; + case ESP_SPP_SRV_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT"); + spp_wr_task_start_up(spp_read_handle, param->srv_open.fd); + break; + default: + break; + } +} + +static void esp_spp_stack_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) +{ + spp_task_work_dispatch(esp_spp_cb, event, param, sizeof(esp_spp_cb_param_t), NULL); +} + +void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_AUTH_CMPL_EVT:{ + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT:{ + ESP_LOGI(SPP_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(SPP_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(SPP_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_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: { + ESP_LOGI(SPP_TAG, "event: %d", event); + break; + } + } + return; +} + +void app_main(void) +{ + 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 ); + + 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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s initialize controller failed", __func__); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable controller failed", __func__); + return; + } + + if (esp_bluedroid_init() != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed", __func__); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable bluedroid failed", __func__); + return; + } + + if (esp_bt_gap_register_callback(esp_bt_gap_cb) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s gap register failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if (esp_spp_register_callback(esp_spp_stack_cb) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp register failed", __func__); + return; + } + esp_spp_vfs_register(); + spp_task_task_start_up(); + + if (esp_spp_init(esp_spp_mode) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp init failed", __func__); + return; + } + +#if (CONFIG_BT_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_IO; + 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); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/spp_task.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/spp_task.c new file mode 100644 index 00000000..7db49707 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/spp_task.c @@ -0,0 +1,128 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "spp_task.h" + +static void spp_task_task_handler(void *arg); +static bool spp_task_send_msg(spp_task_msg_t *msg); +static void spp_task_work_dispatched(spp_task_msg_t *msg); + +static xQueueHandle spp_task_task_queue = NULL; +static xTaskHandle spp_task_task_handle = NULL; + +bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback) +{ + ESP_LOGD(SPP_TASK_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + spp_task_msg_t msg; + memset(&msg, 0, sizeof(spp_task_msg_t)); + + msg.sig = SPP_TASK_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return spp_task_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return spp_task_send_msg(&msg); + } + } + + return false; +} + +static bool spp_task_send_msg(spp_task_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(spp_task_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(SPP_TASK_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void spp_task_work_dispatched(spp_task_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void spp_task_task_handler(void *arg) +{ + spp_task_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(spp_task_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(SPP_TASK_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case SPP_TASK_SIG_WORK_DISPATCH: + spp_task_work_dispatched(&msg); + break; + default: + ESP_LOGW(SPP_TASK_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } + + if (msg.param) { + free(msg.param); + } + } + } +} + +void spp_task_task_start_up(void) +{ + spp_task_task_queue = xQueueCreate(10, sizeof(spp_task_msg_t)); + xTaskCreate(spp_task_task_handler, "SPPAppT", 2048, NULL, 10, spp_task_task_handle); + return; +} + +void spp_task_task_shut_down(void) +{ + if (spp_task_task_handle) { + vTaskDelete(spp_task_task_handle); + spp_task_task_handle = NULL; + } + if (spp_task_task_queue) { + vQueueDelete(spp_task_task_queue); + spp_task_task_queue = NULL; + } +} + +void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd) +{ + xTaskCreate(p_cback, "write_read", 2048, (void *)fd, 5, NULL); +} +void spp_wr_task_shut_down(void) +{ + vTaskDelete(NULL); +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/spp_task.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/spp_task.h new file mode 100644 index 00000000..512773ec --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/main/spp_task.h @@ -0,0 +1,63 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __SPP_TASK_H__ +#define __SPP_TASK_H__ + +#include +#include +#include + +#define SPP_TASK_TAG "SPP_TASK" + +#define SPP_TASK_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* spp_task_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to spp_task_task */ + uint16_t event; /*!< message event id */ + spp_task_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} spp_task_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* spp_task_copy_cb_t) (spp_task_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback); + +void spp_task_task_start_up(void); + +void spp_task_task_shut_down(void); + + +/** + * @brief handler for write and read + */ +typedef void (* spp_wr_task_cb_t) (void *fd); + +void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd); + +void spp_wr_task_shut_down(void); + +#endif ///__SPP_TASK_H__ \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/sdkconfig.defaults new file mode 100644 index 00000000..a3035995 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_acceptor/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_WIFI_ENABLED=n +CONFIG_BT_SPP_ENABLED=y +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/CMakeLists.txt new file mode 100644 index 00000000..a1339039 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bt_spp_vfs_initiator_demo) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/Makefile new file mode 100644 index 00000000..d0817d8f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := bt_spp_vfs_initiator_demo + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/README.rst b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/README.rst new file mode 100644 index 00000000..4f2f0eb4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/README.rst @@ -0,0 +1,18 @@ +================= ===== +Supported Targets ESP32 +================= ===== + +ESP-IDF BT-SPP-VFS-INITATOR demo +====================== + +Demo of SPP initator role + +This is the demo for user to use ESP_APIs to create a SPP initator. + +Options choose step: + 1. idf.py menuconfig. + 2. enter menuconfig "Component config", choose "Bluetooth" + 3. enter menu Bluetooth, choose "Classic Bluetooth" and "SPP Profile" + 4. choose your options. + +After the program started, It will connect to bt_spp_vfs_acceptor and send data. diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/CMakeLists.txt new file mode 100644 index 00000000..03781e47 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "example_spp_vfs_initiator_demo.c" + "spp_task.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/example_spp_vfs_initiator_demo.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/example_spp_vfs_initiator_demo.c new file mode 100644 index 00000000..f7ecf1a4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/example_spp_vfs_initiator_demo.c @@ -0,0 +1,296 @@ +/* + 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. +*/ + +/**************************************************************************** +* +* This file is for bt_spp_vfs_initiator demo. It can discovery servers, connect one device and send data. +* run bt_spp_vfs_initiator demo, the bt_spp_vfs_initiator demo will automatically connect the bt_spp_vfs_acceptor demo, +* then send data. +* +****************************************************************************/ + +#include +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_gap_bt_api.h" +#include "esp_bt_device.h" +#include "esp_spp_api.h" +#include "spp_task.h" + +#include "time.h" +#include "sys/time.h" + +#include "esp_vfs.h" +#include "sys/unistd.h" + +#define SPP_TAG "SPP_INITIATOR_DEMO" +#define EXAMPLE_DEVICE_NAME "ESP_SPP_INITIATOR" + +static const esp_spp_mode_t esp_spp_mode = ESP_SPP_MODE_VFS; + +static const esp_spp_sec_t sec_mask = ESP_SPP_SEC_AUTHENTICATE; +static const esp_spp_role_t role_master = ESP_SPP_ROLE_MASTER; + +static esp_bd_addr_t peer_bd_addr; +static uint8_t peer_bdname_len; +static char peer_bdname[ESP_BT_GAP_MAX_BDNAME_LEN + 1]; +static const char remote_device_name[] = "ESP_SPP_ACCEPTOR"; +static const esp_bt_inq_mode_t inq_mode = ESP_BT_INQ_MODE_GENERAL_INQUIRY; +static const uint8_t inq_len = 30; +static const uint8_t inq_num_rsps = 0; + +#define SPP_DATA_LEN 20 +static uint8_t spp_data[SPP_DATA_LEN]; + +static void spp_write_handle(void * param) +{ + int size = 0; + int fd = (int)param; + printf("%s %d %p\n", __func__,fd,param); + do { + /*Controll the log frequency, retry after 1s*/ + vTaskDelay(1000 / portTICK_PERIOD_MS); + + size = write (fd, spp_data, SPP_DATA_LEN); + ESP_LOGI(SPP_TAG, "fd = %d data_len = %d",fd, size); + if (size == -1) { + break; + } else if ( size == 0) { + /*write fail due to ringbuf is full, retry after 1s*/ + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } while (1); + spp_wr_task_shut_down(); +} + +static bool get_name_from_eir(uint8_t *eir, char *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 esp_spp_cb(uint16_t e, void *p) +{ + esp_spp_cb_event_t event = e; + esp_spp_cb_param_t *param = p; + + switch (event) { + case ESP_SPP_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_INIT_EVT"); + esp_bt_dev_set_device_name(EXAMPLE_DEVICE_NAME); + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + esp_bt_gap_start_discovery(inq_mode, inq_len, inq_num_rsps); + + break; + case ESP_SPP_DISCOVERY_COMP_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_DISCOVERY_COMP_EVT status=%d scn_num=%d",param->disc_comp.status, param->disc_comp.scn_num); + if (param->disc_comp.status == ESP_SPP_SUCCESS) { + esp_spp_connect(sec_mask, role_master, param->disc_comp.scn[0], peer_bd_addr); + } + break; + case ESP_SPP_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_OPEN_EVT"); + spp_wr_task_start_up(spp_write_handle, param->open.fd); + break; + case ESP_SPP_CLOSE_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CLOSE_EVT"); + break; + case ESP_SPP_START_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_START_EVT"); + break; + case ESP_SPP_CL_INIT_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_CL_INIT_EVT"); + break; + case ESP_SPP_SRV_OPEN_EVT: + ESP_LOGI(SPP_TAG, "ESP_SPP_SRV_OPEN_EVT"); + break; + default: + break; + } +} + +static void esp_bt_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch(event){ + case ESP_BT_GAP_DISC_RES_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_RES_EVT"); + esp_log_buffer_hex(SPP_TAG, param->disc_res.bda, ESP_BD_ADDR_LEN); + for (int i = 0; i < param->disc_res.num_prop; i++){ + if (param->disc_res.prop[i].type == ESP_BT_GAP_DEV_PROP_EIR + && get_name_from_eir(param->disc_res.prop[i].val, peer_bdname, &peer_bdname_len)){ + esp_log_buffer_char(SPP_TAG, peer_bdname, peer_bdname_len); + if (strlen(remote_device_name) == peer_bdname_len + && strncmp(peer_bdname, remote_device_name, peer_bdname_len) == 0) { + memcpy(peer_bd_addr, param->disc_res.bda, ESP_BD_ADDR_LEN); + esp_spp_start_discovery(peer_bd_addr); + esp_bt_gap_cancel_discovery(); + } + } + } + break; + case ESP_BT_GAP_DISC_STATE_CHANGED_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_DISC_STATE_CHANGED_EVT"); + break; + case ESP_BT_GAP_RMT_SRVCS_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVCS_EVT"); + break; + case ESP_BT_GAP_RMT_SRVC_REC_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_RMT_SRVC_REC_EVT"); + break; + case ESP_BT_GAP_AUTH_CMPL_EVT:{ + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(SPP_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(SPP_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(SPP_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } + case ESP_BT_GAP_PIN_REQ_EVT:{ + ESP_LOGI(SPP_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(SPP_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(SPP_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_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(SPP_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(SPP_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + + default: + break; + } +} + +static void esp_spp_stack_cb(esp_spp_cb_event_t event, esp_spp_cb_param_t *param) +{ + spp_task_work_dispatch(esp_spp_cb, event, param, sizeof(esp_spp_cb_param_t), NULL); +} + +void app_main(void) +{ + for (int i = 0; i < SPP_DATA_LEN; ++i) { + spp_data[i] = i; + } + + 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 ); + + 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 (esp_bt_controller_init(&bt_cfg) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s initialize controller failed", __func__); + return; + } + + if (esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable controller failed", __func__); + return; + } + + if (esp_bluedroid_init() != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s initialize bluedroid failed", __func__); + return; + } + + if (esp_bluedroid_enable() != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s enable bluedroid failed", __func__); + return; + } + + if (esp_bt_gap_register_callback(esp_bt_gap_cb) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s gap register failed", __func__); + return; + } + + if (esp_spp_register_callback(esp_spp_stack_cb) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp register failed", __func__); + return; + } + esp_spp_vfs_register(); + spp_task_task_start_up(); + if (esp_spp_init(esp_spp_mode) != ESP_OK) { + ESP_LOGE(SPP_TAG, "%s spp init failed", __func__); + return; + } + +#if (CONFIG_BT_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_IO; + 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); +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/spp_task.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/spp_task.c new file mode 100644 index 00000000..a82ca191 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/spp_task.c @@ -0,0 +1,128 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "spp_task.h" + +static void spp_task_task_handler(void *arg); +static bool spp_task_send_msg(spp_task_msg_t *msg); +static void spp_task_work_dispatched(spp_task_msg_t *msg); + +static xQueueHandle spp_task_task_queue = NULL; +static xTaskHandle spp_task_task_handle = NULL; + +bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback) +{ + ESP_LOGD(SPP_TASK_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + spp_task_msg_t msg; + memset(&msg, 0, sizeof(spp_task_msg_t)); + + msg.sig = SPP_TASK_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return spp_task_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return spp_task_send_msg(&msg); + } + } + + return false; +} + +static bool spp_task_send_msg(spp_task_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(spp_task_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(SPP_TASK_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void spp_task_work_dispatched(spp_task_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void spp_task_task_handler(void *arg) +{ + spp_task_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(spp_task_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(SPP_TASK_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case SPP_TASK_SIG_WORK_DISPATCH: + spp_task_work_dispatched(&msg); + break; + default: + ESP_LOGW(SPP_TASK_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } + + if (msg.param) { + free(msg.param); + } + } + } +} + +void spp_task_task_start_up(void) +{ + spp_task_task_queue = xQueueCreate(10, sizeof(spp_task_msg_t)); + xTaskCreate(spp_task_task_handler, "SPPAppT", 2048, NULL, 10, &spp_task_task_handle); + return; +} + +void spp_task_task_shut_down(void) +{ + if (spp_task_task_handle) { + vTaskDelete(spp_task_task_handle); + spp_task_task_handle = NULL; + } + if (spp_task_task_queue) { + vQueueDelete(spp_task_task_queue); + spp_task_task_queue = NULL; + } +} + +void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd) +{ + xTaskCreate(p_cback, "write_read", 2048, (void *)fd, 5, NULL); +} +void spp_wr_task_shut_down(void) +{ + vTaskDelete(NULL); +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/spp_task.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/spp_task.h new file mode 100644 index 00000000..512773ec --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/main/spp_task.h @@ -0,0 +1,63 @@ +// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __SPP_TASK_H__ +#define __SPP_TASK_H__ + +#include +#include +#include + +#define SPP_TASK_TAG "SPP_TASK" + +#define SPP_TASK_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* spp_task_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to spp_task_task */ + uint16_t event; /*!< message event id */ + spp_task_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} spp_task_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* spp_task_copy_cb_t) (spp_task_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool spp_task_work_dispatch(spp_task_cb_t p_cback, uint16_t event, void *p_params, int param_len, spp_task_copy_cb_t p_copy_cback); + +void spp_task_task_start_up(void); + +void spp_task_task_shut_down(void); + + +/** + * @brief handler for write and read + */ +typedef void (* spp_wr_task_cb_t) (void *fd); + +void spp_wr_task_start_up(spp_wr_task_cb_t p_cback, int fd); + +void spp_wr_task_shut_down(void); + +#endif ///__SPP_TASK_H__ \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/sdkconfig.defaults new file mode 100644 index 00000000..a3035995 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/bt_spp_vfs_initiator/sdkconfig.defaults @@ -0,0 +1,10 @@ +# Override some defaults so BT stack is enabled +# and WiFi disabled by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BTDM_CTRL_MODE_BTDM=n +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_WIFI_ENABLED=n +CONFIG_BT_SPP_ENABLED=y +CONFIG_BT_BLE_ENABLED=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/CMakeLists.txt new file mode 100644 index 00000000..ab8aa0ac --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hfp_ag) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/Makefile new file mode 100644 index 00000000..b539a2d5 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := hfp_ag + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md new file mode 100644 index 00000000..d8582780 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/README.md @@ -0,0 +1,285 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# Hands-Free Audio Gateway + +This example is to show how to use the APIs of Hands-Free (HF) Audio Gateway (AG) Component and the effects of them by providing a set of commands. You can use this example to communicate with a Hands-Free Unit (e.g. a headphone set). This example uses UART for user commands. + +## How to use example + +### Hardware Required + +This example is designed to run on commonly available ESP32 development board, e.g. ESP32-DevKitC. To operate this example, it should be connected to a Hands-Free Unit running on a Headphone/Headset or on another ESP32 development board loaded with Hands Free Unit (hfp_hf) example from ESP-IDF. + +### Configure the project + +``` +idf.py menuconfig +``` + +ESP32 supports two types of audio data paths: PCM and HCI. Because the default sdkconfig of this example does not configured the data path in a specific way. You should config the data path you want: + +- PCM : When using PCM, audio data stream is directed to GPIO pins and you should link these GPIO pins to a speaker via I2S port. You should choose PCM in `menuconfig` path: `Component config --> Bluetooth controller --> BR/EDR Sync(SCO/eSCO) default data path --> PCM`and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> PCM`. +- HCI : When using HCI, audio data stream will be transported to HF unit app via HCI. You should choose HCI in `menuconfig` path: `Component config -->Bluetooth controller -->BR/EDR Sync(SCO/eSCO) default data path --> HCI` and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> HCI`. + +**Note: Wide Band Speech is disabled by default, if you want to use it please select it in the menuconfig path: `Component config --> Bluetooth --> Bluedroid Options --> Wide Band Speech`.** + +### 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](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +When you flash and monitor this example, the commands help table prints the following log at the very begining: + +``` +######################################################################## +HFP AG command usage manual +HFP AG commands begins with "hf" and end with ";" +Supported commands are as follows, arguments are embraced with < and > + +hf con; -- set up connection with peer device +hf dis; -- release connection with peer device +hf cona; -- set up audio connection with peer device +hf disa; -- release audio connection with peer device +hf vron; -- start voice recognition +hf vroff; -- stop voice recognition +hf vu ; -- volume update + tgt: 0-speaker, 1-microphone + vol: volume gain ranges from 0 to 15 +hf ind ; -- unsolicited indication device status to HF Client + call: call status [0,1] + callsetup: call setup status [0,3] + ntk: network status [0,1] + sig: signal strength value from 0~5 +hf ate ; -- send extended at error code + rep: response code from 0 to 7 + err: error code from 0 to 32 +hf iron; -- in-band ring tone provided +hf iroff; -- in-band ring tone not provided +hf ac; -- Answer Incoming Call from AG +hf rc; -- Reject Incoming Call from AG +hf d ; -- Dial Number by AG, e.g. hf d 11223344 +hf end; -- End a call by AG +hf h; -- to see the command for HFP AG +######################################################################## +``` + +**Note:** + +- This command help table will print out in monitor whenever you type `hf h;` or if you input a command that is not required by the command parse rule. +- The command you type will not echo in monitor and your command should always start with `hf` and end with `;` or the example will not respond. +- The command you type in will not echo in monitor. + +### Service Level Connection and Disconnection + +You can type `hf con;` to establish a service level connection with HF Unit device and log prints such as: + +``` +W (2211) BT_APPL: new conn_srvc id:5, app_id:0 +I (2221) BT_APP_HF: APP HFP event: CONNECTION_STATE_EVT +I (2221) BT_APP_HF: --connection state CONNECTED, peer feats 0x0, chld_feats 0x0 +I (2291) BT_APP_HF: APP HFP event: CIND_RESPONSE_EVT +I (2291) BT_APP_HF: --CIND Start. +I (2331) BT_APP_HF: APP HFP event: CONNECTION_STATE_EVT +I (2331) BT_APP_HF: --connection state SLC_CONNECTED, peer feats 0xff, chld_feats 0x4010 +``` + +**Note: Only after Hands-free Profile(HFP) service is initialized and a service level connection exists between an HF Unit and an AG device, could other commands be available.** + +You can type `hf dis;` to disconnect with the connected HF Unit device, and log prints such as: + +``` +E (100147) CNSL: Command [hf dis;] +disconnect +W (77321) BT_RFCOMM: port_rfc_closed RFCOMM connection in state 2 closed: Closed (res: 19) +I (77321) BT_APP_HF: APP HFP event: CONNECTION_STATE_EVT +I (77321) BT_APP_HF: --connection state DISCONNECTED, peer feats 0x0, chld_feats 0x0 +W (77381) BT_RFCOMM: rfc_find_lcid_mcb LCID reused LCID:0x41 current:0x0 +W (77381) BT_RFCOMM: RFCOMM_DisconnectInd LCID:0x41 +``` + +### Audio Connection and Disconnection + +You can type `hf cona;` to establish the audio connection between HF Unit and AG device. Also, you can type `hf disa;` to close the audio data stream. + +#### Scenarios for Audio Connection + +- Answer an incoming call +- Enable voice recognition +- Dial an outgoing call + +#### Scenarios for Audio Disconnection + +- Reject an incoming call +- Disable the voice recognition + +#### Choice of Codec + +ESP32 supports both CVSD and mSBC codec. HF Unit and AG device determine which codec to use by exchanging features during service level connection. The choice of codec also depends on the your configuration in `menuconfig`. + +Since CVSD is the default codec in HFP, we just show the scenarios using mSBC: + +- If you enable `BT_HFP_WBS_ENABLE` in `menuconfig`, mSBC will be available. +- If both HF Unit and AG support mSBC and `BT_HFP_WBS_ENABLE` is enabled, ESP32 chooses mSBC. +- If you use PCM data path, mSBC is not available. + +### Answer or Reject an Incoming Call + +#### Answer an Incoming Call + +You can type `hf ac;` to answer an incoming call and log prints such as: + +``` +E (1066280) CNSL: Command [hf ac;] +Answer Call from AG. +W (1066280) BT_APPL: BTA_AG_SCO_CODEC_ST: Ignoring event 1 +I (1067200) BT_APP_HF: APP HFP event: BCS_EVT +I (1067200) BT_APP_HF: --AG choose codec mode: CVSD Only +E (1067230) BT_BTM: btm_sco_connected, handle 180 +I (1067240) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (1067240) BT_APP_HF: --Audio State connected +``` + +#### Reject an Incoming Call + +You can type `hf rc;` to reject an incoming call and log prints such as: + +``` +E (10040) CNSL: Command [hf rc;] +Reject Call from AG. +I (1067240) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (1067240) BT_APP_HF: --Audio State disconnected +``` + +#### End a Call + +You can type `hf end;` to end the current ongoing call and log prints such as: + +``` +E (157741) CNSL: Command [hf end;] +End Call from AG. +W (157741) BT_APPL: BTA_AG_SCO_CLOSING_ST: Ignoring event 3 +I (159311) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (159311) BT_APP_HF: --Audio State disconnected +I (159311) BT_APP_HF: --ESP AG Audio Connection Disconnected. +``` + +### Dial Number + +You can type `hf d ;` to dial `` from AG and log prints such as: + +``` +E (207351) CNSL: Command [hf d 123456;] +Dial number 123456 +I (207361) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (207361) BT_APP_HF: --Audio State connecting +W (207361) BT_APPL: BTA_AG_SCO_OPENING_ST: Ignoring event 1 +W (207371) BT_APPL: BTA_AG_SCO_OPENING_ST: Ignoring event 1 +E (208801) BT_BTM: btm_sco_connected, handle 181 +I (208811) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (208811) BT_APP_HF: --Audio State connected +``` + +### Volume Control + +You can type `hf vu ;` to update the volume of a headset or microphone. The parameter should be set as follows: + +- `` : 0 - headset, 1 - microphone. +- `` : Integer among 0 - 15. + +For example, `hf vu 0 9;` updates the volume of headset and the log on the AG side prints `Volume Update`, while on the HF Unit side the log prints: + +``` +E (17087) BT_HF: APP HFP event: VOLUME_CONTROL_EVT +E (17087) BT_HF: --volume_target: SPEAKER, volume 9 +``` + +And also, `hf vu 1 9;` updates the volume of a microphone and the log on the HF Unit side prints: + +``` +E (32087) BT_HF: APP HFP event: VOLUME_CONTROL_EVT +E (32087) BT_HF: --volume_target: MICROPHONE, volume 9 +``` + +#### Voice Recognition + +You can type `hf vron;` to start the voice recognition and type `hf vroff;` to terminate this function in the AG device. Both commands will notify the HF Unit the status of voice recognition. For example, type `hf vron;` and the log will print: + +``` +E (244131) CNSL: Command [hf vron;] +Start Voice Recognition. +I (244141) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (244141) BT_APP_HF: --Audio State connecting +E (245301) BT_BTM: btm_sco_connected, handle 181 +I (245311) BT_APP_HF: APP HFP event: AUDIO_STATE_EVT +I (245311) BT_APP_HF: --Audio State connected +``` + +#### Device Status Indication + +You can type `hf ind ` to send device status of AG to HF Unit. Log on AG prints such as: `Device Indicator Changed!` and on HF Unit side prints such as: + +``` +E (293641) BT_HF: APP HFP event: CALL_IND_EVT +E (293641) BT_HF: --Call indicator call in progress +E (293641) BT_HF: APP HFP event: CALL_SETUP_IND_EVT +E (293651) BT_HF: --Call setup indicator INCOMING +E (293651) BT_HF: APP HFP event: SIGNAL_STRENGTH_IND_EVT +E (293661) BT_HF: -- signal strength: 5 +``` + +**Note: The AG device sends only the changed status to the HF Unit.** + +#### Send Extended AT Error Code + +You can type `hf ate ` to send extended AT error code to HF Unit. The parameter should be set as follows: + +- `` : integer among 0 - 7. +- `` : integer among 0 - 32. + +When you type `hf ate 7 7;` the log on the AG side prints `Send CME Error.` while on the HF Unit side prints: + +``` +E (448146) BT_HF: APP HFP event: AT_RESPONSE +E (448146) BT_HF: --AT response event, code 7, cme 7 +``` + +#### In-Band Ring Tone Setting + +You can type `hf iron;` to enable the in-band ring tone and type `hf iroff;` to disable it. The log on the AG side prints such as `Device Indicator Changed!` and on HF Unit side it prints such as: + +``` +E (19546) BT_HF: APP HFP event: IN-BAND_RING_TONE_EVT +E (19556) BT_HF: --in-band ring state Provided +``` + +## Troubleshooting + +If you encounter any problems, please check if the following rules are followed: + +- You should type the command in the terminal according to the format described in the commands help table. +- Not all commands in the table are supported by the HF Unit. +- If you want to `hf con;` to establish a service level connection with a specific HF Unit, you should add the MAC address of the HF Unit in `app_hf_msg_set.c` for example: `esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93};` +- Use `esp_hf_client_register_callback()` and `esp_hf_client_init();` before establishing a service level connection. + +## Example Breakdown + +Due to the complexity of the HFP, this example has more source files than other bluetooth examples. To show the functions of HFP in a simple way, we use the Commands and Effects scheme to illustrate APIs of the HFP in ESP-IDF. + +- The example will respond to user command through the UART console. Please go to `console_uart.c` for the configuration details. +- For the voice interface, ESP32 has provided PCM input/output signals which can be directed to GPIO pins. So, please go to `gpio_pcm_config.c` for the configuration details. +- If you want to update the command table, please refer to `app_hf_msg_set.c`. +- If you want to update the command parse rules, please refer to `app_hf_msg_prs.c`. +- If you want to update the responses of the AG or want to update the log, please refer to `bt_app_hf.c`. +- The task configuration part is in `bt_app_core.c`. \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt new file mode 100644 index 00000000..eff05563 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register(SRCS "app_hf_msg_prs.c" + "app_hf_msg_set.c" + "bt_app_core.c" + "bt_app_hf.c" + "console_uart.c" + "gpio_pcm_config.c" + "main.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_prs.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_prs.c new file mode 100644 index 00000000..2610bf23 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_prs.c @@ -0,0 +1,168 @@ +/* + 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 +#include +#include +#include +#include +#include +#include "app_hf_msg_prs.h" +#include "app_hf_msg_set.h" + +// according to the design, message header length shall be no less than 2. +#define HF_MSG_HDR_LEN (3) +const static char hf_msg_hdr[HF_MSG_HDR_LEN] = {'h', 'f', ' '}; + +// according to the design, message header length shall be no less than 2. +#define HF_MSG_TAIL_LEN (1) +const static char hf_msg_tail[HF_MSG_TAIL_LEN] = {';'}; + +void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs) +{ + prs->state = HF_MSG_PRS_IDLE; + prs->cnt = 0; + prs->h_idx = 0; + prs->t_idx = 0; +} + +void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb) +{ + prs->callback = cb; +} + +hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs) +{ + hf_msg_prs_err_t err = HF_MSG_PRS_ERR_IN_PROGRESS; + switch (prs->state) + { + case HF_MSG_PRS_IDLE: + { + if (c == hf_msg_hdr[0]) { + prs->state = HF_MSG_PRS_HDR; + prs->buf[0] = c; + prs->cnt = 1; + prs->h_idx = 1; + } else { + err = HF_MSG_PRS_ERR_HDR_UNDETECTED; + } + } + break; + + case HF_MSG_PRS_HDR: + { + if (c == hf_msg_hdr[prs->h_idx]) { + prs->buf[prs->cnt++] = c; + if (++(prs->h_idx) == HF_MSG_HDR_LEN) { + prs->state = HF_MSG_PRS_PAYL; + prs->t_idx = 0; + } + } else { + hf_msg_parser_reset_state(prs); + err = HF_MSG_PRS_ERR_HDR_SYNC_FAILED; + } + } + break; + + case HF_MSG_PRS_PAYL: + { + prs->buf[prs->cnt++] = c; + if (c == hf_msg_tail[prs->t_idx]) { + if (++(prs->t_idx) == HF_MSG_TAIL_LEN) { + prs->buf[prs->cnt] = '\0'; + prs->callback(prs->buf, prs->cnt); + hf_msg_parser_reset_state(prs); + err = HF_MSG_PRS_ERR_OK; + break; + } + } else { + prs->t_idx = 0; + } + + if (prs->cnt >= HF_MSG_LEN_MAX) { + hf_msg_parser_reset_state(prs); + err = HF_MSG_PRS_ERR_BUF_OVERFLOW; + } + } + break; + } + return err; +} + + +void hf_msg_split_args(char *start, char *end, char **argv, int *argn) +{ + if (argn == NULL || *argn == 0) { + return; + } + + memset(argv, 0, (*argn) * sizeof(void *)); + + int max_argn = *argn; + *argn = 0; + + char *p = start; + for (int i = 0; i < max_argn; ++i) { + while (isspace((int)*p) && p != end) { + ++p; + } + if (p == end) { + return; + } + + argv[i] = p++; + ++(*argn); + + while (!isspace((int)*p) && p != end) { + ++p; + } + + if (p == end) { + return; + } else { + *p = '\0'; + ++p; + } + } +} + +void hf_msg_args_parser(char *buf, int len) +{ + char *argv[HF_MSG_ARGS_MAX]; + int argn = HF_MSG_ARGS_MAX; + char *start = buf + HF_MSG_HDR_LEN; + // set the command terminitor to '\0' + char *end = buf + len - HF_MSG_TAIL_LEN; + *end = '\0'; + + hf_msg_split_args(start, end, argv, &argn); + + if (argn == 0) { + return; + } + + bool cmd_supported = false; + + hf_msg_hdl_t *cmd_tbl = hf_get_cmd_tbl(); + size_t cmd_tbl_size = hf_get_cmd_tbl_size(); + for (int i = 0; i < cmd_tbl_size; ++i) { + hf_msg_hdl_t *hdl = &cmd_tbl[i]; + if (strcmp(argv[0], hdl->str) == 0) { + if (hdl->handler) { + hdl->handler(argn, argv); + cmd_supported = true; + break; + } + } + } + if (!cmd_supported) { + printf("unsupported command\n"); + hf_msg_show_usage(); + } + return; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_prs.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_prs.h new file mode 100644 index 00000000..131a7a54 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_prs.h @@ -0,0 +1,48 @@ +/* + 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. +*/ + +#ifndef __APP_HF_MSG_PRS_H__ +#define __APP_HF_MSG_PRS_H__ + +typedef enum { + HF_MSG_PRS_ERR_OK = 0, // a complete message is finished + HF_MSG_PRS_ERR_IN_PROGRESS, // message parsing is in progress + HF_MSG_PRS_ERR_HDR_UNDETECTED, // header not detected + HF_MSG_PRS_ERR_HDR_SYNC_FAILED, // failed to sync header + HF_MSG_PRS_ERR_BUF_OVERFLOW, // exceeds the buffer size: HF_MSG_LEN_MAX +} hf_msg_prs_err_t; + +typedef enum { + HF_MSG_PRS_IDLE = 0, + HF_MSG_PRS_HDR, + HF_MSG_PRS_PAYL, +} hf_msg_prs_state_t; + +typedef void (*hf_msg_callback)(char *buf, int len); + +#define HF_MSG_LEN_MAX (128) + +typedef struct { + hf_msg_prs_state_t state; + char buf[HF_MSG_LEN_MAX + 1]; + int cnt; + int h_idx; + int t_idx; + hf_msg_callback callback; +} hf_msg_prs_cb_t; + +void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs); + +void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb); + +hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs); + +void hf_msg_show_usage(void); + +#endif /* __APP_HF_MSG_PRS_H__*/ + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c new file mode 100644 index 00000000..ee674640 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.c @@ -0,0 +1,255 @@ +/* + 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 +#include +#include "esp_hf_ag_api.h" +#include "app_hf_msg_set.h" +#include "bt_app_hf.h" + +// if you want to connect a specific device, add it's bda here +// esp_bd_addr_t hf_peer_addr = {0x70,0x26,0x05,0xca,0xeb,0x21}; + +void hf_msg_show_usage(void) +{ + printf("########################################################################\n"); + printf("HFP AG command usage manual\n"); + printf("HFP AG commands begins with \"hf\" and end with \";\"\n"); + printf("Supported commands are as follows, arguments are embraced with < and >\n\n"); + printf("hf con; -- set up connection with peer device\n"); + printf("hf dis; -- disconnection with peer device\n"); + printf("hf cona; -- set up audio connection with peer device\n"); + printf("hf disa; -- release audio connection with peer device\n"); + printf("hf vron; -- start voice recognition\n"); + printf("hf vroff; -- stop voice recognition\n"); + printf("hf vu ; -- volume update\n"); + printf(" tgt: 0-speaker, 1-microphone\n"); + printf(" vol: volume gain ranges from 0 to 15\n"); + printf("hf ind ; -- unsolicited indication device status to HF Client\n"); + printf(" call: call status [0,1]\n"); + printf(" callsetup: call setup status [0,3]\n"); + printf(" ntk: network status [0,1]\n"); + printf(" sig: signal strength value from 0~5\n"); + printf("hf ate ; -- send extended at error code\n"); + printf(" rep: response code from 0 to 7\n"); + printf(" err: error code from 0 to 32\n"); + printf("hf iron; -- in-band ring tone provided\n"); + printf("hf iroff; -- in-band ring tone not provided\n"); + printf("hf ac; -- Answer Incoming Call from AG\n"); + printf("hf rc; -- Reject Incoming Call from AG\n"); + printf("hf d ; -- Dial Number by AG, e.g. hf d 11223344\n"); + printf("hf end; -- End up a call by AG\n"); + printf("hf h; -- to see the command for HFP AG\n"); + printf("########################################################################\n"); +} + +#define HF_CMD_HANDLER(cmd) static void hf_##cmd##_handler(int argn, char **argv) + +HF_CMD_HANDLER(help) +{ + hf_msg_show_usage(); +} + +HF_CMD_HANDLER(conn) +{ + printf("Connect.\n"); + esp_bt_hf_connect(hf_peer_addr); +} + +HF_CMD_HANDLER(disc) +{ + printf("Disconnect\n"); + esp_bt_hf_disconnect(hf_peer_addr); +} + +HF_CMD_HANDLER(conn_audio) +{ + printf("Connect Audio\n"); + esp_bt_hf_connect_audio(hf_peer_addr); +} + +HF_CMD_HANDLER(disc_audio) +{ + printf("Disconnect Audio\n"); + esp_bt_hf_disconnect_audio(hf_peer_addr); +} + +//AT+BVRA +HF_CMD_HANDLER(vra_on) +{ + printf("Start Voice Recognition.\n"); + esp_bt_hf_vra(hf_peer_addr,1); +} +//AT+BVRA +HF_CMD_HANDLER(vra_off) +{ + printf("Stop Voicer Recognition.\n"); + esp_bt_hf_vra(hf_peer_addr,0); +} + +//AT+VGS or AT+VGM +HF_CMD_HANDLER(volume_control) +{ + if (argn != 3) { + printf("Insufficient number of arguments"); + return; + } + int target, volume; + if (sscanf(argv[1], "%d", &target) != 1 || + (target != ESP_HF_VOLUME_CONTROL_TARGET_SPK && + target != ESP_HF_VOLUME_CONTROL_TARGET_MIC)) { + printf("Invalid argument for target %s\n", argv[1]); + return; + } + if (sscanf(argv[2], "%d", &volume) != 1 || + (volume < 0 || volume > 15)) { + printf("Invalid argument for volume %s\n", argv[2]); + return; + } + printf("Volume Update\n"); + esp_bt_hf_volume_control(hf_peer_addr, target, volume); +} + +//+CIEV +HF_CMD_HANDLER(ind_change) +{ + if (argn != 5) { + printf("Insufficient number of arguments"); + return; + } + + int call_state, ntk_state, call_setup_state, signal; + + if (sscanf(argv[1], "%d", &call_state) != 1 || + (call_state != ESP_HF_CALL_STATUS_NO_CALLS && + call_state != ESP_HF_CALL_STATUS_CALL_IN_PROGRESS)) { + printf("Invalid argument for call state %s\n", argv[1]); + return; + } + if (sscanf(argv[2], "%d", &call_setup_state) != 1 || + (call_setup_state < ESP_HF_CALL_SETUP_STATUS_IDLE || call_setup_state > ESP_HF_CALL_SETUP_STATUS_OUTGOING_ALERTING)) { + printf("Invalid argument for callsetup state %s\n", argv[2]); + return; + } + if (sscanf(argv[3], "%d", &ntk_state) != 1 || + (ntk_state != ESP_HF_NETWORK_STATE_NOT_AVAILABLE && + ntk_state != ESP_HF_NETWORK_STATE_AVAILABLE)) { + printf("Invalid argument for netwrok state %s\n", argv[3]); + return; + } + if (sscanf(argv[4], "%d", &signal) != 1 || + (signal < 0 || signal > 5)) { + printf("Invalid argument for signal %s\n", argv[4]); + return; + } + printf("Device Indicator Changed!\n"); + esp_bt_hf_indchange_notification(hf_peer_addr, call_state, call_setup_state, ntk_state, signal); +} + +//AT+CMEE +HF_CMD_HANDLER(cme_err) +{ + if (argn != 3) { + printf("Insufficient number of arguments"); + return; + } + + int response_code, error_code; + if (sscanf(argv[1], "%d", &response_code) != 1 || + (response_code < ESP_HF_AT_RESPONSE_CODE_OK && response_code > ESP_HF_AT_RESPONSE_CODE_CME)) { + printf("Invalid argument for response_code %s\n", argv[1]); + return; + } + + if (sscanf(argv[2], "%d", &error_code) != 1 || + (error_code < ESP_HF_CME_AG_FAILURE || error_code > ESP_HF_CME_NETWORK_NOT_ALLOWED)) { + printf("Invalid argument for volume %s\n", argv[2]); + return; + } + + printf("Send CME Error.\n"); + esp_bt_hf_cmee_response(hf_peer_addr,response_code,error_code); +} + +//+BSIR:1 +HF_CMD_HANDLER(ir_on) +{ + printf("Enable Voicer Recognition.\n"); + esp_bt_hf_bsir(hf_peer_addr,1); +} + +//+BSIR:0 +HF_CMD_HANDLER(ir_off) +{ + printf("Disable Voicer Recognition.\n"); + esp_bt_hf_bsir(hf_peer_addr,0); +} + +//Answer Call from AG +HF_CMD_HANDLER(ac) +{ + printf("Answer Call from AG.\n"); + char *number = {"123456"}; + esp_bt_hf_answer_call(hf_peer_addr,1,0,1,1,number,0); +} + +//Reject Call from AG +HF_CMD_HANDLER(rc) +{ + printf("Reject Call from AG.\n"); + char *number = {"123456"}; + esp_bt_hf_reject_call(hf_peer_addr,0,0,0,0,number,0); +} + +//End Call from AG +HF_CMD_HANDLER(end) +{ + printf("End Call from AG.\n"); + char *number = {"123456"}; + esp_bt_hf_end_call(hf_peer_addr,0,0,0,0,number,0); +} + +//Dial Call from AG +HF_CMD_HANDLER(d) +{ + if (argn != 2) { + printf("Insufficient number of arguments"); + } else { + printf("Dial number %s\n", argv[1]); + esp_bt_hf_out_call(hf_peer_addr,1,0,1,2,argv[1],0); + } +} + +static hf_msg_hdl_t hf_cmd_tbl[] = { + {0, "h", hf_help_handler}, + {5, "con", hf_conn_handler}, + {10, "dis", hf_disc_handler}, + {20, "cona", hf_conn_audio_handler}, + {30, "disa", hf_disc_audio_handler}, + {40, "vu", hf_volume_control_handler}, + {50, "ind", hf_ind_change_handler}, + {60, "vron", hf_vra_on_handler}, + {70, "vroff", hf_vra_off_handler}, + {80, "ate", hf_cme_err_handler}, + {90, "iron", hf_ir_on_handler}, + {100, "iroff", hf_ir_off_handler}, + {110, "ac", hf_ac_handler}, + {120, "rc", hf_rc_handler}, + {130, "end", hf_end_handler}, + {140, "d", hf_d_handler}, +}; + +hf_msg_hdl_t *hf_get_cmd_tbl(void) +{ + return hf_cmd_tbl; +} + +size_t hf_get_cmd_tbl_size(void) +{ + return sizeof(hf_cmd_tbl) / sizeof(hf_msg_hdl_t); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.h new file mode 100644 index 00000000..de59abf8 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/app_hf_msg_set.h @@ -0,0 +1,26 @@ +/* + 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. +*/ + +#ifndef __APP_HF_MSG_SET_H__ +#define __APP_HF_MSG_SET_H__ + +#define HF_MSG_ARGS_MAX (8) + +typedef void (* hf_cmd_handler)(int argn, char **argv); + +typedef struct { + uint16_t opcode; + const char *str; + hf_cmd_handler handler; +} hf_msg_hdl_t; + +extern hf_msg_hdl_t *hf_get_cmd_tbl(void); +extern size_t hf_get_cmd_tbl_size(void); + +void hf_msg_show_usage(void); +#endif /* __APP_HF_MSG_SET_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_core.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_core.c new file mode 100644 index 00000000..89749c67 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_core.c @@ -0,0 +1,112 @@ +/* + 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 +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle bt_app_task_queue = NULL; +static xTaskHandle bt_app_task_handle = NULL; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } // switch (msg.sig) + + if (msg.param) { + free(msg.param); + } + } + } +} + +void bt_app_task_start_up(void) +{ + bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &bt_app_task_handle); + return; +} + +void bt_app_task_shut_down(void) +{ + if (bt_app_task_handle) { + vTaskDelete(bt_app_task_handle); + bt_app_task_handle = NULL; + } + if (bt_app_task_queue) { + vQueueDelete(bt_app_task_queue); + bt_app_task_queue = NULL; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_core.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_core.h new file mode 100644 index 00000000..4415058a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_core.h @@ -0,0 +1,47 @@ +/* + 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. +*/ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +#define BT_APP_CORE_TAG "BT_APP_CORE" + +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback); + +void bt_app_task_start_up(void); + +void bt_app_task_shut_down(void); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c new file mode 100644 index 00000000..30bdb9f9 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.c @@ -0,0 +1,310 @@ +/* + 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 +#include +#include +#include +#include "esp_log.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_hf_ag_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "time.h" +#include "sys/time.h" +#include "sdkconfig.h" +#include "bt_app_core.h" +#include "bt_app_hf.h" + +const char *c_hf_evt_str[] = { + "CONNECTION_STATE_EVT", /*!< SERVICE LEVEL CONNECTION STATE CONTROL */ + "AUDIO_STATE_EVT", /*!< AUDIO CONNECTION STATE CONTROL */ + "VR_STATE_CHANGE_EVT", /*!< VOICE RECOGNITION CHANGE */ + "VOLUME_CONTROL_EVT", /*!< AUDIO VOLUME CONTROL */ + "UNKNOW_AT_CMD", /*!< UNKNOW AT COMMAND RECIEVED */ + "IND_UPDATE", /*!< INDICATION UPDATE */ + "CIND_RESPONSE_EVT", /*!< CALL & DEVICE INDICATION */ + "COPS_RESPONSE_EVT", /*!< CURRENT OPERATOR EVENT */ + "CLCC_RESPONSE_EVT", /*!< LIST OF CURRENT CALL EVENT */ + "CNUM_RESPONSE_EVT", /*!< SUBSCRIBER INFORTMATION OF CALL EVENT */ + "DTMF_RESPONSE_EVT", /*!< DTMF TRANSFER EVT */ + "NREC_RESPONSE_EVT", /*!< NREC RESPONSE EVT */ + "ANSWER_INCOMING_EVT", /*!< ANSWER INCOMING EVT */ + "REJECT_INCOMING_EVT", /*!< AREJECT INCOMING EVT */ + "DIAL_EVT", /*!< DIAL INCOMING EVT */ + "WBS_EVT", /*!< CURRENT CODEC EVT */ + "BCS_EVT", /*!< CODEC NEGO EVT */ +}; + +//esp_hf_connection_state_t +const char *c_connection_state_str[] = { + "DISCONNECTED", + "CONNECTING", + "CONNECTED", + "SLC_CONNECTED", + "DISCONNECTING", +}; + +// esp_hf_audio_state_t +const char *c_audio_state_str[] = { + "disconnected", + "connecting", + "connected", + "connected_msbc", +}; + +/// esp_hf_vr_state_t +const char *c_vr_state_str[] = { + "Disabled", + "Enabled", +}; + +// esp_hf_nrec_t +const char *c_nrec_status_str[] = { + "NREC DISABLE", + "NREC ABLE", +}; + +// esp_hf_control_target_t +const char *c_volume_control_target_str[] = { + "SPEAKER", + "MICROPHONE", +}; + +// esp_hf_subscriber_service_type_t +char *c_operator_name_str[] = { + "中国移动", + "中国联通", + "中国电信", +}; + +// esp_hf_subscriber_service_type_t +char *c_subscriber_service_type_str[] = { + "UNKNOWN", + "VOICE", + "FAX", +}; + +// esp_hf_nego_codec_status_t +const char *c_codec_mode_str[] = { + "CVSD Only", + "Use CVSD", + "Use MSBC", +}; + +#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI +// Produce a sine audio +static const int16_t sine_int16[] = { + 0, 2057, 4107, 6140, 8149, 10126, 12062, 13952, 15786, 17557, + 19260, 20886, 22431, 23886, 25247, 26509, 27666, 28714, 29648, 30466, + 31163, 31738, 32187, 32509, 32702, 32767, 32702, 32509, 32187, 31738, + 31163, 30466, 29648, 28714, 27666, 26509, 25247, 23886, 22431, 20886, + 19260, 17557, 15786, 13952, 12062, 10126, 8149, 6140, 4107, 2057, + 0, -2057, -4107, -6140, -8149, -10126, -12062, -13952, -15786, -17557, +-19260, -20886, -22431, -23886, -25247, -26509, -27666, -28714, -29648, -30466, +-31163, -31738, -32187, -32509, -32702, -32767, -32702, -32509, -32187, -31738, +-31163, -30466, -29648, -28714, -27666, -26509, -25247, -23886, -22431, -20886, +-19260, -17557, -15786, -13952, -12062, -10126, -8149, -6140, -4107, -2057, +}; + +#define TABLE_SIZE_CVSD 100 +static uint32_t bt_app_hf_outgoing_cb(uint8_t *p_buf, uint32_t sz) +{ + static int sine_phase = 0; + + for (int i = 0; i * 2 + 1 < sz; i++) { + p_buf[i * 2] = sine_int16[sine_phase]; + p_buf[i * 2 + 1] = sine_int16[sine_phase]; + ++sine_phase; + if (sine_phase >= TABLE_SIZE_CVSD) { + sine_phase -= TABLE_SIZE_CVSD; + } + } + return sz; +} + +static void bt_app_hf_incoming_cb(const uint8_t *buf, uint32_t sz) +{ + // direct to i2s + esp_hf_outgoing_data_ready(); +} +#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */ + +void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param) +{ + if (event <= ESP_HF_BCS_RESPONSE_EVT) { + ESP_LOGI(BT_HF_TAG, "APP HFP event: %s", c_hf_evt_str[event]); + } else { + ESP_LOGE(BT_HF_TAG, "APP HFP invalid event %d", event); + } + + switch (event) { + case ESP_HF_CONNECTION_STATE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--connection state %s, peer feats 0x%x, chld_feats 0x%x", + c_connection_state_str[param->conn_stat.state], + param->conn_stat.peer_feat, + param->conn_stat.chld_feat); + memcpy(hf_peer_addr, param->conn_stat.remote_bda, ESP_BD_ADDR_LEN); + break; + } + + case ESP_HF_AUDIO_STATE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Audio State %s", c_audio_state_str[param->audio_stat.state]); +#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI + if (param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED || + param->audio_stat.state == ESP_HF_AUDIO_STATE_CONNECTED_MSBC) + { + esp_bt_hf_register_data_callback(bt_app_hf_incoming_cb, bt_app_hf_outgoing_cb); + } else if (param->audio_stat.state == ESP_HF_AUDIO_STATE_DISCONNECTED) { + ESP_LOGI(BT_HF_TAG, "--ESP AG Audio Connection Disconnected."); + } +#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */ + break; + } + + case ESP_HF_BVRA_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Voice Recognition is %s", c_vr_state_str[param->vra_rep.value]); + break; + } + + case ESP_HF_VOLUME_CONTROL_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Volume Target: %s, Volume %d", c_volume_control_target_str[param->volume_control.type], param->volume_control.volume); + break; + } + + case ESP_HF_UNAT_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--UNKOW AT CMD: %s", param->unat_rep.unat); + esp_hf_unat_response(hf_peer_addr, NULL); + break; + } + + case ESP_HF_IND_UPDATE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--UPDATE INDCATOR!"); + esp_hf_call_status_t call_state = 1; + esp_hf_call_setup_status_t call_setup_state = 2; + esp_hf_network_state_t ntk_state = 1; + int signal = 2; + esp_bt_hf_indchange_notification(hf_peer_addr,call_state,call_setup_state,ntk_state,signal); + break; + } + + case ESP_HF_CIND_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--CIND Start."); + esp_hf_call_status_t call_status = 0; + esp_hf_call_setup_status_t call_setup_status = 0; + esp_hf_network_state_t ntk_state = 1; + int signal = 4; + esp_hf_roaming_status_t roam = 0; + int batt_lev = 3; + esp_hf_call_held_status_t call_held_status = 0; + esp_bt_hf_cind_response(hf_peer_addr,call_status,call_setup_status,ntk_state,signal,roam,batt_lev,call_held_status); + break; + } + + case ESP_HF_COPS_RESPONSE_EVT: + { + const int svc_type = 1; + esp_bt_hf_cops_response(hf_peer_addr, c_operator_name_str[svc_type]); + break; + } + + case ESP_HF_CLCC_RESPONSE_EVT: + { + int index = 1; + //mandatory + esp_hf_current_call_direction_t dir = 1; + esp_hf_current_call_status_t current_call_status = 0; + esp_hf_current_call_mode_t mode = 0; + esp_hf_current_call_mpty_type_t mpty = 0; + //option + char *number = {"123456"}; + esp_hf_call_addr_type_t type = ESP_HF_CALL_ADDR_TYPE_UNKNOWN; + + ESP_LOGI(BT_HF_TAG, "--Calling Line Identification."); + esp_bt_hf_clcc_response(hf_peer_addr, index, dir, current_call_status, mode, mpty, number, type); + break; + } + + case ESP_HF_CNUM_RESPONSE_EVT: + { + char *number = {"123456"}; + esp_hf_subscriber_service_type_t type = 1; + ESP_LOGI(BT_HF_TAG, "--Current Number is %s ,Type is %s.", number, c_subscriber_service_type_str[type]); + esp_bt_hf_cnum_response(hf_peer_addr, number,type); + break; + } + + case ESP_HF_VTS_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--DTMF code is: %s.", param->vts_rep.code); + break; + } + + case ESP_HF_NREC_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--NREC status is: %s.", c_nrec_status_str[param->nrec.state]); + break; + } + + case ESP_HF_ATA_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Asnwer Incoming Call."); + char *number = {"123456"}; + esp_bt_hf_answer_call(hf_peer_addr,1,0,1,0,number,0); + break; + } + + case ESP_HF_CHUP_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Reject Incoming Call."); + char *number = {"123456"}; + esp_bt_hf_reject_call(hf_peer_addr,0,0,0,0,number,0); + break; + } + + case ESP_HF_DIAL_EVT: + { + if (param->out_call.num_or_loc) { + //dia_num_or_mem + ESP_LOGI(BT_HF_TAG, "--Dial \"%s\".", param->out_call.num_or_loc); + esp_bt_hf_out_call(hf_peer_addr,1,0,1,0,param->out_call.num_or_loc,0); + } else { + //dia_last + ESP_LOGI(BT_HF_TAG, "--Dial last number."); + } + break; + } +#if (BTM_WBS_INCLUDED == TRUE) + case ESP_HF_WBS_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Current codec: %s",c_codec_mode_str[param->wbs_rep.codec]); + break; + } +#endif + case ESP_HF_BCS_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Consequence of codec negotiation: %s",c_codec_mode_str[param->bcs_rep.mode]); + break; + } + + default: + ESP_LOGI(BT_HF_TAG, "Unsupported HF_AG EVT: %d.", event); + break; + } +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h new file mode 100644 index 00000000..e341d0f6 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/bt_app_hf.h @@ -0,0 +1,27 @@ +/* + 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. +*/ + +#ifndef __BT_APP_HF_H__ +#define __BT_APP_HF_H__ + +#include +#include "esp_hf_ag_api.h" +#include "esp_bt_defs.h" + +esp_bd_addr_t hf_peer_addr; // Declaration of peer device bdaddr + +#define BT_HF_TAG "BT_APP_HF" + +/** + * @brief callback function for HF client + */ +void bt_app_hf_cb(esp_hf_cb_event_t event, esp_hf_cb_param_t *param); + + +#endif /* __BT_APP_HF_H__*/ + \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/console_uart.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/console_uart.c new file mode 100644 index 00000000..9cdcfa56 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/console_uart.c @@ -0,0 +1,108 @@ +/* + 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 "driver/uart.h" +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "console_uart.h" +#include "app_hf_msg_prs.h" + +#define CONSOLE_UART_NUM UART_NUM_0 + +static QueueHandle_t uart_queue; +static hf_msg_prs_cb_t hf_msg_parser; + +static const uart_config_t uart_cfg = { + .baud_rate = 115200, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 127, +}; + +extern void hf_msg_args_parser(char *buf, int len); + +void hf_msg_handler(char *buf, int len) +{ + ESP_LOGE(TAG_CNSL, "Command [%s]", buf); + hf_msg_args_parser(buf, len); +} + +static void console_uart_task(void *pvParameters) +{ + int len; + uart_event_t event; + hf_msg_prs_cb_t *parser = &hf_msg_parser; + hf_msg_parser_reset_state(parser); + hf_msg_parser_register_callback(parser, hf_msg_handler); + hf_msg_show_usage(); +#define TMP_BUF_LEN 128 + uint8_t tmp_buf[TMP_BUF_LEN] = {0}; + + for (;;) { + //Waiting for UART event. + if (xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { + switch (event.type) { + //Event of UART receving data + case UART_DATA: { + len = uart_read_bytes(CONSOLE_UART_NUM, tmp_buf, TMP_BUF_LEN, 0); + for (int i = 0; i < len; i++) { + hf_msg_parse(tmp_buf[i], parser); + } + break; + } + //Event of HW FIFO overflow detected + case UART_FIFO_OVF: + ESP_LOGI(TAG_CNSL, "hw fifo overflow"); + break; + //Event of UART ring buffer full + case UART_BUFFER_FULL: + ESP_LOGI(TAG_CNSL, "ring buffer full"); + break; + //Event of UART RX break detected + case UART_BREAK: + ESP_LOGI(TAG_CNSL, "uart rx break"); + break; + //Event of UART parity check error + case UART_PARITY_ERR: + ESP_LOGI(TAG_CNSL, "uart parity error"); + break; + //Event of UART frame error + case UART_FRAME_ERR: + ESP_LOGI(TAG_CNSL, "uart frame error"); + break; + //Others + default: + break; + } + } + } + vTaskDelete(NULL); +} + + +esp_err_t console_uart_init(void) +{ + esp_err_t ret; + + ret = uart_param_config(CONSOLE_UART_NUM, &uart_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG_CNSL, "Uart %d initialize err %04x", CONSOLE_UART_NUM, ret); + return ret; + } + + uart_set_pin(CONSOLE_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + uart_driver_install(CONSOLE_UART_NUM, 1024, 1024, 8, &uart_queue, 0); + xTaskCreate(console_uart_task, "uTask", 2048, NULL, 8, NULL); + + return ESP_OK; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/console_uart.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/console_uart.h new file mode 100644 index 00000000..753950de --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/console_uart.h @@ -0,0 +1,19 @@ +/* + 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. +*/ + +#ifndef __CONSOLE_UART_H__ +#define __CONSOLE_UART_H__ + +#define TAG_CNSL "CNSL" + +/** + * @brief configure uart console for command input and process + */ +esp_err_t console_uart_init(void); + +#endif /* __BT_APP_HF_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/gpio_pcm_config.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/gpio_pcm_config.c new file mode 100644 index 00000000..e56da157 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/main/gpio_pcm_config.c @@ -0,0 +1,92 @@ +/* + 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 "driver/gpio.h" +#include "soc/gpio_reg.h" +#include "soc/gpio_sig_map.h" +#include "gpio_pcm_config.h" + +#define GPIO_OUTPUT_PCM_FSYNC (25) +#define GPIO_OUTPUT_PCM_CLK_OUT (5) +#define GPIO_OUTPUT_PCM_DOUT (26) +#define GPIO_INPUT_PCM_DIN (35) + +#define GPIO_OUTPUT_PCM_PIN_SEL ((1ULL< +#include +#include +#include +#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.h" +#include "bt_app_core.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_hf_ag_api.h" +#include "bt_app_hf.h" +#include "gpio_pcm_config.h" +#include "console_uart.h" + +#define BT_HF_AG_TAG "HF_AG_DEMO_MAIN" + +/* event for handler "hf_ag_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/* handler for bluetooth stack enabled events */ +static void bt_hf_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_HF_TAG, "%s evt %d", __func__, event); + switch (event) + { + case BT_APP_EVT_STACK_UP: + { + /* set up device name */ + char *dev_name = "ESP_HFP_AG"; + esp_bt_dev_set_device_name(dev_name); + + esp_bt_hf_register_callback(bt_app_hf_cb); + + // init and register for HFP_AG functions + esp_bt_hf_init(hf_peer_addr); + + /* + * 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; + pin_code[0] = '0'; + pin_code[1] = '0'; + pin_code[2] = '0'; + pin_code[3] = '0'; + esp_bt_gap_set_pin(pin_type, 4, pin_code); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + default: + ESP_LOGE(BT_HF_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +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) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK(ret); + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); + + esp_err_t err; + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + if ((err = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_hf_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + + /* initialize console via UART */ + console_uart_init(); + + /* configure the PCM interface and PINs used */ + app_gpio_pcm_io_cfg(); + + /* configure externel chip for acoustic echo cancellation */ +#if ACOUSTIC_ECHO_CANCELLATION_ENABLE + app_gpio_aec_io_cfg(); +#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */ +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.defaults new file mode 100644 index 00000000..0229678e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_ag/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BT_BLE_ENABLED=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_HFP_ENABLE=y +CONFIG_BT_HFP_AG_ENABLE=y \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/CMakeLists.txt new file mode 100644 index 00000000..372d040e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(hfp_hf) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/Makefile new file mode 100644 index 00000000..2813a67d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := hfp_hf + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md new file mode 100644 index 00000000..010180f5 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/README.md @@ -0,0 +1,335 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# Hands-Free Unit + +This example is to show how to use the APIs of Hands-Free (HF) Unit Component and the effects of them by providing a set of commands. You can use this example to communicate with an Audio Gateway (AG) device (e.g. a smart phone). This example uses UART for user commands. + +## How to use example + +### Hardware Required + +This example is designed to run on commonly available ESP32 development board, e.g. ESP32-DevKitC. To operate it should be connected to an AG running on a smartphone or on another ESP32 development board loaded with Hands Free Audio Gateway (hfp_ag) example from ESP-IDF. + +### Configure the project + +``` +idf.py menuconfig +``` + +ESP32 supports two types of audio data path: PCM() and HCI(Host-Controller Interface) but the default sdkconfig of this example does not config the data path in a specific way. You should config the data path you want: + +- PCM : When using PCM, audio data stream is mapped to GPIO pins and you should link these GPIO pins to a speaker via I2S port. And you should choose PCM in `menuconfig` path: `Component config --> Bluetooth controller --> BR/EDR Sync(SCO/eSCO) default data path --> PCM`and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> PCM`. +- HCI : When using HCI, audio data stream will be transport to HF unit app via HCI. And you should choose HCI in `menuconfig` path: `Component config -->Bluetooth controller -->BR/EDR Sync(SCO/eSCO) default data path --> HCI` and also `Component config --> Bluetooth --> Bluedroid Options -->Hands Free/Handset Profile --> audio(SCO) data path --> HCI`. + +**Note: Wide Band Speech is disabled by default, if you want to use it please select it in menuconfig path: `Component config --> Bluetooth --> Bluedroid Options --> Wide Band Speech`.** + +### 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](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +When you run this example, the commands help table prints the following at the very begining: + +``` +######################################################################## +HF client command usage manual +HF client commands begin with "hf" and end with ";" +Supported commands are as follows, arguments are embraced with < and > +hf con; -- setup connection with peer device +hf dis; -- release connection with peer device +hf cona; -- setup audio connection with peer device +hf disa; -- release connection with peer device +hf qop; -- query current operator name +hf qc; -- query current call status +hf ac; -- answer incoming call +hf rc; -- reject incoming call +hf d ; -- dial , e.g. hf d 11223344 +hf rd; -- redial +hf dm ; -- dial memory +hf vron; -- start voice recognition +hf vroff; -- stop voice recognition +hf vu ; -- volume update + tgt: 0-speaker, 1-microphone + vol: volume gain ranges from 0 to 15 +hf rs; -- retrieve subscriber information +hf rv; -- retrieve last voice tag number +hf rh ; -- response and hold + btrh: + 0 - put call on hold, + 1 - accept the held call, + 2 -reject the held call +hf k ; -- send dtmf code. + dtmf: single character in set 0-9, *, #, A-D +hf h; -- show command manual +######################################################################## +``` + +**Note:** + +- This command help table will print out in monitor whenever you type `hf h;` or if you input a command that is not required by the command parse rule. +- The command you type will not echo in monitor and your command should always start with `hf` and end with `;` or the example will not responds. +- The command you typed will not echo in monitor. + +### Service Level Connection and Disconnection + +You can type `hf con;` to establish a service level connection with AG device and log prints such as: + +``` +E (78502) CNSL: Command [hf con;] +connect +W (79632) BT_APPL: new conn_srvc id:27, app_id:1 +I (79642) BT_HF: APP HFP event: CONNECTION_STATE_EVT +I (79642) BT_HF: --connection state connected, peer feats 0x0, chld_feats 0x0 +I (79792) BT_HF: APP HFP event: CALL_IND_EVT +I (79792) BT_HF: --Call indicator NO call in progress +I (79792) BT_HF: APP HFP event: CALL_SETUP_IND_EVT +I (79802) BT_HF: --Call setup indicator NONE +I (79802) BT_HF: APP HFP event: NETWORK_STATE_EVT +I (79812) BT_HF: --NETWORK STATE available +I (79812) BT_HF: APP HFP event: SIGNAL_STRENGTH_IND_EVT +I (79822) BT_HF: -- signal strength: 4 +I (79822) BT_HF: APP HFP event: ROAMING_STATUS_IND_EVT +I (79832) BT_HF: --ROAMING: inactive +I (79832) BT_HF: APP HFP event: BATTERY_LEVEL_IND_EVT +I (79842) BT_HF: --battery level 3 +I (79842) BT_HF: APP HFP event: CALL_HELD_IND_EVT +I (79852) BT_HF: --Call held indicator NONE held +I (79852) BT_HF: APP HFP event: CONNECTION_STATE_EVT +I (79862) BT_HF: --connection state slc_connected, peer feats 0x16e, chld_feats 0x0 +I (79872) BT_HF: APP HFP event: INBAND_RING_TONE_EVT +I (79872) BT_HF: --inband ring state Provided +``` + +**Note: Only after Hands-Free Profile(HFP) service is initialized and a service level connection exists between an HF Unit and an AG device, could other commands be available.** + +You can type `hf dis;` to disconnect with the connected AG device, and log prints such as: + +``` +E (93382) CNSL: Command [hf dis;] +disconnect +W (93702) BT_RFCOMM: port_rfc_closed RFCOMM connection in state 3 closed: Closed (res: 19) +W (93712) BT_APPL: BTA_HF_CLIENT_SCO_SHUTDOWN_ST: Ignoring event 3 +I (93712) BT_HF: APP HFP event: CONNECTION_STATE_EVT +I (93712) BT_HF: --connection state disconnected, peer feats 0x0, chld_feats 0x0 +``` + +### Audio Connection and Disconnection + +You can type `hf cona;` to establish the audio connection between HF Unit and AG device. Log prints such as: + +``` +E (117232) CNSL: Command [hf cona;] +connect audio +I (117232) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (117232) BT_HF: --audio state connecting +E (117262) BT_BTM: btm_sco_connected, handle 181 +I (117262) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (117262) BT_HF: --audio state connected +``` + +Also, you can type `hf disa;` to close the audio data stream. Log prints such as: + +``` +E (133002) CNSL: Command [hf disa;] +disconnect audio +I (133262) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (133262) BT_HF: --audio state disconnected +``` +#### Scenarios for Audio Connection + +- Answer an incoming call +- Enable voice recognition +- Dial an outgoing call + +#### Scenarios for Audio Disconnection + +- Reject an incoming call +- Disable the voice recognition + +#### Choise of Codec + +ESP32 supports both CVSD and mSBC codec. HF Unit and AG device determine which codec to use by exchanging features during service level connection. The choice of codec also depends on the your configuration in `menuconfig`. + +Since CVSD is the default codec in HFP, we just show the scenarios using mSBC: + +- If you enable `BT_HFP_WBS_ENABLE` in `menuconfig`, mSBC will be available. +- If both HF Unit and AG support mSBC and `BT_HFP_WBS_ENABLE` is enabled, ESP32 chooses mSBC. +- If you use PCM data path, mSBC is not available. + +### Answer or Reject an incoming call + +#### Answer an incoming call + +You can type `hf ac;` to answer an incoming call and log prints such as: + +``` +E (196982) CNSL: Command [hf ac;] +Answer call +I (197102) BT_HF: APP HFP event: AT_RESPONSE +I (197102) BT_HF: --AT response event, code 0, cme 0 +E (197232) BT_BTM: btm_sco_connected, handle 181 +I (197232) BT_HF: APP HFP event: CALL_IND_EVT +I (197232) BT_HF: --Call indicator call in progress +I (197232) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (197242) BT_HF: --audio state connected +``` + +#### Reject an incoming call + +You can type `hf rc;` to reject an incoming call and log prints such as: + +``` +E (210112) CNSL: Command [hf rc;] +Reject call +I (210822) BT_HF: APP HFP event: AT_RESPONSE +I (210822) BT_HF: --AT response event, code 0, cme 0 +I (210842) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (210842) BT_HF: --audio state disconnected +I (210902) BT_HF: APP HFP event: CALL_IND_EVT +I (210902) BT_HF: --Call indicator NO call in progress +``` + +#### Dial Number + +This example supports three dialing commands: + +- `hf d ;` Dial the specific number. +- `hf rd;` Redial the last number. +- `hf dm ` Dial the specific indexed number in the AG memory. + +For example, type `hf d 186xxxx5549;` to make an outgoing call to `186xxxx5549` and log prints such as: + +``` +E (228882) CNSL: Command [hf d 186xxxx5549;] +Dial number 186xxxx5549 +E (229702) BT_BTM: btm_sco_connected, handle 181 +I (229712) BT_HF: APP HFP event: CALL_SETUP_IND_EVT +I (229712) BT_HF: --Call setup indicator OUTGOING_DIALING +I (229712) BT_HF: APP HFP event: CALL_IND_EVT +I (229712) BT_HF: --Call indicator call in progress +I (229722) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (229722) BT_HF: --audio state connected +I (229732) BT_HF: APP HFP event: CALL_SETUP_IND_EVT +I (229732) BT_HF: --Call setup indicator NONE +``` + +#### Respond and Hold + +You can type `hf rh ;` to respond or hold the current call. The parameter should be set as follows: + +- `` : 0 - hold current call, 1 - answer held call, 2 - end held call. + +#### Volume Control + +You can type `hf vu ;` to update volume gain of speaker or microphone. The parameter should be set as follows: + +- `` : 0 - speaker, 1 - microphone. +- `` : Integer among 0 - 15. + +For example, type `hf vu 0 9;` to update the volume of speaker and log on AG prints: + +``` +I (43684) BT_APP_HF: APP HFP event: VOLUME_CONTROL_EVT +I (43684) BT_APP_HF: --Volume Target: SPEAKER, Volume 9 +``` + +And also, `hf vu 1 9;` update the volume gain of microphone and log on AG prints: + +``` +I (177254) BT_APP_HF: APP HFP event: VOLUME_CONTROL_EVT +I (177254) BT_APP_HF: --Volume Target: MICROPHONE, Volume 9 +``` + +#### Voice Recognition + +You can type `hf vron;` to start the voice recognition of AG and type `hf vroff;` to terminate this function. For example, type `hf vron;` and log prints such as: + +``` +E (292432) CNSL: Command [hf vron;] +Start voice recognition +I (293172) BT_HF: APP HFP event: AT_RESPONSE +I (293172) BT_HF: --AT response event, code 0, cme 0 +E (293702) BT_BTM: btm_sco_connected, handle 181 +I (293702) BT_HF: APP HFP event: AUDIO_STATE_EVT +I (293702) BT_HF: --audio state connecte +``` + +#### Query Current Operator Name + +You can type `hf qop;` to query the current operator name and log prints like: + +``` +E (338322) CNSL: Command [hf qop;] +Query operator +I (339202) BT_HF: APP HFP event: CURRENT_OPERATOR_EVT +I (339202) BT_HF: --operator name: 中国联通 +I (339202) BT_HF: APP HFP event: AT_RESPONSE +I (339202) BT_HF: --AT response event, code 0, cme 0 +``` + +#### Retrieve Subscriber Information + +You can type `hf rs;` to retrieve subscriber information and log prints such as: + +``` +E (352902) CNSL: Command [hf rs;] +Retrieve subscriber information +I (353702) BT_HF: APP HFP event: SUBSCRIBER_INFO_EVT +I (353702) BT_HF: --subscriber type unknown, number 186xxxx5549 +I (353702) BT_HF: APP HFP event: AT_RESPONSE +I (353702) BT_HF: --AT response event, code 0, cme 0 +``` + +#### Query Current Call Status + +You can type `hf qc;` to query current call status and log prints like: + +``` +E (354522) CNSL: Command [hf qc;] +Query current call status +I (354582) BT_HF: APP HFP event: CLCC_EVT +I (354582) BT_HF: --Current call: idx 1, dir incoming, state active, mpty single, number 186xxxx5549 +I (354582) BT_HF: APP HFP event: AT_RESPONSE +I (354592) BT_HF: --AT response event, code 0, cme 0 +``` + +#### Transport DTMF Code + +You can type `hf k ;` to transport a DTMF code to AG. Log on HF unit side prints like:`send dtmf code: 9` and log on AG side prints such as: + +``` +I (196284) BT_APP_HF: APP HFP event: DTMF_RESPONSE_EVT +I (196284) BT_APP_HF: --DTMF code is: 9. +``` + +## Troubleshooting + +If you encounter any problems, please check if the following rules are followed: + +- You should type the command in the terminal according to the format described in the commands help table. +- Not all commands in the table are supported by AG device like _Hands Free Audio Gateway (hfp_ag)_ example from ESP-IDF. +- If you want to use `hf con;` to establish a service level connection with specific AG device, you should add the MAC address of the AG device in `bt_app.c`, for example: `esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93};` +- Use `esp_hf_client_register_callback()` and `esp_hf_client_init();` before establishing a service level connection. + +## Example Breakdown + +Due to the complexity of HFP, this example has more source files than other bluetooth examples. To show functions of HFP in a simple way, we use the Commands and Effects scheme to illustrate APIs of HFP in ESP-IDF. + +- The example will respond to user command through UART console. Please go to `console_uart.c` for the configuration details. +- For voice interface, ESP32 has provided PCM input/output signals which can be mapped to GPIO pins. So, please go to `gpio_pcm_config.c` for the configuration details. +- If you want to update the command table, please refer to `app_hf_msg_set.c`. +- If you want to update the command parse rules, please refer to `app_hf_msg_prs.c`. +- If you want to update the responses of HF Unit or want to update the log, please refer to `bt_app_hf.c`. +- Task configuration part is in `bt_app_core.c`. \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt new file mode 100644 index 00000000..eff05563 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/CMakeLists.txt @@ -0,0 +1,8 @@ +idf_component_register(SRCS "app_hf_msg_prs.c" + "app_hf_msg_set.c" + "bt_app_core.c" + "bt_app_hf.c" + "console_uart.c" + "gpio_pcm_config.c" + "main.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_prs.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_prs.c new file mode 100644 index 00000000..a69100ea --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_prs.c @@ -0,0 +1,167 @@ +/* + 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 +#include +#include +#include +#include +#include +#include "app_hf_msg_prs.h" +#include "app_hf_msg_set.h" + +// according to the design, message header length shall be no less than 2. +#define HF_MSG_HDR_LEN (3) +const static char hf_msg_hdr[HF_MSG_HDR_LEN] = {'h', 'f', ' '}; + +// according to the design, message header length shall be no less than 2. +#define HF_MSG_TAIL_LEN (1) +const static char hf_msg_tail[HF_MSG_TAIL_LEN] = {';'}; + +void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs) +{ + prs->state = HF_MSG_PRS_IDLE; + prs->cnt = 0; + prs->h_idx = 0; + prs->t_idx = 0; +} + +void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb) +{ + prs->callback = cb; +} + + hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs) + { + hf_msg_prs_err_t err = HF_MSG_PRS_ERR_IN_PROGRESS; + switch (prs->state) { + case HF_MSG_PRS_IDLE: + { + if (c == hf_msg_hdr[0]) { + prs->state = HF_MSG_PRS_HDR; + prs->buf[0] = c; + prs->cnt = 1; + prs->h_idx = 1; + } else { + err = HF_MSG_PRS_ERR_HDR_UNDETECTED; + } + break; + } + + case HF_MSG_PRS_HDR: + { + if (c == hf_msg_hdr[prs->h_idx]) { + prs->buf[prs->cnt++] = c; + if (++(prs->h_idx) == HF_MSG_HDR_LEN) { + prs->state = HF_MSG_PRS_PAYL; + prs->t_idx = 0; + } + } else { + hf_msg_parser_reset_state(prs); + err = HF_MSG_PRS_ERR_HDR_SYNC_FAILED; + } + break; + } + + case HF_MSG_PRS_PAYL: + { + prs->buf[prs->cnt++] = c; + if (c == hf_msg_tail[prs->t_idx]) { + if (++(prs->t_idx) == HF_MSG_TAIL_LEN) { + prs->buf[prs->cnt] = '\0'; + prs->callback(prs->buf, prs->cnt); + hf_msg_parser_reset_state(prs); + err = HF_MSG_PRS_ERR_OK; + break; + } + } else { + prs->t_idx = 0; + } + + if (prs->cnt >= HF_MSG_LEN_MAX) { + hf_msg_parser_reset_state(prs); + err = HF_MSG_PRS_ERR_BUF_OVERFLOW; + } + break; + } + } + return err; +} + + +void hf_msg_split_args(char *start, char *end, char **argv, int *argn) +{ + if (argn == NULL || *argn == 0) { + return; + } + + memset(argv, 0, (*argn) * sizeof(void *)); + + int max_argn = *argn; + *argn = 0; + + char *p = start; + for (int i = 0; i < max_argn; ++i) { + while (isspace((int)*p) && p != end) { + ++p; + } + if (p == end) { + return; + } + + argv[i] = p++; + ++(*argn); + + while (!isspace((int)*p) && p != end) { + ++p; + } + + if (p == end) { + return; + } else { + *p = '\0'; + ++p; + } + } +} + +void hf_msg_args_parser(char *buf, int len) +{ + char *argv[HF_MSG_ARGS_MAX]; + int argn = HF_MSG_ARGS_MAX; + char *start = buf + HF_MSG_HDR_LEN; + // set the command terminator to '\0' + char *end = buf + len - HF_MSG_TAIL_LEN; + *end = '\0'; + + hf_msg_split_args(start, end, argv, &argn); + + if (argn == 0) { + return; + } + + bool cmd_supported = false; + + hf_msg_hdl_t *cmd_tbl = hf_get_cmd_tbl(); + size_t cmd_tbl_size = hf_get_cmd_tbl_size(); + for (int i = 0; i < cmd_tbl_size; ++i) { + hf_msg_hdl_t *hdl = &cmd_tbl[i]; + if (strcmp(argv[0], hdl->str) == 0) { + if (hdl->handler) { + hdl->handler(argn, argv); + cmd_supported = true; + break; + } + } + } + if (!cmd_supported) { + printf("unsupported command\n"); + hf_msg_show_usage(); + } + return; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_prs.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_prs.h new file mode 100644 index 00000000..131a7a54 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_prs.h @@ -0,0 +1,48 @@ +/* + 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. +*/ + +#ifndef __APP_HF_MSG_PRS_H__ +#define __APP_HF_MSG_PRS_H__ + +typedef enum { + HF_MSG_PRS_ERR_OK = 0, // a complete message is finished + HF_MSG_PRS_ERR_IN_PROGRESS, // message parsing is in progress + HF_MSG_PRS_ERR_HDR_UNDETECTED, // header not detected + HF_MSG_PRS_ERR_HDR_SYNC_FAILED, // failed to sync header + HF_MSG_PRS_ERR_BUF_OVERFLOW, // exceeds the buffer size: HF_MSG_LEN_MAX +} hf_msg_prs_err_t; + +typedef enum { + HF_MSG_PRS_IDLE = 0, + HF_MSG_PRS_HDR, + HF_MSG_PRS_PAYL, +} hf_msg_prs_state_t; + +typedef void (*hf_msg_callback)(char *buf, int len); + +#define HF_MSG_LEN_MAX (128) + +typedef struct { + hf_msg_prs_state_t state; + char buf[HF_MSG_LEN_MAX + 1]; + int cnt; + int h_idx; + int t_idx; + hf_msg_callback callback; +} hf_msg_prs_cb_t; + +void hf_msg_parser_reset_state(hf_msg_prs_cb_t *prs); + +void hf_msg_parser_register_callback(hf_msg_prs_cb_t *prs, hf_msg_callback cb); + +hf_msg_prs_err_t hf_msg_parse(char c, hf_msg_prs_cb_t *prs); + +void hf_msg_show_usage(void); + +#endif /* __APP_HF_MSG_PRS_H__*/ + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_set.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_set.c new file mode 100644 index 00000000..5e5e80c7 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_set.c @@ -0,0 +1,267 @@ +/* + 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 +#include +#include "esp_hf_client_api.h" +#include "app_hf_msg_set.h" + +extern esp_bd_addr_t peer_addr; + +void hf_msg_show_usage(void) +{ + printf("########################################################################\n"); + printf("HF client command usage manual\n"); + printf("HF client commands begin with \"hf\" and end with \";\"\n"); + printf("Supported commands are as follows, arguments are embraced with < and >\n"); + printf("hf con; -- setup connection with peer device\n"); + printf("hf dis; -- release connection with peer device\n"); + printf("hf cona; -- setup audio connection with peer device\n"); + printf("hf disa; -- release connection with peer device\n"); + printf("hf qop; -- query current operator name\n"); + printf("hf qc; -- query current call status\n"); + printf("hf ac; -- answer incoming call\n"); + printf("hf rc; -- reject incoming call\n"); + printf("hf d ; -- dial , e.g. hf d 11223344\n"); + printf("hf rd; -- redial\n"); + printf("hf dm ; -- dial memory\n"); + printf("hf vron; -- start voice recognition\n"); + printf("hf vroff; -- stop voice recognition\n"); + printf("hf vu ; -- volume update\n"); + printf(" tgt: 0-speaker, 1-microphone\n"); + printf(" vol: volume gain ranges from 0 to 15\n"); + printf("hf rs; -- retrieve subscriber information\n"); + printf("hf rv; -- retrieve last voice tag number\n"); + printf("hf rh ; -- response and hold\n"); + printf(" btrh:\n"); + printf(" 0 - put call on hold,\n"); + printf(" 1 - accept the held call,\n"); + printf(" 2 -reject the held call\n"); + printf("hf k ; -- send dtmf code.\n"); + printf(" dtmf: single character in set 0-9, *, #, A-D\n"); + printf("hf h; -- show command manual\n"); + printf("########################################################################\n"); +} + +#define HF_CMD_HANDLER(cmd) static void hf_##cmd##_handler(int argn, char **argv) + + +HF_CMD_HANDLER(help) +{ + hf_msg_show_usage(); +} + +HF_CMD_HANDLER(conn) +{ + printf("connect\n"); + esp_hf_client_connect(peer_addr); +} + +HF_CMD_HANDLER(disc) +{ + printf("disconnect\n"); + esp_hf_client_disconnect(peer_addr); +} + +HF_CMD_HANDLER(conn_audio) +{ + printf("connect audio\n"); + esp_hf_client_connect_audio(peer_addr); +} + +HF_CMD_HANDLER(disc_audio) +{ + printf("disconnect audio\n"); + esp_hf_client_disconnect_audio(peer_addr); +} + +HF_CMD_HANDLER(query_op) +{ + printf("Query operator\n"); + esp_hf_client_query_current_operator_name(); +} + +HF_CMD_HANDLER(answer) +{ + printf("Answer call\n"); + esp_hf_client_answer_call(); +} +HF_CMD_HANDLER(reject) +{ + printf("Reject call\n"); + esp_hf_client_reject_call(); +} + +HF_CMD_HANDLER(dial) +{ + if (argn != 2) { + printf("Insufficient number of arguments"); + } else { + printf("Dial number %s\n", argv[1]); + esp_hf_client_dial(argv[1]); + } +} + +HF_CMD_HANDLER(redial) +{ + printf("Dial number\n"); + esp_hf_client_dial(NULL); +} + +HF_CMD_HANDLER(dial_mem) +{ + if (argn != 2) { + printf("Insufficient number of arguments"); + return; + } + int index; + if (sscanf(argv[1], "%d", &index) != 1) { + printf("Invalid argument %s\n", argv[1]); + return; + } + + printf("Dial memory %d\n", index); + esp_hf_client_dial_memory(index); +} + + +HF_CMD_HANDLER(start_vr) +{ + printf("Start voice recognition\n"); + esp_hf_client_start_voice_recognition(); +} + +HF_CMD_HANDLER(stop_vr) +{ + printf("Stop voice recognition\n"); + esp_hf_client_stop_voice_recognition(); +} + +HF_CMD_HANDLER(volume_update) +{ + if (argn != 3) { + printf("Insufficient number of arguments"); + return; + } + int target, volume; + if (sscanf(argv[1], "%d", &target) != 1 || + (target != ESP_HF_VOLUME_CONTROL_TARGET_SPK && + target != ESP_HF_VOLUME_CONTROL_TARGET_MIC)) { + printf("Invalid argument for target %s\n", argv[1]); + return; + } + + if (sscanf(argv[2], "%d", &volume) != 1 || + (volume < 0 || volume > 15)) { + printf("Invalid argument for volume %s\n", argv[2]); + return; + } + + printf("volume update\n"); + esp_hf_client_volume_update(target, volume); +} + +HF_CMD_HANDLER(query_call) +{ + printf("Query current call status\n"); + esp_hf_client_query_current_calls(); +} + +HF_CMD_HANDLER(retrieve_subscriber) +{ + printf("Retrieve subscriber information\n"); + esp_hf_client_retrieve_subscriber_info(); +} + +HF_CMD_HANDLER(request_last_voice_tag) +{ + printf("Request last voice tag\n"); + esp_hf_client_request_last_voice_tag_number(); +} + +HF_CMD_HANDLER(btrh) +{ + if (argn != 2) { + printf("Insufficient number of arguments"); + return; + } + + int btrh; + if (sscanf(argv[1], "%d", &btrh) != 1) { + printf("Invalid argument %s\n", argv[1]); + return; + } + if (btrh < ESP_HF_BTRH_CMD_HOLD || btrh > ESP_HF_BTRH_CMD_REJECT) { + printf("Invalid argument %s\n", argv[1]); + return; + } + printf("respond and hold command: %d\n", btrh); + esp_hf_client_send_btrh_cmd(btrh); +} + +static bool is_dtmf_code(char c) +{ + if (c >= '0' && c <= '9') { + return true; + } + if (c == '#' || c == '*') { + return true; + } + if (c >= 'A' && c <= 'D') { + return true; + } + return false; +} + +HF_CMD_HANDLER(dtmf) +{ + if (argn != 2) { + printf("Insufficient number of arguments"); + return; + } + + if (strlen(argv[1]) != 1 || !is_dtmf_code(argv[1][0])) { + printf("Invalid argument %s\n", argv[1]); + return; + } + + printf("send dtmf code: %s\n", argv[1]); + esp_hf_client_send_dtmf(argv[1][0]); +} + +static hf_msg_hdl_t hf_cmd_tbl[] = { + {0, "h", hf_help_handler}, + {5, "con", hf_conn_handler}, + {10, "dis", hf_disc_handler}, + {20, "cona", hf_conn_audio_handler}, + {30, "disa", hf_disc_audio_handler}, + {40, "qop", hf_query_op_handler}, + {120, "qc", hf_query_call_handler}, + {50, "ac", hf_answer_handler}, + {60, "rc", hf_reject_handler}, + {70, "d", hf_dial_handler}, + {80, "rd", hf_redial_handler}, + {85, "dm", hf_dial_mem_handler}, + {90, "vron", hf_start_vr_handler}, + {100, "vroff", hf_stop_vr_handler}, + {110, "vu", hf_volume_update_handler}, + {130, "rs", hf_retrieve_subscriber_handler}, + {140, "rv", hf_request_last_voice_tag_handler}, + {150, "rh", hf_btrh_handler}, + {160, "k", hf_dtmf_handler}, +}; + +hf_msg_hdl_t *hf_get_cmd_tbl(void) +{ + return hf_cmd_tbl; +} + +size_t hf_get_cmd_tbl_size(void) +{ + return sizeof(hf_cmd_tbl) / sizeof(hf_msg_hdl_t); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_set.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_set.h new file mode 100644 index 00000000..d7a43854 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/app_hf_msg_set.h @@ -0,0 +1,26 @@ +/* + 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. +*/ + +#ifndef __APP_HF_MSG_SET_H__ +#define __APP_HF_MSG_SET_H__ + +#define HF_MSG_ARGS_MAX (5) + +typedef void (* hf_cmd_handler)(int argn, char **argv); + +typedef struct { + uint16_t opcode; + const char *str; + hf_cmd_handler handler; +} hf_msg_hdl_t; + +extern hf_msg_hdl_t *hf_get_cmd_tbl(void); +extern size_t hf_get_cmd_tbl_size(void); + +void hf_msg_show_usage(void); +#endif /* __APP_HF_MSG_SET_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_core.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_core.c new file mode 100644 index 00000000..8e30c535 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_core.c @@ -0,0 +1,116 @@ +/* + 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 +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle bt_app_task_queue = NULL; +static xTaskHandle bt_app_task_handle = NULL; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + { + bt_app_work_dispatched(&msg); + break; + } + + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } // switch (msg.sig) + + if (msg.param) { + free(msg.param); + } + } + } +} + +void bt_app_task_start_up(void) +{ + bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppT", 2048, NULL, configMAX_PRIORITIES - 3, &bt_app_task_handle); + return; +} + +void bt_app_task_shut_down(void) +{ + if (bt_app_task_handle) { + vTaskDelete(bt_app_task_handle); + bt_app_task_handle = NULL; + } + if (bt_app_task_queue) { + vQueueDelete(bt_app_task_queue); + bt_app_task_queue = NULL; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_core.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_core.h new file mode 100644 index 00000000..86c95ee1 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_core.h @@ -0,0 +1,47 @@ +/* + 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. +*/ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +#define BT_APP_CORE_TAG "BT_APP_CORE" + +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be the last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback); + +void bt_app_task_start_up(void); + +void bt_app_task_shut_down(void); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c new file mode 100644 index 00000000..f984395a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.c @@ -0,0 +1,402 @@ +/* + 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 esp_hf_ag_apiied. +*/ + +#include +#include +#include +#include +#include "esp_log.h" + +#include "bt_app_core.h" +#include "bt_app_hf.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_hf_client_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "freertos/semphr.h" +#include "freertos/ringbuf.h" +#include "time.h" +#include "sys/time.h" +#include "sdkconfig.h" + +const char *c_hf_evt_str[] = { + "CONNECTION_STATE_EVT", /*!< connection state changed event */ + "AUDIO_STATE_EVT", /*!< audio connection state change event */ + "VR_STATE_CHANGE_EVT", /*!< voice recognition state changed */ + "CALL_IND_EVT", /*!< call indication event */ + "CALL_SETUP_IND_EVT", /*!< call setup indication event */ + "CALL_HELD_IND_EVT", /*!< call held indicator event */ + "NETWORK_STATE_EVT", /*!< network state change event */ + "SIGNAL_STRENGTH_IND_EVT", /*!< signal strength indication event */ + "ROAMING_STATUS_IND_EVT", /*!< roaming status indication event */ + "BATTERY_LEVEL_IND_EVT", /*!< battery level indication event */ + "CURRENT_OPERATOR_EVT", /*!< current operator name event */ + "RESP_AND_HOLD_EVT", /*!< response and hold event */ + "CLIP_EVT", /*!< Calling Line Identification notification event */ + "CALL_WAITING_EVT", /*!< call waiting notification */ + "CLCC_EVT", /*!< listing current calls event */ + "VOLUME_CONTROL_EVT", /*!< audio volume control event */ + "AT_RESPONSE", /*!< audio volume control event */ + "SUBSCRIBER_INFO_EVT", /*!< subscriber information event */ + "INBAND_RING_TONE_EVT", /*!< in-band ring tone settings */ + "LAST_VOICE_TAG_NUMBER_EVT", /*!< requested number from AG event */ + "RING_IND_EVT", /*!< ring indication event */ +}; + +// esp_hf_client_connection_state_t +const char *c_connection_state_str[] = { + "disconnected", + "connecting", + "connected", + "slc_connected", + "disconnecting", +}; + +// esp_hf_client_audio_state_t +const char *c_audio_state_str[] = { + "disconnected", + "connecting", + "connected", + "connected_msbc", +}; + +/// esp_hf_vr_state_t +const char *c_vr_state_str[] = { + "disabled", + "enabled", +}; + +// esp_hf_service_availability_status_t +const char *c_service_availability_status_str[] = { + "unavailable", + "available", +}; + +// esp_hf_roaming_status_t +const char *c_roaming_status_str[] = { + "inactive", + "active", +}; + +// esp_hf_client_call_state_t +const char *c_call_str[] = { + "NO call in progress", + "call in progress", +}; + +// esp_hf_client_callsetup_t +const char *c_call_setup_str[] = { + "NONE", + "INCOMING", + "OUTGOING_DIALING", + "OUTGOING_ALERTING" +}; + +// esp_hf_client_callheld_t +const char *c_call_held_str[] = { + "NONE held", + "Held and Active", + "Held", +}; + +// esp_hf_response_and_hold_status_t +const char *c_resp_and_hold_str[] = { + "HELD", + "HELD ACCEPTED", + "HELD REJECTED", +}; + +// esp_hf_client_call_direction_t +const char *c_call_dir_str[] = { + "outgoing", + "incoming", +}; + +// esp_hf_client_call_state_t +const char *c_call_state_str[] = { + "active", + "held", + "dialing", + "alerting", + "incoming", + "waiting", + "held_by_resp_hold", +}; + +// esp_hf_current_call_mpty_type_t +const char *c_call_mpty_type_str[] = { + "single", + "multi", +}; + +// esp_hf_volume_control_target_t +const char *c_volume_control_target_str[] = { + "SPEAKER", + "MICROPHONE" +}; + +// esp_hf_at_response_code_t +const char *c_at_response_code_str[] = { + "OK", + "ERROR" + "ERR_NO_CARRIER", + "ERR_BUSY", + "ERR_NO_ANSWER", + "ERR_DELAYED", + "ERR_BLACKLILSTED", + "ERR_CME", +}; + +// esp_hf_subscriber_service_type_t +const char *c_subscriber_service_type_str[] = { + "unknown", + "voice", + "fax", +}; + +// esp_hf_client_in_band_ring_state_t +const char *c_inband_ring_state_str[] = { + "NOT provided", + "Provided", +}; + +// esp_bd_addr_t peer_addr; +// If you want to connect a specific device, add it's address here +esp_bd_addr_t peer_addr = {0xb4, 0xe6, 0x2d, 0xeb, 0x09, 0x93}; + +#if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI + +#define ESP_HFP_RINGBUF_SIZE 3600 +static RingbufHandle_t m_rb = NULL; + +static void bt_app_hf_client_audio_open(void) +{ + m_rb = xRingbufferCreate(ESP_HFP_RINGBUF_SIZE, RINGBUF_TYPE_BYTEBUF); +} + +static void bt_app_hf_client_audio_close(void) +{ + if (!m_rb) { + return ; + } + + vRingbufferDelete(m_rb); +} + +static uint32_t bt_app_hf_client_outgoing_cb(uint8_t *p_buf, uint32_t sz) +{ + if (!m_rb) { + return 0; + } + + size_t item_size = 0; + uint8_t *data = xRingbufferReceiveUpTo(m_rb, &item_size, 0, sz); + if (item_size == sz) { + memcpy(p_buf, data, item_size); + vRingbufferReturnItem(m_rb, data); + return sz; + } else if (0 < item_size) { + vRingbufferReturnItem(m_rb, data); + return 0; + } else { + // data not enough, do not read + return 0; + } +} + +static void bt_app_hf_client_incoming_cb(const uint8_t *buf, uint32_t sz) +{ + if (! m_rb) { + return; + } + BaseType_t done = xRingbufferSend(m_rb, (uint8_t *)buf, sz, 0); + if (! done) { + ESP_LOGE(BT_HF_TAG, "rb send fail"); + } + + esp_hf_client_outgoing_data_ready(); +} +#endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */ + +/* callback for HF_CLIENT */ +void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param) +{ + if (event <= ESP_HF_CLIENT_RING_IND_EVT) { + ESP_LOGI(BT_HF_TAG, "APP HFP event: %s", c_hf_evt_str[event]); + } else { + ESP_LOGE(BT_HF_TAG, "APP HFP invalid event %d", event); + } + + switch (event) { + case ESP_HF_CLIENT_CONNECTION_STATE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--connection state %s, peer feats 0x%x, chld_feats 0x%x", + c_connection_state_str[param->conn_stat.state], + param->conn_stat.peer_feat, + param->conn_stat.chld_feat); + memcpy(peer_addr,param->conn_stat.remote_bda,ESP_BD_ADDR_LEN); + break; + } + + case ESP_HF_CLIENT_AUDIO_STATE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--audio state %s", + c_audio_state_str[param->audio_stat.state]); + #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI + if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED || + param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_CONNECTED_MSBC) { + esp_hf_client_register_data_callback(bt_app_hf_client_incoming_cb, + bt_app_hf_client_outgoing_cb); + bt_app_hf_client_audio_open(); + } else if (param->audio_stat.state == ESP_HF_CLIENT_AUDIO_STATE_DISCONNECTED) { + bt_app_hf_client_audio_close(); + } + #endif /* #if CONFIG_BTDM_CTRL_BR_EDR_SCO_DATA_PATH_HCI */ + break; + } + + case ESP_HF_CLIENT_BVRA_EVT: + { + ESP_LOGI(BT_HF_TAG, "--VR state %s", + c_vr_state_str[param->bvra.value]); + break; + } + + case ESP_HF_CLIENT_CIND_SERVICE_AVAILABILITY_EVT: + { + ESP_LOGI(BT_HF_TAG, "--NETWORK STATE %s", + c_service_availability_status_str[param->service_availability.status]); + break; + } + + case ESP_HF_CLIENT_CIND_ROAMING_STATUS_EVT: + { + ESP_LOGI(BT_HF_TAG, "--ROAMING: %s", + c_roaming_status_str[param->roaming.status]); + break; + } + + case ESP_HF_CLIENT_CIND_SIGNAL_STRENGTH_EVT: + { + ESP_LOGI(BT_HF_TAG, "-- signal strength: %d", + param->signal_strength.value); + break; + } + + case ESP_HF_CLIENT_CIND_BATTERY_LEVEL_EVT: + { + ESP_LOGI(BT_HF_TAG, "--battery level %d", + param->battery_level.value); + break; + } + + case ESP_HF_CLIENT_COPS_CURRENT_OPERATOR_EVT: + { + ESP_LOGI(BT_HF_TAG, "--operator name: %s", + param->cops.name); + break; + } + + case ESP_HF_CLIENT_CIND_CALL_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Call indicator %s", + c_call_str[param->call.status]); + break; + } + + case ESP_HF_CLIENT_CIND_CALL_SETUP_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Call setup indicator %s", + c_call_setup_str[param->call_setup.status]); + break; + } + + case ESP_HF_CLIENT_CIND_CALL_HELD_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Call held indicator %s", + c_call_held_str[param->call_held.status]); + break; + } + + case ESP_HF_CLIENT_BTRH_EVT: + { + ESP_LOGI(BT_HF_TAG, "--response and hold %s", + c_resp_and_hold_str[param->btrh.status]); + break; + } + + case ESP_HF_CLIENT_CLIP_EVT: + { + ESP_LOGI(BT_HF_TAG, "--clip number %s", + (param->clip.number == NULL) ? "NULL" : (param->clip.number)); + break; + } + + case ESP_HF_CLIENT_CCWA_EVT: + { + ESP_LOGI(BT_HF_TAG, "--call_waiting %s", + (param->ccwa.number == NULL) ? "NULL" : (param->ccwa.number)); + break; + } + + case ESP_HF_CLIENT_CLCC_EVT: + { + ESP_LOGI(BT_HF_TAG, "--Current call: idx %d, dir %s, state %s, mpty %s, number %s", + param->clcc.idx, + c_call_dir_str[param->clcc.dir], + c_call_state_str[param->clcc.status], + c_call_mpty_type_str[param->clcc.mpty], + (param->clcc.number == NULL) ? "NULL" : (param->clcc.number)); + break; + } + + case ESP_HF_CLIENT_VOLUME_CONTROL_EVT: + { + ESP_LOGI(BT_HF_TAG, "--volume_target: %s, volume %d", + c_volume_control_target_str[param->volume_control.type], + param->volume_control.volume); + break; + } + + case ESP_HF_CLIENT_AT_RESPONSE_EVT: + { + ESP_LOGI(BT_HF_TAG, "--AT response event, code %d, cme %d", + param->at_response.code, param->at_response.cme); + break; + } + + case ESP_HF_CLIENT_CNUM_EVT: + { + ESP_LOGI(BT_HF_TAG, "--subscriber type %s, number %s", + c_subscriber_service_type_str[param->cnum.type], + (param->cnum.number == NULL) ? "NULL" : param->cnum.number); + break; + } + + case ESP_HF_CLIENT_BSIR_EVT: + { + ESP_LOGI(BT_HF_TAG, "--inband ring state %s", + c_inband_ring_state_str[param->bsir.state]); + break; + } + + case ESP_HF_CLIENT_BINP_EVT: + { + ESP_LOGI(BT_HF_TAG, "--last voice tag number: %s", + (param->binp.number == NULL) ? "NULL" : param->binp.number); + break; + } + + default: + ESP_LOGE(BT_HF_TAG, "HF_CLIENT EVT: %d", event); + break; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.h new file mode 100644 index 00000000..a97980cf --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/bt_app_hf.h @@ -0,0 +1,24 @@ +/* + 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. +*/ + +#ifndef __BT_APP_HF_H__ +#define __BT_APP_HF_H__ + +#include +#include "esp_hf_client_api.h" + + +#define BT_HF_TAG "BT_HF" + +/** + * @brief callback function for HF client + */ +void bt_app_hf_client_cb(esp_hf_client_cb_event_t event, esp_hf_client_cb_param_t *param); + + +#endif /* __BT_APP_HF_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/component.mk new file mode 100644 index 00000000..088dacf5 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding include to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/console_uart.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/console_uart.c new file mode 100644 index 00000000..ee004860 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/console_uart.c @@ -0,0 +1,119 @@ +/* + 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 "driver/uart.h" +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_log.h" +#include "console_uart.h" +#include "app_hf_msg_prs.h" + +#define CONSOLE_UART_NUM UART_NUM_0 + +static QueueHandle_t uart_queue; +static hf_msg_prs_cb_t hf_msg_parser; + +static const uart_config_t uart_cfg = { + .baud_rate = 115200, //1.5M + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, + .rx_flow_ctrl_thresh = 127, +}; + +extern void hf_msg_args_parser(char *buf, int len); + +void hf_msg_handler(char *buf, int len) +{ + ESP_LOGE(TAG_CNSL, "Command [%s]", buf); + hf_msg_args_parser(buf, len); +} + +static void console_uart_task(void *pvParameters) +{ + int len; + uart_event_t event; + hf_msg_prs_cb_t *parser = &hf_msg_parser; + hf_msg_parser_reset_state(parser); + hf_msg_parser_register_callback(parser, hf_msg_handler); + hf_msg_show_usage(); +#define TMP_BUF_LEN 128 + uint8_t tmp_buf[128] = {0}; + + for (;;) { + //Waiting for UART event. + if (xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY)) { + switch (event.type) { + //Event of UART receving data + case UART_DATA: + { + len = uart_read_bytes(CONSOLE_UART_NUM, tmp_buf, TMP_BUF_LEN, 0); + for (int i = 0; i < len; i++) { + hf_msg_parse(tmp_buf[i], parser); + } + break; + } + //Event of HW FIFO overflow detected + case UART_FIFO_OVF: + { + ESP_LOGI(TAG_CNSL, "hw fifo overflow"); + break; + } + //Event of UART ring buffer full + case UART_BUFFER_FULL: + { + ESP_LOGI(TAG_CNSL, "ring buffer full"); + break; + } + //Event of UART RX break detected + case UART_BREAK: + { + ESP_LOGI(TAG_CNSL, "uart rx break"); + break; + } + //Event of UART parity check error + case UART_PARITY_ERR: + { + ESP_LOGI(TAG_CNSL, "uart parity error"); + break; + } + //Event of UART frame error + case UART_FRAME_ERR: + { + ESP_LOGI(TAG_CNSL, "uart frame error"); + break; + } + //Others + default: + break; + } + } + } + vTaskDelete(NULL); +} + + +esp_err_t console_uart_init(void) +{ + esp_err_t ret; + + ret = uart_param_config(CONSOLE_UART_NUM, &uart_cfg); + if (ret != ESP_OK) { + ESP_LOGE(TAG_CNSL, "Uart %d initialize err %04x", CONSOLE_UART_NUM, ret); + return ret; + } + + uart_set_pin(CONSOLE_UART_NUM, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE); + uart_driver_install(CONSOLE_UART_NUM, 1024, 1024, 8, &uart_queue, 0); + xTaskCreate(console_uart_task, "uTask", 2048, NULL, 8, NULL); + + return ESP_OK; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/console_uart.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/console_uart.h new file mode 100644 index 00000000..753950de --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/console_uart.h @@ -0,0 +1,19 @@ +/* + 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. +*/ + +#ifndef __CONSOLE_UART_H__ +#define __CONSOLE_UART_H__ + +#define TAG_CNSL "CNSL" + +/** + * @brief configure uart console for command input and process + */ +esp_err_t console_uart_init(void); + +#endif /* __BT_APP_HF_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/gpio_pcm_config.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/gpio_pcm_config.c new file mode 100644 index 00000000..e56da157 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/main/gpio_pcm_config.c @@ -0,0 +1,92 @@ +/* + 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 "driver/gpio.h" +#include "soc/gpio_reg.h" +#include "soc/gpio_sig_map.h" +#include "gpio_pcm_config.h" + +#define GPIO_OUTPUT_PCM_FSYNC (25) +#define GPIO_OUTPUT_PCM_CLK_OUT (5) +#define GPIO_OUTPUT_PCM_DOUT (26) +#define GPIO_INPUT_PCM_DIN (35) + +#define GPIO_OUTPUT_PCM_PIN_SEL ((1ULL< +#include +#include +#include +#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.h" +#include "bt_app_core.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_hf_client_api.h" +#include "bt_app_hf.h" +#include "gpio_pcm_config.h" +#include "console_uart.h" + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; + +/* handler for bluetooth stack enabled events */ +static void bt_hf_client_hdl_stack_evt(uint16_t event, void *p_param); + +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) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_BLE)); + + esp_err_t err; + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((err = esp_bt_controller_enable(ESP_BT_MODE_CLASSIC_BT)) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((err = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(BT_HF_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_hf_client_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + + /* initialize console via UART */ + console_uart_init(); + + /* configure the PCM interface and PINs used */ + app_gpio_pcm_io_cfg(); + + /* configure externel chip for acoustic echo cancellation */ +#if ACOUSTIC_ECHO_CANCELLATION_ENABLE + app_gpio_aec_io_cfg(); +#endif /* ACOUSTIC_ECHO_CANCELLATION_ENABLE */ +} + + +static void bt_hf_client_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_HF_TAG, "%s evt %d", __func__, event); + switch (event) { + case BT_APP_EVT_STACK_UP: { + /* set up device name */ + char *dev_name = "ESP_HFP_HF"; + esp_bt_dev_set_device_name(dev_name); + + esp_hf_client_register_callback(bt_app_hf_client_cb); + esp_hf_client_init(); + + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; + esp_bt_pin_code_t pin_code; + pin_code[0] = '0'; + pin_code[1] = '0'; + pin_code[2] = '0'; + pin_code[3] = '0'; + esp_bt_gap_set_pin(pin_type, 4, pin_code); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + default: + ESP_LOGE(BT_HF_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults new file mode 100644 index 00000000..115339e2 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/classic_bt/hfp_hf/sdkconfig.defaults @@ -0,0 +1,9 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BT_BLE_ENABLED=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_HFP_ENABLE=y +CONFIG_BT_HFP_CLIENT_ENABLE=y \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/CMakeLists.txt new file mode 100644 index 00000000..3c9fa474 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(bt_ble_coex) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/Makefile new file mode 100644 index 00000000..4c3cfbe0 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/Makefile @@ -0,0 +1,9 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := bt_ble_coex + +include $(IDF_PATH)/make/project.mk + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/README.md new file mode 100644 index 00000000..d5a3be3b --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/README.md @@ -0,0 +1,10 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF A2DP-GATTS_COEX demo +====================== +This demo showcases APIs to create a GATT service and A2DP profile and demonstrates BLE and classic Bluetooth coexistence. + +The BLE GATT SERVER part of demo creates a GATT service and then starts advertising, waiting to be connected by a GATT client. After the program is started, the GATT client can discover a device named "ESP_COEX_BLE_DEMO". Once a connection is established, GATT client can read or write data to the device. Or GATT client can receive notification or indication data. + +The classic bluetooth A2DP part of the demo implements Advanced Audio Distribution Profile to receive an audio stream. After the program is started, other bluetooth devices such as smart phones can discover a device named "ESP_COEX_A2DP_DEMO". Once a connection is established, audio data can be transmitted. This will be visible in the application log including a count of audio data packets. \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/CMakeLists.txt new file mode 100644 index 00000000..d1d916ef --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register(SRCS "bt_app_av.c" + "bt_app_core.c" + "main.c" + INCLUDE_DIRS ".") + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/Kconfig.projbuild b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/Kconfig.projbuild new file mode 100644 index 00000000..73f6d669 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/Kconfig.projbuild @@ -0,0 +1,42 @@ +menu "A2DP Example Configuration" + + choice EXAMPLE_A2DP_SINK_OUTPUT + prompt "A2DP Sink Output" + default EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + Select to use Internal DAC or external I2S driver + + config EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC + bool "Internal DAC" + help + Select this to use Internal DAC sink output + + config EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + bool "External I2S Codec" + help + Select this to use External I2S sink output + + endchoice + + config EXAMPLE_I2S_LRCK_PIN + int "I2S LRCK (WS) GPIO" + default 22 + depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + GPIO number to use for I2S LRCK(WS) Driver. + + config EXAMPLE_I2S_BCK_PIN + int "I2S BCK GPIO" + default 26 + depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + GPIO number to use for I2S BCK Driver. + + config EXAMPLE_I2S_DATA_PIN + int "I2S DATA GPIO" + default 25 + depends on EXAMPLE_A2DP_SINK_OUTPUT_EXTERNAL_I2S + help + GPIO number to use for I2S Data Driver. + +endmenu diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_av.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_av.c new file mode 100644 index 00000000..17c2edbb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_av.c @@ -0,0 +1,361 @@ + +/* + 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 +#include +#include +#include +#include "esp_log.h" + +#include "bt_app_core.h" +#include "bt_app_av.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/i2s.h" + +#include "sys/lock.h" + +// AVRCP used transaction label +#define APP_RC_CT_TL_GET_CAPS (0) +#define APP_RC_CT_TL_GET_META_DATA (1) +#define APP_RC_CT_TL_RN_TRACK_CHANGE (2) +#define APP_RC_CT_TL_RN_PLAYBACK_CHANGE (3) +#define APP_RC_CT_TL_RN_PLAY_POS_CHANGE (4) + +/* a2dp event handler */ +static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param); +/* avrc CT event handler */ +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param); +/* avrc TG event handler */ +static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param); + +static uint32_t s_pkt_cnt = 0; +static esp_a2d_audio_state_t s_audio_state = ESP_A2D_AUDIO_STATE_STOPPED; +static const char *s_a2d_conn_state_str[] = {"Disconnected", "Connecting", "Connected", "Disconnecting"}; +static const char *s_a2d_audio_state_str[] = {"Suspended", "Stopped", "Started"}; +static esp_avrc_rn_evt_cap_mask_t s_avrc_peer_rn_cap; +static _lock_t s_volume_lock; +static xTaskHandle s_vcs_task_hdl = NULL; +static uint8_t s_volume = 0; +static bool s_volume_notify; + +/* callback for A2DP sink */ +void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param) +{ + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: + case ESP_A2D_AUDIO_STATE_EVT: + case ESP_A2D_AUDIO_CFG_EVT: { + bt_app_work_dispatch(bt_av_hdl_a2d_evt, event, param, sizeof(esp_a2d_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_AV_TAG, "Invalid A2DP event: %d", event); + break; + } +} + +void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len) +{ + write_ringbuf(data, len); + if (++s_pkt_cnt % 100 == 0) { + ESP_LOGI(BT_AV_TAG, "Audio packet count %u", s_pkt_cnt); + } +} + +void bt_app_alloc_meta_buffer(esp_avrc_ct_cb_param_t *param) +{ + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(param); + uint8_t *attr_text = (uint8_t *) malloc (rc->meta_rsp.attr_length + 1); + memcpy(attr_text, rc->meta_rsp.attr_text, rc->meta_rsp.attr_length); + attr_text[rc->meta_rsp.attr_length] = 0; + + rc->meta_rsp.attr_text = attr_text; +} + +void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_CT_METADATA_RSP_EVT: + bt_app_alloc_meta_buffer(param); + /* fall through */ + case ESP_AVRC_CT_CONNECTION_STATE_EVT: + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + bt_app_work_dispatch(bt_av_hdl_avrc_ct_evt, event, param, sizeof(esp_avrc_ct_cb_param_t), NULL); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param) +{ + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: + bt_app_work_dispatch(bt_av_hdl_avrc_tg_evt, event, param, sizeof(esp_avrc_tg_cb_param_t), NULL); + break; + default: + ESP_LOGE(BT_RC_TG_TAG, "Invalid AVRC event: %d", event); + break; + } +} + +static void bt_av_hdl_a2d_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_AV_TAG, "%s evt %d", __func__, event); + esp_a2d_cb_param_t *a2d = NULL; + switch (event) { + case ESP_A2D_CONNECTION_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + uint8_t *bda = a2d->conn_stat.remote_bda; + ESP_LOGI(BT_AV_TAG, "A2DP connection state: %s, [%02x:%02x:%02x:%02x:%02x:%02x]", + s_a2d_conn_state_str[a2d->conn_stat.state], bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_DISCONNECTED) { + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + bt_i2s_task_shut_down(); + } else if (a2d->conn_stat.state == ESP_A2D_CONNECTION_STATE_CONNECTED){ + esp_bt_gap_set_scan_mode(ESP_BT_NON_CONNECTABLE, ESP_BT_NON_DISCOVERABLE); + bt_i2s_task_start_up(); + } + break; + } + case ESP_A2D_AUDIO_STATE_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + ESP_LOGI(BT_AV_TAG, "A2DP audio state: %s", s_a2d_audio_state_str[a2d->audio_stat.state]); + s_audio_state = a2d->audio_stat.state; + if (ESP_A2D_AUDIO_STATE_STARTED == a2d->audio_stat.state) { + s_pkt_cnt = 0; + } + break; + } + case ESP_A2D_AUDIO_CFG_EVT: { + a2d = (esp_a2d_cb_param_t *)(p_param); + ESP_LOGI(BT_AV_TAG, "A2DP audio stream configuration, codec type %d", a2d->audio_cfg.mcc.type); + // for now only SBC stream is supported + if (a2d->audio_cfg.mcc.type == ESP_A2D_MCT_SBC) { + int sample_rate = 16000; + char oct0 = a2d->audio_cfg.mcc.cie.sbc[0]; + if (oct0 & (0x01 << 6)) { + sample_rate = 32000; + } else if (oct0 & (0x01 << 5)) { + sample_rate = 44100; + } else if (oct0 & (0x01 << 4)) { + sample_rate = 48000; + } + i2s_set_clk(0, sample_rate, 16, 2); + + ESP_LOGI(BT_AV_TAG, "Configure audio player %x-%x-%x-%x", + a2d->audio_cfg.mcc.cie.sbc[0], + a2d->audio_cfg.mcc.cie.sbc[1], + a2d->audio_cfg.mcc.cie.sbc[2], + a2d->audio_cfg.mcc.cie.sbc[3]); + ESP_LOGI(BT_AV_TAG, "Audio player configured, sample rate=%d", sample_rate); + } + break; + } + default: + ESP_LOGE(BT_AV_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void bt_av_new_track(void) +{ + // request metadata + uint8_t attr_mask = ESP_AVRC_MD_ATTR_TITLE | ESP_AVRC_MD_ATTR_ARTIST | ESP_AVRC_MD_ATTR_ALBUM | ESP_AVRC_MD_ATTR_GENRE; + esp_avrc_ct_send_metadata_cmd(APP_RC_CT_TL_GET_META_DATA, attr_mask); + + // register notification if peer support the event_id + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_TRACK_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_TRACK_CHANGE, ESP_AVRC_RN_TRACK_CHANGE, 0); + } +} + +static void bt_av_playback_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_PLAY_STATUS_CHANGE)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAYBACK_CHANGE, ESP_AVRC_RN_PLAY_STATUS_CHANGE, 0); + } +} + +static void bt_av_play_pos_changed(void) +{ + if (esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_TEST, &s_avrc_peer_rn_cap, + ESP_AVRC_RN_PLAY_POS_CHANGED)) { + esp_avrc_ct_send_register_notification_cmd(APP_RC_CT_TL_RN_PLAY_POS_CHANGE, ESP_AVRC_RN_PLAY_POS_CHANGED, 10); + } +} + +void bt_av_notify_evt_handler(uint8_t event_id, esp_avrc_rn_param_t *event_parameter) +{ + switch (event_id) { + case ESP_AVRC_RN_TRACK_CHANGE: + bt_av_new_track(); + break; + case ESP_AVRC_RN_PLAY_STATUS_CHANGE: + ESP_LOGI(BT_AV_TAG, "Playback status changed: 0x%x", event_parameter->playback); + bt_av_playback_changed(); + break; + case ESP_AVRC_RN_PLAY_POS_CHANGED: + ESP_LOGI(BT_AV_TAG, "Play position changed: %d-ms", event_parameter->play_pos); + bt_av_play_pos_changed(); + break; + } +} + +static void bt_av_hdl_avrc_ct_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_CT_TAG, "%s evt %d", __func__, event); + esp_avrc_ct_cb_param_t *rc = (esp_avrc_ct_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_CT_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_CT_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + + if (rc->conn_stat.connected) { + // get remote supported event_ids of peer AVRCP Target + esp_avrc_ct_send_get_rn_capabilities_cmd(APP_RC_CT_TL_GET_CAPS); + } else { + // clear peer notification capability record + s_avrc_peer_rn_cap.bits = 0; + } + break; + } + case ESP_AVRC_CT_PASSTHROUGH_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC passthrough rsp: key_code 0x%x, key_state %d", rc->psth_rsp.key_code, rc->psth_rsp.key_state); + break; + } + case ESP_AVRC_CT_METADATA_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC metadata rsp: attribute id 0x%x, %s", rc->meta_rsp.attr_id, rc->meta_rsp.attr_text); + free(rc->meta_rsp.attr_text); + break; + } + case ESP_AVRC_CT_CHANGE_NOTIFY_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC event notification: %d", rc->change_ntf.event_id); + bt_av_notify_evt_handler(rc->change_ntf.event_id, &rc->change_ntf.event_parameter); + break; + } + case ESP_AVRC_CT_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "AVRC remote features %x, TG features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.tg_feat_flag); + break; + } + case ESP_AVRC_CT_GET_RN_CAPABILITIES_RSP_EVT: { + ESP_LOGI(BT_RC_CT_TAG, "remote rn_cap: count %d, bitmask 0x%x", rc->get_rn_caps_rsp.cap_count, + rc->get_rn_caps_rsp.evt_set.bits); + s_avrc_peer_rn_cap.bits = rc->get_rn_caps_rsp.evt_set.bits; + bt_av_new_track(); + bt_av_playback_changed(); + bt_av_play_pos_changed(); + break; + } + default: + ESP_LOGE(BT_RC_CT_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +static void volume_set_by_controller(uint8_t volume) +{ + ESP_LOGI(BT_RC_TG_TAG, "Volume is set by remote controller %d%%\n", (uint32_t)volume * 100 / 0x7f); + _lock_acquire(&s_volume_lock); + s_volume = volume; + _lock_release(&s_volume_lock); +} + +static void volume_set_by_local_host(uint8_t volume) +{ + ESP_LOGI(BT_RC_TG_TAG, "Volume is set locally to: %d%%", (uint32_t)volume * 100 / 0x7f); + _lock_acquire(&s_volume_lock); + s_volume = volume; + _lock_release(&s_volume_lock); + + if (s_volume_notify) { + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_CHANGED, &rn_param); + s_volume_notify = false; + } +} + +static void volume_change_simulation(void *arg) +{ + ESP_LOGI(BT_RC_TG_TAG, "start volume change simulation"); + + for (;;) { + vTaskDelay(10000 / portTICK_RATE_MS); + + uint8_t volume = (s_volume + 5) & 0x7f; + volume_set_by_local_host(volume); + } +} + +static void bt_av_hdl_avrc_tg_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_RC_TG_TAG, "%s evt %d", __func__, event); + esp_avrc_tg_cb_param_t *rc = (esp_avrc_tg_cb_param_t *)(p_param); + switch (event) { + case ESP_AVRC_TG_CONNECTION_STATE_EVT: { + uint8_t *bda = rc->conn_stat.remote_bda; + ESP_LOGI(BT_RC_TG_TAG, "AVRC conn_state evt: state %d, [%02x:%02x:%02x:%02x:%02x:%02x]", + rc->conn_stat.connected, bda[0], bda[1], bda[2], bda[3], bda[4], bda[5]); + if (rc->conn_stat.connected) { + // create task to simulate volume change + xTaskCreate(volume_change_simulation, "vcsT", 2048, NULL, 5, &s_vcs_task_hdl); + } else { + vTaskDelete(s_vcs_task_hdl); + ESP_LOGI(BT_RC_TG_TAG, "Stop volume change simulation"); + } + break; + } + case ESP_AVRC_TG_PASSTHROUGH_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC passthrough cmd: key_code 0x%x, key_state %d", rc->psth_cmd.key_code, rc->psth_cmd.key_state); + break; + } + case ESP_AVRC_TG_SET_ABSOLUTE_VOLUME_CMD_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC set absolute volume: %d%%", (int)rc->set_abs_vol.volume * 100/ 0x7f); + volume_set_by_controller(rc->set_abs_vol.volume); + break; + } + case ESP_AVRC_TG_REGISTER_NOTIFICATION_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC register event notification: %d, param: 0x%x", rc->reg_ntf.event_id, rc->reg_ntf.event_parameter); + if (rc->reg_ntf.event_id == ESP_AVRC_RN_VOLUME_CHANGE) { + s_volume_notify = true; + esp_avrc_rn_param_t rn_param; + rn_param.volume = s_volume; + esp_avrc_tg_send_rn_rsp(ESP_AVRC_RN_VOLUME_CHANGE, ESP_AVRC_RN_RSP_INTERIM, &rn_param); + } + break; + } + case ESP_AVRC_TG_REMOTE_FEATURES_EVT: { + ESP_LOGI(BT_RC_TG_TAG, "AVRC remote features %x, CT features %x", rc->rmt_feats.feat_mask, rc->rmt_feats.ct_feat_flag); + break; + } + default: + ESP_LOGE(BT_RC_TG_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_av.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_av.h new file mode 100644 index 00000000..46342daa --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_av.h @@ -0,0 +1,40 @@ +/* + 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. +*/ + +#ifndef __BT_APP_AV_H__ +#define __BT_APP_AV_H__ + +#include +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" + +#define BT_AV_TAG "BT_AV" +#define BT_RC_TG_TAG "RCTG" +#define BT_RC_CT_TAG "RCCT" + +/** + * @brief callback function for A2DP sink + */ +void bt_app_a2d_cb(esp_a2d_cb_event_t event, esp_a2d_cb_param_t *param); + +/** + * @brief callback function for A2DP sink audio data stream + */ +void bt_app_a2d_data_cb(const uint8_t *data, uint32_t len); + +/** + * @brief callback function for AVRCP controller + */ +void bt_app_rc_ct_cb(esp_avrc_ct_cb_event_t event, esp_avrc_ct_cb_param_t *param); + +/** + * @brief callback function for AVRCP target + */ +void bt_app_rc_tg_cb(esp_avrc_tg_cb_event_t event, esp_avrc_tg_cb_param_t *param); + +#endif /* __BT_APP_AV_H__*/ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_core.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_core.c new file mode 100644 index 00000000..7dee9e7f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_core.c @@ -0,0 +1,166 @@ +/* + 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 +#include +#include +#include "freertos/xtensa_api.h" +#include "freertos/FreeRTOSConfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/queue.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "bt_app_core.h" +#include "driver/i2s.h" +#include "freertos/ringbuf.h" + +static void bt_app_task_handler(void *arg); +static bool bt_app_send_msg(bt_app_msg_t *msg); +static void bt_app_work_dispatched(bt_app_msg_t *msg); + +static xQueueHandle s_bt_app_task_queue = NULL; +static xTaskHandle s_bt_app_task_handle = NULL; +static xTaskHandle s_bt_i2s_task_handle = NULL; +static RingbufHandle_t s_ringbuf_i2s = NULL;; + +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback) +{ + ESP_LOGD(BT_APP_CORE_TAG, "%s event 0x%x, param len %d", __func__, event, param_len); + + bt_app_msg_t msg; + memset(&msg, 0, sizeof(bt_app_msg_t)); + + msg.sig = BT_APP_SIG_WORK_DISPATCH; + msg.event = event; + msg.cb = p_cback; + + if (param_len == 0) { + return bt_app_send_msg(&msg); + } else if (p_params && param_len > 0) { + if ((msg.param = malloc(param_len)) != NULL) { + memcpy(msg.param, p_params, param_len); + /* check if caller has provided a copy callback to do the deep copy */ + if (p_copy_cback) { + p_copy_cback(&msg, msg.param, p_params); + } + return bt_app_send_msg(&msg); + } + } + + return false; +} + +static bool bt_app_send_msg(bt_app_msg_t *msg) +{ + if (msg == NULL) { + return false; + } + + if (xQueueSend(s_bt_app_task_queue, msg, 10 / portTICK_RATE_MS) != pdTRUE) { + ESP_LOGE(BT_APP_CORE_TAG, "%s xQueue send failed", __func__); + return false; + } + return true; +} + +static void bt_app_work_dispatched(bt_app_msg_t *msg) +{ + if (msg->cb) { + msg->cb(msg->event, msg->param); + } +} + +static void bt_app_task_handler(void *arg) +{ + bt_app_msg_t msg; + for (;;) { + if (pdTRUE == xQueueReceive(s_bt_app_task_queue, &msg, (portTickType)portMAX_DELAY)) { + ESP_LOGD(BT_APP_CORE_TAG, "%s, sig 0x%x, 0x%x", __func__, msg.sig, msg.event); + switch (msg.sig) { + case BT_APP_SIG_WORK_DISPATCH: + bt_app_work_dispatched(&msg); + break; + default: + ESP_LOGW(BT_APP_CORE_TAG, "%s, unhandled sig: %d", __func__, msg.sig); + break; + } // switch (msg.sig) + + if (msg.param) { + free(msg.param); + } + } + } +} + +void bt_app_task_start_up(void) +{ + s_bt_app_task_queue = xQueueCreate(10, sizeof(bt_app_msg_t)); + xTaskCreate(bt_app_task_handler, "BtAppT", 3072, NULL, configMAX_PRIORITIES - 3, &s_bt_app_task_handle); + return; +} + +void bt_app_task_shut_down(void) +{ + if (s_bt_app_task_handle) { + vTaskDelete(s_bt_app_task_handle); + s_bt_app_task_handle = NULL; + } + if (s_bt_app_task_queue) { + vQueueDelete(s_bt_app_task_queue); + s_bt_app_task_queue = NULL; + } +} + +static void bt_i2s_task_handler(void *arg) +{ + uint8_t *data = NULL; + size_t item_size = 0; + size_t bytes_written = 0; + + for (;;) { + data = (uint8_t *)xRingbufferReceive(s_ringbuf_i2s, &item_size, (portTickType)portMAX_DELAY); + if (item_size != 0){ + i2s_write(0, data, item_size, &bytes_written, portMAX_DELAY); + vRingbufferReturnItem(s_ringbuf_i2s,(void *)data); + } + } +} + +void bt_i2s_task_start_up(void) +{ + s_ringbuf_i2s = xRingbufferCreate(8 * 1024, RINGBUF_TYPE_BYTEBUF); + if(s_ringbuf_i2s == NULL){ + return; + } + + xTaskCreate(bt_i2s_task_handler, "BtI2ST", 1024, NULL, configMAX_PRIORITIES - 3, &s_bt_i2s_task_handle); + return; +} + +void bt_i2s_task_shut_down(void) +{ + if (s_bt_i2s_task_handle) { + vTaskDelete(s_bt_i2s_task_handle); + s_bt_i2s_task_handle = NULL; + } + + if (s_ringbuf_i2s) { + vRingbufferDelete(s_ringbuf_i2s); + s_ringbuf_i2s = NULL; + } +} + +size_t write_ringbuf(const uint8_t *data, size_t size) +{ + BaseType_t done = xRingbufferSend(s_ringbuf_i2s, (void *)data, size, (portTickType)portMAX_DELAY); + if(done){ + return size; + } else { + return 0; + } +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_core.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_core.h new file mode 100644 index 00000000..eaa390de --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/bt_app_core.h @@ -0,0 +1,53 @@ +/* + 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. +*/ + +#ifndef __BT_APP_CORE_H__ +#define __BT_APP_CORE_H__ + +#include +#include +#include + +#define BT_APP_CORE_TAG "BT_APP_CORE" + +#define BT_APP_SIG_WORK_DISPATCH (0x01) + +/** + * @brief handler for the dispatched work + */ +typedef void (* bt_app_cb_t) (uint16_t event, void *param); + +/* message to be sent */ +typedef struct { + uint16_t sig; /*!< signal to bt_app_task */ + uint16_t event; /*!< message event id */ + bt_app_cb_t cb; /*!< context switch callback */ + void *param; /*!< parameter area needs to be last */ +} bt_app_msg_t; + +/** + * @brief parameter deep-copy function to be customized + */ +typedef void (* bt_app_copy_cb_t) (bt_app_msg_t *msg, void *p_dest, void *p_src); + +/** + * @brief work dispatcher for the application task + */ +bool bt_app_work_dispatch(bt_app_cb_t p_cback, uint16_t event, void *p_params, int param_len, bt_app_copy_cb_t p_copy_cback); + +void bt_app_task_start_up(void); + +void bt_app_task_shut_down(void); + +void bt_i2s_task_start_up(void); + +void bt_i2s_task_shut_down(void); + +size_t write_ringbuf(const uint8_t *data, size_t size); + +#endif /* __BT_APP_CORE_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/component.mk new file mode 100644 index 00000000..0b9d7585 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c new file mode 100644 index 00000000..e1cb81b9 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/main/main.c @@ -0,0 +1,762 @@ +/* + 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. +*/ + +/**************************************************************************** +* The demo shows BLE and classic Bluetooth coexistence. You can use BLE GATT server and classic bluetooth A2DP together. +* The BLE GATT server part of the demo creates a GATT service and then starts advertising, waiting to be connected by a GATT client. +* After the program is started, a GATT client can discover the device named "ESP_COEX_BLE_DEMO". Once the connection is established, +* GATT client can read or write data to the device. It can also receive notification or indication data. +* Attention: If you test the demo with iPhone, BLE GATT server adv name will change to "ESP_COEX_A2DP_DEMO" after you connect it. +* The classic bluetooth A2DP part of the demo implements Advanced Audio Distribution Profile to receive an audio stream. +* After the program is started, other bluetooth devices such as smart phones can discover the device named "ESP_COEX_A2DP_DEMO". +* Once the connection is established, audio data can be transmitted. This will be visible in the application log including a count +* of audio data packets. +****************************************************************************/ + +#include +#include +#include +#include +#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.h" +#include "bt_app_core.h" +#include "bt_app_av.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "esp_gap_bt_api.h" +#include "esp_a2dp_api.h" +#include "esp_avrc_api.h" +#include "driver/i2s.h" + +#include "esp_gap_ble_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_gatt_common_api.h" + +#define BT_BLE_COEX_TAG "BT_BLE_COEX" +#define BT_DEVICE_NAME "ESP_COEX_A2DP_DEMO" +#define BLE_ADV_NAME "ESP_COEX_BLE_DEMO" + +#define GATTS_SERVICE_UUID_A 0x00FF +#define GATTS_CHAR_UUID_A 0xFF01 +#define GATTS_DESCR_UUID_A 0x3333 +#define GATTS_NUM_HANDLE_A 4 + +#define GATTS_SERVICE_UUID_B 0x00EE +#define GATTS_CHAR_UUID_B 0xEE01 +#define GATTS_DESCR_UUID_B 0x2222 +#define GATTS_NUM_HANDLE_B 4 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 +#define PREPARE_BUF_MAX_SIZE 1024 +#define PROFILE_NUM 2 +#define PROFILE_A_APP_ID 0 +#define PROFILE_B_APP_ID 1 + +typedef struct { + uint8_t *prepare_buf; + int prepare_len; +} prepare_type_env_t; + +static prepare_type_env_t a_prepare_write_env; +static prepare_type_env_t b_prepare_write_env; + +//Declare the static function +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); +void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); + +/* event for handler "bt_av_hdl_stack_up */ +enum { + BT_APP_EVT_STACK_UP = 0, +}; +static uint8_t ble_char_value_str[] = {0x11, 0x22, 0x33}; +esp_gatt_char_prop_t a_property = 0; +esp_gatt_char_prop_t b_property = 0; + +esp_attr_value_t gatts_initial_char_val = { + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(ble_char_value_str), + .attr_value = ble_char_value_str, +}; + +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x060, + .adv_int_max = 0x060, + .adv_type = ADV_TYPE_IND, + .own_addr_type = BLE_ADDR_TYPE_RANDOM, + .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; +}; + +/* 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 gl_profile_tab[PROFILE_NUM] = { + [PROFILE_A_APP_ID] = { + .gatts_cb = gatts_profile_a_event_handler, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [PROFILE_B_APP_ID] = { + .gatts_cb = gatts_profile_b_event_handler,/* This demo does not implement, similar as profile A */ + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; +static void ble_init_adv_data(const char *name) +{ + int len = strlen(name); + uint8_t raw_adv_data[len+5]; + //flag + raw_adv_data[0] = 2; + raw_adv_data[1] = ESP_BT_EIR_TYPE_FLAGS; + raw_adv_data[2] = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT); + //adv name + raw_adv_data[3] = len + 1; + raw_adv_data[4] = ESP_BLE_AD_TYPE_NAME_CMPL; + for (int i = 0;i < len;i++) + { + raw_adv_data[i+5] = *(name++); + } + //The length of adv data must be less than 31 bytes + 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(BT_BLE_COEX_TAG, "config raw adv data failed, error code = 0x%x ", raw_adv_ret); + } + esp_err_t raw_scan_ret = esp_ble_gap_config_scan_rsp_data_raw(raw_adv_data, sizeof(raw_adv_data)); + if (raw_scan_ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "config raw scan rsp data failed, error code = 0x%x", raw_scan_ret); + } +} + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: + //esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: + esp_ble_gap_start_advertising(&adv_params); + break; + 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(BT_BLE_COEX_TAG, "Advertising start failed\n"); + }else { + ESP_LOGI(BT_BLE_COEX_TAG, "Start adv successfully\n"); + } + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(BT_BLE_COEX_TAG, "Advertising stop failed\n"); + } + else { + ESP_LOGI(BT_BLE_COEX_TAG, "Stop adv successfully\n"); + } + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + default: + break; + } +} + +void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param){ + esp_gatt_status_t status = ESP_GATT_OK; + if (param->write.need_rsp){ + if (param->write.is_prep){ + if (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(BT_BLE_COEX_TAG, "Gatt_server prep no mem\n"); + status = ESP_GATT_NO_RESOURCES; + } + } else { + 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; + } + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + 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(BT_BLE_COEX_TAG, "Send response error\n"); + } + free(gatt_rsp); + 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; + + }else{ + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); + } + } +} + +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){ + esp_log_buffer_hex(BT_BLE_COEX_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + }else{ + ESP_LOGI(BT_BLE_COEX_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_a_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_LOGI(BT_BLE_COEX_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + esp_ble_gap_config_local_privacy(true); + gl_profile_tab[PROFILE_A_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_A; + //init BLE adv data and scan response data + ble_init_adv_data(BLE_ADV_NAME); + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_A); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep){ + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(BT_BLE_COEX_TAG, param->write.value, param->write.len); + if (gl_profile_tab[PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ + uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ + ESP_LOGI(BT_BLE_COEX_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) + { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(notify_data), notify_data, false); + } + }else if (descr_value == 0x0002){ + if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ + ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i%0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_A_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } + else if (descr_value == 0x0000){ + ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(BT_BLE_COEX_TAG, "unknown descr value"); + esp_log_buffer_hex(BT_BLE_COEX_TAG, param->write.value, param->write.len); + } + + } + } + example_write_event_env(gatts_if, &a_prepare_write_env, param); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(BT_BLE_COEX_TAG,"ESP_GATTS_EXEC_WRITE_EVT"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&a_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].service_handle = param->create.service_handle; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_A; + + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_A_APP_ID].service_handle); + a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret = esp_ble_gatts_add_char(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + a_property, + &gatts_initial_char_val, NULL); + if (add_char_ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "add char failed, error code = 0x%x",add_char_ret); + } + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: { + ESP_LOGI(BT_BLE_COEX_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + gl_profile_tab[PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_A_APP_ID].service_handle, &gl_profile_tab[PROFILE_A_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL); + if (add_descr_ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "add char descr failed, error code = 0x%x", add_descr_ret); + } + break; + } + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + gl_profile_tab[PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(BT_BLE_COEX_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: { + esp_ble_conn_update_params_t conn_params = {0}; + memcpy(conn_params.bda, param->connect.remote_bda, sizeof(esp_bd_addr_t)); + break; + } + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_DISCONNECT_EVT"); + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_CONF_EVT, status %d", param->conf.status); + if (param->conf.status != ESP_GATT_OK){ + esp_log_buffer_hex(BT_BLE_COEX_TAG, param->conf.value, param->conf.len); + } + break; + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + default: + break; + } +} + +static void gatts_profile_b_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_LOGI(BT_BLE_COEX_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gl_profile_tab[PROFILE_B_APP_ID].service_id.is_primary = true; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.inst_id = 0x00; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_B; + + esp_ble_gatts_create_service(gatts_if, &gl_profile_tab[PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_B); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep){ + ESP_LOGI(BT_BLE_COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(BT_BLE_COEX_TAG, param->write.value, param->write.len); + if (gl_profile_tab[PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2){ + uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == 0x0001){ + if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY){ + ESP_LOGI(BT_BLE_COEX_TAG, "notify enable"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++i) + { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(notify_data), notify_data, false); + } + }else if (descr_value == 0x0002){ + if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE){ + ESP_LOGI(BT_BLE_COEX_TAG, "indicate enable"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++i) + { + indicate_data[i] = i%0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gl_profile_tab[PROFILE_B_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } + else if (descr_value == 0x0000){ + ESP_LOGI(BT_BLE_COEX_TAG, "notify/indicate disable "); + }else{ + ESP_LOGE(BT_BLE_COEX_TAG, "unknown value"); + } + + } + } + example_write_event_env(gatts_if, &b_prepare_write_env, param); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(BT_BLE_COEX_TAG,"ESP_GATTS_EXEC_WRITE_EVT"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&b_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_MTU_EVT, MTU %d", param->mtu.mtu); + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gl_profile_tab[PROFILE_B_APP_ID].service_handle = param->create.service_handle; + gl_profile_tab[PROFILE_B_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_B; + + esp_ble_gatts_start_service(gl_profile_tab[PROFILE_B_APP_ID].service_handle); + b_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret =esp_ble_gatts_add_char( gl_profile_tab[PROFILE_B_APP_ID].service_handle, &gl_profile_tab[PROFILE_B_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + b_property, + NULL, NULL); + if (add_char_ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "add char failed, error code = 0x%x",add_char_ret); + } + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + + gl_profile_tab[PROFILE_B_APP_ID].char_handle = param->add_char.attr_handle; + gl_profile_tab[PROFILE_B_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gl_profile_tab[PROFILE_B_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_ble_gatts_add_char_descr(gl_profile_tab[PROFILE_B_APP_ID].service_handle, &gl_profile_tab[PROFILE_B_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + NULL, NULL); + break; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + gl_profile_tab[PROFILE_B_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(BT_BLE_COEX_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x:", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]); + gl_profile_tab[PROFILE_B_APP_ID].conn_id = param->connect.conn_id; + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_GATTS_CONF_EVT status %d", param->conf.status); + if (param->conf.status != ESP_GATT_OK){ + esp_log_buffer_hex(BT_BLE_COEX_TAG, param->conf.value, param->conf.len); + } + break; + case ESP_GATTS_DISCONNECT_EVT: + case ESP_GATTS_OPEN_EVT: + case ESP_GATTS_CANCEL_OPEN_EVT: + case ESP_GATTS_CLOSE_EVT: + case ESP_GATTS_LISTEN_EVT: + case ESP_GATTS_CONGEST_EVT: + 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) { + gl_profile_tab[param->reg.app_id].gatts_if = gatts_if; + } else { + ESP_LOGI(BT_BLE_COEX_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gatts_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < PROFILE_NUM; idx++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gatts_if == gl_profile_tab[idx].gatts_if) { + if (gl_profile_tab[idx].gatts_cb) { + gl_profile_tab[idx].gatts_cb(event, gatts_if, param); + } + } + } + } while (0); +} + +static void ble_gatts_init(void) +{ + esp_err_t ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "gatts register error, error code = 0x%x", ret); + return; + } + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "gap register error, error code = 0x%x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_A_APP_ID); + if (ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "gatts app register error, error code = 0x%x", ret); + return; + } + ret = esp_ble_gatts_app_register(PROFILE_B_APP_ID); + if (ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "gatts app register error, error code = 0x%x", ret); + return; + } + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret){ + ESP_LOGE(BT_BLE_COEX_TAG, "set local MTU failed, error code = 0x%x", local_mtu_ret); + } +} + +void bt_app_gap_cb(esp_bt_gap_cb_event_t event, esp_bt_gap_cb_param_t *param) +{ + switch (event) { + case ESP_BT_GAP_AUTH_CMPL_EVT: { + if (param->auth_cmpl.stat == ESP_BT_STATUS_SUCCESS) { + ESP_LOGI(BT_BLE_COEX_TAG, "authentication success: %s", param->auth_cmpl.device_name); + esp_log_buffer_hex(BT_BLE_COEX_TAG, param->auth_cmpl.bda, ESP_BD_ADDR_LEN); + } else { + ESP_LOGE(BT_BLE_COEX_TAG, "authentication failed, status:%d", param->auth_cmpl.stat); + } + break; + } +#if (CONFIG_BT_SSP_ENABLED == true) + case ESP_BT_GAP_CFM_REQ_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_BT_GAP_CFM_REQ_EVT Please compare the numeric value: %d", 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(BT_BLE_COEX_TAG, "ESP_BT_GAP_KEY_NOTIF_EVT passkey:%d", param->key_notif.passkey); + break; + case ESP_BT_GAP_KEY_REQ_EVT: + ESP_LOGI(BT_BLE_COEX_TAG, "ESP_BT_GAP_KEY_REQ_EVT Please enter passkey!"); + break; +#endif + default: { + ESP_LOGI(BT_BLE_COEX_TAG, "event: %d", event); + break; + } + } + return; +} + +/* handler for bluetooth stack enabled events */ +static void bt_av_hdl_stack_evt(uint16_t event, void *p_param) +{ + ESP_LOGD(BT_BLE_COEX_TAG, "%s evt %d", __func__, event); + switch (event) { + case BT_APP_EVT_STACK_UP: { + /* set up bt device name */ + esp_bt_dev_set_device_name(BT_DEVICE_NAME); + + esp_bt_gap_register_callback(bt_app_gap_cb); + + /* initialize AVRCP controller */ + esp_avrc_ct_init(); + esp_avrc_ct_register_callback(bt_app_rc_ct_cb); + /* initialize AVRCP target */ + assert (esp_avrc_tg_init() == ESP_OK); + esp_avrc_tg_register_callback(bt_app_rc_tg_cb); + + esp_avrc_rn_evt_cap_mask_t evt_set = {0}; + esp_avrc_rn_evt_bit_mask_operation(ESP_AVRC_BIT_MASK_OP_SET, &evt_set, ESP_AVRC_RN_VOLUME_CHANGE); + assert(esp_avrc_tg_set_rn_evt_cap(&evt_set) == ESP_OK); + + /* initialize A2DP sink */ + esp_a2d_register_callback(&bt_app_a2d_cb); + esp_a2d_sink_register_data_callback(bt_app_a2d_data_cb); + esp_a2d_sink_init(); + + /* set discoverable and connectable mode, wait to be connected */ + esp_bt_gap_set_scan_mode(ESP_BT_CONNECTABLE, ESP_BT_GENERAL_DISCOVERABLE); + break; + } + default: + ESP_LOGE(BT_BLE_COEX_TAG, "%s unhandled evt %d", __func__, event); + break; + } +} + +void app_main(void) +{ + /* Initialize NVS — it is used to store PHY calibration data */ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); + + i2s_config_t i2s_config = { +#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC + .mode = I2S_MODE_MASTER | I2S_MODE_TX | I2S_MODE_DAC_BUILT_IN, +#else + .mode = I2S_MODE_MASTER | I2S_MODE_TX, // Only TX +#endif + .communication_format = I2S_COMM_FORMAT_STAND_MSB, + .sample_rate = 44100, + .bits_per_sample = 16, + .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT, //2-channels + .dma_buf_count = 6, + .dma_buf_len = 60, + .intr_alloc_flags = 0, //Default interrupt priority + .tx_desc_auto_clear = true //Auto clear tx descriptor on underflow + }; + + + i2s_driver_install(0, &i2s_config, 0, NULL); +#ifdef CONFIG_EXAMPLE_A2DP_SINK_OUTPUT_INTERNAL_DAC + i2s_set_dac_mode(I2S_DAC_CHANNEL_BOTH_EN); + i2s_set_pin(0, NULL); +#else + i2s_pin_config_t pin_config = { + .bck_io_num = CONFIG_EXAMPLE_I2S_BCK_PIN, + .ws_io_num = CONFIG_EXAMPLE_I2S_LRCK_PIN, + .data_out_num = CONFIG_EXAMPLE_I2S_DATA_PIN, + .data_in_num = -1 //Not used + }; + + i2s_set_pin(0, &pin_config); +#endif + + esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT(); + if ((err = esp_bt_controller_init(&bt_cfg)) != ESP_OK) { + ESP_LOGE(BT_BLE_COEX_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bt_controller_enable(ESP_BT_MODE_BTDM)) != ESP_OK) { + ESP_LOGE(BT_BLE_COEX_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bluedroid_init()) != ESP_OK) { + ESP_LOGE(BT_BLE_COEX_TAG, "%s initialize bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + if ((err = esp_bluedroid_enable()) != ESP_OK) { + ESP_LOGE(BT_BLE_COEX_TAG, "%s enable bluedroid failed: %s\n", __func__, esp_err_to_name(err)); + return; + } + + /* create application task */ + bt_app_task_start_up(); + + /* Bluetooth device name, connection mode and profile set up */ + bt_app_work_dispatch(bt_av_hdl_stack_evt, BT_APP_EVT_STACK_UP, NULL, 0, NULL); + +#if (CONFIG_BT_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_IO; + esp_bt_gap_set_security_param(param_type, &iocap, sizeof(uint8_t)); +#endif + + /* + * Set default parameters for Legacy Pairing + * Use fixed pin code + */ + esp_bt_pin_type_t pin_type = ESP_BT_PIN_TYPE_FIXED; + 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_set_pin(pin_type, 4, pin_code); + + //gatt server init + ble_gatts_init(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/sdkconfig.defaults new file mode 100644 index 00000000..ee81cabe --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/a2dp_gatts_coex/sdkconfig.defaults @@ -0,0 +1,31 @@ +# Override some defaults so BT stack is enabled and +# Classic BT is enabled and BT_DRAM_RELEASE is disabled +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=n +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=y +CONFIG_BTDM_CTRL_PINNED_TO_CORE_0=y +CONFIG_BTDM_CTRL_PINNED_TO_CORE_1=n +CONFIG_BTDM_CTRL_PINNED_TO_CORE=0 +CONFIG_BTDM_CTRL_HCI_MODE_VHCI=y +CONFIG_BTDM_CTRL_HCI_MODE_UART_H4=n +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLUEDROID_PINNED_TO_CORE_0=y +CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1=n +CONFIG_BT_BLUEDROID_PINNED_TO_CORE=0 +CONFIG_BT_BTC_TASK_STACK_SIZE=3072 +CONFIG_BT_BLUEDROID_MEM_DEBUG=n +CONFIG_BT_CLASSIC_ENABLED=y +CONFIG_BT_A2DP_ENABLE=y +CONFIG_A2DP_SINK_ENABLE=y +CONFIG_A2DP_SRC_ENABLE=n +CONFIG_BT_SPP_ENABLED=n +CONFIG_BT_GATTS_ENABLE=y +CONFIG_BT_GATTC_ENABLE=n +CONFIG_BT_BLE_SMP_ENABLE=n +CONFIG_BLE_ENABLE_SRVCHG_REG=y +CONFIG_BT_STACK_NO_LOG=n +CONFIG_BT_ACL_CONNECTIONS=4 +CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=n +CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=n +CONFIG_BT_SMP_ENABLE=y diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/CMakeLists.txt new file mode 100644 index 00000000..f520a3ae --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(gattc_gatts_coex) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/Makefile new file mode 100644 index 00000000..e8b51186 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := gattc_gatts_coex + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/README.md new file mode 100644 index 00000000..7690711c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/README.md @@ -0,0 +1,17 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +ESP-IDF Gattc and Gatts Coexistence example +============================================== + +This example demonstrates the coexistence of gattc and gatts. + +This example creates a GATT service and starts ADV. The ADV name is `ESP_GATTS_DEMO`, then waits to be connected. At the same time, a gatt client is created, the ADV name is `ESP_GATTS_DEMO`, the device is connected, and the data is exchanged. If the device is not found within 120 seconds, the example will stop scanning. + +ESP-IDF also allows users to create a GATT service via an attribute table, rather than add attributes one by one. And it is recommended for users to use. For more information about this method, please refer to [gatt_server_service_table_demo](../../ble/gatt_server_service_table). + +To test this example, you can run the [gatt_client_demo](../../ble/gatt_client), which can scan for and connect to this example automatically, and run [gatt_server_demo](../../ble/gatt_server), Waiting to be connected. They will start exchanging data once the GATT client has enabled the notification function of the GATT server. + +Please check the [tutorial](../../ble/gatt_server/tutorial/Gatt_Server_Example_Walkthrough.md) for more information about the gatts part of this example. +Please check the [tutorial](../../ble/gatt_client/tutorial/Gatt_Client_Example_Walkthrough.md) for more information about the gattc part of this example. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/CMakeLists.txt new file mode 100644 index 00000000..f0c3ba90 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "gattc_gatts_coex.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/component.mk new file mode 100644 index 00000000..a98f634e --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/component.mk @@ -0,0 +1,4 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c new file mode 100644 index 00000000..00172ebd --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/main/gattc_gatts_coex.c @@ -0,0 +1,1028 @@ +/* + 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 +#include +#include +#include +#include "nvs.h" +#include "nvs_flash.h" +#include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/event_groups.h" +#include "esp_bt.h" +#include "esp_gap_ble_api.h" +#include "esp_gattc_api.h" +#include "esp_gatt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" +#include "esp_gatts_api.h" +#include "esp_bt_defs.h" +#include "esp_system.h" +#include "sdkconfig.h" + + +#define GATTS_SERVICE_UUID_TEST_A 0x00FF +#define GATTS_CHAR_UUID_TEST_A 0xFF01 +#define GATTS_NUM_HANDLE_TEST_A 4 + +#define GATTS_SERVICE_UUID_TEST_B 0x00EE +#define GATTS_CHAR_UUID_TEST_B 0xEE01 +#define GATTS_NUM_HANDLE_TEST_B 4 + +#define GATTS_DEMO_CHAR_VAL_LEN_MAX 0x40 +#define PREPARE_BUF_MAX_SIZE 1024 + +#define adv_config_flag (1 << 0) +#define scan_rsp_config_flag (1 << 1) + +#define GATTS_PROFILE_NUM 2 +#define GATTS_PROFILE_A_APP_ID 0 +#define GATTS_PROFILE_B_APP_ID 1 +#define GATTC_PROFILE_NUM 1 +#define GATTC_PROFILE_C_APP_ID 0 +// gattc +#define REMOTE_SERVICE_UUID 0x00FF +#define REMOTE_NOTIFY_CHAR_UUID 0xFF01 +#define INVALID_HANDLE 0 +#define GATTS_ADV_NAME "ESP_GATTS_DEMO" +#define COEX_TAG "GATTC_GATTS_COEX" +#define NOTIFY_ENABLE 0x0001 +#define INDICATE_ENABLE 0x0002 +#define NOTIFY_INDICATE_DISABLE 0x0000 + +static const char remote_device_name[] = "ESP_GATTS_DEMO"; + +typedef struct { + uint8_t *prepare_buf; + int prepare_len; +} prepare_type_env_t; + +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; +}; + +struct gattc_profile_inst { + esp_gattc_cb_t gattc_cb; + uint16_t gattc_if; + uint16_t app_id; + uint16_t conn_id; + uint16_t service_start_handle; + uint16_t service_end_handle; + uint16_t char_handle; + esp_bd_addr_t remote_bda; +}; + +///Declare the static function +static void gatts_profile_a_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +static void gatts_profile_b_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t *param); +/* Declare static functions */ +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param); +static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); +static void example_exec_write_event_env(prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param); + +static esp_gatt_char_prop_t a_property = 0; +static esp_gatt_char_prop_t b_property = 0; +static prepare_type_env_t a_prepare_write_env; +static prepare_type_env_t b_prepare_write_env; +static uint8_t adv_config_done = 0; +static uint8_t char1_str[] = {0x11, 0x22, 0x33}; +static bool connect = false; +static bool get_server = false; +static esp_gattc_char_elem_t *char_elem_result = NULL; +static esp_gattc_descr_elem_t *descr_elem_result = NULL; + +esp_attr_value_t gatts_demo_char1_val = { + .attr_max_len = GATTS_DEMO_CHAR_VAL_LEN_MAX, + .attr_len = sizeof(char1_str), + .attr_value = char1_str, +}; + +static uint8_t service_uuid128[32] = { + /* LSB <--------------------------------------------------------------------------------> MSB */ + //first uuid, 16bit, [12],[13] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xAB, 0xCD, 0x00, 0x00, + //second uuid, 32bit, [12], [13], [14], [15] is the value + 0xfb, 0x34, 0x9b, 0x5f, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0xAB, 0xCD, 0xAB, 0xCD, +}; + +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, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 32, + .p_service_uuid = service_uuid128, + .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 = 0x0006, + .max_interval = 0x0010, + .appearance = 0x00, + .manufacturer_len = 0, + .p_manufacturer_data = NULL, + .service_data_len = 0, + .p_service_data = NULL, + .service_uuid_len = 0, + .p_service_uuid = NULL, + .flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT), +}; + +static esp_ble_adv_params_t adv_params = { + .adv_int_min = 0x20, + .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, +}; + +/* 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 gatts_profile_tab[GATTS_PROFILE_NUM] = { + [GATTS_PROFILE_A_APP_ID] = { + .gatts_cb = gatts_profile_a_event_handler, + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, + [GATTS_PROFILE_B_APP_ID] = { + .gatts_cb = gatts_profile_b_event_handler, /* This demo does not implement, similar as profile A */ + .gatts_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static esp_bt_uuid_t remote_filter_service_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_SERVICE_UUID,}, +}; + +static esp_bt_uuid_t remote_filter_char_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = REMOTE_NOTIFY_CHAR_UUID,}, +}; + +static esp_bt_uuid_t notify_descr_uuid = { + .len = ESP_UUID_LEN_16, + .uuid = {.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG,}, +}; + +static esp_ble_scan_params_t ble_scan_params = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x50, + .scan_window = 0x30, + .scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE +}; + +/* One gatt-based profile one app_id and one gattc_if, this array will store the gattc_if returned by ESP_GATTS_REG_EVT */ +static struct gattc_profile_inst gattc_profile_tab[GATTC_PROFILE_NUM] = { + [GATTC_PROFILE_C_APP_ID] = { + .gattc_cb = gattc_profile_event_handler, + .gattc_if = ESP_GATT_IF_NONE, /* Not get the gatt_if, so initial is ESP_GATT_IF_NONE */ + }, +}; + +static void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + uint8_t *adv_name = NULL; + uint8_t adv_name_len = 0; + + switch (event) { + + 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; + + 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(COEX_TAG, "Advertising start failed\n"); + } + ESP_LOGI(COEX_TAG, "Advertising start successfully\n"); + break; + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(COEX_TAG, "Advertising stop failed\n"); + } else { + ESP_LOGI(COEX_TAG, "Stop adv successfully\n"); + } + break; + case ESP_GAP_BLE_UPDATE_CONN_PARAMS_EVT: + ESP_LOGI(COEX_TAG, "update connection params status = %d, min_int = %d, max_int = %d,conn_int = %d,latency = %d, timeout = %d\n", + param->update_conn_params.status, + param->update_conn_params.min_int, + param->update_conn_params.max_int, + param->update_conn_params.conn_int, + param->update_conn_params.latency, + param->update_conn_params.timeout); + break; + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(COEX_TAG, "scan stop failed, error status = %x\n", param->scan_stop_cmpl.status); + break; + } + ESP_LOGI(COEX_TAG, "ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT, stop scan successfully\n"); + break; + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + ESP_LOGI(COEX_TAG, "ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT, set scan sparameters complete\n"); + //the unit of the duration is second + uint32_t duration = 120; + esp_ble_gap_start_scanning(duration); + break; + } + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(COEX_TAG, "scan start failed, error status = %x\n", param->scan_start_cmpl.status); + break; + } + ESP_LOGI(COEX_TAG, "ESP_GAP_BLE_SCAN_START_COMPLETE_EVT, scan start success\n"); + break; + case ESP_GAP_BLE_SCAN_RESULT_EVT: { + esp_ble_gap_cb_param_t *scan_result = (esp_ble_gap_cb_param_t *)param; + switch (scan_result->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + adv_name = esp_ble_resolve_adv_data(scan_result->scan_rst.ble_adv, + ESP_BLE_AD_TYPE_NAME_CMPL, &adv_name_len); + if (adv_name != NULL) { + if (strlen(remote_device_name) == adv_name_len && strncmp((char *)adv_name, remote_device_name, adv_name_len) == 0) { + if (connect == false) { + connect = true; + ESP_LOGI(COEX_TAG, "connect to the remote device %s\n", remote_device_name); + esp_ble_gap_stop_scanning(); + esp_ble_gattc_open(gattc_profile_tab[GATTC_PROFILE_C_APP_ID].gattc_if, scan_result->scan_rst.bda, scan_result->scan_rst.ble_addr_type, true); + } + } + } + break; + case ESP_GAP_SEARCH_INQ_CMPL_EVT: + ESP_LOGI(COEX_TAG, "ESP_GAP_SEARCH_INQ_CMPL_EVT, scan stop\n"); + break; + default: + break; + } + break; + } + default: + break; + } +} + +static void gattc_profile_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + esp_ble_gattc_cb_param_t *p_data = (esp_ble_gattc_cb_param_t *)param; + + switch (event) { + case ESP_GATTC_REG_EVT: + ESP_LOGI(COEX_TAG, "REG_EVT\n"); + esp_err_t scan_ret = esp_ble_gap_set_scan_params(&ble_scan_params); + if (scan_ret) { + ESP_LOGE(COEX_TAG, "set scan params error, error code = %x", scan_ret); + } + break; + case ESP_GATTC_CONNECT_EVT: { + ESP_LOGI(COEX_TAG, "ESP_GATTC_CONNECT_EVT conn_id %d, if %d\n", p_data->connect.conn_id, gattc_if); + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id = p_data->connect.conn_id; + memcpy(gattc_profile_tab[GATTC_PROFILE_C_APP_ID].remote_bda, p_data->connect.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(COEX_TAG, "REMOTE BDA:"); + esp_log_buffer_hex(COEX_TAG, gattc_profile_tab[GATTC_PROFILE_C_APP_ID].remote_bda, sizeof(esp_bd_addr_t)); + esp_err_t mtu_ret = esp_ble_gattc_send_mtu_req (gattc_if, p_data->connect.conn_id); + if (mtu_ret) { + ESP_LOGE(COEX_TAG, "config MTU error, error code = %x\n", mtu_ret); + } + break; + } + case ESP_GATTC_OPEN_EVT: + if (param->open.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "open failed, status %d\n", p_data->open.status); + break; + } + ESP_LOGI(COEX_TAG, "open success\n"); + break; + case ESP_GATTC_DIS_SRVC_CMPL_EVT: + if (param->dis_srvc_cmpl.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "discover service failed, status %d\n", param->dis_srvc_cmpl.status); + break; + } + ESP_LOGI(COEX_TAG, "discover service complete conn_id %d\n", param->dis_srvc_cmpl.conn_id); + esp_ble_gattc_search_service(gattc_if, param->cfg_mtu.conn_id, &remote_filter_service_uuid); + break; + case ESP_GATTC_CFG_MTU_EVT: + if (param->cfg_mtu.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG,"config mtu failed, error status = %x\n", param->cfg_mtu.status); + } + ESP_LOGI(COEX_TAG, "ESP_GATTC_CFG_MTU_EVT, Status %d, MTU %d, conn_id %d\n", param->cfg_mtu.status, param->cfg_mtu.mtu, param->cfg_mtu.conn_id); + break; + case ESP_GATTC_SEARCH_RES_EVT: { + ESP_LOGI(COEX_TAG, "SEARCH RES: conn_id = %x is primary service %d\n", p_data->search_res.conn_id, p_data->search_res.is_primary); + ESP_LOGI(COEX_TAG, "start handle %d end handle %d current handle value %d\n", p_data->search_res.start_handle, p_data->search_res.end_handle, p_data->search_res.srvc_id.inst_id); + if (p_data->search_res.srvc_id.uuid.len == ESP_UUID_LEN_16 && p_data->search_res.srvc_id.uuid.uuid.uuid16 == REMOTE_SERVICE_UUID) { + ESP_LOGI(COEX_TAG, "service found\n"); + get_server = true; + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_start_handle = p_data->search_res.start_handle; + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_end_handle = p_data->search_res.end_handle; + ESP_LOGI(COEX_TAG, "UUID16: %x\n", p_data->search_res.srvc_id.uuid.uuid.uuid16); + } + break; + } + case ESP_GATTC_SEARCH_CMPL_EVT: + if (p_data->search_cmpl.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "search service failed, error status = %x\n", p_data->search_cmpl.status); + break; + } + if(p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_REMOTE_DEVICE) { + ESP_LOGI(COEX_TAG, "Get service information from remote device\n"); + } else if (p_data->search_cmpl.searched_service_source == ESP_GATT_SERVICE_FROM_NVS_FLASH) { + ESP_LOGI(COEX_TAG, "Get service information from flash\n"); + } else { + ESP_LOGI(COEX_TAG, "unknown service source\n"); + } + ESP_LOGI(COEX_TAG, "ESP_GATTC_SEARCH_CMPL_EVT\n"); + if (get_server) { + uint16_t count = 0; + esp_gatt_status_t status = esp_ble_gattc_get_attr_count( gattc_if, + p_data->search_cmpl.conn_id, + ESP_GATT_DB_CHARACTERISTIC, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_start_handle, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_end_handle, + INVALID_HANDLE, + &count); + if (status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "esp_ble_gattc_get_attr_count error\n"); + } + + if (count > 0) { + char_elem_result = (esp_gattc_char_elem_t *)malloc(sizeof(esp_gattc_char_elem_t) * count); + if (!char_elem_result) { + ESP_LOGE(COEX_TAG, "gattc no mem\n"); + }else { + status = esp_ble_gattc_get_char_by_uuid( gattc_if, + p_data->search_cmpl.conn_id, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_start_handle, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_end_handle, + remote_filter_char_uuid, + char_elem_result, + &count); + if (status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "esp_ble_gattc_get_char_by_uuid error\n"); + } + + /* Every service have only one char in our 'ESP_GATTS_DEMO' demo, so we used first 'char_elem_result' */ + if (count > 0 && (char_elem_result[0].properties & ESP_GATT_CHAR_PROP_BIT_NOTIFY)) { + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].char_handle = char_elem_result[0].char_handle; + esp_ble_gattc_register_for_notify (gattc_if, gattc_profile_tab[GATTC_PROFILE_C_APP_ID].remote_bda, char_elem_result[0].char_handle); + } + } + /* free char_elem_result */ + free(char_elem_result); + } else { + ESP_LOGE(COEX_TAG, "no char found\n"); + } + } + break; + case ESP_GATTC_REG_FOR_NOTIFY_EVT: { + ESP_LOGI(COEX_TAG, "ESP_GATTC_REG_FOR_NOTIFY_EVT\n"); + if (p_data->reg_for_notify.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "REG FOR NOTIFY failed: error status = %d\n", p_data->reg_for_notify.status); + } else { + uint16_t count = 0; + uint16_t notify_en = 1; + esp_gatt_status_t ret_status = esp_ble_gattc_get_attr_count( gattc_if, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id, + ESP_GATT_DB_DESCRIPTOR, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_start_handle, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].service_end_handle, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].char_handle, + &count); + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "esp_ble_gattc_get_attr_count error\n"); + } + if (count > 0) { + descr_elem_result = malloc(sizeof(esp_gattc_descr_elem_t) * count); + if (!descr_elem_result) { + ESP_LOGE(COEX_TAG, "malloc error, gattc no mem\n"); + } else { + ret_status = esp_ble_gattc_get_descr_by_char_handle( gattc_if, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id, + p_data->reg_for_notify.handle, + notify_descr_uuid, + descr_elem_result, + &count); + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "esp_ble_gattc_get_descr_by_char_handle error\n"); + } + /* Every char has only one descriptor in our 'ESP_GATTS_DEMO' demo, so we used first 'descr_elem_result' */ + if (count > 0 && descr_elem_result[0].uuid.len == ESP_UUID_LEN_16 && descr_elem_result[0].uuid.uuid.uuid16 == ESP_GATT_UUID_CHAR_CLIENT_CONFIG) { + ret_status = esp_ble_gattc_write_char_descr( gattc_if, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id, + descr_elem_result[0].handle, + sizeof(notify_en), + (uint8_t *)¬ify_en, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + } + + if (ret_status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "esp_ble_gattc_write_char_descr error\n"); + } + + /* free descr_elem_result */ + free(descr_elem_result); + } + } else { + ESP_LOGE(COEX_TAG, "decsr not found\n"); + } + + } + break; + } + case ESP_GATTC_NOTIFY_EVT: + if (p_data->notify.is_notify) { + ESP_LOGI(COEX_TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value:"); + } else { + ESP_LOGI(COEX_TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value:"); + } + esp_log_buffer_hex(COEX_TAG, p_data->notify.value, p_data->notify.value_len); + break; + case ESP_GATTC_WRITE_DESCR_EVT: + if (p_data->write.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "write descr failed, error status = %x\n", p_data->write.status); + break; + } + ESP_LOGI(COEX_TAG, "write descr success \n"); + uint8_t write_char_data[35]; + for (int i = 0; i < sizeof(write_char_data); ++ i) { + write_char_data[i] = i % 256; + } + esp_ble_gattc_write_char( gattc_if, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].conn_id, + gattc_profile_tab[GATTC_PROFILE_C_APP_ID].char_handle, + sizeof(write_char_data), + write_char_data, + ESP_GATT_WRITE_TYPE_RSP, + ESP_GATT_AUTH_REQ_NONE); + break; + case ESP_GATTC_SRVC_CHG_EVT: { + esp_bd_addr_t bda; + memcpy(bda, p_data->srvc_chg.remote_bda, sizeof(esp_bd_addr_t)); + ESP_LOGI(COEX_TAG, "ESP_GATTC_SRVC_CHG_EVT, bd_addr:"); + esp_log_buffer_hex(COEX_TAG, bda, sizeof(esp_bd_addr_t)); + break; + } + case ESP_GATTC_WRITE_CHAR_EVT: + if (p_data->write.status != ESP_GATT_OK) { + ESP_LOGE(COEX_TAG, "write char failed, error status = %x\n", p_data->write.status); + break; + } + ESP_LOGI(COEX_TAG, "write char success \n"); + break; + case ESP_GATTC_DISCONNECT_EVT: { + connect = false; + get_server = false; + ESP_LOGI(COEX_TAG, "ESP_GATTC_DISCONNECT_EVT, reason = %d\n", p_data->disconnect.reason); + break; + } + default: + break; + } +} + +static void example_write_event_env(esp_gatt_if_t gatts_if, prepare_type_env_t *prepare_write_env, esp_ble_gatts_cb_param_t *param) { + esp_gatt_status_t status = ESP_GATT_OK; + if (param->write.need_rsp) { + if (param->write.is_prep) { + if (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(COEX_TAG, "Gatt_server prep no mem\n"); + status = ESP_GATT_NO_RESOURCES; + } + } else { + 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; + } + } + + esp_gatt_rsp_t *gatt_rsp = (esp_gatt_rsp_t *)malloc(sizeof(esp_gatt_rsp_t)); + 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(COEX_TAG, "Send response error\n"); + } + free(gatt_rsp); + 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; + + } else { + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, status, NULL); + } + } +} + +static 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) { + esp_log_buffer_hex(COEX_TAG, prepare_write_env->prepare_buf, prepare_write_env->prepare_len); + } else { + ESP_LOGI(COEX_TAG,"ESP_GATT_PREP_WRITE_CANCEL\n"); + } + 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_a_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_LOGI(COEX_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_id.is_primary = true; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_id.id.inst_id = 0x00; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_A; + + esp_err_t set_dev_name_ret = esp_ble_gap_set_device_name(GATTS_ADV_NAME); + if (set_dev_name_ret) { + ESP_LOGE(COEX_TAG, "set device name failed, error code = %x\n", 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(COEX_TAG, "config raw adv data failed, error code = %x \n", 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(COEX_TAG, "config raw scan rsp data failed, error code = %x\n", 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(COEX_TAG, "config adv data failed, error code = %x\n", 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(COEX_TAG, "config scan response data failed, error code = %x\n", ret); + } + adv_config_done |= scan_rsp_config_flag; + +#endif + esp_ble_gatts_create_service(gatts_if, &gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_A); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep) { + ESP_LOGI(COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(COEX_TAG, param->write.value, param->write.len); + if (gatts_profile_tab[GATTS_PROFILE_A_APP_ID].descr_handle == param->write.handle && param->write.len == 2) { + uint16_t descr_value = param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == NOTIFY_ENABLE) { + if (a_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY) { + ESP_LOGI(COEX_TAG, "notify enable\n"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++ i) { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatts_profile_tab[GATTS_PROFILE_A_APP_ID].char_handle, + sizeof(notify_data), notify_data, false); + } + } else if (descr_value == INDICATE_ENABLE) { + if (a_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) { + ESP_LOGI(COEX_TAG, "indicate enable\n"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++ i) { + indicate_data[i] = i%0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatts_profile_tab[GATTS_PROFILE_A_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } else if (descr_value == NOTIFY_INDICATE_DISABLE) { + ESP_LOGI(COEX_TAG, "notify/indicate disable \n"); + } else { + ESP_LOGE(COEX_TAG, "unknown descr value\n"); + esp_log_buffer_hex(COEX_TAG, param->write.value, param->write.len); + } + + } + } + example_write_event_env(gatts_if, &a_prepare_write_env, param); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(COEX_TAG,"ESP_GATTS_EXEC_WRITE_EVT\n"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&a_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(COEX_TAG, "ESP_GATTS_MTU_EVT, MTU %d\n", param->mtu.mtu); + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(COEX_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_handle = param->create.service_handle; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_A; + + esp_ble_gatts_start_service(gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_handle); + a_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret = esp_ble_gatts_add_char(gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_handle, &gatts_profile_tab[GATTS_PROFILE_A_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + a_property, + &gatts_demo_char1_val, NULL); + if (add_char_ret) { + ESP_LOGE(COEX_TAG, "add char failed, error code =%x\n",add_char_ret); + } + break; + case ESP_GATTS_ADD_CHAR_EVT: { + ESP_LOGI(COEX_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].char_handle = param->add_char.attr_handle; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + + esp_err_t add_descr_ret = esp_ble_gatts_add_char_descr(gatts_profile_tab[GATTS_PROFILE_A_APP_ID].service_handle, &gatts_profile_tab[GATTS_PROFILE_A_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, NULL, NULL); + if (add_descr_ret) { + ESP_LOGE(COEX_TAG, "add char descr failed, error code =%x\n", add_descr_ret); + } + break; + } + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(COEX_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(COEX_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_CONNECT_EVT: { + + ESP_LOGI(COEX_TAG, "ESP_GATTS_CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x\n", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]); + gatts_profile_tab[GATTS_PROFILE_A_APP_ID].conn_id = param->connect.conn_id; + break; + } + case ESP_GATTS_DISCONNECT_EVT: + ESP_LOGI(COEX_TAG, "ESP_GATTS_DISCONNECT_EVT, disconnect reason 0x%x\n", param->disconnect.reason); + esp_ble_gap_start_advertising(&adv_params); + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(COEX_TAG, "ESP_GATTS_CONF_EVT, status %d attr_handle %d\n", param->conf.status, param->conf.handle); + if (param->conf.status != ESP_GATT_OK) { + esp_log_buffer_hex(COEX_TAG, param->conf.value, param->conf.len); + } + break; + case ESP_GATTS_OPEN_EVT: + default: + break; + } +} + +static void gatts_profile_b_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_LOGI(COEX_TAG, "REGISTER_APP_EVT, status %d, app_id %d\n", param->reg.status, param->reg.app_id); + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_id.is_primary = true; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_id.id.inst_id = 0x00; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_id.id.uuid.len = ESP_UUID_LEN_16; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_id.id.uuid.uuid.uuid16 = GATTS_SERVICE_UUID_TEST_B; + + esp_ble_gatts_create_service(gatts_if, &gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_id, GATTS_NUM_HANDLE_TEST_B); + break; + case ESP_GATTS_READ_EVT: { + ESP_LOGI(COEX_TAG, "GATT_READ_EVT, conn_id %d, trans_id %d, handle %d\n", param->read.conn_id, param->read.trans_id, param->read.handle); + esp_gatt_rsp_t rsp; + memset(&rsp, 0, sizeof(esp_gatt_rsp_t)); + rsp.attr_value.handle = param->read.handle; + rsp.attr_value.len = 4; + rsp.attr_value.value[0] = 0xde; + rsp.attr_value.value[1] = 0xed; + rsp.attr_value.value[2] = 0xbe; + rsp.attr_value.value[3] = 0xef; + esp_ble_gatts_send_response(gatts_if, param->read.conn_id, param->read.trans_id, + ESP_GATT_OK, &rsp); + break; + } + case ESP_GATTS_WRITE_EVT: { + ESP_LOGI(COEX_TAG, "GATT_WRITE_EVT, conn_id %d, trans_id %d, handle %d\n", param->write.conn_id, param->write.trans_id, param->write.handle); + if (!param->write.is_prep) { + ESP_LOGI(COEX_TAG, "GATT_WRITE_EVT, value len %d, value :", param->write.len); + esp_log_buffer_hex(COEX_TAG, param->write.value, param->write.len); + if (gatts_profile_tab[GATTS_PROFILE_B_APP_ID].descr_handle == param->write.handle && param->write.len == 2) { + uint16_t descr_value= param->write.value[1]<<8 | param->write.value[0]; + if (descr_value == NOTIFY_ENABLE) { + if (b_property & ESP_GATT_CHAR_PROP_BIT_NOTIFY) { + ESP_LOGI(COEX_TAG, "notify enable\n"); + uint8_t notify_data[15]; + for (int i = 0; i < sizeof(notify_data); ++ i) { + notify_data[i] = i%0xff; + } + //the size of notify_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatts_profile_tab[GATTS_PROFILE_B_APP_ID].char_handle, + sizeof(notify_data), notify_data, false); + } + } else if (descr_value == INDICATE_ENABLE) { + if (b_property & ESP_GATT_CHAR_PROP_BIT_INDICATE) { + ESP_LOGI(COEX_TAG, "indicate enable\n"); + uint8_t indicate_data[15]; + for (int i = 0; i < sizeof(indicate_data); ++ i) { + indicate_data[i] = i%0xff; + } + //the size of indicate_data[] need less than MTU size + esp_ble_gatts_send_indicate(gatts_if, param->write.conn_id, gatts_profile_tab[GATTS_PROFILE_B_APP_ID].char_handle, + sizeof(indicate_data), indicate_data, true); + } + } else if (descr_value == NOTIFY_INDICATE_DISABLE) { + ESP_LOGI(COEX_TAG, "notify/indicate disable \n"); + } else { + ESP_LOGE(COEX_TAG, "unknown value\n"); + } + + } + } + example_write_event_env(gatts_if, &b_prepare_write_env, param); + break; + } + case ESP_GATTS_EXEC_WRITE_EVT: + ESP_LOGI(COEX_TAG,"ESP_GATTS_EXEC_WRITE_EVT\n"); + esp_ble_gatts_send_response(gatts_if, param->write.conn_id, param->write.trans_id, ESP_GATT_OK, NULL); + example_exec_write_event_env(&b_prepare_write_env, param); + break; + case ESP_GATTS_MTU_EVT: + ESP_LOGI(COEX_TAG, "ESP_GATTS_MTU_EVT, MTU %d\n", param->mtu.mtu); + break; + case ESP_GATTS_UNREG_EVT: + break; + case ESP_GATTS_CREATE_EVT: + ESP_LOGI(COEX_TAG, "CREATE_SERVICE_EVT, status %d, service_handle %d\n", param->create.status, param->create.service_handle); + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_handle = param->create.service_handle; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].char_uuid.len = ESP_UUID_LEN_16; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].char_uuid.uuid.uuid16 = GATTS_CHAR_UUID_TEST_B; + + esp_ble_gatts_start_service(gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_handle); + b_property = ESP_GATT_CHAR_PROP_BIT_READ | ESP_GATT_CHAR_PROP_BIT_WRITE | ESP_GATT_CHAR_PROP_BIT_NOTIFY; + esp_err_t add_char_ret =esp_ble_gatts_add_char( gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_handle, &gatts_profile_tab[GATTS_PROFILE_B_APP_ID].char_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + b_property, + NULL, NULL); + if (add_char_ret) { + ESP_LOGE(COEX_TAG, "add char failed, error code =%x\n",add_char_ret); + } + break; + case ESP_GATTS_ADD_INCL_SRVC_EVT: + break; + case ESP_GATTS_ADD_CHAR_EVT: + ESP_LOGI(COEX_TAG, "ADD_CHAR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char.status, param->add_char.attr_handle, param->add_char.service_handle); + + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].char_handle = param->add_char.attr_handle; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].descr_uuid.len = ESP_UUID_LEN_16; + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].descr_uuid.uuid.uuid16 = ESP_GATT_UUID_CHAR_CLIENT_CONFIG; + esp_ble_gatts_add_char_descr(gatts_profile_tab[GATTS_PROFILE_B_APP_ID].service_handle, &gatts_profile_tab[GATTS_PROFILE_B_APP_ID].descr_uuid, + ESP_GATT_PERM_READ | ESP_GATT_PERM_WRITE, + NULL, NULL); + break; + case ESP_GATTS_ADD_CHAR_DESCR_EVT: + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].descr_handle = param->add_char_descr.attr_handle; + ESP_LOGI(COEX_TAG, "ADD_DESCR_EVT, status %d, attr_handle %d, service_handle %d\n", + param->add_char_descr.status, param->add_char_descr.attr_handle, param->add_char_descr.service_handle); + break; + case ESP_GATTS_DELETE_EVT: + break; + case ESP_GATTS_START_EVT: + ESP_LOGI(COEX_TAG, "SERVICE_START_EVT, status %d, service_handle %d\n", + param->start.status, param->start.service_handle); + break; + case ESP_GATTS_STOP_EVT: + break; + case ESP_GATTS_CONNECT_EVT: + ESP_LOGI(COEX_TAG, "CONNECT_EVT, conn_id %d, remote %02x:%02x:%02x:%02x:%02x:%02x\n", + param->connect.conn_id, + param->connect.remote_bda[0], param->connect.remote_bda[1], param->connect.remote_bda[2], + param->connect.remote_bda[3], param->connect.remote_bda[4], param->connect.remote_bda[5]); + gatts_profile_tab[GATTS_PROFILE_B_APP_ID].conn_id = param->connect.conn_id; + break; + case ESP_GATTS_CONF_EVT: + ESP_LOGI(COEX_TAG, "ESP_GATTS_CONF_EVT status %d attr_handle %d\n", param->conf.status, param->conf.handle); + if (param->conf.status != ESP_GATT_OK) { + esp_log_buffer_hex(COEX_TAG, param->conf.value, param->conf.len); + } + break; + case ESP_GATTS_DISCONNECT_EVT: + case ESP_GATTS_OPEN_EVT: + default: + break; + } +} + +static void esp_gattc_cb(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t *param) +{ + /* If event is register event, store the gattc_if for each profile */ + if (event == ESP_GATTC_REG_EVT) { + if (param->reg.status == ESP_GATT_OK) { + gattc_profile_tab[param->reg.app_id].gattc_if = gattc_if; + } else { + ESP_LOGI(COEX_TAG, "reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gattc_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < GATTC_PROFILE_NUM; idx ++) { + if (gattc_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gattc_if == gattc_profile_tab[idx].gattc_if) { + if (gattc_profile_tab[idx].gattc_cb) { + gattc_profile_tab[idx].gattc_cb(event, gattc_if, param); + } + } + } + } while (0); +} + +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) { + gatts_profile_tab[param->reg.app_id].gatts_if = gatts_if; + } else { + ESP_LOGI(COEX_TAG, "Reg app failed, app_id %04x, status %d\n", + param->reg.app_id, + param->reg.status); + return; + } + } + + /* If the gatts_if equal to profile A, call profile A cb handler, + * so here call each profile's callback */ + do { + int idx; + for (idx = 0; idx < GATTS_PROFILE_NUM; idx ++) { + if (gatts_if == ESP_GATT_IF_NONE || /* ESP_GATT_IF_NONE, not specify a certain gatt_if, need to call every profile cb function */ + gatts_if == gatts_profile_tab[idx].gatts_if) { + if (gatts_profile_tab[idx].gatts_cb) { + gatts_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 || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + ESP_ERROR_CHECK( ret ); + + 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(COEX_TAG, "%s initialize controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bt_controller_enable(ESP_BT_MODE_BLE); + if (ret) { + ESP_LOGE(COEX_TAG, "%s enable controller failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_init(); + if (ret) { + ESP_LOGE(COEX_TAG, "%s init bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_bluedroid_enable(); + if (ret) { + ESP_LOGE(COEX_TAG, "%s enable bluetooth failed: %s\n", __func__, esp_err_to_name(ret)); + return; + } + + ret = esp_ble_gap_register_callback(gap_event_handler); + if (ret) { + ESP_LOGE(COEX_TAG, "gap register error, error code = %x", ret); + return; + } + + // gatts register + ret = esp_ble_gatts_register_callback(gatts_event_handler); + if (ret) { + ESP_LOGE(COEX_TAG, "gatts register error, error code = %x", ret); + return; + } + + ret = esp_ble_gatts_app_register(GATTS_PROFILE_A_APP_ID); + if (ret) { + ESP_LOGE(COEX_TAG, "gatts app register error, error code = %x", ret); + return; + } + + ret = esp_ble_gatts_app_register(GATTS_PROFILE_B_APP_ID); + if (ret) { + ESP_LOGE(COEX_TAG, "gatts app register error, error code = %x", ret); + return; + } + + // gattc regisrter + ret = esp_ble_gattc_register_callback(esp_gattc_cb); + if(ret) { + ESP_LOGE(COEX_TAG, "%s gattc register failed, error code = %x\n", __func__, ret); + return; + } + + ret = esp_ble_gattc_app_register(GATTC_PROFILE_C_APP_ID); + if (ret) { + ESP_LOGE(COEX_TAG, "%s gattc app register failed, error code = %x\n", __func__, ret); + } + + esp_err_t local_mtu_ret = esp_ble_gatt_set_local_mtu(500); + if (local_mtu_ret) { + ESP_LOGE(COEX_TAG, "set local MTU failed, error code = %x", local_mtu_ret); + } + + return; +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/sdkconfig.defaults new file mode 100644 index 00000000..ee53a228 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/bluedroid/coex/gattc_gatts_coex/sdkconfig.defaults @@ -0,0 +1,6 @@ +# Override some defaults so BT stack is enabled +# by default in this example +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_MODE_BR_EDR_ONLY=n +CONFIG_BTDM_CTRL_MODE_BTDM=n diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/README.md new file mode 100644 index 00000000..6552c842 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/README.md @@ -0,0 +1,94 @@ +# ESP-BLE-MESH Examples + +[ESP-BLE-MESH](../../../components/bt/esp_ble_mesh/) is the official Bluetooth® Mesh stack of Espressif Systems. We will provide long-term support for new features, performance optimization, etc. + +Please help note that breaking changes may be introduced into ESP-BLE-MESH on [minor IDF versions](https://docs.espressif.com/projects/esp-idf/en/latest/versions.html). + +Note: To use examples in this directory, you need to have Bluetooth enabled in configuration, and either Bluedroid or NimBLE can be selected as the host stack. + +# Example Layout + +This directory includes examples to demonstrate ESP-BLE-MESH functionality based on [Zephyr Bluetooth Mesh stack](https://github.com/zephyrproject-rtos/zephyr/tree/master/subsys/bluetooth/mesh). + +## ble_mesh_console + +This example demonstrates how ESP-BLE-MESH uses Console for message transmitting/receiving tests. + +#### ble_mesh_node + +This example shows how ESP32 acts as a BLE Mesh Node and sends vendor messages for testing. + +See [ble_mesh_node](ble_mesh_console/ble_mesh_node) folder for more details. + +#### ble_mesh_provisioner + +This example shows how ESP32 acts as a BLE Mesh Provisioner and sends vendor messages for testing. + +See [ble_mesh_provisioner](ble_mesh_console/ble_mesh_provisioner) folder for more details. + +## ble_mesh_fast_provision + +This example illustrates the solution of ESP-BLE-MESH Fast Provisioning. + +#### fast_prov_client + +This example shows how ESP32, acting as a BLE Mesh Fast Provisioning Client, provisions other unprovisioned devices and then controls the nodes. + +See [fast_prov_client](ble_mesh_fast_provision/fast_prov_client) folder for more details. + +#### fast_prov_server + +This example illustrates the process that: +1. ESP32 as a BLE Mesh Fast Provisioning Server is provisioned into a node; +2. ESP32 as a Temporary Provisioner provisions other unprovisioned devices. + +See [fast_prov_server](ble_mesh_fast_provision/fast_prov_server) folder for more details. + +## ble_mesh_node + +This example demonstrates how ESP32 acts as a BLE Mesh node with Generic OnOff Server model or Generic OnOff Client model on board. + +#### onoff_client + +This example shows how ESP32 acts as a BLE Mesh Node with Generic OnOff Client model in the Primary Element. + +See [onoff_client](ble_mesh_node/onoff_client) folder for more details. + +#### onoff_server + +This example shows how ESP32 acts as a BLE Mesh Node with only Generic OnOff Server model in the Primary Element. + +See [onoff_server](ble_mesh_node/onoff_server) folder for more details. + +## ble_mesh_provisioner + +This example shows how ESP32 acts as a BLE Mesh Provisioner and provisions other unprovisioned devices. + +See [ble_mesh_provisioner](ble_mesh_provisioner) folder for more details. + +## ble_mesh_vendor_model + +This example demonstrates how ESP32 acts as a BLE Mesh Provisioner with vendor client model or as a BLE Mesh node with vendor server model. + +#### vendor_client + +This example shows how ESP32 acts as a BLE Mesh Provisioner with a vendor client model in the Primary Element. + +See [vendor_client](ble_mesh_vendor_model/vendor_client) folder for more details. + +#### vendor_server + +This example shows how ESP32 acts as a BLE Mesh Node with a vendor server model in the Primary Element. + +See [vendor_server](ble_mesh_vendor_model/vendor_server) folder for more details. + +## ble_mesh_wifi_coexist + +This example shows how ESP32 acts as a BLE Mesh Fast Provisioning Server and coexists with Wi-Fi iperf functionality. + +See [ble_mesh_wifi_coexist](ble_mesh_wifi_coexist) folder for more details. + +# More + +See the [README.md](../../README.md) file in the upper level [examples](../../) directory for more information about examples. + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/CMakeLists.txt new file mode 100644 index 00000000..af50d633 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/CMakeLists.txt @@ -0,0 +1,7 @@ +# 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.5) + + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_coex_test) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/Makefile new file mode 100644 index 00000000..9e4e00a3 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/Makefile @@ -0,0 +1,8 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_coex_test + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/README.md new file mode 100644 index 00000000..4c9ebb15 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/README.md @@ -0,0 +1,110 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# Example of BLE Mesh and TCP Server/Client Coexistence + +This example introduces how to test the basic functions of `BLE Mesh data interface` and `TCP Server/Client Coexistence`. `BLE Mesh data interface` is GAP scanning and advertising. + +There are two working modes here: + + * In automatic mode, the program coordinates three development boards working through a synchronization mechanism. + + * In manual mode, you will work with three development boards via commands + + +## Test Preparation + +* Before running the test, you need to prepare a router and three ESP32 development boards. This Example of BLE Mesh and TCP Server/Client Coexistence has the following three items, and any of the three development boards is for running one specific item. + + * ble_dev : Run only the BLE program. + * coex_dev: Run BLE and Wi-Fi program. + * wifi_dev: Run only the Wi-Fi program. + +``Note: If you want better performance in BLE and WiFi coexistence, you should use a development board with PSRAM that could run a coexistence program. Such as ESP32_LyraT, ESP32-WROVER-B and etc.`` + +* The following structure shows the parameters you need to configure. And usually, there are two methods for configuration, i.e. configuring during initialization or configuring with the command `env`. + +```c +coex_test_env_t test_env = { +#if defined(CONFIG_EXAMPLE_MANAUL) + .ap_ssid = CONFIG_EXAMPLE_WIFI_SSID, + .ap_password = CONFIG_EXAMPLE_WIFI_PASSWORD, +#endif +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + .ap_ssid = CONFIG_EXAMPLE_WIFI_SSID, + .ap_password = CONFIG_EXAMPLE_WIFI_PASSWORD, +#endif + .test_port = "8080", + .server_ip = "192.168.3.32", + .duration = "120000", + .is_start = false, +}; +``` + + +## Run Test Case Manually +Configure to Manual Mode via `Example Configuration --->run mode (manual) ` + +The meaning of the numeric argument of the command `run_tc` is as follows: + +| id | case name | description | +|:-:|:-|:-| +| 0 | wifi_tcp_tx_throughput| Test the case of Wi-Fi tcp tx, which will create a tcp client that will continuously send data to the tcp server. | +| 1 |wifi_tcp_rx_throughput| Test the case of Wi-Fi tcp rx, which will create a tcp server that will continuously receive data from the tcp client. | +| 2 | ble_adv | Test the case of BLE advertising. | +| 3 | ble_scan| Test the case of BLE Scan.| + + +### Case 1: tcp tx + scan +1. wifi_dev: run_tc -w 1 +2. coex_dev: env -s -k server_ip -v 192.168.3.34 run_tc -w 0 -b 3 +3. ble_dev : run_tc -b 2 + + +### Case 2: tcp rx + scan +1. coex_dev: run_tc -w 1 -b 3 +2. wifi_dev: env -s -k server_ip -v 192.168.3.34 run_tc -w 0 +3. ble_dev : run_tc -b 2 + +### Case 3: tcp tx + adv +1. wifi_dev: run_tc -w 1 +2. coex_dev: env -s -k server_ip -v 192.168.3.13 run_tc -w 0 -b 2 +3. ble_dev : run_tc -b 3 + + +### Case 4: tcp rx + adv +1. coex_dev: run_tc -w 1 -b 2 +2. wifi_dev: env -s -k server_ip -v 192.168.3.34 run_tc -w 0 +3. ble_dev : run_tc -b 3 + +## Run Test Case By Automation +Configure to Automatic Mode via `Example Configuration --->run mode (auto) ` + +### Coexistence device configuration +1. Select a development board as coexistence role by `Example Configuration --->select role (run device as coex role) ` +2. Select a test case by `Example Configuration --->select case `. +* There are four types of cases: + * TCP TX and BLE ADV: The TCP client will be created on the coexistence device, and bluetooth will start advertising when the Wi-Fi is running tx throughput program. + * TCP RX and BLE ADV: The TCP server will be created on the coexistence device, and bluetooth will start advertising when the Wi-Fi is running rx throughput program. + * TCP TX and BLE SCAN: The TCP client will be created on the coexistence device, and bluetooth will start scanning when the Wi-Fi is running tx throughput program. + * TCP RX and BLE SCAN: The TCP server will be created on the coexistence device, and bluetooth will start scanning when the Wi-Fi is running rx throughput program. + +### Bluetooth device configuration +1. Select a development board as bluetooth role by `select role (run device as bluetooth role) ` + +### Wi-Fi device configuration +1. Select a development board as bluetooth role by `select role (run device as wifi role) ` + + +## Coexistence Configuration +In theory, the performance of BLE and Wi-Fi coexistence will drop to half of the performance in BLE Only mode or Wi-Fi Only mode. + +* ESP32 working frequency: + * Component config ---> ESP32-specific ---> CPU frequency (240 MHz) + +* ESP32 external PSRAM + * Component config ---> ESP32-specific ---> Support for external, SPI-connected RAM + * Devices that do not support PSRAM cannot open this option! + +* ESP32 coexistence mode + * Component config ---> Wi-Fi ---> WiFi/Bluetooth coexistence performance preference (Balance) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/CMakeLists.txt new file mode 100644 index 00000000..b08fff23 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/CMakeLists.txt @@ -0,0 +1,11 @@ +set(srcs "ble_unit.c" + "run_tc.c" + "sync.c" + "test_env.c" + "wifi_connect.c" + "wifi_unit.c") + +idf_component_register(SRCS "${srcs}" + INCLUDE_DIRS "." + REQUIRES console nvs_flash bt + REQUIRED_IDF_TARGETS esp32) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/ble_unit.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/ble_unit.c new file mode 100644 index 00000000..757fb178 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/ble_unit.c @@ -0,0 +1,253 @@ +/* ESP BLE Mesh 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 "ble_unit.h" +#include "sync.h" +#define TAG "BLE_UINT" + +#define GAP_TRANS_DEFAULT_SHORT_TO 50 // 50ms for events expected to reported as soon as API called + +static uint8_t default_adv_data[] = { + 0x02, 0x01, 0x06, + 0x02, 0x0a, 0xeb, 0x03, 0x03, 0xab, 0xcd +}; +static uint8_t default_scan_rsp_data[] = { + 0x0f, 0x09, 0x45, 0x53, 0x50, 0x5f, 0x47, 0x41, 0x54, 0x54, 0x53, 0x5f, 0x43, + 0x4f, 0x45, 0x58 +}; + +esp_ble_scan_params_t default_scan_param = { + .scan_type = BLE_SCAN_TYPE_ACTIVE, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL, + .scan_interval = 0x100, + .scan_window = 0x100 +}; + +esp_ble_adv_params_t default_adv_param = { + .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, +}; + +ble_util_scan_count_t scan_count; + +static void ble_gap_event_default_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + switch (event) { + case ESP_GAP_BLE_SCAN_PARAM_SET_COMPLETE_EVT: { + if (param->scan_param_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "set scan parameter failed, error status = %x", param->scan_param_cmpl.status); + } + break; + } + case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT: { + if (param->adv_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "set row data failed, error status = %x", param->adv_data_raw_cmpl.status); + } + break; + } + case ESP_GAP_BLE_SCAN_RSP_DATA_RAW_SET_COMPLETE_EVT: { + if (param->scan_rsp_data_raw_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "set scan response data failed, error status = %x", param->scan_rsp_data_raw_cmpl.status); + } + 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\n"); + } + break; + } + case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT: + if (param->adv_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "Advertising stop failed\n"); + } + break; + case ESP_GAP_BLE_SCAN_START_COMPLETE_EVT: { + if (param->scan_start_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "scan start failed, error status = %x", param->scan_start_cmpl.status); + } + break; + } + case ESP_GAP_BLE_SCAN_STOP_COMPLETE_EVT: + if (param->scan_stop_cmpl.status != ESP_BT_STATUS_SUCCESS) { + ESP_LOGE(TAG, "scan stop failed, error status = %x", param->scan_stop_cmpl.status); + } + break; + default: + break; + } +} + +static void ble_gap_util_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) +{ + if (sync_obj.cmd_recv != NULL) { + sync_obj.cmd_recv(param->scan_rst.ble_adv, param->scan_rst.adv_data_len); + } + + switch (event) { + case ESP_GAP_BLE_SCAN_RESULT_EVT: + switch (param->scan_rst.search_evt) { + case ESP_GAP_SEARCH_INQ_RES_EVT: + if (param->scan_rst.adv_data_len > 0 \ + && (memcmp(default_adv_data, param->scan_rst.ble_adv, sizeof(default_adv_data)) == 0)) { + scan_count.adv_count += 1; + if (scan_count.adv_count % 10 == 0) { + ESP_LOGI(TAG, "adv count:%d scan_res count %d\n", scan_count.adv_count, scan_count.scan_res_count); + } + } + + if (param->scan_rst.scan_rsp_len > 0 \ + && (memcmp(default_scan_rsp_data, (param->scan_rst.ble_adv + param->scan_rst.adv_data_len), sizeof(default_scan_rsp_data)) == 0)) { + scan_count.scan_res_count += 1; + } + break; + default: + break; + } + break; + default: + ble_gap_event_default_handler(event, param); + break; + } +} + + +esp_err_t ble_gap_util_set_adv_data(uint8_t *adv_data, uint32_t adv_data_len, uint8_t *scan_rsp_data, uint32_t scan_rsp_data_len) +{ + esp_err_t ret; + + ret = esp_ble_gap_config_adv_data_raw(adv_data, adv_data_len); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_config_adv_data_raw error, %d", ret); + return ret; + } + ret = esp_ble_gap_config_scan_rsp_data_raw(scan_rsp_data, scan_rsp_data_len); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_config_scan_rsp_data_raw error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t ble_gap_util_set_default_adv_data(void) +{ + return ble_gap_util_set_adv_data(default_adv_data, sizeof(default_adv_data), default_scan_rsp_data, sizeof(default_scan_rsp_data)); +} + +esp_err_t ble_gap_util_start_adv(esp_ble_adv_params_t *adv_param) +{ + esp_err_t ret; + ret = esp_ble_gap_start_advertising(adv_param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_advertising error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t ble_gap_util_start_adv_default(void) +{ + esp_err_t ret; + + ret = esp_ble_gap_start_advertising(&default_adv_param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_advertising error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t ble_gap_util_stop_adv(void) +{ + esp_err_t ret; + ret = esp_ble_gap_stop_advertising(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_stop_advertising error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t ble_gap_util_set_scan_param(esp_ble_scan_params_t *scan_param) +{ + esp_err_t ret; + ret = esp_ble_gap_set_scan_params(scan_param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_set_scan_params error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t ble_gap_util_set_default_scan_param(void) +{ + return ble_gap_util_set_scan_param(&default_scan_param); +} + +esp_err_t ble_gap_util_start_scan(uint32_t duration) +{ + esp_err_t ret; + ret = esp_ble_gap_start_scanning(duration); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_scanning error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t ble_gap_util_stop_scan(void) +{ + esp_err_t ret; + ret = esp_ble_gap_stop_scanning(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_stop_scanning error, %d", ret); + return ret; + } + return 0; +} + + +void ble_gap_util_stop(void) +{ + ble_gap_util_stop_adv(); + ble_gap_util_stop_scan(); +} + +esp_err_t init_ble_gap_test_util(void) +{ + esp_err_t ret; + ret = esp_ble_gap_register_callback(ble_gap_util_handler); + if (ret) { + ESP_LOGE(TAG, "gap register error, error code = %x", ret); + } + return ret; +} + +void bt_test_init(void) +{ + 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(); + ESP_ERROR_CHECK(esp_bt_controller_init(&bt_cfg)); + ESP_ERROR_CHECK(esp_bt_controller_enable(ESP_BT_MODE_BLE)); + ESP_ERROR_CHECK(esp_bluedroid_init()); + ESP_ERROR_CHECK(esp_bluedroid_enable()); +} + +void bt_test_deinit(void) +{ + esp_bluedroid_disable(); + esp_bluedroid_deinit(); + esp_bt_controller_disable(); + esp_bt_controller_deinit(); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/ble_unit.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/ble_unit.h new file mode 100644 index 00000000..93272a91 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/ble_unit.h @@ -0,0 +1,57 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef _BLE_UNIT_H_ +#define _BLE_UNIT_H_ +#include +#include +#include "esp_system.h" +#include "esp_timer.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_gattc_api.h" +#include "esp_bt_defs.h" +#include "esp_bt_main.h" +#include "esp_gatt_common_api.h" + +#define BLE_TC_SCAN_REPORT_PERIOD 10000 + +typedef struct { + uint32_t adv_count; + uint32_t scan_res_count; +} ble_util_scan_count_t; + + +extern esp_bd_addr_t bt_addr; +extern ble_util_scan_count_t scan_count; +extern esp_ble_adv_params_t default_adv_param; +extern esp_ble_scan_params_t default_scan_param; + +void bt_test_init(void); +void bt_test_deinit(void); +void ble_gap_util_stop(void); + +esp_err_t ble_gap_util_set_adv_data(uint8_t *adv_data, uint32_t adv_data_len, uint8_t *scan_rsp_data, uint32_t scan_rsp_data_len); +esp_err_t ble_gap_util_set_default_adv_data(void); +esp_err_t ble_gap_util_start_adv(esp_ble_adv_params_t *adv_param); +esp_err_t ble_gap_util_start_adv_default(void); +esp_err_t ble_gap_util_stop_adv(void); + +esp_err_t ble_gap_util_set_scan_param(esp_ble_scan_params_t *scan_param); +esp_err_t ble_gap_util_set_default_scan_param(void); +esp_err_t ble_gap_util_start_scan(uint32_t duration); +esp_err_t ble_gap_util_stop_scan(void); + +esp_err_t init_ble_gap_test_util(void); + +#endif /* _BLE_UNIT_H_ */ \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/component.mk new file mode 100644 index 00000000..f2fef2fe --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/component.mk @@ -0,0 +1,7 @@ +# +# Component Makefile +# + +COMPONENT_SRCDIRS := . + +COMPONENT_ADD_INCLUDEDIRS = . diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/run_tc.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/run_tc.c new file mode 100644 index 00000000..9f243dce --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/run_tc.c @@ -0,0 +1,291 @@ +/* ESP BLE Mesh 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 "run_tc.h" +#include "test_env.h" +#include "wifi_unit.h" +#include "ble_unit.h" +#include "sync.h" +#include "wifi_connect.h" + +#define TAG "CASE" + +xQueueHandle xTaskQueue = 0; + +static const char *coex_get_case_env(coex_test_env_t *test_env, const char *keyword) +{ + const char *ret = NULL; + if (!strcmp(keyword, "ap_ssid")) { + ret = test_env->ap_ssid; + } else if (!strcmp(keyword, "ap_password")) { + ret = test_env->ap_password; + } else if (!strcmp(keyword, "test_port")) { + ret = test_env->test_port; + } else if (!strcmp(keyword, "server_ip")) { + ret = test_env->server_ip; + } else if (!strcmp(keyword, "duration")) { + ret = test_env->duration; + } + + return ret; +} + +static void wifi_tc_sta_throughput_timeout(void *arg) +{ + static uint32_t statistic_count = 0; + static uint64_t accumulate_speed = 0; + + uint32_t now = utils_get_system_ts(); + uint32_t *report = (uint32_t *) arg; + uint32_t last_timestamp = report[0]; + + if (now > last_timestamp) { + uint32_t speed = report[1] * 8 / (now - last_timestamp); + accumulate_speed += speed; + statistic_count += 1; + printf("speed: %d kbps average speed: %lld kbps\n", speed, accumulate_speed / statistic_count ); + report[1] = 0; + report[0] = now; + } +} + +static esp_err_t create_statistic_timer(esp_timer_handle_t *timer_hdl , uint32_t statistic_date[]) +{ + esp_err_t ret; + esp_timer_create_args_t tca = { + .callback = (esp_timer_cb_t)wifi_tc_sta_throughput_timeout, + .dispatch_method = ESP_TIMER_TASK, + .name = "TCP_STATISTIC", + }; + tca.arg = statistic_date; + ret = esp_timer_create(&tca, timer_hdl); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "statistic_timer create failed"); + return ret; + } + esp_timer_start_periodic(*timer_hdl, 1000000); //1000ms + return ret; +} + + +void wifi_tcp_tx_throught_start(void *param) +{ + esp_timer_handle_t timer_hdl = NULL; + esp_err_t ret; + int sock = -1; + uint32_t statistic_date[2] = {0}; + + const char *ssid = coex_get_case_env(param, "ap_ssid"); + const char *passwd = coex_get_case_env(param, "ap_password"); + + + wifi_util_init(); + // wifi_unit_connect_ap(ssid, passwd); + example_connect(ssid, passwd); + +#if defined(CONFIG_EXAMPLE_AUTO) + if (((coex_test_env_t *)param)->run_mutex != NULL) { + //This will be blocked by the sync timer. + xSemaphoreTake(((coex_test_env_t *)param)->run_mutex, portMAX_DELAY); + xSemaphoreGive(((coex_test_env_t *)param)->run_mutex); + } +#endif + const char *ip = coex_get_case_env(param, "server_ip"); + const char *port = coex_get_case_env(param, "test_port"); + const char *duration = coex_get_case_env(param, "duration"); + wifi_unit_client_establish(&sock, ip, port); + + ret = create_statistic_timer(&timer_hdl, statistic_date); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "statistic_timer create failed"); + goto _stop; + } + + ret = wifi_util_tcp_send(sock, 1460, 0, &statistic_date[1], atoi(duration)); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "send failed, %x", ret); + } + +_stop: + if (timer_hdl) { + esp_timer_stop(timer_hdl); + esp_timer_delete(timer_hdl); + } + + if (sock > 0) { + close(sock); + } +} + +void wifi_tcp_tx_throught_end(void) +{ + esp_wifi_disconnect(); +} + +void wifi_tcp_rx_throught_start(void *param) +{ + esp_timer_handle_t timer_hdl = NULL; + esp_err_t ret; + int sock = -1; + uint32_t statistic_date[2] = {0}; + + const char *ssid = coex_get_case_env(param, "ap_ssid"); + const char *passwd = coex_get_case_env(param, "ap_password"); + const char *port = coex_get_case_env(param, "test_port"); + const char *duration = coex_get_case_env(param, "duration"); + + wifi_util_init(); + // wifi_unit_connect_ap(ssid, passwd); + example_connect(ssid, passwd); + wifi_unit_server_establish( &sock, port); + + ret = create_statistic_timer(&timer_hdl, statistic_date); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "statistic_timer create failed"); + goto _stop; + } + ret = wifi_unit_tcp_recv(sock, duration, statistic_date); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "tcp receive failed"); + goto _stop; + } +_stop: + if (timer_hdl) { + esp_timer_stop(timer_hdl); + esp_timer_delete(timer_hdl); + } + + if (sock > 0) { + close(sock); + } +} + +void wifi_tcp_rx_throught_end(void) +{ + esp_wifi_disconnect(); +} + +void ble_adv_start(void *param) +{ + esp_err_t ret; + const char *duration = coex_get_case_env(param, "duration"); +#if defined(CONFIG_EXAMPLE_MANAUL) + bt_test_init(); +#endif + ret = ble_gap_util_set_default_adv_data(); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to set adv data"); + return; + } + + ret = ble_gap_util_start_adv(&default_adv_param); + + if (ret != ESP_OK) { + ESP_LOGE(TAG, "failed to start adv"); + return; + } + + vTaskDelay(atoi(duration) / portTICK_PERIOD_MS); + +} + +void ble_adv_end(void) +{ + ble_gap_util_stop(); +} + + +void ble_scan_start(void *param) +{ + esp_err_t ret; +#if defined(CONFIG_EXAMPLE_MANAUL) + bt_test_init(); + vTaskDelay(10 / portTICK_PERIOD_MS); + init_ble_gap_test_util(); +#endif + + + ret = ble_gap_util_set_scan_param(&default_scan_param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "set scan param fail"); + return; + } + vTaskDelay(10 / portTICK_PERIOD_MS); + + scan_count.adv_count = 0; + scan_count.scan_res_count = 0; + + ret = esp_ble_gap_start_scanning(BLE_TC_SCAN_REPORT_PERIOD); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_scanning error, %d", ret); + return; + } +} + +void ble_scan_end(void) +{ + ESP_LOGI(TAG, "%s \n", __func__); +} + +tc_t tc_case[] = { + DECLARE_TC(TC_WIFI_COEX_TCP_TX_THROUGHPUT, 0, wifi_tcp_tx_throught_start, wifi_tcp_tx_throught_end, (void *)&test_env), + DECLARE_TC(TC_WIFI_COEX_TCP_RX_THROUGHPUT, 1, wifi_tcp_rx_throught_start, wifi_tcp_rx_throught_end, (void *)&test_env), + DECLARE_TC(TC_BLE_COEX_ADV, 2, ble_adv_start, ble_adv_end, (void *)&test_env), + DECLARE_TC(TC_BLE_COEX_SCAN, 3, ble_scan_start, ble_scan_end, (void *)&test_env), +}; + +static void excute_case(void *arg) +{ + tc_t *run_case = (tc_t *) arg; + if (run_case && run_case->func_start != NULL) { + run_case->func_start(run_case->param_list); + } + + if (run_case && run_case->func_stop != NULL ) { + vTaskDelay(100 / portTICK_RATE_MS); + run_case->func_stop(); + } + vTaskDelete(NULL); +} + +static void run_task(void *arg) +{ + tc_t *tc_case_table = (tc_t *) arg; + run_task_msg_t msg; + + for (;;) { + if (pdTRUE == xQueueReceive(xTaskQueue, &msg, (portTickType)portMAX_DELAY)) { + if ( msg.case_id < sizeof(tc_case) / sizeof(tc_case[0]) ) { + xTaskCreatePinnedToCore(excute_case, tc_case_table->name, 4096, &tc_case_table[msg.case_id], RUN_TASK_PRIORITY, NULL, 0); + } else { + ESP_LOGW(TAG, "msg.case_id %d\n", msg.case_id); + } + + } + } + vTaskDelete(NULL); +} + + +void run_tc_init(void) +{ + xTaskQueue = xQueueCreate(RUN_TASK_QUEUE_LEN, sizeof(run_task_msg_t)); + if (!xTaskQueue) { + ESP_LOGE(TAG, "xTaskQueue create failed"); + return; + } + xTaskCreatePinnedToCore(run_task, "run_task", 4096, tc_case, RUN_TASK_PRIORITY, NULL, 0); +} + + + + + + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/run_tc.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/run_tc.h new file mode 100644 index 00000000..1e82df68 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/run_tc.h @@ -0,0 +1,56 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef __RUN_TC_H__ +#define __RUN_TC_H__ + +#include +#include +#include +#include + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#define TC_NAME_LEN 63 +typedef void (*tc_func_start_t)(void *param); +typedef void (*tc_func_end_t)(void); + + +typedef struct tc { + char name[TC_NAME_LEN + 1]; + uint8_t case_id; + tc_func_start_t func_start; + tc_func_end_t func_stop; + void *param_list; +} tc_t; + + +#define TC_WIFI_COEX_TCP_TX_THROUGHPUT "TCP_COEX_TX_THROUGHPUT" +#define TC_WIFI_COEX_TCP_RX_THROUGHPUT "TCP_COEX_RX_THROUGHPUT" +#define TC_BLE_COEX_ADV "BLE_COEX_ADVERTISING" +#define TC_BLE_COEX_SCAN "BLE_COEX_SCAN" + +// run_task queue size +#define RUN_TASK_QUEUE_LEN 6 +#define RUN_TASK_PRIORITY 18 + +#define DECLARE_TC(name, id, start_func, stop_func, param_list) \ + {name, id, start_func, stop_func, param_list} + +typedef struct run_task_msg { + uint8_t case_id; +} run_task_msg_t; + +extern xQueueHandle xTaskQueue ; + +void run_tc_init(void); + +#endif /* __RUN_TC_H__ */ \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/sync.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/sync.c new file mode 100644 index 00000000..8dabe92c --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/sync.c @@ -0,0 +1,542 @@ +/* ESP BLE Mesh 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 "sync.h" +#include "run_tc.h" +#include "wifi_unit.h" +#define TAG "SYNC" + +SemaphoreHandle_t client_mutex; + +struct sync_t sync_obj = { + .except_recv_wifi_id = false, + .except_recv_bt_id = false, + .recv_param_bit = 0x0, + .start_time = 0x0, + +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + .state = ASSIGN_CASE, +#else + .state = WAIT_CASE, +#endif +#if defined(CONFIG_EXAMPLE_COEX_TX_ADV) + .own_wifi_case = WIFI_TCP_TX_CASE, + .own_ble_case = BLE_ADV_CASE, +#elif defined(CONFIG_EXAMPLE_COEX_RX_ADV) + .own_wifi_case = WIFI_TCP_RX_CASE, + .own_ble_case = BLE_ADV_CASE, +#elif defined(CONFIG_EXAMPLE_COEX_TX_SCAN) + .own_wifi_case = WIFI_TCP_TX_CASE, + .own_ble_case = BLE_SCAN_CASE, +#elif defined(CONFIG_EXAMPLE_COEX_RX_SCAN) + .own_wifi_case = WIFI_TCP_RX_CASE, + .own_ble_case = BLE_SCAN_CASE, +#else + .own_wifi_case = NOT_CASE, + .own_ble_case = NOT_CASE, +#endif +}; + + +auto_tc auto_tb[6] = { + {WIFI_TCP_TX_CASE, PARAMTER(0b011011)}, //need paramter: wifi_case_id ssid password server ip + {WIFI_TCP_RX_CASE, PARAMTER(0b010011)}, //need paramter: wifi_case_id ssid password + {BLE_ADV_CASE, PARAMTER(0b100000)}, //need paramter: ble_case_id + {BLE_SCAN_CASE, PARAMTER(0b100000)}, //need paramter: ble_case_id +}; + + +esp_err_t send_adv(uint8_t *raw_data, uint32_t raw_data_len) +{ + esp_err_t ret; + + esp_ble_adv_params_t adv_param = { + .adv_int_min = 0x40, + .adv_int_max = 0x40, + .adv_type = ADV_TYPE_NONCONN_IND, + .own_addr_type = BLE_ADDR_TYPE_PUBLIC, + .channel_map = ADV_CHNL_ALL, + .adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY, + }; + ret = esp_ble_gap_config_adv_data_raw(raw_data, raw_data_len); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_config_adv_data_raw error, %d", ret); + return ret; + } + + ret = esp_ble_gap_start_advertising(&adv_param); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_advertising error, %d", ret); + return ret; + } + vTaskDelay(30 / portTICK_PERIOD_MS); //every 30ms send one packet + + ret = esp_ble_gap_stop_advertising(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_stop_advertising error, %d", ret); + return ret; + } + return 0; +} + +esp_err_t send_adv_data(sync_msg *msg) +{ + send_adv((uint8_t *)msg, sizeof(sync_msg)); + return 0; +} + +void send_start_msg(uint8_t start_time) +{ + sync_msg msg = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = 0x4, + .ctl = MSG_CONTINUE, + .param_bit = 0b000100 << PARAM_MAX, + .reserve = 0xff, + .data = {start_time}, + }; + send_adv_data(&msg); +} +void send_start_countdown(void) +{ + if (sync_obj.except_recv_bt_id == true && sync_obj.except_recv_wifi_id == true) { + esp_timer_stop(sync_obj.sync_timer); + esp_timer_start_once(sync_obj.sync_timer, 1000000); + sync_obj.state = START_CASE; + } +} +void send_tcp_rx_inited_msg(void) +{ + uint8_t hex_ip[4]; + uint32_t ip = wifi_util_get_ip(); + memcpy(hex_ip, &ip, sizeof(hex_ip)); + if (hex_ip[0] == 0x0) { + return; + } + sync_msg msg = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = MSG_ID_WIFI_DEV_INIT_FINISH, + .ctl = MSG_CONTINUE, + .param_bit = 0b001000 << PARAM_MAX, + .reserve = 0xff, + .data = {hex_ip[0], hex_ip[1], hex_ip[2], hex_ip[3]}, + }; + send_adv_data(&msg); +} +void send_case_inited_msg(uint8_t msg_id) +{ + sync_msg msg = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = msg_id, + .ctl = MSG_CONTINUE, + .param_bit = 0b000000 << PARAM_MAX, + .reserve = 0xff, + .data = {0}, + }; + send_adv_data(&msg); +} + +void assign_case_to_dev(uint8_t ble_id, uint8_t wifi_id) +{ + if ( sync_obj.own_wifi_case == WIFI_TCP_RX_CASE ) { + uint8_t hex_ip[4]; + uint32_t ip = wifi_util_get_ip(); + ESP_LOGI(TAG, "ip:%s", inet_ntoa(ip)); + memcpy(hex_ip, &ip, sizeof(hex_ip)); + if (hex_ip[0] == 0x0) { + return; + } + sync_msg msg = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = 0x1, + .ctl = MSG_CONTINUE, + .param_bit = 0b111000 << PARAM_MAX, + .reserve = 0xff, + .data = {ble_id, wifi_id, hex_ip[0], hex_ip[1], hex_ip[2], hex_ip[3]}, + }; + send_adv_data(&msg); + } else if ( sync_obj.own_wifi_case == WIFI_TCP_TX_CASE ) { + sync_msg msg1 = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = 0x1, + .ctl = MSG_CONTINUE, + .param_bit = 0b110000 << PARAM_MAX, + .reserve = 0xff, + .data = {ble_id, wifi_id}, + }; + send_adv_data(&msg1); + } +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + if (strlen(CONFIG_EXAMPLE_WIFI_SSID) < 20) { + sync_msg msg = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = 0x1, + .ctl = MSG_CONTINUE, + .param_bit = 0b000010 << PARAM_MAX, + .reserve = 0xff, + .data[0] = strlen(CONFIG_EXAMPLE_WIFI_SSID), + }; + for (uint8_t i = 0 ; i < strlen(CONFIG_EXAMPLE_WIFI_SSID); i++) { + msg.data[i + 1] = CONFIG_EXAMPLE_WIFI_SSID[i] - '0'; + } + send_adv_data(&msg); + } + + if (strlen(CONFIG_EXAMPLE_WIFI_PASSWORD) < 20) { + sync_msg msg = { + .length = 30, + .type = MSG_TYPE, + .head = MSG_HEAD, + .msg_id = 0x1, + .ctl = MSG_END, + .param_bit = 0b000001 << PARAM_MAX, + .reserve = 0xff, + .data[0] = strlen(CONFIG_EXAMPLE_WIFI_PASSWORD), + }; + for (int i = 0 ; i < strlen(CONFIG_EXAMPLE_WIFI_PASSWORD); i++) { + msg.data[i + 1] = CONFIG_EXAMPLE_WIFI_PASSWORD[i] - '0'; + } + send_adv_data(&msg); + } +#endif +} + + +void excute_case(uint8_t run_case) +{ + run_task_msg_t msg; + msg.case_id = run_case; + if (xQueueSend(xTaskQueue, &msg, portMAX_DELAY) != pdTRUE) { + ESP_LOGE(TAG, "xTaskQueue Post failed\n"); + } +} + + +void assign_test_case(void) +{ +#if defined(CONFIG_EXAMPLE_COEX_TX_ADV) + assign_case_to_dev(BLE_SCAN_CASE, WIFI_TCP_RX_CASE); +#elif defined(CONFIG_EXAMPLE_COEX_RX_ADV) + assign_case_to_dev(BLE_SCAN_CASE, WIFI_TCP_TX_CASE); +#elif defined(CONFIG_EXAMPLE_COEX_TX_SCAN) + assign_case_to_dev(BLE_ADV_CASE, WIFI_TCP_RX_CASE); +#elif defined(CONFIG_EXAMPLE_COEX_RX_SCAN) + assign_case_to_dev(BLE_ADV_CASE, WIFI_TCP_TX_CASE); +#endif +} + + +void analys_param(uint16_t param_bit, uint8_t data[], uint16_t *recv_param_bit) +{ + + uint8_t data_ptr = 0; + + for (int i = 0 ; i < PARAM_MAX; i++ ) { + switch (GET_PARAM(param_bit, 0x1000 >> i)) { + case BLE_CASE_ID: + ESP_LOGD(TAG, "BLE_CASE_ID\n"); +#if defined(CONFIG_EXAMPLE_BT_ROLE) + sync_obj.own_ble_case = data[data_ptr]; + (*recv_param_bit) |= BLE_CASE_ID; +#endif + data_ptr += 1; + break; + case WIFI_CASE_ID: + ESP_LOGD(TAG, "WIFI_CASE_ID\n"); +#if defined(CONFIG_EXAMPLE_WIFI_ROLE) + sync_obj.own_wifi_case = data[data_ptr]; + (*recv_param_bit) |= WIFI_CASE_ID; +#endif + data_ptr += 1; + break; + case START_TIME: + ESP_LOGD(TAG, "START_TIME\n"); + sync_obj.start_time = data[data_ptr]; + data_ptr += 1; + break; + case SERVER_IP: { + ESP_LOGD(TAG, "SERVER_IP\n"); + char server_ip[16]; + memset(server_ip, '0', sizeof(server_ip)); + sprintf(server_ip, "%d.%d.%d.%d", data[data_ptr], data[data_ptr + 1], data[data_ptr + 2], data[data_ptr + 3]); // size conversion + coex_set_test_env("server_ip", server_ip, sizeof(server_ip)); + coex_print_test_env(); + data_ptr += 4; + (*recv_param_bit) |= SERVER_IP; + break; + } + case WIFI_SSID: { + ESP_LOGD(TAG, "WIFI_SSID\n"); + uint8_t length = data[data_ptr]; + data_ptr += 1; + if ( length > 20) { + ESP_LOGE(TAG, "ssid length error"); + break; + } + char *ssid = malloc(length + 1); + if (ssid == NULL) { + ESP_LOGE(TAG, "%s malloc fail\n", __func__); + return ; + } + memset(ssid, '0', sizeof(length + 1)); + + for (int i = length - 1 ; i >= 0 ; i--) { + ssid[i] = data[data_ptr + i] + '0'; + } + ssid[length] = '\n'; + coex_set_test_env("ap_ssid", ssid, length); + coex_print_test_env(); + (*recv_param_bit) |= WIFI_SSID; + free(ssid); + break; + } + case WIFI_PASSWIRD: { + ESP_LOGD(TAG, "WIFI_PASSWIRD\n"); + uint8_t length = data[data_ptr]; + data_ptr += 1; + if ( length > 20) { + ESP_LOGE(TAG, "password length error"); + break; + } + char *password = malloc(length + 1); + if (password == NULL) { + ESP_LOGE(TAG, "%s malloc fail\n", __func__); + return ; + } + memset(password, '0', sizeof(length + 1)); + for (int i = length - 1 ; i >= 0 ; i--) { + password[i] = data[data_ptr + i] + '0'; + } + password[length] = '\n'; + coex_set_test_env("ap_password", password, length); + coex_print_test_env(); + (*recv_param_bit) |= WIFI_PASSWIRD; + free(password); + break; + } + default: + break; + } + } + +} + +void sync_cmd_recv(uint8_t *raw_data, uint32_t raw_data_len) +{ + if (raw_data_len < MSG_MIN_LEN) { + ESP_LOGD(TAG, "msg length is low"); + } + sync_msg_head msg_head = {0}; + memcpy(&msg_head, raw_data, sizeof(sync_msg_head)); + + if (msg_head.type != MSG_TYPE || msg_head.head != MSG_HEAD) { + ESP_LOGD(TAG, "msg is unknown"); + return; + } + ESP_LOGD(TAG, "msg_id: %x\n", msg_head.msg_id); + // ESP_LOG_BUFFER_HEX("sync recv:", raw_data, raw_data_len); + switch (sync_obj.state) { +#if defined(CONFIG_EXAMPLE_WIFI_ROLE) || defined(CONFIG_EXAMPLE_BT_ROLE) + case WAIT_CASE: { + ESP_LOGD(TAG, "WAIT_CASE\n"); + if (msg_head.msg_id == MSG_ID_ASSIGN_CASE) { + analys_param(msg_head.param_bit, raw_data + MSG_DATA_BASE, &sync_obj.recv_param_bit ); +#if defined(CONFIG_EXAMPLE_WIFI_ROLE) + if (sync_obj.own_wifi_case != NOT_CASE) { + if ((sync_obj.recv_param_bit & auto_tb[sync_obj.own_wifi_case].excpet_param_bit) == auto_tb[sync_obj.own_wifi_case].excpet_param_bit) { + excute_case(sync_obj.own_wifi_case); + sync_obj.state = WAIT_START; + esp_timer_start_periodic(sync_obj.sync_timer, SYNC_TIMEOUT ); + } + } +#endif +#if defined(CONFIG_EXAMPLE_BT_ROLE) + if (sync_obj.own_ble_case != NOT_CASE) { + if ((sync_obj.recv_param_bit & auto_tb[sync_obj.own_ble_case].excpet_param_bit) == auto_tb[sync_obj.own_ble_case].excpet_param_bit) { + sync_obj.state = WAIT_START; + esp_timer_start_periodic(sync_obj.sync_timer, SYNC_TIMEOUT ); + } + } +#endif + } + break; + } + case WAIT_START: + ESP_LOGD(TAG, "WAIT_START\n"); + ESP_LOGD(TAG, "WAIT_START %x\n", msg_head.msg_id); + if (msg_head.msg_id == MSG_ID_START_CASE) { + analys_param(msg_head.param_bit, raw_data + MSG_DATA_BASE, &sync_obj.recv_param_bit ); + sync_obj.state = START_CASE; + ble_gap_util_stop(); +#if defined(CONFIG_EXAMPLE_WIFI_ROLE) + bt_test_deinit(); +#endif + esp_timer_stop(sync_obj.sync_timer); + esp_timer_start_once(sync_obj.sync_timer, sync_obj.start_time * 1000000); + } + break; +#endif +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + case ASSIGN_CASE: + ESP_LOGD(TAG, "ASSIGN_CASE\n"); + switch (msg_head.msg_id) { + case MSG_ID_WIFI_DEV_INIT_FINISH: + if (msg_head.param_bit != 0x0) { + analys_param(msg_head.param_bit, raw_data + MSG_DATA_BASE, &sync_obj.recv_param_bit ); + } + sync_obj.except_recv_wifi_id = true; + break; + case MSG_ID_BT_DEV_INIT_FINISH: + sync_obj.except_recv_bt_id = true; + break; + default: + break; + } + send_start_countdown(); + break; +#endif + default: + ESP_LOGD(TAG, "state is unknown %s", __func__); + break; + } +} + +static void handle_sync_timeout(void *arg) +{ + static bool run_first = true; + if (run_first == true) { + xSemaphoreTake((SemaphoreHandle_t)arg, (portTickType)portMAX_DELAY); + esp_timer_start_periodic( (SemaphoreHandle_t)arg, 1000000); + run_first = false; + } + switch (sync_obj.state) { +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + case ASSIGN_CASE: + ESP_LOGD(TAG, "ASSIGN_CASE\n"); + assign_test_case(); + esp_timer_start_periodic(sync_obj.sync_timer, SYNC_TIMEOUT); + break; +#endif + +#if defined(CONFIG_EXAMPLE_WIFI_ROLE) || defined(CONFIG_EXAMPLE_BT_ROLE) + case WAIT_START: { + ESP_LOGD(TAG, "WAIT_START\n"); +#if defined(CONFIG_EXAMPLE_WIFI_ROLE) + if ( WIFI_TCP_RX_CASE == sync_obj.own_wifi_case ) { + send_tcp_rx_inited_msg(); + } else { + send_case_inited_msg(MSG_ID_WIFI_DEV_INIT_FINISH); + } +#elif defined(CONFIG_EXAMPLE_BT_ROLE) + send_case_inited_msg(MSG_ID_BT_DEV_INIT_FINISH); +#endif + esp_timer_start_periodic(sync_obj.sync_timer, SYNC_TIMEOUT); + break; + } +#endif + + + case START_CASE: { + ESP_LOGD(TAG, "START_CASE\n"); +#if defined(CONFIG_EXAMPLE_BT_ROLE) + excute_case(sync_obj.own_ble_case); + +#elif defined(CONFIG_EXAMPLE_WIFI_ROLE) + ESP_LOGD(TAG, "START_CASE\n"); + if (arg != NULL) { + xSemaphoreGive((SemaphoreHandle_t)arg); + run_first = true; + esp_timer_stop(sync_obj.sync_timer); + + } +#else + static uint8_t send_start_count = 10; + if (send_start_count == 0) { + excute_case(sync_obj.own_ble_case); + xSemaphoreGive((SemaphoreHandle_t)arg); + run_first = true; + esp_timer_stop(sync_obj.sync_timer); + break; + } + + send_start_msg(send_start_count); + send_start_count -= 1; + esp_timer_start_once(sync_obj.sync_timer, 1000000); +#endif + break; + } + default: + ESP_LOGD(TAG, "state is unknown%s", __func__); + break; + } +} + +esp_err_t create_sync_timer(esp_timer_handle_t *timer_hdl) +{ + esp_err_t ret; + esp_timer_create_args_t tca = { + .callback = (esp_timer_cb_t)handle_sync_timeout, + .dispatch_method = ESP_TIMER_TASK, + .name = "SYNC_TIMER", + }; + tca.arg = client_mutex; + + ret = esp_timer_create(&tca, timer_hdl); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "timer create failed %d %x\n", __LINE__, ret); + return ret; + } + esp_timer_start_once( *timer_hdl, 10); + + return ret; +} + + +void sync_init(void) +{ + esp_err_t ret; + sync_obj.cmd_recv = &sync_cmd_recv; + client_mutex = xSemaphoreCreateMutex(); + if (!client_mutex) { + ESP_LOGE(TAG, "client_mutex Create failed "); + return; + } + coex_set_test_env("mutex", NULL, 0); + + bt_test_init(); + init_ble_gap_test_util(); + ret = esp_ble_gap_start_scanning(BLE_TC_SCAN_REPORT_PERIOD); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "esp_ble_gap_start_scanning error, %d", ret); + return ; + } + ret = create_sync_timer(&sync_obj.sync_timer); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "sync timer create failed"); + return ; + } + +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + excute_case(sync_obj.own_wifi_case); + vTaskDelay(3000 / portTICK_PERIOD_MS); +#endif + +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/sync.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/sync.h new file mode 100644 index 00000000..ad9df667 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/sync.h @@ -0,0 +1,122 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef __SYNC_H__ +#define __SYNC_H__ + +#include +#include +#include +#include +#include "esp_log.h" +#include "esp_timer.h" +#include "ble_unit.h" +#include "test_env.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" +#include "freertos/task.h" +#include "freertos/queue.h" + +#include "mesh_util.h" +#define PARAM_MAX 7 + +#define SYNC_TIMEOUT 500000 //500ms +#define PARAMTER(value) (((uint32_t)value) << 7) + +#define WIFI_TCP_TX_CASE 0x0 +#define WIFI_TCP_RX_CASE 0x1 +#define BLE_ADV_CASE 0x2 +#define BLE_SCAN_CASE 0x3 +#define NOT_CASE 0xff + +#define MSG_HEAD 0xcbb3 +#define MSG_TYPE 0xff +#define MSG_CONTINUE 0x0 +#define MSG_END 0x1 +#define MSG_DATA_BASE 0x8 +#define MSG_ID_ASSIGN_CASE 0x1 +#define MSG_ID_WIFI_DEV_INIT_FINISH 0x2 +#define MSG_ID_BT_DEV_INIT_FINISH 0x3 +#define MSG_ID_START_CASE 0x4 + +#define BLE_CASE_ID 0b100000 << PARAM_MAX +#define WIFI_CASE_ID 0b010000 << PARAM_MAX +#define SERVER_IP 0b001000 << PARAM_MAX +#define START_TIME 0b000100 << PARAM_MAX +#define WIFI_SSID 0b000010 << PARAM_MAX +#define WIFI_PASSWIRD 0b000001 << PARAM_MAX + + +#define GET_PARAM(value,bit) (((value) & (bit)) ? bit:0x0) + +#define MSG_MIN_LEN 8 + +typedef void (*sync_recv)(uint8_t *raw_data, uint32_t raw_data_len); + +typedef struct { + uint8_t length; + uint8_t type; + uint16_t head; + uint8_t msg_id; + uint16_t ctl: 3, + param_bit: 13; +} __attribute__((packed)) sync_msg_head; + +typedef struct { + uint8_t length; + uint8_t type; + uint16_t head; + uint8_t msg_id; + uint16_t ctl: 3, + param_bit: 13; + uint8_t reserve; //reserved for extend param_bit + uint8_t data[23]; +} __attribute__((packed)) sync_msg; + +typedef struct { + uint8_t case_id; + const uint16_t excpet_param_bit; +} auto_tc; +auto_tc auto_tb[6]; + + +typedef enum { +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + ASSIGN_CASE, +#else + WAIT_CASE, + WAIT_START, +#endif + START_CASE, +} sync_state; + +struct sync_t { + sync_state state; + uint8_t own_wifi_case; + uint8_t own_ble_case; + uint8_t start_time; + + uint16_t recv_param_bit; + + bool except_recv_wifi_id; + bool except_recv_bt_id; + + esp_timer_handle_t sync_timer; + sync_recv cmd_recv; +}; +struct sync_t sync_obj; + +extern SemaphoreHandle_t client_mutex; +void sync_init(void); + +#endif \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/test_env.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/test_env.c new file mode 100644 index 00000000..c843598f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/test_env.c @@ -0,0 +1,128 @@ +/* ESP BLE Mesh 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 +#include + +#include "esp_err.h" +#include "esp_log.h" +#include "test_env.h" +#include "sync.h" + +#define TAG "ENV" + +coex_test_env_t test_env = { +#if defined(CONFIG_EXAMPLE_MANAUL) + .ap_ssid = CONFIG_EXAMPLE_WIFI_SSID, + .ap_password = CONFIG_EXAMPLE_WIFI_PASSWORD, +#endif +#if defined(CONFIG_EXAMPLE_COEX_ROLE) + .ap_ssid = CONFIG_EXAMPLE_WIFI_SSID, + .ap_password = CONFIG_EXAMPLE_WIFI_PASSWORD, +#endif + .test_port = "8080", + .server_ip = "192.168.3.32", + .duration = "120000", + .is_start = false, +}; + +esp_err_t coex_set_test_env(const char *keyword, const char *value, uint8_t length) +{ + esp_err_t ret = ESP_OK; + if (!strcmp(keyword, "ap_ssid")) { + memset(test_env.ap_ssid, '\0', sizeof(test_env.ap_ssid)); + strncpy(test_env.ap_ssid, value, length); + } else if (!strcmp(keyword, "ap_password")) { + memset(test_env.ap_password, '\0', sizeof(test_env.ap_password)); + strncpy(test_env.ap_password, value, length); + } else if (!strcmp( keyword, "test_port")) { + memset(test_env.test_port, '\0', sizeof(test_env.test_port)); + strncpy(test_env.test_port, value, length); + } else if (!strcmp(keyword, "server_ip")) { + memset(test_env.server_ip, '\0', sizeof(test_env.server_ip)); + strncpy(test_env.server_ip, value, length); + } else if (!strcmp(keyword, "duration")) { + strncpy(test_env.duration, value, length); + } else if (!strcmp(keyword, "mutex")) { + test_env.run_mutex = client_mutex; + + } else { + ret = ESP_ERR_NOT_SUPPORTED; + } + return ret; +} + +const char *coex_get_test_env(const char *keyword) +{ + const char *ret = NULL; + if (!strcmp(keyword, "ap_ssid")) { + ret = test_env.ap_ssid; + } else if (!strcmp(keyword, "ap_password")) { + ret = test_env.ap_password; + } else if (!strcmp(keyword, "test_port")) { + ret = test_env.test_port; + } else if (!strcmp(keyword, "server_ip")) { + ret = test_env.server_ip; + } else if (!strcmp(keyword, "duration")) { + ret = test_env.duration; + } + return ret; +} + +void coex_print_test_env(void) +{ + ESP_LOGI(TAG, "current test env:"); + ESP_LOGI(TAG, "\tap_ssid: %s", test_env.ap_ssid); + ESP_LOGI(TAG, "\tap_password: %s", test_env.ap_password); + ESP_LOGI(TAG, "\ttest_port: %s", test_env.test_port); + ESP_LOGI(TAG, "\tserver_ip: %s", test_env.server_ip); + ESP_LOGI(TAG, "\tduration: %s", test_env.duration); +} + +bool coex_env_str_to_mac(uint8_t *str, uint8_t *dest) +{ + uint8_t loop = 0; + uint8_t tmp = 0; + uint8_t *src_p = str; + + if (strlen((char *)src_p) != 17) { // must be like 12:34:56:78:90:AB + ESP_LOGE(TAG, "wrong format"); + return false; + } + + for (loop = 0; loop < 17 ; loop++) { + if (loop % 3 == 2) { + if (src_p[loop] != ':') { + ESP_LOGE(TAG, "wrong format"); + return false; + } + + continue; + } + + if ((src_p[loop] >= '0') && (src_p[loop] <= '9')) { + tmp = tmp * 16 + src_p[loop] - '0'; + } else if ((src_p[loop] >= 'A') && (src_p[loop] <= 'F')) { + tmp = tmp * 16 + src_p[loop] - 'A' + 10; + } else if ((src_p[loop] >= 'a') && (src_p[loop] <= 'f')) { + tmp = tmp * 16 + src_p[loop] - 'a' + 10; + } else { + ESP_LOGE(TAG, "wrong format"); + return false; + } + + if (loop % 3 == 1) { + *dest++ = tmp; + tmp = 0; + } + } + + return true; +} + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/test_env.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/test_env.h new file mode 100644 index 00000000..aa419b93 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/test_env.h @@ -0,0 +1,43 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef __TEST_ENV_H__ +#define __TEST_ENV_H__ + +#include +#include "esp_err.h" +#include "freertos/FreeRTOS.h" +#include "freertos/semphr.h" + +#define MAX_SSID_LEN 32 +#define MAX_PASSWORD_LEN 64 +#define MAX_IP_STR_LEN 15 +#define MAX_PORT_STR_LEN 5 +#define MAX_MAC_ADDR_LEN 17 +#define INVALID_REMOTE_BT_MAC "ff:ff:ff:ff:ff:ff" +#define DURATION_MAX_LEN 10 + +typedef struct { + char ap_ssid[MAX_SSID_LEN + 1]; + char ap_password[MAX_PASSWORD_LEN + 1]; + char test_port[MAX_PORT_STR_LEN + 1]; + char server_ip[MAX_IP_STR_LEN + 1]; + char duration[DURATION_MAX_LEN + 1]; + bool is_start; + SemaphoreHandle_t run_mutex; +} coex_test_env_t; + +extern coex_test_env_t test_env; + +esp_err_t coex_set_test_env(const char *keyword, const char *value, uint8_t length); +const char *coex_get_test_env(const char *keyword); +void coex_print_test_env(void); +bool coex_env_str_to_mac(uint8_t *str, uint8_t *dest); + +#endif /* __TEST_ENV_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_connect.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_connect.c new file mode 100644 index 00000000..720a532f --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_connect.c @@ -0,0 +1,158 @@ +/* ESP BLE Mesh 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 +#include + +#include "esp_err.h" +#include "esp_log.h" + +#include "wifi_connect.h" + + +#include +#include "sdkconfig.h" +#include "esp_event.h" +#include "esp_wifi.h" +#include "esp_wifi_default.h" + +#include "esp_log.h" +#include "esp_netif.h" +#include "driver/gpio.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/event_groups.h" +#include "lwip/err.h" +#include "lwip/sys.h" + +#define GOT_IPV4_BIT BIT(0) + +#define CONNECTED_BITS (GOT_IPV4_BIT) + + +static EventGroupHandle_t s_connect_event_group; +static esp_ip4_addr_t s_ip_addr; +static const char *s_connection_name; +static esp_netif_t *s_example_esp_netif = NULL; + + + +static const char *TAG = "example_connect"; + +/* set up connection, Wi-Fi or Ethernet */ +static void start(const char *ssid, const char *passwd); + +/* tear down connection, release resources */ +static void stop(void); + +static void on_got_ip(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "Got IP event!"); + ip_event_got_ip_t *event = (ip_event_got_ip_t *)event_data; + memcpy(&s_ip_addr, &event->ip_info.ip, sizeof(s_ip_addr)); + xEventGroupSetBits(s_connect_event_group, GOT_IPV4_BIT); +} + +esp_err_t example_connect(const char *ssid, const char *passwd) +{ + if (s_connect_event_group != NULL) { + return ESP_ERR_INVALID_STATE; + } + s_connect_event_group = xEventGroupCreate(); + start(ssid, passwd); + ESP_ERROR_CHECK(esp_register_shutdown_handler(&stop)); + ESP_LOGI(TAG, "Waiting for IP"); + xEventGroupWaitBits(s_connect_event_group, CONNECTED_BITS, true, true, portMAX_DELAY); + ESP_LOGI(TAG, "Connected to %s", s_connection_name); + ESP_LOGI(TAG, "IPv4 address: " IPSTR, IP2STR(&s_ip_addr)); + return ESP_OK; +} + +esp_err_t example_disconnect(void) +{ + if (s_connect_event_group == NULL) { + return ESP_ERR_INVALID_STATE; + } + vEventGroupDelete(s_connect_event_group); + s_connect_event_group = NULL; + stop(); + ESP_LOGI(TAG, "Disconnected from %s", s_connection_name); + s_connection_name = NULL; + return ESP_OK; +} + + +static void on_wifi_disconnect(void *arg, esp_event_base_t event_base, + int32_t event_id, void *event_data) +{ + ESP_LOGI(TAG, "Wi-Fi disconnected, trying to reconnect..."); + esp_err_t err = esp_wifi_connect(); + if (err == ESP_ERR_WIFI_NOT_STARTED) { + return; + } + ESP_ERROR_CHECK(err); +} + +static void start(const char *ssid, const char *passwd) +{ + wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); + ESP_ERROR_CHECK(esp_wifi_init(&cfg)); + + esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_WIFI_STA(); + + esp_netif_t *netif = esp_netif_new(&netif_config); + + assert(netif); + + esp_netif_attach_wifi_station(netif); + esp_wifi_set_default_wifi_sta_handlers(); + + s_example_esp_netif = netif; + + ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect, NULL)); + ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL)); + ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); + + wifi_config_t wifi_config; + memset(&wifi_config, 0, sizeof(wifi_config)); + if (ssid) { + strncpy((char *)wifi_config.sta.ssid, ssid, strlen(ssid)); + } + if (passwd) { + strncpy((char *)wifi_config.sta.password, passwd, strlen(passwd)); + } + + ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config)); + ESP_ERROR_CHECK(esp_wifi_start()); + ESP_ERROR_CHECK(esp_wifi_connect()); + s_connection_name = ssid; +} + +static void stop(void) +{ + ESP_ERROR_CHECK(esp_event_handler_unregister(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect)); + ESP_ERROR_CHECK(esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip)); + esp_err_t err = esp_wifi_stop(); + if (err == ESP_ERR_WIFI_NOT_INIT) { + return; + } + ESP_ERROR_CHECK(err); + ESP_ERROR_CHECK(esp_wifi_deinit()); + ESP_ERROR_CHECK(esp_wifi_clear_default_wifi_driver_and_handlers(s_example_esp_netif)); + esp_netif_destroy(s_example_esp_netif); + s_example_esp_netif = NULL; +} + +esp_netif_t *get_example_netif(void) +{ + return s_example_esp_netif; +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_connect.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_connect.h new file mode 100644 index 00000000..57c2a4d8 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_connect.h @@ -0,0 +1,22 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef __WIFI_CONNECT_H__ +#define __WIFI_CONNECT_H__ + +#include "esp_err.h" +#include "esp_netif.h" +#include "esp_event.h" + +esp_err_t example_connect(const char *ssid, const char *passwd); +esp_err_t example_disconnect(void); +esp_err_t example_configure_stdin_stdout(void); +esp_netif_t *get_example_netif(void); + +#endif /* __WIFI_CONNECT_H__ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_unit.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_unit.c new file mode 100644 index 00000000..41f99860 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_unit.c @@ -0,0 +1,187 @@ +/* ESP BLE Mesh 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 +#include +#include +#include + +#include "wifi_unit.h" +#include "wifi_connect.h" +#define TAG "WIFI_UINT" + +uint32_t utils_get_system_ts(void) +{ + return esp_log_timestamp(); +} + +esp_err_t wifi_unit_client_establish(int *sock, const char *ip, const char *port) +{ + esp_err_t ret = 0; + uint32_t start_ts; + uint32_t timeout = 10000; + struct sockaddr_in sock_addr; + int s; + + memset(&sock_addr, 0, sizeof(sock_addr)); + sock_addr.sin_family = AF_INET; + sock_addr.sin_addr.s_addr = ipaddr_addr(ip); + sock_addr.sin_port = htons(atoi(port)); + + start_ts = utils_get_system_ts(); + do { + s = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (s < 0) { + ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); + } + ret = connect(s, (struct sockaddr *)&sock_addr, sizeof(sock_addr)); + if (ret == 0) { + *sock = s; + break; + } else if (s > 0) { + close(s); + } + } while (utils_get_system_ts() - start_ts < timeout); + return ret; +} + +esp_err_t wifi_unit_server_establish(int *socket_id, const char *port) +{ + esp_err_t ret = -1; + struct sockaddr_in local_addr; + uint32_t local_ip; + + static int ls_sock = -1; + + local_ip = wifi_util_get_ip(); + + if (ls_sock < 0) { + ls_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (ls_sock < 0) { + ESP_LOGE(TAG, "create socket failed"); + return ls_sock; + } + + local_addr.sin_family = AF_INET; + local_addr.sin_port = htons(atoi(port)); + local_addr.sin_addr.s_addr = local_ip; + ret = bind(ls_sock, (struct sockaddr *)&local_addr, sizeof(local_addr)); + if (ret != 0) { + ESP_LOGE(TAG, "socket bind failed"); + return ret; + } + + ret = listen(ls_sock, 1); + if (ret < 0) { + ESP_LOGE(TAG, "socket listen failed"); + return ret; + } + } + + struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6 + socklen_t addrLen = sizeof(sourceAddr); + *socket_id = accept(ls_sock, (struct sockaddr *)&sourceAddr, &addrLen); + if (*socket_id < 0) { + ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); + return -1; + } + ESP_LOGI(TAG, "Socket accepted"); + return ret; +} + +esp_err_t wifi_unit_tcp_recv(int socket_id, const char *duration, uint32_t user_date[]) +{ + esp_err_t ret = -1; + uint32_t start_ts; + uint8_t *buffer; + struct timeval tv_t; + uint32_t *recv_len = &user_date[1]; + tv_t.tv_sec = 1; + tv_t.tv_usec = 0; + ret = setsockopt(socket_id, SOL_SOCKET, SO_RCVTIMEO, &tv_t, sizeof(tv_t)); + + buffer = malloc(2920); + + if (buffer == NULL) { + ESP_LOGE(TAG, "%s malloc fail\n", __func__); + return ESP_ERR_NO_MEM; + } + + start_ts = utils_get_system_ts(); + + while (utils_get_system_ts() - start_ts < atoi(duration)) { + ret = recv(socket_id, buffer, 2920, 0); + if (ret > 0) { + *recv_len = *recv_len + ret; + } else if (ret == 0) { + break; + } + } + free(buffer); + if (ret > 0 ) { + ret = ESP_OK; + } + return ret; +} + +esp_err_t wifi_util_tcp_send(int socket_id, uint32_t len, uint32_t delay, uint32_t *sent_len, uint32_t timeout) +{ + esp_err_t ret = ESP_OK; + uint32_t start_ts; + uint8_t *buffer; + + if ( len == 0) { + return ESP_ERR_INVALID_ARG; + } + buffer = malloc(len); + if (buffer == NULL) { + ESP_LOGE(TAG, "%s malloc fail\n", __func__); + return ESP_ERR_NO_MEM; + } + + start_ts = utils_get_system_ts(); + + while (utils_get_system_ts() - start_ts < timeout) { + ret = send(socket_id, buffer, len, 0); + if (ret < 0) { + ESP_LOGE(TAG, "recv failed: errno %d", errno); + break; + } + *sent_len = *sent_len + ret; + + if (delay) { + vTaskDelay(delay / portTICK_PERIOD_MS); + } + } + if (ret == len) { + ret = ESP_OK; + } else { + ESP_LOGE(TAG, "tcp send error, %d", ret); + ret = -2; + } + + free(buffer); + + return ret; +} + +uint32_t wifi_util_get_ip(void) +{ + esp_netif_ip_info_t ip_info; + esp_netif_t *netif = get_example_netif(); + esp_netif_get_ip_info(netif, &ip_info); + return ip_info.ip.addr; +} + +void wifi_util_init(void) +{ + ESP_ERROR_CHECK(nvs_flash_init()); + ESP_ERROR_CHECK(esp_netif_init()); + ESP_ERROR_CHECK(esp_event_loop_create_default()); +} \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_unit.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_unit.h new file mode 100644 index 00000000..d0b664c7 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/components/case/wifi_unit.h @@ -0,0 +1,38 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef _WIFI_UNIT_H +#define _WIFI_UNIT_H + +#include "esp_wifi_types.h" +#include "esp_wifi.h" +#include "esp_event.h" +#include "esp_log.h" +#include "esp_timer.h" +#include "nvs_flash.h" + +#include "esp_netif.h" + +#include "lwip/err.h" +#include "lwip/sockets.h" +#include "lwip/sys.h" +#include "lwip/netdb.h" +#include "lwip/dns.h" + +uint32_t utils_get_system_ts(void); + +void wifi_util_init(void); + +uint32_t wifi_util_get_ip(void); +esp_err_t wifi_unit_client_establish(int *sock, const char *ip, const char *port); +esp_err_t wifi_unit_server_establish(int *socket_id, const char *port); + +esp_err_t wifi_unit_tcp_recv(int socket_id, const char *duration, uint32_t user_date[]); +esp_err_t wifi_util_tcp_send(int socket_id, uint32_t len, uint32_t delay, uint32_t *sent_len, uint32_t timeout); +#endif /* _WIFI_UNIT_H */ \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/CMakeLists.txt new file mode 100644 index 00000000..cf4489a4 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/CMakeLists.txt @@ -0,0 +1,3 @@ +idf_component_register(SRCS "main.c" "coex_cmd.c" + INCLUDE_DIRS "." + REQUIRED_IDF_TARGETS esp32) \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/Kconfig.projbuild b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/Kconfig.projbuild new file mode 100644 index 00000000..ac52aa2a --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/Kconfig.projbuild @@ -0,0 +1,70 @@ +menu "Example Configuration" + + choice EXAMPLE_RUN_MODE + prompt "select run mode" + help + select run mode + + config EXAMPLE_AUTO + bool "auto" + help + In automatic mode, the program coordinates three development board work + through a synchronization mechanism. + + config EXAMPLE_MANAUL + bool "manual" + help + In manual mode, you will work with three development boards via commands. + endchoice + + config EXAMPLE_WIFI_SSID + depends on EXAMPLE_MANAUL + string "WiFi SSID" + help + SSID (network name) for the example to connect to. The length cannot exceed 20 bytes. + config EXAMPLE_WIFI_PASSWORD + depends on EXAMPLE_MANAUL + string "WiFi Password" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + The length cannot exceed 20 bytes. + + choice EXAMPLE_SELECT_ROLE + prompt "select role" + depends on EXAMPLE_AUTO + config EXAMPLE_COEX_ROLE + bool "run device as coex role" + config EXAMPLE_WIFI_ROLE + bool "run device as wifi role" + config EXAMPLE_BT_ROLE + bool "run device as bluetooth role" + endchoice + + choice EXAMPLE_SELECT_CASE + prompt "select case" + depends on EXAMPLE_COEX_ROLE + config EXAMPLE_COEX_TX_ADV + bool "TCP TX and BLE ADV" + config EXAMPLE_COEX_RX_ADV + bool "TCP RX and BLE ADV" + config EXAMPLE_COEX_TX_SCAN + bool "TCP TX and BLE SCAN" + config EXAMPLE_COEX_RX_SCAN + bool "TCP RX and BLE SCAN" + endchoice + + config EXAMPLE_WIFI_SSID + depends on EXAMPLE_COEX_ROLE + string "WiFi SSID" + help + SSID (network name) for the example to connect to. The length cannot exceed 20 bytes. + config EXAMPLE_WIFI_PASSWORD + depends on EXAMPLE_COEX_ROLE + string "WiFi Password" + help + WiFi password (WPA or WPA2) for the example to use. + Can be left blank if the network has no security set. + The length cannot exceed 20 bytes. + +endmenu #"Example Configuration End" \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/coex_cmd.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/coex_cmd.c new file mode 100644 index 00000000..5df2616d --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/coex_cmd.c @@ -0,0 +1,168 @@ +/* ESP BLE Mesh 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 +#include +#include "esp_log.h" +#include "esp_console.h" +#include "argtable3/argtable3.h" + +#include "esp_bt.h" +#include "esp_bt_main.h" +#include "esp_bt_device.h" + +#include "run_tc.h" +#include "test_env.h" + + +#define TAG_CNSL "CNSL" + +typedef struct { + struct arg_str *wifi_tc_idx; + struct arg_str *bt_tc_idx; + struct arg_end *end; +} tc_run_args_t; + +typedef struct { + struct arg_lit *set; + struct arg_lit *get; + struct arg_str *key; + struct arg_str *value; + struct arg_end *end; +} env_param_cmd_args_t; + +static tc_run_args_t tc_run_args; +static env_param_cmd_args_t env_param_cmd_args; + +static int process_env_parameter_cmd(int argc, char **argv) +{ + int nerrors = arg_parse(argc, argv, (void **) &env_param_cmd_args); + int ret; + const char *env_value = ""; + + if (nerrors != 0) { + arg_print_errors(stderr, env_param_cmd_args.end, argv[0]); + return 1; + } + if (env_param_cmd_args.set->count == 1) { + if (env_param_cmd_args.key->count == 1) { + if (env_param_cmd_args.value->count == 1) { + env_value = env_param_cmd_args.value->sval[0]; + } + ret = coex_set_test_env(env_param_cmd_args.key->sval[0], env_value, strlen(env_param_cmd_args.value->sval[0])); + if (ret == ESP_ERR_NOT_SUPPORTED) { + ESP_LOGE(TAG_CNSL, "Not supported env key"); + } else if (ret == ESP_ERR_INVALID_ARG) { + ESP_LOGE(TAG_CNSL, "Invalid value"); + } + ESP_LOGI(TAG_CNSL, "env set done"); + } else { + ESP_LOGE(TAG_CNSL, "env key not set correctly"); + } + } else if (env_param_cmd_args.get->count == 1) { + coex_print_test_env(); + } + return 0; +} + +static int process_restart_cmd(int argc, char **argv) +{ + ESP_LOGI(TAG_CNSL, "restarting..."); + esp_restart(); + return 0; +} + + +static int process_run_tc_cmd(int argc, char **argv) +{ + run_task_msg_t msg; + int nerrors = arg_parse(argc, argv, (void **) &tc_run_args); + if (nerrors != 0) { + arg_print_errors(stderr, tc_run_args.end, argv[0]); + return 1; + } + + if (tc_run_args.wifi_tc_idx->count == 1) { + msg.case_id = atoi(tc_run_args.wifi_tc_idx->sval[0]); + if (xQueueSend(xTaskQueue, &msg, portMAX_DELAY) != pdTRUE) { + ESP_LOGE(TAG_CNSL, "xTaskQueue Post failed\n"); + } + } + + if (tc_run_args.bt_tc_idx->count == 1) { + msg.case_id = atoi(tc_run_args.bt_tc_idx->sval[0]); + if (xQueueSend(xTaskQueue, &msg, portMAX_DELAY) != pdTRUE) { + ESP_LOGE(TAG_CNSL, "xTaskQueue Post failed\n"); + } + } + + return 0; +} + +static int process_get_mac_addr_cmd(int argc, char **argv) +{ + const uint8_t *mac = esp_bt_dev_get_address(); + + if (mac != NULL) { + ESP_LOGI(TAG_CNSL, "+BTMAC:"MACSTR"\n", MAC2STR(mac)); + } + return 0; +} +void register_coex_cmd(void) +{ + const esp_console_cmd_t restart_cmd = { + .command = "restart", + .help = "restart cmd", + .hint = NULL, + .func = &process_restart_cmd, + .argtable = NULL + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&restart_cmd) ); + + const esp_console_cmd_t get_mac_cmd = { + .command = "mac", + .help = "Get DUT mac address", + .hint = NULL, + .func = &process_get_mac_addr_cmd, + .argtable = NULL + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&get_mac_cmd) ); + + tc_run_args.wifi_tc_idx = arg_str0("w", "wifi", "", "0 : wifi_tcp_tx_throught 1 : wifi_tcp_rx_throught\n"); + tc_run_args.bt_tc_idx = arg_str0("b", "bluetooth", "", "2 :ble_adv 3 : ble_scan\n"); + tc_run_args.end = arg_end(2); + + const esp_console_cmd_t run_tc_cmd = { + .command = "run_tc", + .help = "run wifi bt test case command", + .hint = NULL, + .func = &process_run_tc_cmd, + .argtable = &tc_run_args + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&run_tc_cmd) ); + + env_param_cmd_args.set = arg_lit0("s", "set", "set env parameter"); + env_param_cmd_args.get = arg_lit0("g", "get", "get env parameter"); + env_param_cmd_args.key = arg_str0("k", "key", "", "env parameter key"); + env_param_cmd_args.value = arg_str0("v", "value", "", "env parameter value (only used with set)"); + env_param_cmd_args.end = arg_end(4); + + const esp_console_cmd_t env_cmd = { + .command = "env", + .help = "Set or get test environment parameters", + .hint = NULL, + .func = &process_env_parameter_cmd, + .argtable = &env_param_cmd_args, + }; + + ESP_ERROR_CHECK( esp_console_cmd_register(&env_cmd) ); +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/coex_cmd.h b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/coex_cmd.h new file mode 100644 index 00000000..397c1b05 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/coex_cmd.h @@ -0,0 +1,16 @@ +/* ESP BLE Mesh 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. +*/ + +#ifndef MAIN_COEX_CMD_H_ +#define MAIN_COEX_CMD_H_ + +#define CNSL_CMD_OUTPUT_PREFIX "COEX_CNSL_OUTPUT" +void register_coex_cmd(void); + +#endif /* MAIN_COEX_CMD_H_ */ diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/component.mk b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/component.mk new file mode 100644 index 00000000..d68c5375 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/component.mk @@ -0,0 +1,5 @@ +# +# "main" pseudo-component makefile. +# +# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) +# \ No newline at end of file diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/main.c b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/main.c new file mode 100644 index 00000000..b868b2bb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/main/main.c @@ -0,0 +1,145 @@ +/* ESP BLE Mesh 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 +#include +#include "esp_system.h" +#include "esp_log.h" +#include "esp_console.h" +#include "esp_vfs_dev.h" +#include "driver/uart.h" +#include "linenoise/linenoise.h" +#include "argtable3/argtable3.h" +#include "esp_vfs_fat.h" +#include "nvs.h" +#include "nvs_flash.h" + +#include "esp_coexist.h" +#include "coex_cmd.h" +#include "run_tc.h" +#include "sync.h" + +static void initialize_nvs(void) +{ + esp_err_t err = nvs_flash_init(); + if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_ERROR_CHECK( nvs_flash_erase() ); + err = nvs_flash_init(); + } + ESP_ERROR_CHECK(err); +} + +static void initialize_console(void) +{ + /* Disable buffering on stdin and stdout */ + setvbuf(stdin, NULL, _IONBF, 0); + + /* Minicom, screen, idf_monitor send CR when ENTER key is pressed */ + esp_vfs_dev_uart_port_set_rx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CR); + /* Move the caret to the beginning of the next line on '\n' */ + esp_vfs_dev_uart_port_set_tx_line_endings(CONFIG_ESP_CONSOLE_UART_NUM, ESP_LINE_ENDINGS_CRLF); + + /* Configure UART. Note that REF_TICK is used so that the baud rate remains + * correct while APB frequency is changing in light sleep mode. + */ + const uart_config_t uart_config = { + .baud_rate = CONFIG_CONSOLE_UART_BAUDRATE, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .use_ref_tick = true + }; + ESP_ERROR_CHECK( uart_param_config(CONFIG_ESP_CONSOLE_UART_NUM, &uart_config) ); + + /* Install UART driver for interrupt-driven reads and writes */ + ESP_ERROR_CHECK( uart_driver_install(CONFIG_ESP_CONSOLE_UART_NUM, + 256, 0, 0, NULL, 0) ); + + /* Tell VFS to use UART driver */ + esp_vfs_dev_uart_use_driver(CONFIG_ESP_CONSOLE_UART_NUM); + + /* Initialize the console */ + esp_console_config_t console_config = { + .max_cmdline_args = 8, + .max_cmdline_length = 256, +#if CONFIG_LOG_COLORS + .hint_color = atoi(LOG_COLOR_CYAN) +#endif + }; + ESP_ERROR_CHECK( esp_console_init(&console_config) ); + + /* Configure linenoise line completion library */ + /* Enable multiline editing. If not set, long commands will scroll within + * single line. + */ + linenoiseSetMultiLine(1); + + /* Tell linenoise where to get command completions and hints */ + linenoiseSetCompletionCallback(&esp_console_get_completion); + linenoiseSetHintsCallback((linenoiseHintsCallback *) &esp_console_get_hint); + + /* Set command history size */ + linenoiseHistorySetMaxLen(100); + +} + + + +void app_main(void) +{ + initialize_nvs(); + + initialize_console(); + run_tc_init(); + + /* Register commands */ + esp_console_register_help_command(); + register_coex_cmd(); + +#if defined(CONFIG_EXAMPLE_AUTO) + sync_init(); +#endif + + /* Prompt to be printed before each line. + * This can be customized, made dynamic, etc. + */ + printf("esp-idf version: %s\n\n", esp_get_idf_version()); + printf("coexist version: %s\n\n", esp_coex_version_get()); + const char *prompt = "esp32> "; + linenoiseSetDumbMode(1); + + + /* Main loop */ + while (true) { + /* Get a line using linenoise. + * The line is returned when ENTER is pressed. + */ + char *line = linenoise(prompt); + if (line == NULL) { /* Ignore empty lines */ + continue; + } + /* Add the command to the history */ + linenoiseHistoryAdd(line); + + /* Try to run the command */ + int ret; + esp_err_t err = esp_console_run(line, &ret); + if (err == ESP_ERR_NOT_FOUND) { + printf("Unrecognized command\n"); + } else if (err == ESP_ERR_INVALID_ARG) { + // command was empty + } else if (err == ESP_OK && ret != ESP_OK) { + printf("Command returned non-zero error code: 0x%x (%s)\n", ret, esp_err_to_name(err)); + } else if (err != ESP_OK) { + printf("Internal error: %s\n", esp_err_to_name(err)); + } + /* linenoise allocates line buffer on the heap, so need to free it */ + linenoiseFree(line); + } +} diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/partitions.csv b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/partitions.csv new file mode 100644 index 00000000..61249457 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/partitions.csv @@ -0,0 +1,5 @@ +# Name, Type, SubType, Offset, Size, Flags +# Note: if you change the phy_init or app partition offset, make sure to change the offset in Kconfig.projbuild +nvs, data, nvs, 0x9000, 0x6000, +phy_init, data, phy, 0xf000, 0x1000, +factory, app, factory, 0x10000, 0x1F0000, diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/sdkconfig.defaults b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/sdkconfig.defaults new file mode 100644 index 00000000..ab918779 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_coex_test/sdkconfig.defaults @@ -0,0 +1,110 @@ +# +# Automatically generated file; DO NOT EDIT. +# Espressif IoT Development Framework Configuration +# + +# +# SDK tool configuration +# +CONFIG_SDK_MAKE_WARN_UNDEFINED_VARIABLES=y +# +# Partition Table +# +# CONFIG_PARTITION_TABLE_SINGLE_APP is not set +# CONFIG_PARTITION_TABLE_TWO_OTA is not set +CONFIG_PARTITION_TABLE_CUSTOM=y +CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" +CONFIG_PARTITION_TABLE_CUSTOM_APP_BIN_OFFSET=0x10000 +CONFIG_PARTITION_TABLE_FILENAME="partitions.csv" +CONFIG_APP_OFFSET=0x10000 + + +# +# Serial flasher config +# +CONFIG_ESPTOOLPY_BAUD_921600B=y +CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y +CONFIG_ESPTOOLPY_FLASHFREQ_80M=y + +# +# Component config +# +# +# Bluetooth +# +CONFIG_BT_ENABLED=y +CONFIG_BTDM_CTRL_MODE_BLE_ONLY=y +CONFIG_BTDM_CTRL_PINNED_TO_CORE=1 +CONFIG_BTDM_MODEM_SLEEP=n +CONFIG_BTDM_BLE_SCAN_DUPL=y +CONFIG_BTDM_SCAN_DUPL_TYPE_DATA_DEVICE=y +CONFIG_BTDM_BLE_MESH_SCAN_DUPL_EN=y +CONFIG_BT_BLUEDROID_ENABLED=y +CONFIG_BT_BLUEDROID_PINNED_TO_CORE_1=y +CONFIG_BT_BTU_TASK_STACK_SIZE=4512 +CONFIG_BT_GATTS_SEND_SERVICE_CHANGE_MANUAL=y +CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST=y +CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY=y +CONFIG_BT_SMP_ENABLE=y +CONFIG_BT_RESERVE_DRAM=0x10000 + +# +# ESP32-specific +# +CONFIG_ESP32_DEFAULT_CPU_FREQ_240=y +CONFIG_MEMMAP_SMP=y +CONFIG_ESP32_SPIRAM_SUPPORT=y +CONFIG_SPIRAM_SPEED_80M=y +CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 +CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y +CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=6 +CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200 +CONFIG_SPIRAM_IGNORE_NOTFOUND=y + +# +# Wi-Fi +# +CONFIG_ESP32_WIFI_SW_COEXIST_ENABLE=y +CONFIG_ESP32_WIFI_SW_COEXIST_PREFERENCE_BALANCE=y +CONFIG_ESP32_WIFI_SW_COEXIST_PREFERENCE_VALUE=2 +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=64 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=16 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=16 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=6 + +# +# FreeRTOS +# +CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=1536 +CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=5 +CONFIG_FREERTOS_HZ=1000 + +# +# LWIP +# +CONFIG_LWIP_IP_FRAG=y +CONFIG_LWIP_IP_REASSEMBLY=y +CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=64 +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_TASK_AFFINITY_CPU0=y +CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x0 + +# +# ble mesh +# +CONFIG_BLE_MESH=y +CONFIG_BLE_MESH_NODE=y +CONFIG_BLE_MESH_PB_GATT=y +CONFIG_BLE_MESH_TX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_RX_SEG_MSG_COUNT=10 +CONFIG_BLE_MESH_GENERIC_ONOFF_CLI=y diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/CMakeLists.txt b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/CMakeLists.txt new file mode 100644 index 00000000..ec534dfb --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/CMakeLists.txt @@ -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.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(ble_mesh_console) diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/Makefile b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/Makefile new file mode 100644 index 00000000..718b7922 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/Makefile @@ -0,0 +1,10 @@ +# +# This is a project Makefile. It is assumed the directory this Makefile resides in is a +# project subdirectory. +# + +PROJECT_NAME := ble_mesh_console + +COMPONENT_ADD_INCLUDEDIRS := components/include + +include $(IDF_PATH)/make/project.mk diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/README.md new file mode 100644 index 00000000..86a244b8 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/README.md @@ -0,0 +1,12 @@ +| Supported Targets | ESP32 | +| ----------------- | ----- | + +# ble mesh node console demo +## Introduction +This demo implements ble mesh node basic features.Based on this demo, node can be scaned and proved by provisioner, reply get/set message to provisioner. + +Demo steps: +1. Build the ble mesh node console demo with sdkconfig.default +2. register node and set oob info, load model to init ble mesh node +3. enable bearer, so that it can be scaned and provisioned by provisioner + diff --git a/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_commands_README.md b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_commands_README.md new file mode 100644 index 00000000..b9277422 --- /dev/null +++ b/firmware/esp-idf/wumei-smart-firmware/examples/bluetooth/esp_ble_mesh/ble_mesh_console/ble_mesh_commands_README.md @@ -0,0 +1,166 @@ +# The Document of ESP32 BLE_MESH Commands + +## Overall Command + + +* `bmreg`: Provisioner/node register callback + * Example: `bmreg` + * Result: `Bm:Reg,OK` + +* `bminit`: Provisioner/node initialize + * `-m`: `mesh modle` + * Example: `bminit -m 0x0001` + * Result: `Bm:Init,OK` + +* `bmpbind`: Provisioner binds Appkey with local model + * `-a`: `:appkey index` + * `-e`: `:element address` + * `-n`: `:network index` + * `-m`: `:model id` + * `-c`: `:company id` + * Example: `bmpbind -a 0 -e 0x01 -m 0x1001 -n 0x00` + * Result: `provisioning:AppKeyBind,OK` + +* `bmpdev`: Provisioner add/delete unprovisioned device + * `-z`: `action type ` + * `-d`: `device address` + * `-u`: `device uuid` + * `-a`: `address type` + * `-f`: `address flag` + * `-b`: `used bearer` + * `-o`: `oob information` + * Example: `bmpdev -z add -d bt_mac -b -1 -a 0` + * Result: `provisioner:DevAdd/Del,OK` + +* `bmoob`: Provisioner/node config OOB parameters + * `-s`: `Static OOB value` + * `-l`: `Static OOB value length` + * `-x`: `Maximum size of Output OOB` + * `-o`: `Supported Output OOB Actions` + * `-y`: `Maximum size of Input OOB` + * `-i`: `Supported Input OOB Actions` + * `-p`: `start address assigned by provisioner` + * Example: `bmoob -o -0 -x 0` + * Result: `OOB:Load,OK` + +* `bmpbearer`: Enable/disable provisioner different bearer + * `-b`: `bearer supported` + * `-e`: `enable or disable bearer` + * Example: `bmpbearer -b 1 -e 1` + * Result: `provisioner:EnBearer,OK` + +* `bmnbearer`: Enable/disable node different bearer + * `-b`: `bearer supported` + * `-e`: `enable or disable bearer` + * Example: `bmnbearer -b 1 -e 1` + * Result: `Node:EnBearer,OK` + +* `bmpkey`: Add/Delete NetKey and AppKey of Provisioner + * `-z`: `:add app key or network key` + * `-n`: `:network key index` + * `-k`: `:appkey or network` + * `-a`: `:appkey index` + * Example: `bmpkey -z netkey -n 1 -k ` + * Result: `provisioner:NetKeyAdd,OK` + +* `bmccm`: BLE Mesh configuration client model operations + * `-z`: `:action type`:add or del client model + * `-x`: `:set state` + * `-o`: `:message opcode` + * `-u`: `
:unicast address` + * `-n`: `:net work index` + * `-i`: `:appkey index` + * `-r`: `:relay statue` + * `-t`: `:relay transmit` + * `-c`: `:company id` + * `-v`: `:value` + * `-a`: `
:address` + * `-m`: `:model id` + * Example: `bmccm -z reg` + * Result: `ConfigClient:OK` + +* `bmgocm`: BLE Mesh onoff client model operations + * `-z`: `:action type`: on or off client model + * `-o`: `:message opcode` + * `-u`: `
:unicast address` + * `-n`: `:network key index` + * `-a`: `:appkey index` + * `-r`: `:role` + * `-t`: `