NimBLE: Port NimBLE stack to IDF Release v3.3 (Backport v3.3)

Includes all the latest NimBLE stack changes from idf-v4.0 with few idf-v3.3
specific modifications.
- Addition of nimble component as submodule (`nimble-1.1.0-idf-v3.3`), contains
  IDF v3.3 specific minor changes.
- Example applications are identical to idf-v4.0
- Modification in `bt/Kconfig` to accommodate NimBLE as a BT host.
This commit is contained in:
Prasad Alatkar 2019-10-18 13:23:24 +08:00 committed by Angus Gratton
parent 9f10f684d3
commit 3545b58986
68 changed files with 8386 additions and 3 deletions

4
.gitmodules vendored
View File

@ -69,3 +69,7 @@
[submodule "examples/build_system/cmake/import_lib/main/lib/tinyxml2"]
path = examples/build_system/cmake/import_lib/main/lib/tinyxml2
url = https://github.com/leethomason/tinyxml2
[submodule "components/nimble/nimble"]
path = components/nimble/nimble
url = https://github.com/espressif/esp-nimble.git

View File

@ -283,7 +283,7 @@ if(CONFIG_BT_ENABLED)
endif()
# requirements can't depend on config
set(COMPONENT_PRIV_REQUIRES nvs_flash)
set(COMPONENT_PRIV_REQUIRES nvs_flash nimble)
register_component()

View File

@ -1301,4 +1301,289 @@ menu Bluetooth
default 0xdb5c if BT_ENABLED
default 0
menuconfig NIMBLE_ENABLED
bool "Enable NimBLE host stack"
depends on BTDM_CONTROLLER_HCI_MODE_VHCI && !BLUEDROID_ENABLED
default n
help
This enables NimBLE host stack
config NIMBLE_MAX_CONNECTIONS
int "Maximum number of concurrent connections"
range 1 9
default 1
depends on NIMBLE_ENABLED
help
Defines maximum number of concurrent BLE connections
config NIMBLE_MAX_BONDS
int "Maximum number of bonds to save across reboots"
default 3
depends on NIMBLE_ENABLED
help
Defines maximum number of bonds to save for peer security and our security
config NIMBLE_MAX_CCCDS
int "Maximum number of CCC descriptors to save across reboots"
default 8
depends on NIMBLE_ENABLED
help
Defines maximum number of CCC descriptors to save
config NIMBLE_L2CAP_COC_MAX_NUM
int "Maximum number of connection oriented channels"
range 0 9
depends on NIMBLE_ENABLED
default 0
help
Defines maximum number of BLE Connection Oriented Channels. When set to (0), BLE COC is not compiled in
choice NIMBLE_PINNED_TO_CORE_CHOICE
prompt "The CPU core on which NimBLE host will run"
depends on NIMBLE_ENABLED && !FREERTOS_UNICORE
help
The CPU core on which NimBLE host will run. You can choose Core 0 or Core 1.
Cannot specify no-affinity
config NIMBLE_PINNED_TO_CORE_0
bool "Core 0 (PRO CPU)"
config NIMBLE_PINNED_TO_CORE_1
bool "Core 1 (APP CPU)"
depends on !FREERTOS_UNICORE
endchoice
config NIMBLE_PINNED_TO_CORE
int
depends on NIMBLE_ENABLED
default 0 if NIMBLE_PINNED_TO_CORE_0
default 1 if NIMBLE_PINNED_TO_CORE_1
default 0
config NIMBLE_TASK_STACK_SIZE
int "NimBLE Host task stack size"
depends on NIMBLE_ENABLED
default 4096
help
This configures stack size of NimBLE host task
config NIMBLE_ROLE_CENTRAL
bool "Enable BLE Central role"
depends on NIMBLE_ENABLED
default y
config NIMBLE_ROLE_PERIPHERAL
bool "Enable BLE Peripheral role"
depends on NIMBLE_ENABLED
default y
config NIMBLE_ROLE_BROADCASTER
bool "Enable BLE Broadcaster role"
depends on NIMBLE_ENABLED
default y
config NIMBLE_ROLE_OBSERVER
bool "Enable BLE Observer role"
depends on NIMBLE_ENABLED
default y
config NIMBLE_NVS_PERSIST
bool "Persist the BLE Bonding keys in NVS"
depends on NIMBLE_ENABLED
default y
help
Enable this flag to make bonding persistent across device reboots
config NIMBLE_SM_LEGACY
bool "Security manager legacy pairing"
depends on NIMBLE_ENABLED
default y
help
Enable security manager legacy pairing
config NIMBLE_SM_SC
bool "Security manager secure connections (4.2)"
depends on NIMBLE_ENABLED
default y
help
Enable security manager secure connections
config NIMBLE_DEBUG
bool "Enable extra runtime asserts and host debugging"
default n
depends on NIMBLE_ENABLED
help
This enables extra runtime asserts and host debugging
config NIMBLE_SM_SC_DEBUG_KEYS
bool "Use predefined public-private key pair"
default n
depends on NIMBLE_ENABLED && NIMBLE_SM_SC
help
If this option is enabled, SM uses predefined DH key pair as described
in Core Specification, Vol. 3, Part H, 2.3.5.6.1. This allows to
decrypt air traffic easily and thus should only be used for debugging.
config NIMBLE_SVC_GAP_DEVICE_NAME
string "BLE GAP default device name"
depends on NIMBLE_ENABLED
default "nimble"
help
The Device Name characteristic shall contain the name of the device as an UTF-8 string.
This name can be changed by using API ble_svc_gap_device_name_set()
config NIMBLE_GAP_DEVICE_NAME_MAX_LEN
int "Maximum length of BLE device name in octets"
depends on NIMBLE_ENABLED
default 31
help
Device Name characteristic value shall be 0 to 248 octets in length
config NIMBLE_ATT_PREFERRED_MTU
int "Preferred MTU size in octets"
depends on NIMBLE_ENABLED
default 256
help
This is the default value of ATT MTU indicated by the device during an ATT MTU exchange.
This value can be changed using API ble_att_set_preferred_mtu()
config NIMBLE_SVC_GAP_APPEARANCE
hex "External appearance of the device"
depends on NIMBLE_ENABLED
default 0
help
Standard BLE GAP Appearance value in HEX format e.g. 0x02C0
config NIMBLE_ACL_BUF_COUNT
int "ACL Buffer count"
depends on NIMBLE_ENABLED
default 12
help
The number of ACL data buffers.
config NIMBLE_ACL_BUF_SIZE
int "ACL Buffer size"
depends on NIMBLE_ENABLED
default 255
help
This is the maximum size of the data portion of HCI ACL data packets.
It does not include the HCI data header (of 4 bytes)
config NIMBLE_HCI_EVT_BUF_SIZE
int "HCI Event Buffer size"
depends on NIMBLE_ENABLED
default 70
help
This is the size of each HCI event buffer in bytes
config NIMBLE_HCI_EVT_HI_BUF_COUNT
int "High Priority HCI Event Buffer count"
depends on NIMBLE_ENABLED
default 30
help
This is the high priority HCI events' buffer size. High-priority
event buffers are for everything except advertising reports. If there
are no free high-priority event buffers then host will try to allocate a
low-priority buffer instead
config NIMBLE_HCI_EVT_LO_BUF_COUNT
int "Low Priority HCI Event Buffer count"
depends on NIMBLE_ENABLED
default 8
help
This is the low priority HCI events' buffer size. Low-priority event
buffers are only used for advertising reports. If there are no free
low-priority event buffers, then an incoming advertising report will
get dropped
menuconfig NIMBLE_MESH
bool "Enable BLE mesh functionality"
select NIMBLE_SM_SC
depends on NIMBLE_ENABLED
default n
help
Enable BLE Mesh functionality
config NIMBLE_MESH_PROXY
bool "Enable mesh proxy functionality"
default n
depends on NIMBLE_MESH
help
Enable proxy. This is automatically set whenever NIMBLE_MESH_PB_GATT or
NIMBLE_MESH_GATT_PROXY is set
config NIMBLE_MESH_PROV
bool "Enable BLE mesh provisioning"
default y
depends on NIMBLE_MESH
help
Enable mesh provisioning
config NIMBLE_MESH_PB_ADV
bool "Enable mesh provisioning over advertising bearer"
default y
depends on NIMBLE_MESH_PROV
help
Enable this option to allow the device to be provisioned over
the advertising bearer
config NIMBLE_MESH_PB_GATT
bool "Enable mesh provisioning over GATT bearer"
default y
select NIMBLE_MESH_PROXY
depends on NIMBLE_MESH_PROV
help
Enable this option to allow the device to be provisioned over the GATT
bearer
config NIMBLE_MESH_GATT_PROXY
bool "Enable GATT Proxy functionality"
default y
select NIMBLE_MESH_PROXY
depends on NIMBLE_MESH
help
This option enables support for the Mesh GATT Proxy Service,
i.e. the ability to act as a proxy between a Mesh GATT Client
and a Mesh network
config NIMBLE_MESH_RELAY
bool "Enable mesh relay functionality"
default n
depends on NIMBLE_MESH
help
Support for acting as a Mesh Relay Node
config NIMBLE_MESH_LOW_POWER
bool "Enable mesh low power mode"
default n
depends on NIMBLE_MESH
help
Enable this option to be able to act as a Low Power Node
config NIMBLE_MESH_FRIEND
bool "Enable mesh friend functionality"
default n
depends on NIMBLE_MESH
help
Enable this option to be able to act as a Friend Node
config NIMBLE_MESH_DEVICE_NAME
string "Set mesh device name"
default "nimble-mesh-node"
depends on NIMBLE_MESH
help
This value defines Bluetooth Mesh device/node name
config NIMBLE_CRYPTO_STACK_MBEDTLS
bool "Override TinyCrypt with mbedTLS for crypto computations"
default y
depends on NIMBLE_ENABLED
select MBEDTLS_ECP_RESTARTABLE
select MBEDTLS_CMAC_C
help
Enable this option to choose mbedTLS instead of TinyCrypt for crypto
computations.
endmenu

View File

@ -92,6 +92,19 @@ menu "mbedTLS"
at runtime in order to enable mbedTLS debug output via the ESP
log mechanism.
config MBEDTLS_ECP_RESTARTABLE
bool "Enable mbedTLS ecp restartable"
default n
help
Enable "non-blocking" ECC operations that can return early and be resumed.
config MBEDTLS_CMAC_C
bool "Enable CMAC mode for block ciphers"
default n
help
Enable the CMAC (Cipher-based Message Authentication Code) mode for
block ciphers.
config MBEDTLS_HARDWARE_AES
bool "Enable hardware AES acceleration"
default y

@ -1 +1 @@
Subproject commit 97959e77912524bd8db7cbb2e00fc9f6189f7a82
Subproject commit f5f2e5926cd294ae7cb579ff6a12ad9303caeb6e

View File

@ -448,6 +448,47 @@
#define MBEDTLS_REMOVE_ARC4_CIPHERSUITES
#endif
/**
* \def MBEDTLS_ECP_RESTARTABLE
*
* Enable "non-blocking" ECC operations that can return early and be resumed.
*
* This allows various functions to pause by returning
* #MBEDTLS_ERR_ECP_IN_PROGRESS (or, for functions in the SSL module,
* #MBEDTLS_ERR_SSL_CRYPTO_IN_PROGRESS) and then be called later again in
* order to further progress and eventually complete their operation. This is
* controlled through mbedtls_ecp_set_max_ops() which limits the maximum
* number of ECC operations a function may perform before pausing; see
* mbedtls_ecp_set_max_ops() for more information.
*
* This is useful in non-threaded environments if you want to avoid blocking
* for too long on ECC (and, hence, X.509 or SSL/TLS) operations.
*
* Uncomment this macro to enable restartable ECC computations.
*
* \note This option only works with the default software implementation of
* elliptic curve functionality. It is incompatible with
* MBEDTLS_ECP_ALT, MBEDTLS_ECDH_XXX_ALT and MBEDTLS_ECDSA_XXX_ALT.
*/
#ifdef CONFIG_MBEDTLS_ECP_RESTARTABLE
#define MBEDTLS_ECP_RESTARTABLE
#endif
/**
* \def MBEDTLS_CMAC_C
*
* Enable the CMAC (Cipher-based Message Authentication Code) mode for block
* ciphers.
*
* Module: library/cmac.c
*
* Requires: MBEDTLS_AES_C or MBEDTLS_DES_C
*
*/
#ifdef CONFIG_MBEDTLS_CMAC_C
#define MBEDTLS_CMAC_C
#endif
/**
* \def MBEDTLS_ECP_DP_SECP192R1_ENABLED
*

View File

@ -0,0 +1,148 @@
if(CONFIG_NIMBLE_ENABLED)
set(COMPONENT_ADD_INCLUDEDIRS
nimble/porting/nimble/include
port/include
nimble/nimble/include
nimble/nimble/host/include
nimble/nimble/host/services/ans/include
nimble/nimble/host/services/bas/include
nimble/nimble/host/services/gap/include
nimble/nimble/host/services/gatt/include
nimble/nimble/host/services/ias/include
nimble/nimble/host/services/lls/include
nimble/nimble/host/services/tps/include
nimble/nimble/host/util/include
nimble/nimble/host/store/ram/include
nimble/nimble/host/store/config/include
nimble/porting/npl/freertos/include
esp-hci/include)
set(COMPONENT_SRCS "./nimble/nimble/host/util/src/addr.c"
"./nimble/nimble/host/services/gatt/src/ble_svc_gatt.c"
"./nimble/nimble/host/services/tps/src/ble_svc_tps.c"
"./nimble/nimble/host/services/ias/src/ble_svc_ias.c"
"./nimble/nimble/host/services/ans/src/ble_svc_ans.c"
"./nimble/nimble/host/services/gap/src/ble_svc_gap.c"
"./nimble/nimble/host/services/bas/src/ble_svc_bas.c"
"./nimble/nimble/host/services/lls/src/ble_svc_lls.c"
"./nimble/nimble/host/src/ble_hs_conn.c"
"./nimble/nimble/host/src/ble_store_util.c"
"./nimble/nimble/host/src/ble_sm.c"
"./nimble/nimble/host/src/ble_hs_shutdown.c"
"./nimble/nimble/host/src/ble_l2cap_sig_cmd.c"
"./nimble/nimble/host/src/ble_hs_hci_cmd.c"
"./nimble/nimble/host/src/ble_hs_id.c"
"./nimble/nimble/host/src/ble_att_svr.c"
"./nimble/nimble/host/src/ble_gatts_lcl.c"
"./nimble/nimble/host/src/ble_ibeacon.c"
"./nimble/nimble/host/src/ble_hs_atomic.c"
"./nimble/nimble/host/src/ble_sm_alg.c"
"./nimble/nimble/host/src/ble_hs_stop.c"
"./nimble/nimble/host/src/ble_hs.c"
"./nimble/nimble/host/src/ble_hs_hci_evt.c"
"./nimble/nimble/host/src/ble_hs_dbg.c"
"./nimble/nimble/host/src/ble_hs_mqueue.c"
"./nimble/nimble/host/src/ble_att.c"
"./nimble/nimble/host/src/ble_gattc.c"
"./nimble/nimble/host/src/ble_store.c"
"./nimble/nimble/host/src/ble_sm_lgcy.c"
"./nimble/nimble/host/src/ble_hs_cfg.c"
"./nimble/nimble/host/src/ble_monitor.c"
"./nimble/nimble/host/src/ble_att_clt.c"
"./nimble/nimble/host/src/ble_l2cap_coc.c"
"./nimble/nimble/host/src/ble_hs_mbuf.c"
"./nimble/nimble/host/src/ble_att_cmd.c"
"./nimble/nimble/host/src/ble_hs_log.c"
"./nimble/nimble/host/src/ble_eddystone.c"
"./nimble/nimble/host/src/ble_hs_startup.c"
"./nimble/nimble/host/src/ble_l2cap_sig.c"
"./nimble/nimble/host/src/ble_gap.c"
"./nimble/nimble/host/src/ble_sm_cmd.c"
"./nimble/nimble/host/src/ble_uuid.c"
"./nimble/nimble/host/src/ble_hs_pvcy.c"
"./nimble/nimble/host/src/ble_hs_flow.c"
"./nimble/nimble/host/src/ble_l2cap.c"
"./nimble/nimble/host/src/ble_sm_sc.c"
"./nimble/nimble/host/src/ble_hs_misc.c"
"./nimble/nimble/host/src/ble_gatts.c"
"./nimble/nimble/host/src/ble_hs_adv.c"
"./nimble/nimble/host/src/ble_hs_hci.c"
"./nimble/nimble/host/src/ble_hs_hci_util.c"
"./nimble/nimble/host/store/ram/src/ble_store_ram.c"
"./nimble/nimble/host/store/config/src/ble_store_config.c"
"./nimble/nimble/host/store/config/src/ble_store_nvs.c"
"./nimble/nimble/src/ble_util.c"
"./nimble/porting/npl/freertos/src/nimble_port_freertos.c"
"./nimble/porting/npl/freertos/src/npl_os_freertos.c"
"./nimble/porting/nimble/src/endian.c"
"./nimble/porting/nimble/src/os_cputime_pwr2.c"
"./nimble/porting/nimble/src/hal_timer.c"
"./nimble/porting/nimble/src/os_mempool.c"
"./nimble/porting/nimble/src/os_msys_init.c"
"./nimble/porting/nimble/src/nimble_port.c"
"./nimble/porting/nimble/src/mem.c"
"./nimble/porting/nimble/src/os_mbuf.c"
"./nimble/porting/nimble/src/os_cputime.c"
"./esp-hci/src/esp_nimble_hci.c")
if(NOT CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS)
list(APPEND COMPONENT_ADD_INCLUDEDIRS
nimble/ext/tinycrypt/include)
list(APPEND COMPONENT_SRCS "./nimble/ext/tinycrypt/src/utils.c"
"./nimble/ext/tinycrypt/src/sha256.c"
"./nimble/ext/tinycrypt/src/ecc.c"
"./nimble/ext/tinycrypt/src/ctr_prng.c"
"./nimble/ext/tinycrypt/src/ctr_mode.c"
"./nimble/ext/tinycrypt/src/aes_decrypt.c"
"./nimble/ext/tinycrypt/src/aes_encrypt.c"
"./nimble/ext/tinycrypt/src/ccm_mode.c"
"./nimble/ext/tinycrypt/src/ecc_dsa.c"
"./nimble/ext/tinycrypt/src/cmac_mode.c"
"./nimble/ext/tinycrypt/src/ecc_dh.c"
"./nimble/ext/tinycrypt/src/hmac_prng.c"
"./nimble/ext/tinycrypt/src/ecc_platform_specific.c"
"./nimble/ext/tinycrypt/src/hmac.c"
"./nimble/ext/tinycrypt/src/cbc_mode.c")
endif()
if(CONFIG_NIMBLE_MESH)
list(APPEND COMPONENT_ADD_INCLUDEDIRS
nimble/nimble/host/mesh/include)
list(APPEND COMPONENT_SRCS
"./nimble/nimble/host/mesh/src/shell.c"
"./nimble/nimble/host/mesh/src/friend.c"
"./nimble/nimble/host/mesh/src/crypto.c"
"./nimble/nimble/host/mesh/src/settings.c"
"./nimble/nimble/host/mesh/src/adv.c"
"./nimble/nimble/host/mesh/src/model_srv.c"
"./nimble/nimble/host/mesh/src/beacon.c"
"./nimble/nimble/host/mesh/src/glue.c"
"./nimble/nimble/host/mesh/src/model_cli.c"
"./nimble/nimble/host/mesh/src/transport.c"
"./nimble/nimble/host/mesh/src/prov.c"
"./nimble/nimble/host/mesh/src/mesh.c"
"./nimble/nimble/host/mesh/src/access.c"
"./nimble/nimble/host/mesh/src/cfg_srv.c"
"./nimble/nimble/host/mesh/src/cfg_cli.c"
"./nimble/nimble/host/mesh/src/light_model.c"
"./nimble/nimble/host/mesh/src/health_cli.c"
"./nimble/nimble/host/mesh/src/lpn.c"
"./nimble/nimble/host/mesh/src/proxy.c"
"./nimble/nimble/host/mesh/src/health_srv.c"
"./nimble/nimble/host/mesh/src/testing.c"
"./nimble/nimble/host/mesh/src/net.c")
endif()
endif()
# requirements can't depend on config
set(COMPONENT_PRIV_REQUIRES bt nvs_flash)
register_component()

View File

@ -0,0 +1,53 @@
#
# Component Makefile
#
ifeq ($(CONFIG_NIMBLE_ENABLED),y)
COMPONENT_ADD_INCLUDEDIRS += nimble/nimble/include \
nimble/nimble/host/include \
nimble/porting/nimble/include \
nimble/porting/npl/freertos/include \
nimble/nimble/host/services/ans/include \
nimble/nimble/host/services/bas/include \
nimble/nimble/host/services/gap/include \
nimble/nimble/host/services/gatt/include \
nimble/nimble/host/services/ias/include \
nimble/nimble/host/services/lls/include \
nimble/nimble/host/services/tps/include \
nimble/nimble/host/util/include \
nimble/nimble/host/store/ram/include \
nimble/nimble/host/store/config/include \
esp-hci/include \
port/include
ifndef CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS
COMPONENT_ADD_INCLUDEDIRS += nimble/ext/tinycrypt/include
endif
COMPONENT_SRCDIRS += nimble/nimble/host/src \
nimble/porting/nimble/src \
nimble/porting/npl/freertos/src \
nimble/nimble/host/services/ans/src \
nimble/nimble/host/services/bas/src \
nimble/nimble/host/services/gap/src \
nimble/nimble/host/services/gatt/src \
nimble/nimble/host/services/ias/src \
nimble/nimble/host/services/lls/src \
nimble/nimble/host/services/tps/src \
nimble/nimble/host/util/src \
nimble/nimble/host/store/ram/src \
nimble/nimble/host/store/config/src \
esp-hci/src
ifndef CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS
COMPONENT_SRCDIRS += nimble/ext/tinycrypt/src
endif
COMPONENT_OBJEXCLUDE += nimble/nimble/host/store/config/src/ble_store_config_conf.o
ifeq ($(CONFIG_NIMBLE_MESH),y)
COMPONENT_ADD_INCLUDEDIRS += nimble/nimble/host/mesh/include
COMPONENT_SRCDIRS += nimble/nimble/host/mesh/src
endif
endif

View File

@ -0,0 +1,138 @@
/*
* Copyright 2019 Espressif Systems (Shanghai) PTE LTD
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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_NIMBLE_HCI_H__
#define __ESP_NIMBLE_HCI_H__
#include "nimble/ble_hci_trans.h"
#ifdef __cplusplus
extern "C" {
#endif
#define BLE_HCI_UART_H4_NONE 0x00
#define BLE_HCI_UART_H4_CMD 0x01
#define BLE_HCI_UART_H4_ACL 0x02
#define BLE_HCI_UART_H4_SCO 0x03
#define BLE_HCI_UART_H4_EVT 0x04
/**
* @brief Initialize VHCI transport layer between NimBLE Host and
* ESP Bluetooth controller
*
* This function initializes the transport buffers to be exchanged
* between NimBLE host and ESP controller. It also registers required
* host callbacks with the controller.
*
* @return
* - ESP_OK if the initialization is successful
* - Appropriate error code from esp_err_t in case of an error
*/
esp_err_t esp_nimble_hci_init(void);
/**
* @brief Initialize ESP Bluetooth controller(link layer) and VHCI transport
* layer between NimBLE Host and ESP Bluetooth controller
*
* This function initializes ESP controller in BLE only mode and the
* transport buffers to be exchanged between NimBLE host and ESP controller.
* It also registers required host callbacks with the controller.
*
* Below is the sequence of APIs to be called to init/enable NimBLE host and ESP controller:
*
* @code{c}
* void ble_host_task(void *param)
* {
* nimble_port_run(); //This function will return only when nimble_port_stop() is executed.
* nimble_port_freertos_deinit();
* }
*
* int ret = esp_nimble_hci_and_controller_init();
* if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_nimble_hci_and_controller_init() failed with error: %d", ret);
* return;
* }
*
* nimble_port_init();
*
* //Initialize the NimBLE Host configuration
*
* nimble_port_freertos_init(ble_host_task);
* @endcode
*
* nimble_port_freertos_init() is an optional call that creates a new task in which the NimBLE
* host will run. The task function should have a call to nimble_port_run(). If a separate task
* is not required, calling nimble_port_run() will run the NimBLE host in the current task.
*
* @return
* - ESP_OK if the initialization is successful
* - Appropriate error code from esp_err_t in case of an error
*/
esp_err_t esp_nimble_hci_and_controller_init(void);
/**
* @brief Deinitialize VHCI transport layer between NimBLE Host and
* ESP Bluetooth controller
*
* @note This function should be called after the NimBLE host is deinitialized.
*
* @return
* - ESP_OK if the deinitialization is successful
* - Appropriate error codes from esp_err_t in case of an error
*/
esp_err_t esp_nimble_hci_deinit(void);
/**
* @brief Deinitialize VHCI transport layer between NimBLE Host and
* ESP Bluetooth controller and disable and deinitialize the controller
*
* @note This function should not be executed in the context of Bluetooth host task.
*
* @note This function should be called after the NimBLE host is deinitialized.
*
* Below is the sequence of APIs to be called to disable/deinit NimBLE host and ESP controller:
*
* @code{c}
* int ret = nimble_port_stop();
* if (ret == 0) {
* nimble_port_deinit();
*
* ret = esp_nimble_hci_and_controller_deinit();
* if (ret != ESP_OK) {
ESP_LOGE(TAG, "esp_nimble_hci_and_controller_deinit() failed with error: %d", ret);
* }
* }
* @endcode
*
* If nimble_port_freertos_init() is used during initialization, then
* nimble_port_freertos_deinit() should be called in the host task after nimble_port_run().
*
* @return
* - ESP_OK if the deinitialization is successful
* - Appropriate error codes from esp_err_t in case of an error
*/
esp_err_t esp_nimble_hci_and_controller_deinit(void);
#ifdef __cplusplus
}
#endif
#endif /* __ESP_NIMBLE_HCI_H__ */

View File

@ -0,0 +1,439 @@
/*
* Copyright 2019 Espressif Systems (Shanghai) PTE LTD
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <assert.h>
#include "sysinit/sysinit.h"
#include "nimble/hci_common.h"
#include "host/ble_hs.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "esp_nimble_hci.h"
#include "esp_bt.h"
static ble_hci_trans_rx_cmd_fn *ble_hci_rx_cmd_hs_cb;
static void *ble_hci_rx_cmd_hs_arg;
static ble_hci_trans_rx_acl_fn *ble_hci_rx_acl_hs_cb;
static void *ble_hci_rx_acl_hs_arg;
static struct os_mbuf_pool ble_hci_acl_mbuf_pool;
static struct os_mempool_ext ble_hci_acl_pool;
/*
* The MBUF payload size must accommodate the HCI data header size plus the
* maximum ACL data packet length. The ACL block size is the size of the
* mbufs we will allocate.
*/
#define ACL_BLOCK_SIZE OS_ALIGN(MYNEWT_VAL(BLE_ACL_BUF_SIZE) \
+ BLE_MBUF_MEMBLOCK_OVERHEAD \
+ BLE_HCI_DATA_HDR_SZ, OS_ALIGNMENT)
static os_membuf_t ble_hci_acl_buf[
OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_ACL_BUF_COUNT),
ACL_BLOCK_SIZE)];
static struct os_mempool ble_hci_cmd_pool;
static os_membuf_t ble_hci_cmd_buf[
OS_MEMPOOL_SIZE(1, BLE_HCI_TRANS_CMD_SZ)
];
static struct os_mempool ble_hci_evt_hi_pool;
static os_membuf_t ble_hci_evt_hi_buf[
OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT),
MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE))
];
static struct os_mempool ble_hci_evt_lo_pool;
static os_membuf_t ble_hci_evt_lo_buf[
OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT),
MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE))
];
const static char *TAG = "NimBLE";
void ble_hci_trans_cfg_hs(ble_hci_trans_rx_cmd_fn *cmd_cb,
void *cmd_arg,
ble_hci_trans_rx_acl_fn *acl_cb,
void *acl_arg)
{
ble_hci_rx_cmd_hs_cb = cmd_cb;
ble_hci_rx_cmd_hs_arg = cmd_arg;
ble_hci_rx_acl_hs_cb = acl_cb;
ble_hci_rx_acl_hs_arg = acl_arg;
}
int ble_hci_trans_hs_cmd_tx(uint8_t *cmd)
{
uint16_t len;
assert(cmd != NULL);
*cmd = BLE_HCI_UART_H4_CMD;
len = BLE_HCI_CMD_HDR_LEN + cmd[3] + 1;
if (!esp_vhci_host_check_send_available()) {
ESP_LOGE(TAG, "Controller not ready to receive packets from host at this time, try again after sometime");
return BLE_HS_EAGAIN;
}
esp_vhci_host_send_packet(cmd, len);
ble_hci_trans_buf_free(cmd);
return 0;
}
int ble_hci_trans_ll_evt_tx(uint8_t *hci_ev)
{
int rc = ESP_FAIL;
if (ble_hci_rx_cmd_hs_cb) {
rc = ble_hci_rx_cmd_hs_cb(hci_ev, ble_hci_rx_cmd_hs_arg);
}
return rc;
}
int ble_hci_trans_hs_acl_tx(struct os_mbuf *om)
{
uint16_t len = 0;
uint8_t data[MYNEWT_VAL(BLE_ACL_BUF_SIZE) + 1];
/* If this packet is zero length, just free it */
if (OS_MBUF_PKTLEN(om) == 0) {
os_mbuf_free_chain(om);
return 0;
}
data[0] = BLE_HCI_UART_H4_ACL;
len++;
if (!esp_vhci_host_check_send_available()) {
ESP_LOGE(TAG, "Controller not ready to receive packets from host at this time, try again after sometime");
return BLE_HS_EAGAIN;
}
os_mbuf_copydata(om, 0, OS_MBUF_PKTLEN(om), &data[1]);
len += OS_MBUF_PKTLEN(om);
esp_vhci_host_send_packet(data, len);
os_mbuf_free_chain(om);
return 0;
}
int ble_hci_trans_ll_acl_tx(struct os_mbuf *om)
{
int rc = ESP_FAIL;
if (ble_hci_rx_acl_hs_cb) {
rc = ble_hci_rx_acl_hs_cb(om, ble_hci_rx_acl_hs_arg);
}
return rc;
}
uint8_t *ble_hci_trans_buf_alloc(int type)
{
uint8_t *buf;
switch (type) {
case BLE_HCI_TRANS_BUF_CMD:
buf = os_memblock_get(&ble_hci_cmd_pool);
break;
case BLE_HCI_TRANS_BUF_EVT_HI:
buf = os_memblock_get(&ble_hci_evt_hi_pool);
if (buf == NULL) {
/* If no high-priority event buffers remain, try to grab a
* low-priority one.
*/
buf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_LO);
}
break;
case BLE_HCI_TRANS_BUF_EVT_LO:
buf = os_memblock_get(&ble_hci_evt_lo_pool);
break;
default:
assert(0);
buf = NULL;
}
return buf;
}
void ble_hci_trans_buf_free(uint8_t *buf)
{
int rc;
/* XXX: this may look a bit odd, but the controller uses the command
* buffer to send back the command complete/status as an immediate
* response to the command. This was done to insure that the controller
* could always send back one of these events when a command was received.
* Thus, we check to see which pool the buffer came from so we can free
* it to the appropriate pool
*/
if (os_memblock_from(&ble_hci_evt_hi_pool, buf)) {
rc = os_memblock_put(&ble_hci_evt_hi_pool, buf);
assert(rc == 0);
} else if (os_memblock_from(&ble_hci_evt_lo_pool, buf)) {
rc = os_memblock_put(&ble_hci_evt_lo_pool, buf);
assert(rc == 0);
} else {
assert(os_memblock_from(&ble_hci_cmd_pool, buf));
rc = os_memblock_put(&ble_hci_cmd_pool, buf);
assert(rc == 0);
}
}
/**
* Unsupported; the RAM transport does not have a dedicated ACL data packet
* pool.
*/
int ble_hci_trans_set_acl_free_cb(os_mempool_put_fn *cb, void *arg)
{
return BLE_ERR_UNSUPPORTED;
}
int ble_hci_trans_reset(void)
{
/* No work to do. All allocated buffers are owned by the host or
* controller, and they will get freed by their owners.
*/
return 0;
}
/**
* Allocates a buffer (mbuf) for ACL operation.
*
* @return The allocated buffer on success;
* NULL on buffer exhaustion.
*/
static struct os_mbuf *ble_hci_trans_acl_buf_alloc(void)
{
struct os_mbuf *m;
uint8_t usrhdr_len;
#if MYNEWT_VAL(BLE_DEVICE)
usrhdr_len = sizeof(struct ble_mbuf_hdr);
#elif MYNEWT_VAL(BLE_HS_FLOW_CTRL)
usrhdr_len = BLE_MBUF_HS_HDR_LEN;
#else
usrhdr_len = 0;
#endif
m = os_mbuf_get_pkthdr(&ble_hci_acl_mbuf_pool, usrhdr_len);
return m;
}
static void ble_hci_rx_acl(uint8_t *data, uint16_t len)
{
struct os_mbuf *m;
int sr;
if (len < BLE_HCI_DATA_HDR_SZ || len > MYNEWT_VAL(BLE_ACL_BUF_SIZE)) {
return;
}
m = ble_hci_trans_acl_buf_alloc();
if (!m) {
return;
}
if (os_mbuf_append(m, data, len)) {
os_mbuf_free_chain(m);
return;
}
OS_ENTER_CRITICAL(sr);
if (ble_hci_rx_acl_hs_cb) {
ble_hci_rx_acl_hs_cb(m, NULL);
}
OS_EXIT_CRITICAL(sr);
}
static void ble_hci_transport_init(void)
{
int rc;
/* Ensure this function only gets called by sysinit. */
SYSINIT_ASSERT_ACTIVE();
rc = os_mempool_ext_init(&ble_hci_acl_pool,
MYNEWT_VAL(BLE_ACL_BUF_COUNT),
ACL_BLOCK_SIZE,
ble_hci_acl_buf,
"ble_hci_acl_pool");
SYSINIT_PANIC_ASSERT(rc == 0);
rc = os_mbuf_pool_init(&ble_hci_acl_mbuf_pool,
&ble_hci_acl_pool.mpe_mp,
ACL_BLOCK_SIZE,
MYNEWT_VAL(BLE_ACL_BUF_COUNT));
SYSINIT_PANIC_ASSERT(rc == 0);
/*
* Create memory pool of HCI command buffers. NOTE: we currently dont
* allow this to be configured. The controller will only allow one
* outstanding command. We decided to keep this a pool in case we allow
* allow the controller to handle more than one outstanding command.
*/
rc = os_mempool_init(&ble_hci_cmd_pool,
1,
BLE_HCI_TRANS_CMD_SZ,
ble_hci_cmd_buf,
"ble_hci_cmd_pool");
SYSINIT_PANIC_ASSERT(rc == 0);
rc = os_mempool_init(&ble_hci_evt_hi_pool,
MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT),
MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE),
ble_hci_evt_hi_buf,
"ble_hci_evt_hi_pool");
SYSINIT_PANIC_ASSERT(rc == 0);
rc = os_mempool_init(&ble_hci_evt_lo_pool,
MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT),
MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE),
ble_hci_evt_lo_buf,
"ble_hci_evt_lo_pool");
SYSINIT_PANIC_ASSERT(rc == 0);
}
/*
* @brief: BT controller callback function, used to notify the upper layer that
* controller is ready to receive command
*/
static void controller_rcv_pkt_ready(void)
{
}
/*
* @brief: BT controller callback function, to transfer data packet to the host
*/
static int host_rcv_pkt(uint8_t *data, uint16_t len)
{
if (data[0] == BLE_HCI_UART_H4_EVT) {
uint8_t *evbuf;
int totlen;
int rc;
totlen = BLE_HCI_EVENT_HDR_LEN + data[2];
assert(totlen <= UINT8_MAX + BLE_HCI_EVENT_HDR_LEN);
if (data[1] == BLE_HCI_EVCODE_HW_ERROR) {
assert(0);
}
/* Allocate LE Advertising Report Event from lo pool only */
if ((data[1] == BLE_HCI_EVCODE_LE_META) && (data[3] == BLE_HCI_LE_SUBEV_ADV_RPT)) {
evbuf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_LO);
/* Skip advertising report if we're out of memory */
if (!evbuf) {
return 0;
}
} else {
evbuf = ble_hci_trans_buf_alloc(BLE_HCI_TRANS_BUF_EVT_HI);
assert(evbuf != NULL);
}
memcpy(evbuf, &data[1], totlen);
rc = ble_hci_trans_ll_evt_tx(evbuf);
assert(rc == 0);
} else if (data[0] == BLE_HCI_UART_H4_ACL) {
ble_hci_rx_acl(data + 1, len - 1);
}
return 0;
}
static esp_vhci_host_callback_t vhci_host_cb = {
controller_rcv_pkt_ready,
host_rcv_pkt
};
esp_err_t esp_nimble_hci_init(void)
{
esp_err_t ret;
if ((ret = esp_vhci_host_register_callback(&vhci_host_cb)) != ESP_OK) {
return ret;
}
ble_hci_transport_init();
return ESP_OK;
}
esp_err_t esp_nimble_hci_and_controller_init(void)
{
esp_err_t ret;
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
if ((ret = esp_bt_controller_init(&bt_cfg)) != ESP_OK) {
return ret;
}
if ((ret = esp_bt_controller_enable(ESP_BT_MODE_BLE)) != ESP_OK) {
return ret;
}
return esp_nimble_hci_init();
}
static esp_err_t ble_hci_transport_deinit(void)
{
int ret = 0;
ret += os_mempool_clear(&ble_hci_evt_lo_pool);
ret += os_mempool_clear(&ble_hci_evt_hi_pool);
ret += os_mempool_clear(&ble_hci_cmd_pool);
ret += os_mempool_ext_clear(&ble_hci_acl_pool);
if (ret) {
return ESP_FAIL;
} else {
return ESP_OK;
}
}
esp_err_t esp_nimble_hci_deinit(void)
{
return ble_hci_transport_deinit();
}
esp_err_t esp_nimble_hci_and_controller_deinit(void)
{
int ret;
ret = esp_nimble_hci_deinit();
if (ret != ESP_OK) {
return ret;
}
ret = esp_bt_controller_disable();
if (ret != ESP_OK) {
return ret;
}
ret = esp_bt_controller_deinit();
if (ret != ESP_OK) {
return ret;
}
return ESP_OK;
}

@ -0,0 +1 @@
Subproject commit 8326807c5e580c3bb8ad13d1e80819aa0029004e

View File

@ -0,0 +1,21 @@
// Copyright 2019 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 _CONSOLE_H
#define _CONSOLE_H
#include <stdio.h>
#define console_printf printf
#endif

File diff suppressed because it is too large Load Diff

View File

@ -53,6 +53,8 @@ INPUT = \
../../components/bt/bluedroid/api/include/api/esp_spp_api.h \
../../components/bt/bluedroid/api/include/api/esp_hf_defs.h \
../../components/bt/bluedroid/api/include/api/esp_hf_client_api.h \
## NimBLE related Bluetooth APIs
../../components/nimble/esp-hci/include/esp_nimble_hci.h \
##
## Ethernet - API Reference
##

BIN
docs/_static/esp32_nimble_stack.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -57,6 +57,8 @@ These third party libraries can be included into the application (firmware) prod
* :component:`ESP-MQTT <mqtt>` MQTT Package (contiki-mqtt) - Copyright (c) 2014, Stephen Robinson, MQTT-ESP - Tuan PM <tuanpm at live dot com> is licensed under Apache License 2.0.
* `mynewt-nimble`_ Apache Mynewt NimBLE, Copyright 2015-2018, The Apache Software Foundation, is licensed under Apache License 2.0.
Build Tools
-----------
@ -158,3 +160,4 @@ Copyright (C) 2011, ChaN, all right reserved.
.. _spiffs: https://github.com/pellepl/spiffs
.. _asio: https://github.com/chriskohlhoff/asio
.. _mqtt: https://github.com/espressif/esp-mqtt
.. _mynewt-nimble: https://github.com/apache/mynewt-nimble

View File

@ -8,8 +8,13 @@ Bluetooth API
Bluetooth Common <bt_common>
Bluetooth LE <bt_le>
Bluetooth Classic <classic_bt>
NimBLE <nimble/index>
ESP-IDF currently supports two host stacks. The Bluedroid based stack (default) supports classic Bluetooth as well as BLE. On the other hand, Apache NimBLE based stack is BLE only. For users to make a
* For usecases involving classic Bluetooth as well as BLE, Bluedroid should be used.
* For BLE-only usecases, using NimBLE is recommended. It is less demanding in terms of code footprint and runtime memory, making it suitable for such scenarios.
To see the overview of the ESP32 Bluetooth stack architecture, follow links below:
* `ESP32 Bluetooth Architecture (PDF) [English] <http://espressif.com/sites/default/files/documentation/esp32_bluetooth_architecture_en.pdf>`_

View File

@ -0,0 +1,44 @@
NimBLE-based host APIs
**********************
Overview
========
Apache MyNewt NimBLE is a highly configurable and BT SIG qualifiable BLE stack providing both host and controller functionalities. ESP-IDF now supports NimBLE host stack which is specifically ported for ESP32 platform and FreeRTOS. The underlying controller is still the same (as in case of Bluedroid) providing VHCI interface. Refer to `NimBLE user guide <http://mynewt.apache.org/latest/network/docs/index.html#>`_ for a complete list of features and additional information on NimBLE stack. Most features of NimBLE including BLE Mesh are supported by ESP-IDF. The porting layer is kept cleaner by maintaining all the existing APIs of NimBLE along with a single ESP-NimBLE API for initialization, making it simpler for the application developers.
Architecture
============
Currently, NimBLE host and controller support different transports such UART and RAM between them. However, RAM transport cannot be used as is in case of ESP as ESP controller supports VHCI interface and buffering schemes used by NimBLE host is incompatible with that used by ESP controller. Therefore, a new transport between NimBLE host and ESP controller has been added. This is depicted in the figure below. This layer is responsible for maintaining pool of transport buffers and formatting buffers exchanges between host and controller as per the requirements.
.. figure:: ../../../../_static/esp32_nimble_stack.png
:align: center
:alt: ESP NimBLE Stack.
:scale: 50
ESP NimBLE Stack
Threading Model
===============
The NimBLE host can run inside the application thread or can have its own independent thread. This flexibility is inherently provided by NimBLE design. By default, a thread is spawned by the porting function ``nimble_port_freertos_init``. This behavior can be changed by overriding the same function. For BLE Mesh, additional thread (advertising thread) is used which keeps on feeding advertisement events to the main thread.
Programming Sequence
====================
To begin with, make sure that the NimBLE stack is enabled from menuconfig :ref:`Enable NimBLE host stack<CONFIG_NIMBLE_ENABLED>`, for this option to be visible please disable :ref:`Bluedroid Enable<CONFIG_BLUEDROID_ENABLED>`.
Typical programming sequence with NimBLE stack consists of the following steps:
* Initialize NVS flash using :cpp:func:`nvs_flash_init` API. This is because ESP controller uses NVS during initialization.
* Call :cpp:func:`esp_nimble_hci_and_controller_init` to initialize ESP controller as well as transport layer. This will also link the host and controller modules together. Alternatively, if ESP controller is already initialized, then ``esp_nimble_hci_init`` can be called for the remaining initialization.
* Initialize the host stack using ``nimble_port_init``.
* Initialize the required NimBLE host configuration parameters and callbacks
* Perform application specific tasks/initialization
* Run the thread for host stack using ``nimble_port_freertos_init``
This documentation does not cover NimBLE APIs. Refer to `NimBLE tutorial <https://mynewt.apache.org/latest/network/docs/index.html#ble-user-guide>`_ for more details on the programming sequence/NimBLE APIs for different scenarios.
API Reference
=============
.. include:: /_build/inc/esp_nimble_hci.inc

View File

@ -0,0 +1 @@
.. include:: ../../../../en/api-reference/bluetooth/nimble/index.rst

View File

@ -6,7 +6,8 @@ This directory contains a range of example ESP-IDF projects. These are intended
The examples are grouped into subdirectories by category. Each category directory contains one or more example projects:
* `bluetooth` contains Bluetooth (BLE & BT Classic) examples.
* `bluetooth` contains Bluetooth (BLE & BT Classic) examples using default Bluedroid host stack.
* `bluetooth/nimble` contains BLE examples using NimBLE host stack
* `ethernet` contains Ethernet examples.
* `get-started` contains some very simple examples with minimal functionality.
* `mesh` contains Wi-Fi Mesh examples.

View File

@ -0,0 +1,36 @@
# Bluetooth Examples for NimBLE host
Note: To use examples in this directory, you need to have Bluetooth enabled in configuration and NimBLE selected as the host stack.
# Example Layout
This directory includes examples to demonstrate BLE functionality using Apache MyNewt NimBLE (https://github.com/apache/mynewt-nimble) host stack.
## bleprph
Shows how ESP32 acts as a BLE Peripheral.
See the [README.md](./bleprph/README.md) file in the example [bleprph](./bleprph/).
## blecent
Shows how ESP32 acts as a BLE central.
See the [README.md](./blecent/README.md) file in the example [blecent](./blecent/).
## blemesh
Demonstrates BLE mesh functionality of NimBLE.
See the [README.md](./blemesh/README.md) file in the example [blemesh](./blemesh/).
## blehr
Demonstrates standard Heart Rate measurement BLE peripheral.
See the [README.md](./blehr/README.md) file in the example [blehr](./blehr/).
# More
See the [README.md](../../README.md) file in the upper level [examples](../../) directory for more information about examples.

View File

@ -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(blecent)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := blecent
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,159 @@
# BLE central example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example creates GATT client and performs passive scan, it then connects to peripheral device if the device advertises connectability and the device advertises support for the Alert Notification service (0x1811) as primary service UUID.
It performs three GATT operations against the specified peer:
* Reads the ANS Supported New Alert Category characteristic.
* After the read operation is completed, writes the ANS Alert Notification Control Point characteristic.
* After the write operation is completed, subscribes to notifications for the ANS Unread Alert Status characteristic.
If the peer does not support a required service, characteristic, or descriptor, then the peer lied when it claimed support for the alert notification service! When this happens, or if a GATT procedure fails, this function immediately terminates the connection.
It uses ESP32's Bluetooth controller and NimBLE stack based BLE host.
This example aims at understanding BLE service discovery, connection and characteristic operations.
To test this demo, use any BLE GATT server app that advertises support for the Alert Notification service (0x1811) and includes it in the GATT database.
A Python based utility `blecent_test.py` is also provided (which will run as a BLE GATT server) and can be used to test this example.
Note :
* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed.
* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus).
## How to use example
### Configure the project
```
idf.py menuconfig
```
* Set serial port under Serial Flasher Options.
### 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-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
This is the console output on successful connection:
```
I (202) BTDM_INIT: BT controller compile version [0b60040]
I (202) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
W (212) phy_init: failed to load RF calibration data (0xffffffff), falling back to full calibration
I (422) phy: phy_version: 4007, 9c6b43b, Jan 11 2019, 16:45:07, 0, 2
I (722) NimBLE_BLE_CENT: BLE Host Task Started
GAP procedure initiated: stop advertising.
GAP procedure initiated: discovery; own_addr_type=0 filter_policy=0 passive=1 limited=0 filter_duplicates=1 duration=forever
GAP procedure initiated: connect; peer_addr_type=1 peer_addr=xx:xx:xx:xx:xx:xx scan_itvl=16 scan_window=16 itvl_min=24 itvl_max=40 latency=0 supervision_timeout=256 min_ce_len=16 max_ce_len=768 own_addr_type=0
Connection established
GATT procedure initiated: discover all services
GATT procedure initiated: discover all characteristics; start_handle=1 end_handle=3
GATT procedure initiated: discover all characteristics; start_handle=20 end_handle=26
GATT procedure initiated: discover all characteristics; start_handle=40 end_handle=65535
GATT procedure initiated: discover all descriptors; chr_val_handle=42 end_handle=43
GATT procedure initiated: discover all descriptors; chr_val_handle=49 end_handle=65535
Service discovery complete; status=0 conn_handle=0
GATT procedure initiated: read; att_handle=45
GATT procedure initiated: write; att_handle=47 len=2
GATT procedure initiated: write; att_handle=43 len=2
Read complete; status=0 conn_handle=0 attr_handle=45 value=0x02
Write complete; status=0 conn_handle=0 attr_handle=47
Subscribe complete; status=0 conn_handle=0 attr_handle=43
```
This is the console output on failure (or peripheral does not support New Alert Service category):
```
I (180) BTDM_INIT: BT controller compile version [8e87ec7]
I (180) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (250) phy: phy_version: 4000, b6198fa, Sep 3 2018, 15:11:06, 0, 0
I (480) NimBLE_BLE_CENT: BLE Host Task Started
GAP procedure initiated: stop advertising.
GAP procedure initiated: discovery; own_addr_type=0 filter_policy=0 passive=1 limited=0 filter_duplicates=1 duration=forever
GAP procedure initiated: connect; peer_addr_type=1 peer_addr=xx:xx:xx:xx:xx:xx scan_itvl=16 scan_window=16 itvl_min=24 itvl_max=40 latency=0 supervision_timeout=256 min_ce_len=16 max_ce_len=768 own_addr_type=0
Connection established
GATT procedure initiated: discover all services
GATT procedure initiated: discover all characteristics; start_handle=1 end_handle=3
GATT procedure initiated: discover all characteristics; start_handle=20 end_handle=26
GATT procedure initiated: discover all characteristics; start_handle=40 end_handle=65535
GATT procedure initiated: discover all descriptors; chr_val_handle=42 end_handle=43
GATT procedure initiated: discover all descriptors; chr_val_handle=47 end_handle=65535
Service discovery complete; status=0 conn_handle=0
Error: Peer doesn't support the Supported New Alert Category characteristic
GAP procedure initiated: terminate connection; conn_handle=0 hci_reason=19
disconnect; reason=534
```
## Running Python Utility
```
python blecent_test.py
```
## Python Utility Output
This is this output seen on the python side on successful connection:
```
discovering adapter...
bluetooth adapter discovered
powering on adapter...
bluetooth adapter powered on
Advertising started
GATT Data created
GATT Application registered
Advertising data created
Advertisement registered
Read Request received
SupportedNewAlertCategoryCharacteristic
Value: [dbus.Byte(2)]
Write Request received
AlertNotificationControlPointCharacteristic
Current value: [dbus.Byte(0)]
New value: [dbus.Byte(99), dbus.Byte(100)]
Notify Started
New value on write: [dbus.Byte(1), dbus.Byte(0)]
Value on read: [dbus.Byte(1), dbus.Byte(0)]
Notify Stopped
exiting from test...
GATT Data removed
GATT Application unregistered
Advertising data removed
Advertisement unregistered
Stop Advertising status: True
disconnecting device...
device disconnected
powering off adapter...
bluetooth adapter powered off
Service discovery passed
Service Discovery Status: 0
Read passed
SupportedNewAlertCategoryCharacteristic
Read Status: 0
Write passed
AlertNotificationControlPointCharacteristic
Write Status: 0
Subscribe passed
ClientCharacteristicConfigurationDescriptor
Subscribe Status: 0
```

View File

@ -0,0 +1,131 @@
#!/usr/bin/env python
#
# Copyright 2019 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.
from __future__ import print_function
import os
import sys
import re
import uuid
import subprocess
try:
# This environment variable is expected on the host machine
test_fw_path = os.getenv("TEST_FW_PATH")
if test_fw_path and test_fw_path not in sys.path:
sys.path.insert(0, test_fw_path)
import IDF
except ImportError as e:
print(e)
print("\nCheck your IDF_PATH\nOR")
print("Try `export TEST_FW_PATH=$IDF_PATH/tools/tiny-test-fw` for resolving the issue\nOR")
print("Try `pip install -r $IDF_PATH/tools/tiny-test-fw/requirements.txt` for resolving the issue")
import IDF
try:
import lib_ble_client
except ImportError:
lib_ble_client_path = os.getenv("IDF_PATH") + "/tools/ble"
if lib_ble_client_path and lib_ble_client_path not in sys.path:
sys.path.insert(0, lib_ble_client_path)
import lib_ble_client
import Utility
# When running on local machine execute the following before running this script
# > make app bootloader
# > make print_flash_cmd | tail -n 1 > build/download.config
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
@IDF.idf_example_test(env_tag="Example_WIFI_BT")
def test_example_app_ble_central(env, extra_data):
"""
Steps:
1. Discover Bluetooth Adapter and Power On
"""
interface = 'hci0'
adv_host_name = "BleCentTestApp"
adv_iface_index = 0
adv_type = 'peripheral'
adv_uuid = '1811'
subprocess.check_output(['rm','-rf','/var/lib/bluetooth/*'])
subprocess.check_output(['hciconfig','hci0','reset'])
# Acquire DUT
dut = env.get_dut("blecent", "examples/bluetooth/nimble/blecent")
# Get binary file
binary_file = os.path.join(dut.app.binary_path, "blecent.bin")
bin_size = os.path.getsize(binary_file)
IDF.log_performance("blecent_bin_size", "{}KB".format(bin_size // 1024))
# Upload binary and start testing
Utility.console_log("Starting blecent example test app")
dut.start_app()
dut.reset()
device_addr = ':'.join(re.findall('..', '%012x' % uuid.getnode()))
# Get BLE client module
ble_client_obj = lib_ble_client.BLE_Bluez_Client(interface)
if not ble_client_obj:
raise RuntimeError("Get DBus-Bluez object failed !!")
# Discover Bluetooth Adapter and power on
is_adapter_set = ble_client_obj.set_adapter()
if not is_adapter_set:
raise RuntimeError("Adapter Power On failed !!")
# Write device address to dut
dut.expect("BLE Host Task Started", timeout=60)
dut.write(device_addr + "\n")
'''
Blecent application run:
Create GATT data
Register GATT Application
Create Advertising data
Register advertisement
Start advertising
'''
ble_client_obj.start_advertising(adv_host_name, adv_iface_index, adv_type, adv_uuid)
# Call disconnect to perform cleanup operations before exiting application
ble_client_obj.disconnect()
# Check dut responses
dut.expect("Connection established", timeout=30)
dut.expect("Service discovery complete; status=0", timeout=30)
print("Service discovery passed\n\tService Discovery Status: 0")
dut.expect("GATT procedure initiated: read;", timeout=30)
dut.expect("Read complete; status=0", timeout=30)
print("Read passed\n\tSupportedNewAlertCategoryCharacteristic\n\tRead Status: 0")
dut.expect("GATT procedure initiated: write;", timeout=30)
dut.expect("Write complete; status=0", timeout=30)
print("Write passed\n\tAlertNotificationControlPointCharacteristic\n\tWrite Status: 0")
dut.expect("GATT procedure initiated: write;", timeout=30)
dut.expect("Subscribe complete; status=0", timeout=30)
print("Subscribe passed\n\tClientCharacteristicConfigurationDescriptor\n\tSubscribe Status: 0")
if __name__ == '__main__':
test_example_app_ble_central()

View File

@ -0,0 +1,6 @@
set(COMPONENT_SRCS "main.c"
"misc.c"
"peer.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,9 @@
menu "Example Configuration"
config EXAMPLE_PEER_ADDR
string "Peer Address"
default "ADDR_ANY"
help
Enter the peer address in aa:bb:cc:dd:ee:ff form to connect to a specific peripheral
endmenu

View File

@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 H_BLECENT_
#define H_BLECENT_
#include "modlog/modlog.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_hs_adv_fields;
struct ble_gap_conn_desc;
struct ble_hs_cfg;
union ble_store_value;
union ble_store_key;
#define BLECENT_SVC_ALERT_UUID 0x1811
#define BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
#define BLECENT_CHR_NEW_ALERT 0x2A46
#define BLECENT_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
#define BLECENT_CHR_UNR_ALERT_STAT_UUID 0x2A45
#define BLECENT_CHR_ALERT_NOT_CTRL_PT 0x2A44
/** Misc. */
void print_bytes(const uint8_t *bytes, int len);
void print_mbuf(const struct os_mbuf *om);
char *addr_str(const void *addr);
void print_uuid(const ble_uuid_t *uuid);
void print_conn_desc(const struct ble_gap_conn_desc *desc);
void print_adv_fields(const struct ble_hs_adv_fields *fields);
/** Peer. */
struct peer_dsc {
SLIST_ENTRY(peer_dsc) next;
struct ble_gatt_dsc dsc;
};
SLIST_HEAD(peer_dsc_list, peer_dsc);
struct peer_chr {
SLIST_ENTRY(peer_chr) next;
struct ble_gatt_chr chr;
struct peer_dsc_list dscs;
};
SLIST_HEAD(peer_chr_list, peer_chr);
struct peer_svc {
SLIST_ENTRY(peer_svc) next;
struct ble_gatt_svc svc;
struct peer_chr_list chrs;
};
SLIST_HEAD(peer_svc_list, peer_svc);
struct peer;
typedef void peer_disc_fn(const struct peer *peer, int status, void *arg);
struct peer {
SLIST_ENTRY(peer) next;
uint16_t conn_handle;
/** List of discovered GATT services. */
struct peer_svc_list svcs;
/** Keeps track of where we are in the service discovery process. */
uint16_t disc_prev_chr_val;
struct peer_svc *cur_svc;
/** Callback that gets executed when service discovery completes. */
peer_disc_fn *disc_cb;
void *disc_cb_arg;
};
int peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb,
void *disc_cb_arg);
const struct peer_dsc *
peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid);
const struct peer_chr *
peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid);
const struct peer_svc *
peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid);
int peer_delete(uint16_t conn_handle);
int peer_add(uint16_t conn_handle);
int peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs);
struct peer *
peer_find(uint16_t conn_handle);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,550 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "nvs_flash.h"
/* BLE */
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "blecent.h"
static const char *tag = "NimBLE_BLE_CENT";
static int blecent_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t peer_addr[6];
void ble_store_config_init(void);
/**
* Application callback. Called when the attempt to subscribe to notifications
* for the ANS Unread Alert Status characteristic has completed.
*/
static int
blecent_on_subscribe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d "
"attr_handle=%d\n",
error->status, conn_handle, attr->handle);
return 0;
}
/**
* Application callback. Called when the write to the ANS Alert Notification
* Control Point characteristic has completed.
*/
static int
blecent_on_write(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO,
"Write complete; status=%d conn_handle=%d attr_handle=%d\n",
error->status, conn_handle, attr->handle);
/* Subscribe to notifications for the Unread Alert Status characteristic.
* A central enables notifications by writing two bytes (1, 0) to the
* characteristic's client-characteristic-configuration-descriptor (CCCD).
*/
const struct peer_dsc *dsc;
uint8_t value[2];
int rc;
const struct peer *peer = peer_find(conn_handle);
dsc = peer_dsc_find_uuid(peer,
BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
BLE_UUID16_DECLARE(BLECENT_CHR_UNR_ALERT_STAT_UUID),
BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16));
if (dsc == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer lacks a CCCD for the Unread Alert "
"Status characteristic\n");
goto err;
}
value[0] = 1;
value[1] = 0;
rc = ble_gattc_write_flat(conn_handle, dsc->dsc.handle,
value, sizeof value, blecent_on_subscribe, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to subscribe to characteristic; "
"rc=%d\n", rc);
goto err;
}
return 0;
err:
/* Terminate the connection. */
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
/**
* Application callback. Called when the read of the ANS Supported New Alert
* Category characteristic has completed.
*/
static int
blecent_on_read(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO, "Read complete; status=%d conn_handle=%d", error->status,
conn_handle);
if (error->status == 0) {
MODLOG_DFLT(INFO, " attr_handle=%d value=", attr->handle);
print_mbuf(attr->om);
}
MODLOG_DFLT(INFO, "\n");
/* Write two bytes (99, 100) to the alert-notification-control-point
* characteristic.
*/
const struct peer_chr *chr;
uint8_t value[2];
int rc;
const struct peer *peer = peer_find(conn_handle);
chr = peer_chr_find_uuid(peer,
BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
BLE_UUID16_DECLARE(BLECENT_CHR_ALERT_NOT_CTRL_PT));
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't support the Alert "
"Notification Control Point characteristic\n");
goto err;
}
value[0] = 99;
value[1] = 100;
rc = ble_gattc_write_flat(conn_handle, chr->chr.val_handle,
value, sizeof value, blecent_on_write, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to write characteristic; rc=%d\n",
rc);
goto err;
}
return 0;
err:
/* Terminate the connection. */
return ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
/**
* Performs three GATT operations against the specified peer:
* 1. Reads the ANS Supported New Alert Category characteristic.
* 2. After read is completed, writes the ANS Alert Notification Control Point characteristic.
* 3. After write is completed, subscribes to notifications for the ANS Unread Alert Status
* characteristic.
*
* If the peer does not support a required service, characteristic, or
* descriptor, then the peer lied when it claimed support for the alert
* notification service! When this happens, or if a GATT procedure fails,
* this function immediately terminates the connection.
*/
static void
blecent_read_write_subscribe(const struct peer *peer)
{
const struct peer_chr *chr;
int rc;
/* Read the supported-new-alert-category characteristic. */
chr = peer_chr_find_uuid(peer,
BLE_UUID16_DECLARE(BLECENT_SVC_ALERT_UUID),
BLE_UUID16_DECLARE(BLECENT_CHR_SUP_NEW_ALERT_CAT_UUID));
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't support the Supported New "
"Alert Category characteristic\n");
goto err;
}
rc = ble_gattc_read(peer->conn_handle, chr->chr.val_handle,
blecent_on_read, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to read characteristic; rc=%d\n",
rc);
goto err;
}
return;
err:
/* Terminate the connection. */
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
/**
* Called when service discovery of the specified peer has completed.
*/
static void
blecent_on_disc_complete(const struct peer *peer, int status, void *arg)
{
if (status != 0) {
/* Service discovery failed. Terminate the connection. */
MODLOG_DFLT(ERROR, "Error: Service discovery failed; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
return;
}
/* Service discovery has completed successfully. Now we have a complete
* list of services, characteristics, and descriptors that the peer
* supports.
*/
MODLOG_DFLT(ERROR, "Service discovery complete; status=%d "
"conn_handle=%d\n", status, peer->conn_handle);
/* Now perform three GATT procedures against the peer: read,
* write, and subscribe to notifications.
*/
blecent_read_write_subscribe(peer);
}
/**
* Initiates the GAP general discovery procedure.
*/
static void
blecent_scan(void)
{
uint8_t own_addr_type;
struct ble_gap_disc_params disc_params;
int rc;
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Tell the controller to filter duplicates; we don't want to process
* repeated advertisements from the same device.
*/
disc_params.filter_duplicates = 1;
/**
* Perform a passive scan. I.e., don't send follow-up scan requests to
* each advertiser.
*/
disc_params.passive = 1;
/* Use defaults for the rest of the parameters. */
disc_params.itvl = 0;
disc_params.window = 0;
disc_params.filter_policy = 0;
disc_params.limited = 0;
rc = ble_gap_disc(own_addr_type, BLE_HS_FOREVER, &disc_params,
blecent_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error initiating GAP discovery procedure; rc=%d\n",
rc);
}
}
/**
* Indicates whether we should try to connect to the sender of the specified
* advertisement. The function returns a positive result if the device
* advertises connectability and support for the Alert Notification service.
*/
static int
blecent_should_connect(const struct ble_gap_disc_desc *disc)
{
struct ble_hs_adv_fields fields;
int rc;
int i;
/* The device has to be advertising connectability. */
if (disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_ADV_IND &&
disc->event_type != BLE_HCI_ADV_RPT_EVTYPE_DIR_IND) {
return 0;
}
rc = ble_hs_adv_parse_fields(&fields, disc->data, disc->length_data);
if (rc != 0) {
return rc;
}
if (strlen(CONFIG_EXAMPLE_PEER_ADDR) && (strncmp(CONFIG_EXAMPLE_PEER_ADDR, "ADDR_ANY", strlen("ADDR_ANY")) != 0)) {
ESP_LOGI(tag, "Peer address from menuconfig: %s", CONFIG_EXAMPLE_PEER_ADDR);
/* Convert string to address */
sscanf(CONFIG_EXAMPLE_PEER_ADDR, "%hhx:%hhx:%hhx:%hhx:%hhx:%hhx",
&peer_addr[5], &peer_addr[4], &peer_addr[3],
&peer_addr[2], &peer_addr[1], &peer_addr[0]);
if (memcmp(peer_addr, disc->addr.val, sizeof(disc->addr.val)) != 0) {
return 0;
}
}
/* The device has to advertise support for the Alert Notification
* service (0x1811).
*/
for (i = 0; i < fields.num_uuids16; i++) {
if (ble_uuid_u16(&fields.uuids16[i].u) == BLECENT_SVC_ALERT_UUID) {
return 1;
}
}
return 0;
}
/**
* Connects to the sender of the specified advertisement of it looks
* interesting. A device is "interesting" if it advertises connectability and
* support for the Alert Notification service.
*/
static void
blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc)
{
int rc;
/* Don't do anything if we don't care about this advertiser. */
if (!blecent_should_connect(disc)) {
return;
}
/* Scanning must be stopped before a connection can be initiated. */
rc = ble_gap_disc_cancel();
if (rc != 0) {
MODLOG_DFLT(DEBUG, "Failed to cancel scan; rc=%d\n", rc);
return;
}
/* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for
* timeout.
*/
rc = ble_gap_connect(BLE_OWN_ADDR_PUBLIC, &disc->addr, 30000, NULL,
blecent_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Error: Failed to connect to device; addr_type=%d "
"addr=%s\n",
disc->addr.type, addr_str(disc->addr.val));
return;
}
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that is
* established. blecent uses the same callback for all connections.
*
* @param event The event being signalled.
* @param arg Application-specified argument; unused by
* blecent.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int
blecent_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
struct ble_hs_adv_fields fields;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_DISC:
rc = ble_hs_adv_parse_fields(&fields, event->disc.data,
event->disc.length_data);
if (rc != 0) {
return 0;
}
/* An advertisment report was received during GAP discovery. */
print_adv_fields(&fields);
/* Try to connect to the advertiser if it looks interesting. */
blecent_connect_if_interesting(&event->disc);
return 0;
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
if (event->connect.status == 0) {
/* Connection successfully established. */
MODLOG_DFLT(INFO, "Connection established ");
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
/* Remember peer. */
rc = peer_add(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to add peer; rc=%d\n", rc);
return 0;
}
/* Perform service discovery. */
rc = peer_disc_all(event->connect.conn_handle,
blecent_on_disc_complete, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "Failed to discover services; rc=%d\n", rc);
return 0;
}
} else {
/* Connection attempt failed; resume scanning. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
event->connect.status);
blecent_scan();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
/* Connection terminated. */
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Forget about peer. */
peer_delete(event->disconnect.conn.conn_handle);
/* Resume scanning. */
blecent_scan();
return 0;
case BLE_GAP_EVENT_DISC_COMPLETE:
MODLOG_DFLT(INFO, "discovery complete; reason=%d\n",
event->disc_complete.reason);
return 0;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
MODLOG_DFLT(INFO, "encryption change event; status=%d ",
event->enc_change.status);
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
return 0;
case BLE_GAP_EVENT_NOTIFY_RX:
/* Peer sent us a notification or indication. */
MODLOG_DFLT(INFO, "received %s; conn_handle=%d attr_handle=%d "
"attr_len=%d\n",
event->notify_rx.indication ?
"indication" :
"notification",
event->notify_rx.conn_handle,
event->notify_rx.attr_handle,
OS_MBUF_PKTLEN(event->notify_rx.om));
/* Attribute data is contained in event->notify_rx.attr_data. */
return 0;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
case BLE_GAP_EVENT_REPEAT_PAIRING:
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
*/
/* Delete the old bond. */
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
assert(rc == 0);
ble_store_util_delete_peer(&desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
*/
return BLE_GAP_REPEAT_PAIRING_RETRY;
default:
return 0;
}
}
static void
blecent_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
blecent_on_sync(void)
{
int rc;
/* Make sure we have proper identity address set (public preferred) */
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Begin scanning for a peripheral to connect to. */
blecent_scan();
}
void blecent_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
void
app_main(void)
{
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
nimble_port_init();
/* Configure the host. */
ble_hs_cfg.reset_cb = blecent_on_reset;
ble_hs_cfg.sync_cb = blecent_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
/* Initialize data structures to track connected peers. */
rc = peer_init(MYNEWT_VAL(BLE_MAX_CONNECTIONS), 64, 64, 64);
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-blecent");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(blecent_host_task);
}

View File

@ -0,0 +1,211 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "blecent.h"
/**
* Utility function to log an array of bytes.
*/
void
print_bytes(const uint8_t *bytes, int len)
{
int i;
for (i = 0; i < len; i++) {
MODLOG_DFLT(DEBUG, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
}
}
void
print_mbuf(const struct os_mbuf *om)
{
int colon, i;
colon = 0;
while (om != NULL) {
if (colon) {
MODLOG_DFLT(INFO, ":");
} else {
colon = 1;
}
for (i = 0; i < om->om_len; i++) {
MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", om->om_data[i]);
}
om = SLIST_NEXT(om, om_next);
}
}
char *
addr_str(const void *addr)
{
static char buf[6 * 2 + 5 + 1];
const uint8_t *u8p;
u8p = addr;
sprintf(buf, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
return buf;
}
void
print_uuid(const ble_uuid_t *uuid)
{
char buf[BLE_UUID_STR_LEN];
MODLOG_DFLT(DEBUG, "%s", ble_uuid_to_str(uuid, buf));
}
/**
* Logs information about a connection to the console.
*/
void
print_conn_desc(const struct ble_gap_conn_desc *desc)
{
MODLOG_DFLT(DEBUG, "handle=%d our_ota_addr_type=%d our_ota_addr=%s ",
desc->conn_handle, desc->our_ota_addr.type,
addr_str(desc->our_ota_addr.val));
MODLOG_DFLT(DEBUG, "our_id_addr_type=%d our_id_addr=%s ",
desc->our_id_addr.type, addr_str(desc->our_id_addr.val));
MODLOG_DFLT(DEBUG, "peer_ota_addr_type=%d peer_ota_addr=%s ",
desc->peer_ota_addr.type, addr_str(desc->peer_ota_addr.val));
MODLOG_DFLT(DEBUG, "peer_id_addr_type=%d peer_id_addr=%s ",
desc->peer_id_addr.type, addr_str(desc->peer_id_addr.val));
MODLOG_DFLT(DEBUG, "conn_itvl=%d conn_latency=%d supervision_timeout=%d "
"encrypted=%d authenticated=%d bonded=%d",
desc->conn_itvl, desc->conn_latency,
desc->supervision_timeout,
desc->sec_state.encrypted,
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
void
print_adv_fields(const struct ble_hs_adv_fields *fields)
{
char s[BLE_HS_ADV_MAX_SZ];
const uint8_t *u8p;
int i;
if (fields->flags != 0) {
MODLOG_DFLT(DEBUG, " flags=0x%02x\n", fields->flags);
}
if (fields->uuids16 != NULL) {
MODLOG_DFLT(DEBUG, " uuids16(%scomplete)=",
fields->uuids16_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids16; i++) {
print_uuid(&fields->uuids16[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uuids32 != NULL) {
MODLOG_DFLT(DEBUG, " uuids32(%scomplete)=",
fields->uuids32_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids32; i++) {
print_uuid(&fields->uuids32[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uuids128 != NULL) {
MODLOG_DFLT(DEBUG, " uuids128(%scomplete)=",
fields->uuids128_is_complete ? "" : "in");
for (i = 0; i < fields->num_uuids128; i++) {
print_uuid(&fields->uuids128[i].u);
MODLOG_DFLT(DEBUG, " ");
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->name != NULL) {
assert(fields->name_len < sizeof s - 1);
memcpy(s, fields->name, fields->name_len);
s[fields->name_len] = '\0';
MODLOG_DFLT(DEBUG, " name(%scomplete)=%s\n",
fields->name_is_complete ? "" : "in", s);
}
if (fields->tx_pwr_lvl_is_present) {
MODLOG_DFLT(DEBUG, " tx_pwr_lvl=%d\n", fields->tx_pwr_lvl);
}
if (fields->slave_itvl_range != NULL) {
MODLOG_DFLT(DEBUG, " slave_itvl_range=");
print_bytes(fields->slave_itvl_range, BLE_HS_ADV_SLAVE_ITVL_RANGE_LEN);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->svc_data_uuid16 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid16=");
print_bytes(fields->svc_data_uuid16, fields->svc_data_uuid16_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->public_tgt_addr != NULL) {
MODLOG_DFLT(DEBUG, " public_tgt_addr=");
u8p = fields->public_tgt_addr;
for (i = 0; i < fields->num_public_tgt_addrs; i++) {
MODLOG_DFLT(DEBUG, "public_tgt_addr=%s ", addr_str(u8p));
u8p += BLE_HS_ADV_PUBLIC_TGT_ADDR_ENTRY_LEN;
}
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->appearance_is_present) {
MODLOG_DFLT(DEBUG, " appearance=0x%04x\n", fields->appearance);
}
if (fields->adv_itvl_is_present) {
MODLOG_DFLT(DEBUG, " adv_itvl=0x%04x\n", fields->adv_itvl);
}
if (fields->svc_data_uuid32 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid32=");
print_bytes(fields->svc_data_uuid32, fields->svc_data_uuid32_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->svc_data_uuid128 != NULL) {
MODLOG_DFLT(DEBUG, " svc_data_uuid128=");
print_bytes(fields->svc_data_uuid128, fields->svc_data_uuid128_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->uri != NULL) {
MODLOG_DFLT(DEBUG, " uri=");
print_bytes(fields->uri, fields->uri_len);
MODLOG_DFLT(DEBUG, "\n");
}
if (fields->mfg_data != NULL) {
MODLOG_DFLT(DEBUG, " mfg_data=");
print_bytes(fields->mfg_data, fields->mfg_data_len);
MODLOG_DFLT(DEBUG, "\n");
}
}

View File

@ -0,0 +1,807 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <assert.h>
#include <string.h>
#include "host/ble_hs.h"
#include "blecent.h"
static void *peer_svc_mem;
static struct os_mempool peer_svc_pool;
static void *peer_chr_mem;
static struct os_mempool peer_chr_pool;
static void *peer_dsc_mem;
static struct os_mempool peer_dsc_pool;
static void *peer_mem;
static struct os_mempool peer_pool;
static SLIST_HEAD(, peer) peers;
static struct peer_svc *
peer_svc_find_range(struct peer *peer, uint16_t attr_handle);
static struct peer_svc *
peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
struct peer_svc **out_prev);
int
peer_svc_is_empty(const struct peer_svc *svc);
uint16_t
chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr);
int
chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr);
static struct peer_chr *
peer_chr_find(const struct peer_svc *svc, uint16_t chr_def_handle,
struct peer_chr **out_prev);
static void
peer_disc_chrs(struct peer *peer);
static int
peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg);
struct peer *
peer_find(uint16_t conn_handle)
{
struct peer *peer;
SLIST_FOREACH(peer, &peers, next) {
if (peer->conn_handle == conn_handle) {
return peer;
}
}
return NULL;
}
static void
peer_disc_complete(struct peer *peer, int rc)
{
peer->disc_prev_chr_val = 0;
/* Notify caller that discovery has completed. */
if (peer->disc_cb != NULL) {
peer->disc_cb(peer, rc, peer->disc_cb_arg);
}
}
static struct peer_dsc *
peer_dsc_find_prev(const struct peer_chr *chr, uint16_t dsc_handle)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
prev = NULL;
SLIST_FOREACH(dsc, &chr->dscs, next) {
if (dsc->dsc.handle >= dsc_handle) {
break;
}
prev = dsc;
}
return prev;
}
static struct peer_dsc *
peer_dsc_find(const struct peer_chr *chr, uint16_t dsc_handle,
struct peer_dsc **out_prev)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
prev = peer_dsc_find_prev(chr, dsc_handle);
if (prev == NULL) {
dsc = SLIST_FIRST(&chr->dscs);
} else {
dsc = SLIST_NEXT(prev, next);
}
if (dsc != NULL && dsc->dsc.handle != dsc_handle) {
dsc = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return dsc;
}
static int
peer_dsc_add(struct peer *peer, uint16_t chr_val_handle,
const struct ble_gatt_dsc *gatt_dsc)
{
struct peer_dsc *prev;
struct peer_dsc *dsc;
struct peer_svc *svc;
struct peer_chr *chr;
svc = peer_svc_find_range(peer, chr_val_handle);
if (svc == NULL) {
/* Can't find service for discovered descriptor; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
chr = peer_chr_find(svc, chr_val_handle, NULL);
if (chr == NULL) {
/* Can't find characteristic for discovered descriptor; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
dsc = peer_dsc_find(chr, gatt_dsc->handle, &prev);
if (dsc != NULL) {
/* Descriptor already discovered. */
return 0;
}
dsc = os_memblock_get(&peer_dsc_pool);
if (dsc == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(dsc, 0, sizeof * dsc);
dsc->dsc = *gatt_dsc;
if (prev == NULL) {
SLIST_INSERT_HEAD(&chr->dscs, dsc, next);
} else {
SLIST_NEXT(prev, next) = dsc;
}
return 0;
}
static void
peer_disc_dscs(struct peer *peer)
{
struct peer_chr *chr;
struct peer_svc *svc;
int rc;
/* Search through the list of discovered characteristics for the first
* characteristic that contains undiscovered descriptors. Then, discover
* all descriptors belonging to that characteristic.
*/
SLIST_FOREACH(svc, &peer->svcs, next) {
SLIST_FOREACH(chr, &svc->chrs, next) {
if (!chr_is_empty(svc, chr) &&
SLIST_EMPTY(&chr->dscs) &&
peer->disc_prev_chr_val <= chr->chr.def_handle) {
rc = ble_gattc_disc_all_dscs(peer->conn_handle,
chr->chr.val_handle,
chr_end_handle(svc, chr),
peer_dsc_disced, peer);
if (rc != 0) {
peer_disc_complete(peer, rc);
}
peer->disc_prev_chr_val = chr->chr.val_handle;
return;
}
}
}
/* All descriptors discovered. */
peer_disc_complete(peer, 0);
}
static int
peer_dsc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
uint16_t chr_val_handle, const struct ble_gatt_dsc *dsc,
void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_dsc_add(peer, chr_val_handle, dsc);
break;
case BLE_HS_EDONE:
/* All descriptors in this characteristic discovered; start discovering
* descriptors in the next characteristic.
*/
if (peer->disc_prev_chr_val > 0) {
peer_disc_dscs(peer);
}
rc = 0;
break;
default:
/* Error; abort discovery. */
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
uint16_t
chr_end_handle(const struct peer_svc *svc, const struct peer_chr *chr)
{
const struct peer_chr *next_chr;
next_chr = SLIST_NEXT(chr, next);
if (next_chr != NULL) {
return next_chr->chr.def_handle - 1;
} else {
return svc->svc.end_handle;
}
}
int
chr_is_empty(const struct peer_svc *svc, const struct peer_chr *chr)
{
return chr_end_handle(svc, chr) <= chr->chr.val_handle;
}
static struct peer_chr *
peer_chr_find_prev(const struct peer_svc *svc, uint16_t chr_val_handle)
{
struct peer_chr *prev;
struct peer_chr *chr;
prev = NULL;
SLIST_FOREACH(chr, &svc->chrs, next) {
if (chr->chr.val_handle >= chr_val_handle) {
break;
}
prev = chr;
}
return prev;
}
static struct peer_chr *
peer_chr_find(const struct peer_svc *svc, uint16_t chr_val_handle,
struct peer_chr **out_prev)
{
struct peer_chr *prev;
struct peer_chr *chr;
prev = peer_chr_find_prev(svc, chr_val_handle);
if (prev == NULL) {
chr = SLIST_FIRST(&svc->chrs);
} else {
chr = SLIST_NEXT(prev, next);
}
if (chr != NULL && chr->chr.val_handle != chr_val_handle) {
chr = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return chr;
}
static void
peer_chr_delete(struct peer_chr *chr)
{
struct peer_dsc *dsc;
while ((dsc = SLIST_FIRST(&chr->dscs)) != NULL) {
SLIST_REMOVE_HEAD(&chr->dscs, next);
os_memblock_put(&peer_dsc_pool, dsc);
}
os_memblock_put(&peer_chr_pool, chr);
}
static int
peer_chr_add(struct peer *peer, uint16_t svc_start_handle,
const struct ble_gatt_chr *gatt_chr)
{
struct peer_chr *prev;
struct peer_chr *chr;
struct peer_svc *svc;
svc = peer_svc_find(peer, svc_start_handle, NULL);
if (svc == NULL) {
/* Can't find service for discovered characteristic; this shouldn't
* happen.
*/
assert(0);
return BLE_HS_EUNKNOWN;
}
chr = peer_chr_find(svc, gatt_chr->def_handle, &prev);
if (chr != NULL) {
/* Characteristic already discovered. */
return 0;
}
chr = os_memblock_get(&peer_chr_pool);
if (chr == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(chr, 0, sizeof * chr);
chr->chr = *gatt_chr;
if (prev == NULL) {
SLIST_INSERT_HEAD(&svc->chrs, chr, next);
} else {
SLIST_NEXT(prev, next) = chr;
}
return 0;
}
static int
peer_chr_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_chr *chr, void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_chr_add(peer, peer->cur_svc->svc.start_handle, chr);
break;
case BLE_HS_EDONE:
/* All characteristics in this service discovered; start discovering
* characteristics in the next service.
*/
if (peer->disc_prev_chr_val > 0) {
peer_disc_chrs(peer);
}
rc = 0;
break;
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
static void
peer_disc_chrs(struct peer *peer)
{
struct peer_svc *svc;
int rc;
/* Search through the list of discovered service for the first service that
* contains undiscovered characteristics. Then, discover all
* characteristics belonging to that service.
*/
SLIST_FOREACH(svc, &peer->svcs, next) {
if (!peer_svc_is_empty(svc) && SLIST_EMPTY(&svc->chrs)) {
peer->cur_svc = svc;
rc = ble_gattc_disc_all_chrs(peer->conn_handle,
svc->svc.start_handle,
svc->svc.end_handle,
peer_chr_disced, peer);
if (rc != 0) {
peer_disc_complete(peer, rc);
}
return;
}
}
/* All characteristics discovered. */
peer_disc_dscs(peer);
}
int
peer_svc_is_empty(const struct peer_svc *svc)
{
return svc->svc.end_handle <= svc->svc.start_handle;
}
static struct peer_svc *
peer_svc_find_prev(struct peer *peer, uint16_t svc_start_handle)
{
struct peer_svc *prev;
struct peer_svc *svc;
prev = NULL;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (svc->svc.start_handle >= svc_start_handle) {
break;
}
prev = svc;
}
return prev;
}
static struct peer_svc *
peer_svc_find(struct peer *peer, uint16_t svc_start_handle,
struct peer_svc **out_prev)
{
struct peer_svc *prev;
struct peer_svc *svc;
prev = peer_svc_find_prev(peer, svc_start_handle);
if (prev == NULL) {
svc = SLIST_FIRST(&peer->svcs);
} else {
svc = SLIST_NEXT(prev, next);
}
if (svc != NULL && svc->svc.start_handle != svc_start_handle) {
svc = NULL;
}
if (out_prev != NULL) {
*out_prev = prev;
}
return svc;
}
static struct peer_svc *
peer_svc_find_range(struct peer *peer, uint16_t attr_handle)
{
struct peer_svc *svc;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (svc->svc.start_handle <= attr_handle &&
svc->svc.end_handle >= attr_handle) {
return svc;
}
}
return NULL;
}
const struct peer_svc *
peer_svc_find_uuid(const struct peer *peer, const ble_uuid_t *uuid)
{
const struct peer_svc *svc;
SLIST_FOREACH(svc, &peer->svcs, next) {
if (ble_uuid_cmp(&svc->svc.uuid.u, uuid) == 0) {
return svc;
}
}
return NULL;
}
const struct peer_chr *
peer_chr_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid)
{
const struct peer_svc *svc;
const struct peer_chr *chr;
svc = peer_svc_find_uuid(peer, svc_uuid);
if (svc == NULL) {
return NULL;
}
SLIST_FOREACH(chr, &svc->chrs, next) {
if (ble_uuid_cmp(&chr->chr.uuid.u, chr_uuid) == 0) {
return chr;
}
}
return NULL;
}
const struct peer_dsc *
peer_dsc_find_uuid(const struct peer *peer, const ble_uuid_t *svc_uuid,
const ble_uuid_t *chr_uuid, const ble_uuid_t *dsc_uuid)
{
const struct peer_chr *chr;
const struct peer_dsc *dsc;
chr = peer_chr_find_uuid(peer, svc_uuid, chr_uuid);
if (chr == NULL) {
return NULL;
}
SLIST_FOREACH(dsc, &chr->dscs, next) {
if (ble_uuid_cmp(&dsc->dsc.uuid.u, dsc_uuid) == 0) {
return dsc;
}
}
return NULL;
}
static int
peer_svc_add(struct peer *peer, const struct ble_gatt_svc *gatt_svc)
{
struct peer_svc *prev;
struct peer_svc *svc;
svc = peer_svc_find(peer, gatt_svc->start_handle, &prev);
if (svc != NULL) {
/* Service already discovered. */
return 0;
}
svc = os_memblock_get(&peer_svc_pool);
if (svc == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(svc, 0, sizeof * svc);
svc->svc = *gatt_svc;
SLIST_INIT(&svc->chrs);
if (prev == NULL) {
SLIST_INSERT_HEAD(&peer->svcs, svc, next);
} else {
SLIST_INSERT_AFTER(prev, svc, next);
}
return 0;
}
static void
peer_svc_delete(struct peer_svc *svc)
{
struct peer_chr *chr;
while ((chr = SLIST_FIRST(&svc->chrs)) != NULL) {
SLIST_REMOVE_HEAD(&svc->chrs, next);
peer_chr_delete(chr);
}
os_memblock_put(&peer_svc_pool, svc);
}
static int
peer_svc_disced(uint16_t conn_handle, const struct ble_gatt_error *error,
const struct ble_gatt_svc *service, void *arg)
{
struct peer *peer;
int rc;
peer = arg;
assert(peer->conn_handle == conn_handle);
switch (error->status) {
case 0:
rc = peer_svc_add(peer, service);
break;
case BLE_HS_EDONE:
/* All services discovered; start discovering characteristics. */
if (peer->disc_prev_chr_val > 0) {
peer_disc_chrs(peer);
}
rc = 0;
break;
default:
rc = error->status;
break;
}
if (rc != 0) {
/* Error; abort discovery. */
peer_disc_complete(peer, rc);
}
return rc;
}
int
peer_disc_all(uint16_t conn_handle, peer_disc_fn *disc_cb, void *disc_cb_arg)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
/* Undiscover everything first. */
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
peer->disc_prev_chr_val = 1;
peer->disc_cb = disc_cb;
peer->disc_cb_arg = disc_cb_arg;
rc = ble_gattc_disc_all_svcs(conn_handle, peer_svc_disced, peer);
if (rc != 0) {
return rc;
}
return 0;
}
int
peer_delete(uint16_t conn_handle)
{
struct peer_svc *svc;
struct peer *peer;
int rc;
peer = peer_find(conn_handle);
if (peer == NULL) {
return BLE_HS_ENOTCONN;
}
SLIST_REMOVE(&peers, peer, peer, next);
while ((svc = SLIST_FIRST(&peer->svcs)) != NULL) {
SLIST_REMOVE_HEAD(&peer->svcs, next);
peer_svc_delete(svc);
}
rc = os_memblock_put(&peer_pool, peer);
if (rc != 0) {
return BLE_HS_EOS;
}
return 0;
}
int
peer_add(uint16_t conn_handle)
{
struct peer *peer;
/* Make sure the connection handle is unique. */
peer = peer_find(conn_handle);
if (peer != NULL) {
return BLE_HS_EALREADY;
}
peer = os_memblock_get(&peer_pool);
if (peer == NULL) {
/* Out of memory. */
return BLE_HS_ENOMEM;
}
memset(peer, 0, sizeof * peer);
peer->conn_handle = conn_handle;
SLIST_INSERT_HEAD(&peers, peer, next);
return 0;
}
static void
peer_free_mem(void)
{
free(peer_mem);
peer_mem = NULL;
free(peer_svc_mem);
peer_svc_mem = NULL;
free(peer_chr_mem);
peer_chr_mem = NULL;
free(peer_dsc_mem);
peer_dsc_mem = NULL;
}
int
peer_init(int max_peers, int max_svcs, int max_chrs, int max_dscs)
{
int rc;
/* Free memory first in case this function gets called more than once. */
peer_free_mem();
peer_mem = malloc(
OS_MEMPOOL_BYTES(max_peers, sizeof (struct peer)));
if (peer_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_pool, max_peers,
sizeof (struct peer), peer_mem,
"peer_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_svc_mem = malloc(
OS_MEMPOOL_BYTES(max_svcs, sizeof (struct peer_svc)));
if (peer_svc_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_svc_pool, max_svcs,
sizeof (struct peer_svc), peer_svc_mem,
"peer_svc_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_chr_mem = malloc(
OS_MEMPOOL_BYTES(max_chrs, sizeof (struct peer_chr)));
if (peer_chr_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_chr_pool, max_chrs,
sizeof (struct peer_chr), peer_chr_mem,
"peer_chr_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
peer_dsc_mem = malloc(
OS_MEMPOOL_BYTES(max_dscs, sizeof (struct peer_dsc)));
if (peer_dsc_mem == NULL) {
rc = BLE_HS_ENOMEM;
goto err;
}
rc = os_mempool_init(&peer_dsc_pool, max_dscs,
sizeof (struct peer_dsc), peer_dsc_mem,
"peer_dsc_pool");
if (rc != 0) {
rc = BLE_HS_EOS;
goto err;
}
return 0;
err:
peer_free_mem();
return rc;
}

View File

@ -0,0 +1,12 @@
# 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=
CONFIG_BTDM_CTRL_MODE_BTDM=
CONFIG_BLUEDROID_ENABLED=
CONFIG_NIMBLE_ENABLED=y

View File

@ -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(blehr)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := blehr
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,115 @@
# BLE Heart Rate Measurement example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example creates GATT server demonstrating standard Heart Rate measurement service. It simulates Hear rate measurement and notifies to client when the notifications are enabled.
It uses ESP32's Bluetooth controller and NimBLE stack based BLE host
This example aims at understanding notification subscriptions and sending notifications.
To test this demo, any BLE scanner app can be used.
A Python based utility `blehr_test.py` is also provided (which will run as a BLE GATT Client) and can be used to test this example.
Note :
* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed.
* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus).
## How to use example
### Configure the project
```
idf.py menuconfig
```
* Set serial port under Serial Flasher Options.
### 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-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
This console output can be observed when blehr is connected to client and client enables notifications:
```
I (91) BTDM_INIT: BT controller compile version [fe7ced0]
I (91) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (181) phy: phy_version: 4100, 6fa5e27, Jan 25 2019, 17:02:06, 0, 0
I (421) NimBLE_BLE_HeartRate: BLE Host Task Started
GAP procedure initiated: stop advertising.
Device Address: xx:xx:xx:xx:xx:xx
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
connection established; status=0
subscribe event; cur_notify=1
value handle; val_handle=3
I (21611) BLE_GAP_SUBSCRIBE_EVENT: conn_handle from subscribe=0
GATT procedure initiated: notify; att_handle=3
GATT procedure initiated: notify; att_handle=3
GATT procedure initiated: notify; att_handle=3
GATT procedure initiated: notify; att_handle=3
GATT procedure initiated: notify; att_handle=3
GATT procedure initiated: notify; att_handle=3
GATT procedure initiated: notify; att_handle=3
```
## Running Python Utility
```
python blehr_test.py
```
## Python Utility Output
This is this output seen on the python side on successful connection:
```
discovering adapter...
bluetooth adapter discovered
powering on adapter...
bluetooth adapter powered on
Started Discovery
Connecting to device...
Connected to device
Services
[dbus.String(u'00001801-0000-1000-8000-00805f9b34fb', variant_level=1), dbus.String(u'0000180d-0000-1000-8000-00805f9b34fb', variant_level=1), dbus.String(u'0000180a-0000-1000-8000-00805f9b34fb', variant_level=1)]
Subscribe to notifications: On
dbus.Array([dbus.Byte(6), dbus.Byte(90)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(91)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(92)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(93)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(94)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(95)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(96)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(97)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(98)], signature=dbus.Signature('y'), variant_level=1)
dbus.Array([dbus.Byte(6), dbus.Byte(99)], signature=dbus.Signature('y'), variant_level=1)
Subscribe to notifications: Off
Success: blehr example test passed
exiting from test...
disconnecting device...
device disconnected
powering off adapter...
bluetooth adapter powered off
```

View File

@ -0,0 +1,171 @@
#!/usr/bin/env python
#
# Copyright 2019 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.
from __future__ import print_function
import os
import sys
import re
import threading
import traceback
import Queue
import subprocess
try:
# This environment variable is expected on the host machine
test_fw_path = os.getenv("TEST_FW_PATH")
if test_fw_path and test_fw_path not in sys.path:
sys.path.insert(0, test_fw_path)
import IDF
except ImportError as e:
print(e)
print("\nCheck your IDF_PATH\nOR")
print("Try `export TEST_FW_PATH=$IDF_PATH/tools/tiny-test-fw` for resolving the issue\nOR")
print("Try `pip install -r $IDF_PATH/tools/tiny-test-fw/requirements.txt` for resolving the issue\n")
import IDF
try:
import lib_ble_client
except ImportError:
lib_ble_client_path = os.getenv("IDF_PATH") + "/tools/ble"
if lib_ble_client_path and lib_ble_client_path not in sys.path:
sys.path.insert(0, lib_ble_client_path)
import lib_ble_client
import Utility
# When running on local machine execute the following before running this script
# > make app bootloader
# > make print_flash_cmd | tail -n 1 > build/download.config
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
def blehr_client_task(hr_obj, dut_addr):
interface = 'hci0'
ble_devname = 'blehr_sensor_1.0'
hr_srv_uuid = '180d'
hr_char_uuid = '2a37'
# Get BLE client module
ble_client_obj = lib_ble_client.BLE_Bluez_Client(interface, devname=ble_devname, devaddr=dut_addr)
if not ble_client_obj:
raise RuntimeError("Failed to get DBus-Bluez object")
# Discover Bluetooth Adapter and power on
is_adapter_set = ble_client_obj.set_adapter()
if not is_adapter_set:
raise RuntimeError("Adapter Power On failed !!")
# Connect BLE Device
is_connected = ble_client_obj.connect()
if not is_connected:
# Call disconnect to perform cleanup operations before exiting application
ble_client_obj.disconnect()
raise RuntimeError("Connection to device " + str(ble_devname) + " failed !!")
# Read Services
services_ret = ble_client_obj.get_services()
if services_ret:
Utility.console_log("\nServices\n")
Utility.console_log(str(services_ret))
else:
ble_client_obj.disconnect()
raise RuntimeError("Failure: Read Services failed")
'''
Blehr application run:
Start Notifications
Retrieve updated value
Stop Notifications
'''
blehr_ret = ble_client_obj.hr_update_simulation(hr_srv_uuid, hr_char_uuid)
if blehr_ret:
Utility.console_log("Success: blehr example test passed")
else:
raise RuntimeError("Failure: blehr example test failed")
# Call disconnect to perform cleanup operations before exiting application
ble_client_obj.disconnect()
class BleHRThread(threading.Thread):
def __init__(self, dut_addr, exceptions_queue):
threading.Thread.__init__(self)
self.dut_addr = dut_addr
self.exceptions_queue = exceptions_queue
def run(self):
try:
blehr_client_task(self, self.dut_addr)
except Exception:
self.exceptions_queue.put(traceback.format_exc(), block=False)
@IDF.idf_example_test(env_tag="Example_WIFI_BT")
def test_example_app_ble_hr(env, extra_data):
"""
Steps:
1. Discover Bluetooth Adapter and Power On
2. Connect BLE Device
3. Start Notifications
4. Updated value is retrieved
5. Stop Notifications
"""
subprocess.check_output(['rm','-rf','/var/lib/bluetooth/*'])
subprocess.check_output(['hciconfig','hci0','reset'])
# Acquire DUT
dut = env.get_dut("blehr", "examples/bluetooth/nimble/blehr")
# Get binary file
binary_file = os.path.join(dut.app.binary_path, "blehr.bin")
bin_size = os.path.getsize(binary_file)
IDF.log_performance("blehr_bin_size", "{}KB".format(bin_size // 1024))
IDF.check_performance("blehr_bin_size", bin_size // 1024)
# Upload binary and start testing
Utility.console_log("Starting blehr simple example test app")
dut.start_app()
dut.reset()
# Get device address from dut
dut_addr = dut.expect(re.compile(r"Device Address: ([a-fA-F0-9:]+)"), timeout=30)[0]
exceptions_queue = Queue.Queue()
# Starting a py-client in a separate thread
blehr_thread_obj = BleHRThread(dut_addr, exceptions_queue)
blehr_thread_obj.start()
blehr_thread_obj.join()
exception_msg = None
while True:
try:
exception_msg = exceptions_queue.get(block=False)
except Queue.Empty:
break
else:
Utility.console_log("\n" + exception_msg)
if exception_msg:
raise Exception("Thread did not run successfully")
# Check dut responses
dut.expect("subscribe event; cur_notify=1", timeout=30)
dut.expect("subscribe event; cur_notify=0", timeout=30)
dut.expect("disconnect;", timeout=30)
if __name__ == '__main__':
test_example_app_ble_hr()

View File

@ -0,0 +1,5 @@
set(COMPONENT_SRCS "main.c"
"gatt_svr.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,50 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 H_BLEHR_SENSOR_
#define H_BLEHR_SENSOR_
#include "nimble/ble.h"
#include "modlog/modlog.h"
#ifdef __cplusplus
extern "C" {
#endif
/* Heart-rate configuration */
#define GATT_HRS_UUID 0x180D
#define GATT_HRS_MEASUREMENT_UUID 0x2A37
#define GATT_HRS_BODY_SENSOR_LOC_UUID 0x2A38
#define GATT_DEVICE_INFO_UUID 0x180A
#define GATT_MANUFACTURER_NAME_UUID 0x2A29
#define GATT_MODEL_NUMBER_UUID 0x2A24
extern uint16_t hrs_hrm_handle;
struct ble_hs_cfg;
struct ble_gatt_register_ctxt;
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
int gatt_svr_init(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,186 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "blehr_sens.h"
static const char *manuf_name = "Apache Mynewt ESP32 devkitC";
static const char *model_num = "Mynewt HR Sensor demo";
uint16_t hrs_hrm_handle;
static int
gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
static int
gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg);
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
{
/* Service: Heart-rate */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(GATT_HRS_UUID),
.characteristics = (struct ble_gatt_chr_def[])
{ {
/* Characteristic: Heart-rate measurement */
.uuid = BLE_UUID16_DECLARE(GATT_HRS_MEASUREMENT_UUID),
.access_cb = gatt_svr_chr_access_heart_rate,
.val_handle = &hrs_hrm_handle,
.flags = BLE_GATT_CHR_F_NOTIFY,
}, {
/* Characteristic: Body sensor location */
.uuid = BLE_UUID16_DECLARE(GATT_HRS_BODY_SENSOR_LOC_UUID),
.access_cb = gatt_svr_chr_access_heart_rate,
.flags = BLE_GATT_CHR_F_READ,
}, {
0, /* No more characteristics in this service */
},
}
},
{
/* Service: Device Information */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = BLE_UUID16_DECLARE(GATT_DEVICE_INFO_UUID),
.characteristics = (struct ble_gatt_chr_def[])
{ {
/* Characteristic: * Manufacturer name */
.uuid = BLE_UUID16_DECLARE(GATT_MANUFACTURER_NAME_UUID),
.access_cb = gatt_svr_chr_access_device_info,
.flags = BLE_GATT_CHR_F_READ,
}, {
/* Characteristic: Model number string */
.uuid = BLE_UUID16_DECLARE(GATT_MODEL_NUMBER_UUID),
.access_cb = gatt_svr_chr_access_device_info,
.flags = BLE_GATT_CHR_F_READ,
}, {
0, /* No more characteristics in this service */
},
}
},
{
0, /* No more services */
},
};
static int
gatt_svr_chr_access_heart_rate(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
/* Sensor location, set to "Chest" */
static uint8_t body_sens_loc = 0x01;
uint16_t uuid;
int rc;
uuid = ble_uuid_u16(ctxt->chr->uuid);
if (uuid == GATT_HRS_BODY_SENSOR_LOC_UUID) {
rc = os_mbuf_append(ctxt->om, &body_sens_loc, sizeof(body_sens_loc));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
static int
gatt_svr_chr_access_device_info(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt, void *arg)
{
uint16_t uuid;
int rc;
uuid = ble_uuid_u16(ctxt->chr->uuid);
if (uuid == GATT_MODEL_NUMBER_UUID) {
rc = os_mbuf_append(ctxt->om, model_num, strlen(model_num));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
if (uuid == GATT_MANUFACTURER_NAME_UUID) {
rc = os_mbuf_append(ctxt->om, manuf_name, strlen(manuf_name));
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
void
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
"def_handle=%d val_handle=%d\n",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle,
ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
int
gatt_svr_init(void)
{
int rc;
ble_svc_gap_init();
ble_svc_gatt_init();
rc = ble_gatts_count_cfg(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
return 0;
}

View File

@ -0,0 +1,304 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "nvs_flash.h"
#include "freertos/FreeRTOSConfig.h"
/* BLE */
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "blehr_sens.h"
static const char *tag = "NimBLE_BLE_HeartRate";
static xTimerHandle blehr_tx_timer;
static bool notify_state;
static uint16_t conn_handle;
static const char *device_name = "blehr_sensor_1.0";
static int blehr_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t blehr_addr_type;
/* Variable to simulate heart beats */
static uint8_t heartrate = 90;
/**
* Utility function to log an array of bytes.
*/
void
print_bytes(const uint8_t *bytes, int len)
{
int i;
for (i = 0; i < len; i++) {
MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
}
}
void
print_addr(const void *addr)
{
const uint8_t *u8p;
u8p = addr;
MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
}
/*
* Enables advertising with parameters:
* o General discoverable mode
* o Undirected connectable mode
*/
static void
blehr_advertise(void)
{
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
int rc;
/*
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info)
* o Advertising tx power
* o Device name
*/
memset(&fields, 0, sizeof(fields));
/*
* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported)
*/
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
/*
* Indicate that the TX power level field should be included; have the
* stack fill this value automatically. This is done by assigning the
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
fields.name = (uint8_t *)device_name;
fields.name_len = strlen(device_name);
fields.name_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising */
memset(&adv_params, 0, sizeof(adv_params));
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(blehr_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, blehr_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
static void
blehr_tx_hrate_stop(void)
{
xTimerStop( blehr_tx_timer, 1000 / portTICK_PERIOD_MS );
}
/* Reset heart rate measurement */
static void
blehr_tx_hrate_reset(void)
{
int rc;
if (xTimerReset(blehr_tx_timer, 1000 / portTICK_PERIOD_MS ) == pdPASS) {
rc = 0;
} else {
rc = 1;
}
assert(rc == 0);
}
/* This function simulates heart beat and notifies it to the client */
static void
blehr_tx_hrate(xTimerHandle ev)
{
static uint8_t hrm[2];
int rc;
struct os_mbuf *om;
if (!notify_state) {
blehr_tx_hrate_stop();
heartrate = 90;
return;
}
hrm[0] = 0x06; /* contact of a sensor */
hrm[1] = heartrate; /* storing dummy data */
/* Simulation of heart beats */
heartrate++;
if (heartrate == 160) {
heartrate = 90;
}
om = ble_hs_mbuf_from_flat(hrm, sizeof(hrm));
rc = ble_gattc_notify_custom(conn_handle, hrs_hrm_handle, om);
assert(rc == 0);
blehr_tx_hrate_reset();
}
static int
blehr_gap_event(struct ble_gap_event *event, void *arg)
{
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed */
MODLOG_DFLT(INFO, "connection %s; status=%d\n",
event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status != 0) {
/* Connection failed; resume advertising */
blehr_advertise();
}
conn_handle = event->connect.conn_handle;
break;
case BLE_GAP_EVENT_DISCONNECT:
MODLOG_DFLT(INFO, "disconnect; reason=%d\n", event->disconnect.reason);
/* Connection terminated; resume advertising */
blehr_advertise();
break;
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "adv complete\n");
blehr_advertise();
break;
case BLE_GAP_EVENT_SUBSCRIBE:
MODLOG_DFLT(INFO, "subscribe event; cur_notify=%d\n value handle; "
"val_handle=%d\n",
event->subscribe.cur_notify, hrs_hrm_handle);
if (event->subscribe.attr_handle == hrs_hrm_handle) {
notify_state = event->subscribe.cur_notify;
blehr_tx_hrate_reset();
} else if (event->subscribe.attr_handle != hrs_hrm_handle) {
notify_state = event->subscribe.cur_notify;
blehr_tx_hrate_stop();
}
ESP_LOGI("BLE_GAP_SUBSCRIBE_EVENT", "conn_handle from subscribe=%d", conn_handle);
break;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.value);
break;
}
return 0;
}
static void
blehr_on_sync(void)
{
int rc;
rc = ble_hs_id_infer_auto(0, &blehr_addr_type);
assert(rc == 0);
uint8_t addr_val[6] = {0};
rc = ble_hs_id_copy_addr(blehr_addr_type, addr_val, NULL);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr_val);
MODLOG_DFLT(INFO, "\n");
/* Begin advertising */
blehr_advertise();
}
static void
blehr_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
void blehr_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
void app_main(void)
{
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
nimble_port_init();
/* Initialize the NimBLE host configuration */
ble_hs_cfg.sync_cb = blehr_on_sync;
ble_hs_cfg.reset_cb = blehr_on_reset;
/* name, period/time, auto reload, timer ID, callback */
blehr_tx_timer = xTimerCreate("blehr_tx_timer", pdMS_TO_TICKS(1000), pdTRUE, (void *)0, blehr_tx_hrate);
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name */
rc = ble_svc_gap_device_name_set(device_name);
assert(rc == 0);
/* Start the task */
nimble_port_freertos_init(blehr_host_task);
}

View File

@ -0,0 +1,12 @@
# 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=
CONFIG_BTDM_CTRL_MODE_BTDM=
CONFIG_BLUEDROID_ENABLED=
CONFIG_NIMBLE_ENABLED=y

View File

@ -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(blemesh)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := blemesh
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,89 @@
# BLE Mesh example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example implements Bluetooth Mesh node that supports On/Off and Level models.
It has suport for both Advertising Bearer and GATT Bearer.
For more information on NimBLE MESH, please visit [NimBLE_MESH](https://mynewt.apache.org/latest/network/docs/mesh/index.html#bluetooth-mesh).
It uses ESP32's Bluetooth controller and NimBLE stack based BLE host.
This example can be starting step to get basic understanding on how to build BLE MESH node.
To test this demo, any BLE mesh provisioner app can be used.
## How to use example
### Configure the project
```
idf.py menuconfig
```
* Set serial port under Serial Flasher Options.
* Select 'Enable BLE mesh functionality' under 'Component config > Bluetooth > Enable NimBLE host stack'.
### 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-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
There is this console output on successful BLE provisioning:
```
I (285) BTDM_INIT: BT controller compile version [8e87ec7]
I (285) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (355) phy: phy_version: 4000, b6198fa, Sep 3 2018, 15:11:06, 0, 0
GAP procedure initiated: stop advertising.
I (625) NimBLE_MESH: Bluetooth initialized
GAP procedure initiated: discovery; own_addr_type=1 filter_policy=0 passive=1 limited=0 filter_duplicates=0 duration=forever
I (895) NimBLE_MESH: Mesh initialized
GAP procedure initiated: advertise; disc_mode=0 adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=160 adv_itvl_max=160
GAP procedure initiated: stop advertising.
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=160 adv_itvl_max=240
proxy_connected: conn_handle 0
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=1600 adv_itvl_max=1920
proxy_complete_pdu: Mesh Provisioning PDU
prov_invite: Attention Duration: 5 seconds
GATT procedure initiated: notify; att_handle=20
proxy_complete_pdu: Mesh Provisioning PDU
prov_start: Algorithm: 0x00
prov_start: Public Key: 0x00
prov_start: Auth Method: 0x02
prov_start: Auth Action: 0x00
prov_start: Auth Size: 0x04
I (6985) NimBLE_MESH: OOB Number: 5228
proxy_complete_pdu: Mesh Provisioning PDU
prov_pub_key: Remote Public Key: f56c5d5396a4d09cf1ea52e8217eba3b881202e73d09e9c4955903d5836d51b2117176fa5887869ddd5a2985dce9f706d3e4c2729dd9d45edeb86bcbebe4721c
GATT procedure initiated: notify; att_handle=20
proxy_complete_pdu: Mesh Provisioning PDU
prov_confirm: Remote Confirm: ec7a9c169d23408abe051beca357abc1
GATT procedure initiated: notify; att_handle=20
proxy_complete_pdu: Mesh Provisioning PDU
prov_random: Remote Random: 05ca403997576097eb430588bf2b8448
GATT procedure initiated: notify; att_handle=20
proxy_complete_pdu: Mesh Provisioning PDU
GATT procedure initiated: notify; att_handle=20
bt_mesh_provision: Primary Element: 0x0002
GAP procedure initiated: stop advertising.
I (11885) NimBLE_MESH: Local node provisioned, primary address 0x0002
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=1 adv_filter_policy=0 adv_itvl_min=1600 adv_itvl_max=1920
GAP procedure initiated: stop advertising.
```

View File

@ -0,0 +1,4 @@
set(COMPONENT_SRCS "app_mesh.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,445 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "nvs_flash.h"
#include "freertos/FreeRTOSConfig.h"
/* BLE */
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "mesh/mesh.h"
static const char *tag = "NimBLE_MESH";
void ble_store_config_init(void);
#define BT_DBG_ENABLED (MYNEWT_VAL(BLE_MESH_DEBUG))
/* Company ID */
#define CID_VENDOR 0x05C3
#define STANDARD_TEST_ID 0x00
#define TEST_ID 0x01
static int recent_test_id = STANDARD_TEST_ID;
#define FAULT_ARR_SIZE 2
static bool has_reg_fault = true;
static struct bt_mesh_cfg_srv cfg_srv = {
.relay = BT_MESH_RELAY_DISABLED,
.beacon = BT_MESH_BEACON_ENABLED,
#if MYNEWT_VAL(BLE_MESH_FRIEND)
.frnd = BT_MESH_FRIEND_ENABLED,
#endif
#if MYNEWT_VAL(BLE_MESH_GATT_PROXY)
.gatt_proxy = BT_MESH_GATT_PROXY_ENABLED,
#else
.gatt_proxy = BT_MESH_GATT_PROXY_NOT_SUPPORTED,
#endif
.default_ttl = 7,
/* 3 transmissions with 20ms interval */
.net_transmit = BT_MESH_TRANSMIT(2, 20),
.relay_retransmit = BT_MESH_TRANSMIT(2, 20),
};
static int
fault_get_cur(struct bt_mesh_model *model,
uint8_t *test_id,
uint16_t *company_id,
uint8_t *faults,
uint8_t *fault_count)
{
uint8_t reg_faults[FAULT_ARR_SIZE] = { [0 ... FAULT_ARR_SIZE - 1] = 0xff };
ESP_LOGI(tag, "fault_get_cur() has_reg_fault %u\n", has_reg_fault);
*test_id = recent_test_id;
*company_id = CID_VENDOR;
*fault_count = min(*fault_count, sizeof(reg_faults));
memcpy(faults, reg_faults, *fault_count);
return 0;
}
static int
fault_get_reg(struct bt_mesh_model *model,
uint16_t company_id,
uint8_t *test_id,
uint8_t *faults,
uint8_t *fault_count)
{
if (company_id != CID_VENDOR) {
return -BLE_HS_EINVAL;
}
ESP_LOGI(tag, "fault_get_reg() has_reg_fault %u\n", has_reg_fault);
*test_id = recent_test_id;
if (has_reg_fault) {
uint8_t reg_faults[FAULT_ARR_SIZE] = { [0 ... FAULT_ARR_SIZE - 1] = 0xff };
*fault_count = min(*fault_count, sizeof(reg_faults));
memcpy(faults, reg_faults, *fault_count);
} else {
*fault_count = 0;
}
return 0;
}
static int
fault_clear(struct bt_mesh_model *model, uint16_t company_id)
{
if (company_id != CID_VENDOR) {
return -BLE_HS_EINVAL;
}
has_reg_fault = false;
return 0;
}
static int
fault_test(struct bt_mesh_model *model, uint8_t test_id, uint16_t company_id)
{
if (company_id != CID_VENDOR) {
return -BLE_HS_EINVAL;
}
if (test_id != STANDARD_TEST_ID && test_id != TEST_ID) {
return -BLE_HS_EINVAL;
}
recent_test_id = test_id;
has_reg_fault = true;
bt_mesh_fault_update(bt_mesh_model_elem(model));
return 0;
}
static const struct bt_mesh_health_srv_cb health_srv_cb = {
.fault_get_cur = &fault_get_cur,
.fault_get_reg = &fault_get_reg,
.fault_clear = &fault_clear,
.fault_test = &fault_test,
};
static struct bt_mesh_health_srv health_srv = {
.cb = &health_srv_cb,
};
static struct bt_mesh_model_pub health_pub;
static void
health_pub_init(void)
{
health_pub.msg = BT_MESH_HEALTH_FAULT_MSG(0);
}
static struct bt_mesh_model_pub gen_level_pub;
static struct bt_mesh_model_pub gen_onoff_pub;
static uint8_t gen_on_off_state;
static int16_t gen_level_state;
static void gen_onoff_status(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx)
{
struct os_mbuf *msg = NET_BUF_SIMPLE(3);
uint8_t *status;
ESP_LOGI(tag, "#mesh-onoff STATUS\n");
bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_2(0x82, 0x04));
status = net_buf_simple_add(msg, 1);
*status = gen_on_off_state;
if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
ESP_LOGI(tag, "#mesh-onoff STATUS: send status failed\n");
}
os_mbuf_free_chain(msg);
}
static void gen_onoff_get(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
ESP_LOGI(tag, "#mesh-onoff GET\n");
gen_onoff_status(model, ctx);
}
static void gen_onoff_set(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
ESP_LOGI(tag, "#mesh-onoff SET\n");
gen_on_off_state = buf->om_data[0];
gen_onoff_status(model, ctx);
}
static void gen_onoff_set_unack(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
ESP_LOGI(tag, "#mesh-onoff SET-UNACK\n");
gen_on_off_state = buf->om_data[0];
}
static const struct bt_mesh_model_op gen_onoff_op[] = {
{ BT_MESH_MODEL_OP_2(0x82, 0x01), 0, gen_onoff_get },
{ BT_MESH_MODEL_OP_2(0x82, 0x02), 2, gen_onoff_set },
{ BT_MESH_MODEL_OP_2(0x82, 0x03), 2, gen_onoff_set_unack },
BT_MESH_MODEL_OP_END,
};
static void gen_level_status(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx)
{
struct os_mbuf *msg = NET_BUF_SIMPLE(4);
ESP_LOGI(tag, "#mesh-level STATUS\n");
bt_mesh_model_msg_init(msg, BT_MESH_MODEL_OP_2(0x82, 0x08));
net_buf_simple_add_le16(msg, gen_level_state);
if (bt_mesh_model_send(model, ctx, msg, NULL, NULL)) {
ESP_LOGI(tag, "#mesh-level STATUS: send status failed\n");
}
os_mbuf_free_chain(msg);
}
static void gen_level_get(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
ESP_LOGI(tag, "#mesh-level GET\n");
gen_level_status(model, ctx);
}
static void gen_level_set(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
int16_t level;
level = (int16_t) net_buf_simple_pull_le16(buf);
ESP_LOGI(tag, "#mesh-level SET: level=%d\n", level);
gen_level_status(model, ctx);
gen_level_state = level;
ESP_LOGI(tag, "#mesh-level: level=%d\n", gen_level_state);
}
static void gen_level_set_unack(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
int16_t level;
level = (int16_t) net_buf_simple_pull_le16(buf);
ESP_LOGI(tag, "#mesh-level SET-UNACK: level=%d\n", level);
gen_level_state = level;
ESP_LOGI(tag, "#mesh-level: level=%d\n", gen_level_state);
}
static void gen_delta_set(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
int16_t delta_level;
delta_level = (int16_t) net_buf_simple_pull_le16(buf);
ESP_LOGI(tag, "#mesh-level DELTA-SET: delta_level=%d\n", delta_level);
gen_level_status(model, ctx);
gen_level_state += delta_level;
ESP_LOGI(tag, "#mesh-level: level=%d\n", gen_level_state);
}
static void gen_delta_set_unack(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
int16_t delta_level;
delta_level = (int16_t) net_buf_simple_pull_le16(buf);
ESP_LOGI(tag, "#mesh-level DELTA-SET: delta_level=%d\n", delta_level);
gen_level_state += delta_level;
ESP_LOGI(tag, "#mesh-level: level=%d\n", gen_level_state);
}
static void gen_move_set(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
}
static void gen_move_set_unack(struct bt_mesh_model *model,
struct bt_mesh_msg_ctx *ctx,
struct os_mbuf *buf)
{
}
static const struct bt_mesh_model_op gen_level_op[] = {
{ BT_MESH_MODEL_OP_2(0x82, 0x05), 0, gen_level_get },
{ BT_MESH_MODEL_OP_2(0x82, 0x06), 3, gen_level_set },
{ BT_MESH_MODEL_OP_2(0x82, 0x07), 3, gen_level_set_unack },
{ BT_MESH_MODEL_OP_2(0x82, 0x09), 5, gen_delta_set },
{ BT_MESH_MODEL_OP_2(0x82, 0x0a), 5, gen_delta_set_unack },
{ BT_MESH_MODEL_OP_2(0x82, 0x0b), 3, gen_move_set },
{ BT_MESH_MODEL_OP_2(0x82, 0x0c), 3, gen_move_set_unack },
BT_MESH_MODEL_OP_END,
};
static struct bt_mesh_model root_models[] = {
BT_MESH_MODEL_CFG_SRV(&cfg_srv),
BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_op,
&gen_onoff_pub, NULL),
BT_MESH_MODEL(BT_MESH_MODEL_ID_GEN_LEVEL_SRV, gen_level_op,
&gen_level_pub, NULL),
};
static struct bt_mesh_model vnd_models[] = {
BT_MESH_MODEL_VND(CID_VENDOR, BT_MESH_MODEL_ID_GEN_ONOFF_SRV, gen_onoff_op,
&gen_onoff_pub, NULL),
};
static struct bt_mesh_elem elements[] = {
BT_MESH_ELEM(0, root_models, vnd_models),
};
static const struct bt_mesh_comp comp = {
.cid = CID_VENDOR,
.elem = elements,
.elem_count = ARRAY_SIZE(elements),
};
static int output_number(bt_mesh_output_action_t action, uint32_t number)
{
ESP_LOGI(tag, "OOB Number: %u\n", number);
return 0;
}
static void prov_complete(u16_t net_idx, u16_t addr)
{
ESP_LOGI(tag, "Local node provisioned, primary address 0x%04x\n", addr);
}
static const uint8_t dev_uuid[16] = MYNEWT_VAL(BLE_MESH_DEV_UUID);
static const struct bt_mesh_prov prov = {
.uuid = dev_uuid,
.output_size = 4,
.output_actions = BT_MESH_DISPLAY_NUMBER | BT_MESH_BEEP | BT_MESH_VIBRATE | BT_MESH_BLINK,
.output_number = output_number,
.complete = prov_complete,
};
static void
blemesh_on_reset(int reason)
{
BLE_HS_LOG(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
blemesh_on_sync(void)
{
int err;
ble_addr_t addr;
ESP_LOGI(tag, "Bluetooth initialized\n");
/* Use NRPA */
err = ble_hs_id_gen_rnd(1, &addr);
assert(err == 0);
err = ble_hs_id_set_rnd(addr.val);
assert(err == 0);
err = bt_mesh_init(addr.type, &prov, &comp);
if (err) {
ESP_LOGI(tag, "Initializing mesh failed (err %d)\n", err);
return;
}
ESP_LOGI(tag, "Mesh initialized\n");
if (IS_ENABLED(CONFIG_SETTINGS)) {
settings_load();
}
if (bt_mesh_is_provisioned()) {
ESP_LOGI(tag, "Mesh network restored from flash\n");
}
}
void blemesh_host_task(void *param)
{
ble_hs_cfg.reset_cb = blemesh_on_reset;
ble_hs_cfg.sync_cb = blemesh_on_sync;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
health_pub_init();
nimble_port_run();
nimble_port_freertos_deinit();
}
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_nimble_hci_and_controller_init());
nimble_port_init();
ble_svc_gap_init();
ble_svc_gatt_init();
bt_mesh_register_gatt();
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(blemesh_host_task);
}

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,13 @@
# 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=
CONFIG_BTDM_CTRL_MODE_BTDM=
CONFIG_BLUEDROID_ENABLED=
CONFIG_NIMBLE_ENABLED=y
CONFIG_NIMBLE_MESH=y

View File

@ -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(bleprph)

View File

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := bleprph
include $(IDF_PATH)/make/project.mk

View File

@ -0,0 +1,150 @@
# BLE peripheral example
(See the README.md file in the upper level 'examples' directory for more information about examples.)
This example creates GATT server and then starts advertising, waiting to be connected to a GATT client.
It uses ESP32's Bluetooth controller and NimBLE stack based BLE host.
This example aims at understanding GATT database configuration, advertisement and SMP related NimBLE APIs.
It also demonstrates security features of NimBLE stack. SMP parameters like I/O capabilities of device, Bonding flag, MITM protection flag and Secure Connection only mode etc., can be configured through menuconfig options.
To test this demo, any BLE scanner app can be used.
A Python based utility `bleprph_test.py` is also provided (which will run as a BLE GATT Client) and can be used to test this example.
Note :
* Make sure to run `python -m pip install --user -r $IDF_PATH/requirements.txt -r $IDF_PATH/tools/ble/requirements.txt` to install the dependency packages needed.
* Currently this Python utility is only supported on Linux (BLE communication is via BLuez + DBus).
## How to use example
### Configure the project
```
idf.py menuconfig
```
* Set serial port under Serial Flasher Options.
* Select I/O capabilities of device from 'Example Configuration > I/O Capability', default is 'Just_works'.
* Enable/Disable other security related parameters 'Bonding, MITM option, secure connection(SM SC)' from 'Example Configuration'.
### 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-]``.)
See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
## Example Output
There is this console output when bleprph is connected and characteristic is read:
```
I (118) BTDM_INIT: BT controller compile version [fe7ced0]
I (118) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
W (128) phy_init: failed to load RF calibration data (0xffffffff), falling back to full calibration
I (268) phy: phy_version: 4100, 6fa5e27, Jan 25 2019, 17:02:06, 0, 2
I (508) NimBLE_BLE_PRPH: BLE Host Task Started
I (508) uart: queue free spaces: 8
GAP procedure initiated: stop advertising.
Device Address: xx:xx:xx:xx:xx:xx
GAP procedure initiated: advertise; disc_mode=2 adv_channel_map=0 own_addr_type=0 adv_filter_policy=0 adv_itvl_min=0 adv_itvl_max=0
connection established; status=0 handle=0 our_ota_addr_type=0 our_ota_addr=xx:xx:xx:xx:xx:xx our_id_addr_type=0 our_id_addr=xx:xx:xx:xx:xx:xx peer_ota_addr_type=1 peer_ota_addr=xx:xx:xx:xx:xx:xx peer_id_addr_type=1 peer_id_addr=xx:xx:xx:xx:xx:xx conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
connection updated; status=0 handle=0 our_ota_addr_type=0 our_ota_addr=xx:xx:xx:xx:xx:xx our_id_addr_type=0 our_id_addr=xx:xx:xx:xx:xx:xx peer_ota_addr_type=1 peer_ota_addr=xx:xx:xx:xx:xx:xx peer_id_addr_type=1 peer_id_addr=xx:xx:xx:xx:xx:xx conn_itvl=6 conn_latency=0 supervision_timeout=500 encrypted=0 authenticated=0 bonded=0
I (50888) NimBLE_BLE_PRPH: PASSKEY_ACTION_EVENT started
I (50888) NimBLE_BLE_PRPH: Passkey on device's display: xxxxxx
I (50888) NimBLE_BLE_PRPH: Accept or reject the passkey through console in this format -> key Y or key N
key Y
I (50898) NimBLE_BLE_PRPH: ble_sm_inject_io result: 0
encryption change event; status=0 handle=0 our_ota_addr_type=0 our_ota_addr=xx:xx:xx:xx:xx:xx our_id_addr_type=0 our_id_addr=xx:xx:xx:xx:xx:xx peer_ota_addr_type=1 peer_ota_addr=xx:xx:xx:xx:xx:xx peer_id_addr_type=1
peer_id_addr=xx:xx:xx:xx:xx:xx conn_itvl=6 conn_latency=0 supervision_timeout=500 encrypted=1 authenticated=1 bonded=1
connection updated; status=0 handle=0 our_ota_addr_type=0 our_ota_addr=xx:xx:xx:xx:xx:xx our_id_addr_type=0 our_id_addr=xx:xx:xx:xx:xx:xx
peer_ota_addr_type=1 peer_ota_addr=xx:xx:xx:xx:xx:xx peer_id_addr_type=1 peer_id_addr=xx:xx:xx:xx:xx:xx conn_itvl=39 conn_latency=0 supervision_timeout=500 encrypted=1 authenticated=1 bonded=1
```
## Running Python Utility
```
python bleprph_test.py
```
## Python Utility Output
This is this output seen on the python side on successful connection:
```
discovering adapter...
bluetooth adapter discovered
powering on adapter...
bluetooth adapter powered on
Started Discovery
Connecting to device...
Connected to device
Services
[dbus.String(u'00001801-0000-1000-8000-00805f9b34fb', variant_level=1), dbus.String(u'59462f12-9543-9999-12c8-58b459a2712d', variant_level=1)]
Characteristics retrieved
Characteristic: /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/service000a/char000b
Characteristic UUID: 5c3a659e-897e-45e1-b016-007107c96df6
Value: dbus.Array([dbus.Byte(45), dbus.Byte(244), dbus.Byte(81), dbus.Byte(88)], signature=dbus.Signature('y'))
Properties: : dbus.Array([dbus.String(u'read')], signature=dbus.Signature('s'), variant_level=1)
Characteristic: /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/service000a/char000d
Characteristic UUID: 5c3a659e-897e-45e1-b016-007107c96df7
Value: dbus.Array([dbus.Byte(0)], signature=dbus.Signature('y'))
Properties: : dbus.Array([dbus.String(u'read'), dbus.String(u'write')], signature=dbus.Signature('s'), variant_level=1)
Characteristic: /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/service0006/char0007
Characteristic UUID: 00002a05-0000-1000-8000-00805f9b34fb
Value: None
Properties: : dbus.Array([dbus.String(u'indicate')], signature=dbus.Signature('s'), variant_level=1)
Characteristics after write operation
Characteristic: /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/service000a/char000b
Characteristic UUID: 5c3a659e-897e-45e1-b016-007107c96df6
Value: dbus.Array([dbus.Byte(45), dbus.Byte(244), dbus.Byte(81), dbus.Byte(88)], signature=dbus.Signature('y'))
Properties: : dbus.Array([dbus.String(u'read')], signature=dbus.Signature('s'), variant_level=1)
Characteristic: /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/service000a/char000d
Characteristic UUID: 5c3a659e-897e-45e1-b016-007107c96df7
Value: dbus.Array([dbus.Byte(65)], signature=dbus.Signature('y'))
Properties: : dbus.Array([dbus.String(u'read'), dbus.String(u'write')], signature=dbus.Signature('s'), variant_level=1)
Characteristic: /org/bluez/hci0/dev_xx_xx_xx_xx_xx_xx/service0006/char0007
Characteristic UUID: 00002a05-0000-1000-8000-00805f9b34fb
Value: None
Properties: : dbus.Array([dbus.String(u'indicate')], signature=dbus.Signature('s'), variant_level=1)
exiting from test...
disconnecting device...
device disconnected
powering off adapter...
bluetooth adapter powered off
```
## Note
* NVS support is not yet integrated to bonding. So, for now, bonding is not persistent across reboot.

View File

@ -0,0 +1,192 @@
#!/usr/bin/env python
#
# Copyright 2019 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.
from __future__ import print_function
import os
import sys
import re
import Queue
import traceback
import threading
import subprocess
try:
# This environment variable is expected on the host machine
test_fw_path = os.getenv("TEST_FW_PATH")
if test_fw_path and test_fw_path not in sys.path:
sys.path.insert(0, test_fw_path)
import IDF
except ImportError as e:
print(e)
print("Try `export TEST_FW_PATH=$IDF_PATH/tools/tiny-test-fw` for resolving the issue")
print("Try `pip install -r $IDF_PATH/tools/tiny-test-fw/requirements.txt` for resolving the issue")
import IDF
try:
import lib_ble_client
except ImportError:
lib_ble_client_path = os.getenv("IDF_PATH") + "/tools/ble"
if lib_ble_client_path and lib_ble_client_path not in sys.path:
sys.path.insert(0, lib_ble_client_path)
import lib_ble_client
import Utility
# When running on local machine execute the following before running this script
# > make app bootloader
# > make print_flash_cmd | tail -n 1 > build/download.config
# > export TEST_FW_PATH=~/esp/esp-idf/tools/tiny-test-fw
def bleprph_client_task(prph_obj, dut, dut_addr):
interface = 'hci0'
ble_devname = 'nimble-bleprph'
srv_uuid = '2f12'
# Get BLE client module
ble_client_obj = lib_ble_client.BLE_Bluez_Client(interface, devname=ble_devname, devaddr=dut_addr)
if not ble_client_obj:
raise RuntimeError("Failed to get DBus-Bluez object")
# Discover Bluetooth Adapter and power on
is_adapter_set = ble_client_obj.set_adapter()
if not is_adapter_set:
raise RuntimeError("Adapter Power On failed !!")
# Connect BLE Device
is_connected = ble_client_obj.connect()
if not is_connected:
# Call disconnect to perform cleanup operations before exiting application
ble_client_obj.disconnect()
raise RuntimeError("Connection to device " + ble_devname + " failed !!")
# Check dut responses
dut.expect("GAP procedure initiated: advertise;", timeout=30)
# Read Services
services_ret = ble_client_obj.get_services(srv_uuid)
if services_ret:
Utility.console_log("\nServices\n")
Utility.console_log(str(services_ret))
else:
ble_client_obj.disconnect()
raise RuntimeError("Failure: Read Services failed")
# Read Characteristics
chars_ret = {}
chars_ret = ble_client_obj.read_chars()
if chars_ret:
Utility.console_log("\nCharacteristics retrieved")
for path, props in chars_ret.items():
Utility.console_log("\n\tCharacteristic: " + str(path))
Utility.console_log("\tCharacteristic UUID: " + str(props[2]))
Utility.console_log("\tValue: " + str(props[0]))
Utility.console_log("\tProperties: : " + str(props[1]))
else:
ble_client_obj.disconnect()
raise RuntimeError("Failure: Read Characteristics failed")
'''
Write Characteristics
- write 'A' to characteristic with write permission
'''
chars_ret_on_write = {}
chars_ret_on_write = ble_client_obj.write_chars('A')
if chars_ret_on_write:
Utility.console_log("\nCharacteristics after write operation")
for path, props in chars_ret_on_write.items():
Utility.console_log("\n\tCharacteristic:" + str(path))
Utility.console_log("\tCharacteristic UUID: " + str(props[2]))
Utility.console_log("\tValue:" + str(props[0]))
Utility.console_log("\tProperties: : " + str(props[1]))
else:
ble_client_obj.disconnect()
raise RuntimeError("Failure: Write Characteristics failed")
# Call disconnect to perform cleanup operations before exiting application
ble_client_obj.disconnect()
class BlePrphThread(threading.Thread):
def __init__(self, dut, dut_addr, exceptions_queue):
threading.Thread.__init__(self)
self.dut = dut
self.dut_addr = dut_addr
self.exceptions_queue = exceptions_queue
def run(self):
try:
bleprph_client_task(self, self.dut, self.dut_addr)
except Exception:
self.exceptions_queue.put(traceback.format_exc(), block=False)
@IDF.idf_example_test(env_tag="Example_WIFI_BT")
def test_example_app_ble_peripheral(env, extra_data):
"""
Steps:
1. Discover Bluetooth Adapter and Power On
2. Connect BLE Device
3. Read Services
4. Read Characteristics
5. Write Characteristics
"""
subprocess.check_output(['rm','-rf','/var/lib/bluetooth/*'])
subprocess.check_output(['hciconfig','hci0','reset'])
# Acquire DUT
dut = env.get_dut("bleprph", "examples/bluetooth/nimble/bleprph")
# Get binary file
binary_file = os.path.join(dut.app.binary_path, "bleprph.bin")
bin_size = os.path.getsize(binary_file)
IDF.log_performance("bleprph_bin_size", "{}KB".format(bin_size // 1024))
IDF.check_performance("bleprph_bin_size", bin_size // 1024)
# Upload binary and start testing
Utility.console_log("Starting bleprph simple example test app")
dut.start_app()
dut.reset()
# Get device address from dut
dut_addr = dut.expect(re.compile(r"Device Address: ([a-fA-F0-9:]+)"), timeout=30)[0]
exceptions_queue = Queue.Queue()
# Starting a py-client in a separate thread
bleprph_thread_obj = BlePrphThread(dut, dut_addr, exceptions_queue)
bleprph_thread_obj.start()
bleprph_thread_obj.join()
exception_msg = None
while True:
try:
exception_msg = exceptions_queue.get(block=False)
except Queue.Empty:
break
else:
Utility.console_log("\n" + exception_msg)
if exception_msg:
raise Exception("Thread did not run successfully")
# Check dut responses
dut.expect("connection established; status=0", timeout=30)
dut.expect("disconnect;", timeout=30)
if __name__ == '__main__':
test_example_app_ble_peripheral()

View File

@ -0,0 +1,7 @@
set(COMPONENT_SRCS "main.c"
"gatt_svr.c"
"misc.c"
"scli.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

View File

@ -0,0 +1,51 @@
menu "Example Configuration"
choice EXAMPLE_USE_IO_TYPE
prompt "I/O Capability"
default BLE_SM_IO_CAP_NO_IO
help
I/O capability of device.
config BLE_SM_IO_CAP_DISP_ONLY
bool "DISPLAY ONLY"
config BLE_SM_IO_CAP_DISP_YES_NO
bool "DISPLAY YESNO"
config BLE_SM_IO_CAP_KEYBOARD_ONLY
bool "KEYBOARD ONLY"
config BLE_SM_IO_CAP_NO_IO
bool "Just works"
config BLE_SM_IO_CAP_KEYBOARD_DISP
bool "Both KEYBOARD & DISPLAY"
endchoice
config EXAMPLE_IO_TYPE
int
default 0 if BLE_SM_IO_CAP_DISP_ONLY
default 1 if BLE_SM_IO_CAP_DISP_YES_NO
default 2 if BLE_SM_IO_CAP_KEYBOARD_ONLY
default 3 if BLE_SM_IO_CAP_NO_IO
default 4 if BLE_SM_IO_CAP_KEYBOARD_DISP
config EXAMPLE_BONDING
bool
default n
prompt "Use Bonding"
help
Use this option to enable/disable bonding.
config EXAMPLE_MITM
bool
default n
prompt "MITM security"
help
Use this option to enable/disable MITM security.
config EXAMPLE_USE_SC
bool
depends on NIMBLE_SM_SC
default n
prompt "Use Secure Connection feature"
help
Use this option to enable/disable Security Manager Secure Connection 4.2 feature.
endmenu

View File

@ -0,0 +1,56 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 H_BLEPRPH_
#define H_BLEPRPH_
#include <stdbool.h>
#include "nimble/ble.h"
#include "modlog/modlog.h"
#ifdef __cplusplus
extern "C" {
#endif
struct ble_hs_cfg;
struct ble_gatt_register_ctxt;
/** GATT server. */
#define GATT_SVR_SVC_ALERT_UUID 0x1811
#define GATT_SVR_CHR_SUP_NEW_ALERT_CAT_UUID 0x2A47
#define GATT_SVR_CHR_NEW_ALERT 0x2A46
#define GATT_SVR_CHR_SUP_UNR_ALERT_CAT_UUID 0x2A48
#define GATT_SVR_CHR_UNR_ALERT_STAT_UUID 0x2A45
#define GATT_SVR_CHR_ALERT_NOT_CTRL_PT 0x2A44
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
int gatt_svr_init(void);
/* Console */
int scli_init(void);
int scli_receive_key(int *key);
/** Misc. */
void print_bytes(const uint8_t *bytes, int len);
void print_addr(const void *addr);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

View File

@ -0,0 +1,210 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <assert.h>
#include <stdio.h>
#include <string.h>
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "bleprph.h"
/**
* The vendor specific security test service consists of two characteristics:
* o random-number-generator: generates a random 32-bit number each time
* it is read. This characteristic can only be read over an encrypted
* connection.
* o static-value: a single-byte characteristic that can always be read,
* but can only be written over an encrypted connection.
*/
/* 59462f12-9543-9999-12c8-58b459a2712d */
static const ble_uuid128_t gatt_svr_svc_sec_test_uuid =
BLE_UUID128_INIT(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);
/* 5c3a659e-897e-45e1-b016-007107c96df6 */
static const ble_uuid128_t gatt_svr_chr_sec_test_rand_uuid =
BLE_UUID128_INIT(0xf6, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0,
0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c);
/* 5c3a659e-897e-45e1-b016-007107c96df7 */
static const ble_uuid128_t gatt_svr_chr_sec_test_static_uuid =
BLE_UUID128_INIT(0xf7, 0x6d, 0xc9, 0x07, 0x71, 0x00, 0x16, 0xb0,
0xe1, 0x45, 0x7e, 0x89, 0x9e, 0x65, 0x3a, 0x5c);
static uint8_t gatt_svr_sec_test_static_val;
static int
gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg);
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
{
/*** Service: Security test. */
.type = BLE_GATT_SVC_TYPE_PRIMARY,
.uuid = &gatt_svr_svc_sec_test_uuid.u,
.characteristics = (struct ble_gatt_chr_def[])
{ {
/*** Characteristic: Random number generator. */
.uuid = &gatt_svr_chr_sec_test_rand_uuid.u,
.access_cb = gatt_svr_chr_access_sec_test,
.flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_READ_ENC,
}, {
/*** Characteristic: Static value. */
.uuid = &gatt_svr_chr_sec_test_static_uuid.u,
.access_cb = gatt_svr_chr_access_sec_test,
.flags = BLE_GATT_CHR_F_READ |
BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_ENC,
}, {
0, /* No more characteristics in this service. */
}
},
},
{
0, /* No more services. */
},
};
static int
gatt_svr_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len,
void *dst, uint16_t *len)
{
uint16_t om_len;
int rc;
om_len = OS_MBUF_PKTLEN(om);
if (om_len < min_len || om_len > max_len) {
return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
}
rc = ble_hs_mbuf_to_flat(om, dst, max_len, len);
if (rc != 0) {
return BLE_ATT_ERR_UNLIKELY;
}
return 0;
}
static int
gatt_svr_chr_access_sec_test(uint16_t conn_handle, uint16_t attr_handle,
struct ble_gatt_access_ctxt *ctxt,
void *arg)
{
const ble_uuid_t *uuid;
int rand_num;
int rc;
uuid = ctxt->chr->uuid;
/* Determine which characteristic is being accessed by examining its
* 128-bit UUID.
*/
if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_rand_uuid.u) == 0) {
assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR);
/* Respond with a 32-bit random number. */
rand_num = rand();
rc = os_mbuf_append(ctxt->om, &rand_num, sizeof rand_num);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
}
if (ble_uuid_cmp(uuid, &gatt_svr_chr_sec_test_static_uuid.u) == 0) {
switch (ctxt->op) {
case BLE_GATT_ACCESS_OP_READ_CHR:
rc = os_mbuf_append(ctxt->om, &gatt_svr_sec_test_static_val,
sizeof gatt_svr_sec_test_static_val);
return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
case BLE_GATT_ACCESS_OP_WRITE_CHR:
rc = gatt_svr_chr_write(ctxt->om,
sizeof gatt_svr_sec_test_static_val,
sizeof gatt_svr_sec_test_static_val,
&gatt_svr_sec_test_static_val, NULL);
return rc;
default:
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
}
/* Unknown characteristic; the nimble stack should not have called this
* function.
*/
assert(0);
return BLE_ATT_ERR_UNLIKELY;
}
void
gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
char buf[BLE_UUID_STR_LEN];
switch (ctxt->op) {
case BLE_GATT_REGISTER_OP_SVC:
MODLOG_DFLT(DEBUG, "registered service %s with handle=%d\n",
ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf),
ctxt->svc.handle);
break;
case BLE_GATT_REGISTER_OP_CHR:
MODLOG_DFLT(DEBUG, "registering characteristic %s with "
"def_handle=%d val_handle=%d\n",
ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf),
ctxt->chr.def_handle,
ctxt->chr.val_handle);
break;
case BLE_GATT_REGISTER_OP_DSC:
MODLOG_DFLT(DEBUG, "registering descriptor %s with handle=%d\n",
ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf),
ctxt->dsc.handle);
break;
default:
assert(0);
break;
}
}
int
gatt_svr_init(void)
{
int rc;
ble_svc_gap_init();
ble_svc_gatt_init();
rc = ble_gatts_count_cfg(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
rc = ble_gatts_add_svcs(gatt_svr_svcs);
if (rc != 0) {
return rc;
}
return 0;
}

View File

@ -0,0 +1,388 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "nvs_flash.h"
/* BLE */
#include "esp_nimble_hci.h"
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"
#include "host/ble_hs.h"
#include "host/util/util.h"
#include "console/console.h"
#include "services/gap/ble_svc_gap.h"
#include "bleprph.h"
static const char *tag = "NimBLE_BLE_PRPH";
static int bleprph_gap_event(struct ble_gap_event *event, void *arg);
static uint8_t own_addr_type;
void ble_store_config_init(void);
/**
* Logs information about a connection to the console.
*/
static void
bleprph_print_conn_desc(struct ble_gap_conn_desc *desc)
{
MODLOG_DFLT(INFO, "handle=%d our_ota_addr_type=%d our_ota_addr=",
desc->conn_handle, desc->our_ota_addr.type);
print_addr(desc->our_ota_addr.val);
MODLOG_DFLT(INFO, " our_id_addr_type=%d our_id_addr=",
desc->our_id_addr.type);
print_addr(desc->our_id_addr.val);
MODLOG_DFLT(INFO, " peer_ota_addr_type=%d peer_ota_addr=",
desc->peer_ota_addr.type);
print_addr(desc->peer_ota_addr.val);
MODLOG_DFLT(INFO, " peer_id_addr_type=%d peer_id_addr=",
desc->peer_id_addr.type);
print_addr(desc->peer_id_addr.val);
MODLOG_DFLT(INFO, " conn_itvl=%d conn_latency=%d supervision_timeout=%d "
"encrypted=%d authenticated=%d bonded=%d\n",
desc->conn_itvl, desc->conn_latency,
desc->supervision_timeout,
desc->sec_state.encrypted,
desc->sec_state.authenticated,
desc->sec_state.bonded);
}
/**
* Enables advertising with the following parameters:
* o General discoverable mode.
* o Undirected connectable mode.
*/
static void
bleprph_advertise(void)
{
struct ble_gap_adv_params adv_params;
struct ble_hs_adv_fields fields;
const char *name;
int rc;
/**
* Set the advertisement data included in our advertisements:
* o Flags (indicates advertisement type and other general info).
* o Advertising tx power.
* o Device name.
* o 16-bit service UUIDs (alert notifications).
*/
memset(&fields, 0, sizeof fields);
/* Advertise two flags:
* o Discoverability in forthcoming advertisement (general)
* o BLE-only (BR/EDR unsupported).
*/
fields.flags = BLE_HS_ADV_F_DISC_GEN |
BLE_HS_ADV_F_BREDR_UNSUP;
/* Indicate that the TX power level field should be included; have the
* stack fill this value automatically. This is done by assigning the
* special value BLE_HS_ADV_TX_PWR_LVL_AUTO.
*/
fields.tx_pwr_lvl_is_present = 1;
fields.tx_pwr_lvl = BLE_HS_ADV_TX_PWR_LVL_AUTO;
name = ble_svc_gap_device_name();
fields.name = (uint8_t *)name;
fields.name_len = strlen(name);
fields.name_is_complete = 1;
fields.uuids16 = (ble_uuid16_t[]) {
BLE_UUID16_INIT(GATT_SVR_SVC_ALERT_UUID)
};
fields.num_uuids16 = 1;
fields.uuids16_is_complete = 1;
rc = ble_gap_adv_set_fields(&fields);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error setting advertisement data; rc=%d\n", rc);
return;
}
/* Begin advertising. */
memset(&adv_params, 0, sizeof adv_params);
adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER,
&adv_params, bleprph_gap_event, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error enabling advertisement; rc=%d\n", rc);
return;
}
}
/**
* The nimble host executes this callback when a GAP event occurs. The
* application associates a GAP event callback with each connection that forms.
* bleprph uses the same callback for all connections.
*
* @param event The type of event being signalled.
* @param ctxt Various information pertaining to the event.
* @param arg Application-specified argument; unused by
* bleprph.
*
* @return 0 if the application successfully handled the
* event; nonzero on failure. The semantics
* of the return code is specific to the
* particular GAP event being signalled.
*/
static int
bleprph_gap_event(struct ble_gap_event *event, void *arg)
{
struct ble_gap_conn_desc desc;
int rc;
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
/* A new connection was established or a connection attempt failed. */
MODLOG_DFLT(INFO, "connection %s; status=%d ",
event->connect.status == 0 ? "established" : "failed",
event->connect.status);
if (event->connect.status == 0) {
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
bleprph_print_conn_desc(&desc);
}
MODLOG_DFLT(INFO, "\n");
if (event->connect.status != 0) {
/* Connection failed; resume advertising. */
bleprph_advertise();
}
return 0;
case BLE_GAP_EVENT_DISCONNECT:
MODLOG_DFLT(INFO, "disconnect; reason=%d ", event->disconnect.reason);
bleprph_print_conn_desc(&event->disconnect.conn);
MODLOG_DFLT(INFO, "\n");
/* Connection terminated; resume advertising. */
bleprph_advertise();
return 0;
case BLE_GAP_EVENT_CONN_UPDATE:
/* The central has updated the connection parameters. */
MODLOG_DFLT(INFO, "connection updated; status=%d ",
event->conn_update.status);
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
bleprph_print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
return 0;
case BLE_GAP_EVENT_ADV_COMPLETE:
MODLOG_DFLT(INFO, "advertise complete; reason=%d",
event->adv_complete.reason);
bleprph_advertise();
return 0;
case BLE_GAP_EVENT_ENC_CHANGE:
/* Encryption has been enabled or disabled for this connection. */
MODLOG_DFLT(INFO, "encryption change event; status=%d ",
event->enc_change.status);
rc = ble_gap_conn_find(event->connect.conn_handle, &desc);
assert(rc == 0);
bleprph_print_conn_desc(&desc);
MODLOG_DFLT(INFO, "\n");
return 0;
case BLE_GAP_EVENT_SUBSCRIBE:
MODLOG_DFLT(INFO, "subscribe event; conn_handle=%d attr_handle=%d "
"reason=%d prevn=%d curn=%d previ=%d curi=%d\n",
event->subscribe.conn_handle,
event->subscribe.attr_handle,
event->subscribe.reason,
event->subscribe.prev_notify,
event->subscribe.cur_notify,
event->subscribe.prev_indicate,
event->subscribe.cur_indicate);
return 0;
case BLE_GAP_EVENT_MTU:
MODLOG_DFLT(INFO, "mtu update event; conn_handle=%d cid=%d mtu=%d\n",
event->mtu.conn_handle,
event->mtu.channel_id,
event->mtu.value);
return 0;
case BLE_GAP_EVENT_REPEAT_PAIRING:
/* We already have a bond with the peer, but it is attempting to
* establish a new secure link. This app sacrifices security for
* convenience: just throw away the old bond and accept the new link.
*/
/* Delete the old bond. */
rc = ble_gap_conn_find(event->repeat_pairing.conn_handle, &desc);
assert(rc == 0);
ble_store_util_delete_peer(&desc.peer_id_addr);
/* Return BLE_GAP_REPEAT_PAIRING_RETRY to indicate that the host should
* continue with the pairing operation.
*/
return BLE_GAP_REPEAT_PAIRING_RETRY;
case BLE_GAP_EVENT_PASSKEY_ACTION:
ESP_LOGI(tag, "PASSKEY_ACTION_EVENT started \n");
struct ble_sm_io pkey = {0};
int key = 0;
if (event->passkey.params.action == BLE_SM_IOACT_DISP) {
pkey.action = event->passkey.params.action;
pkey.passkey = 123456; // This is the passkey to be entered on peer
ESP_LOGI(tag, "Enter passkey %d on the peer side", pkey.passkey);
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_NUMCMP) {
ESP_LOGI(tag, "Passkey on device's display: %d", event->passkey.params.numcmp);
ESP_LOGI(tag, "Accept or reject the passkey through console in this format -> key Y or key N");
pkey.action = event->passkey.params.action;
if (scli_receive_key(&key)) {
pkey.numcmp_accept = key;
} else {
pkey.numcmp_accept = 0;
ESP_LOGE(tag, "Timeout! Rejecting the key");
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_OOB) {
static uint8_t tem_oob[16] = {0};
pkey.action = event->passkey.params.action;
for (int i = 0; i < 16; i++) {
pkey.oob[i] = tem_oob[i];
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc);
} else if (event->passkey.params.action == BLE_SM_IOACT_INPUT) {
ESP_LOGI(tag, "Enter the passkey through console in this format-> key 123456");
pkey.action = event->passkey.params.action;
if (scli_receive_key(&key)) {
pkey.passkey = key;
} else {
pkey.passkey = 0;
ESP_LOGE(tag, "Timeout! Passing 0 as the key");
}
rc = ble_sm_inject_io(event->passkey.conn_handle, &pkey);
ESP_LOGI(tag, "ble_sm_inject_io result: %d\n", rc);
}
return 0;
}
return 0;
}
static void
bleprph_on_reset(int reason)
{
MODLOG_DFLT(ERROR, "Resetting state; reason=%d\n", reason);
}
static void
bleprph_on_sync(void)
{
int rc;
rc = ble_hs_util_ensure_addr(0);
assert(rc == 0);
/* Figure out address to use while advertising (no privacy for now) */
rc = ble_hs_id_infer_auto(0, &own_addr_type);
if (rc != 0) {
MODLOG_DFLT(ERROR, "error determining address type; rc=%d\n", rc);
return;
}
/* Printing ADDR */
uint8_t addr_val[6] = {0};
rc = ble_hs_id_copy_addr(own_addr_type, addr_val, NULL);
MODLOG_DFLT(INFO, "Device Address: ");
print_addr(addr_val);
MODLOG_DFLT(INFO, "\n");
/* Begin advertising. */
bleprph_advertise();
}
void bleprph_host_task(void *param)
{
ESP_LOGI(tag, "BLE Host Task Started");
/* This function will return only when nimble_port_stop() is executed */
nimble_port_run();
nimble_port_freertos_deinit();
}
void
app_main(void)
{
int rc;
/* Initialize NVS — it is used to store PHY calibration data */
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_nimble_hci_and_controller_init());
nimble_port_init();
/* Initialize the NimBLE host configuration. */
ble_hs_cfg.reset_cb = bleprph_on_reset;
ble_hs_cfg.sync_cb = bleprph_on_sync;
ble_hs_cfg.gatts_register_cb = gatt_svr_register_cb;
ble_hs_cfg.store_status_cb = ble_store_util_status_rr;
ble_hs_cfg.sm_io_cap = CONFIG_EXAMPLE_IO_TYPE;
#ifdef CONFIG_EXAMPLE_BONDING
ble_hs_cfg.sm_bonding = 1;
#endif
#ifdef CONFIG_EXAMPLE_MITM
ble_hs_cfg.sm_mitm = 1;
#endif
#ifdef CONFIG_EXAMPLE_USE_SC
ble_hs_cfg.sm_sc = 1;
#else
ble_hs_cfg.sm_sc = 0;
#ifdef CONFIG_EXAMPLE_BONDING
ble_hs_cfg.sm_our_key_dist = 1;
ble_hs_cfg.sm_their_key_dist = 1;
#endif
#endif
rc = gatt_svr_init();
assert(rc == 0);
/* Set the default device name. */
rc = ble_svc_gap_device_name_set("nimble-bleprph");
assert(rc == 0);
/* XXX Need to have template for store */
ble_store_config_init();
nimble_port_freertos_init(bleprph_host_task);
/* Initialize command line interface to accept input from user */
rc = scli_init();
if (rc != ESP_OK) {
ESP_LOGE(tag, "scli_init() failed");
}
}

View File

@ -0,0 +1,43 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "bleprph.h"
/**
* Utility function to log an array of bytes.
*/
void
print_bytes(const uint8_t *bytes, int len)
{
int i;
for (i = 0; i < len; i++) {
MODLOG_DFLT(INFO, "%s0x%02x", i != 0 ? ":" : "", bytes[i]);
}
}
void
print_addr(const void *addr)
{
const uint8_t *u8p;
u8p = addr;
MODLOG_DFLT(INFO, "%02x:%02x:%02x:%02x:%02x:%02x",
u8p[5], u8p[4], u8p[3], u8p[2], u8p[1], u8p[0]);
}

View File

@ -0,0 +1,161 @@
/*
* Copyright 2019 Espressif Systems (Shanghai) PTE LTD
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 <ctype.h>
#include "esp_log.h"
#include <string.h>
#include <esp_log.h>
#include <esp_console.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/queue.h>
#include <driver/uart.h>
#include "bleprph.h"
#define BLE_RX_TIMEOUT (30000 / portTICK_PERIOD_MS)
static TaskHandle_t cli_task;
static QueueHandle_t cli_handle;
static int stop;
static int enter_passkey_handler(int argc, char *argv[])
{
int key;
char pkey[8];
int num;
if (argc != 2) {
return -1;
}
sscanf(argv[1], "%s", pkey);
ESP_LOGI("You entered", "%s %s", argv[0], argv[1]);
num = pkey[0];
if (isalpha(num)) {
if ((strcasecmp(pkey, "Y") == 0) || (strcasecmp(pkey, "Yes") == 0)) {
key = 1;
xQueueSend(cli_handle, &key, 0);
} else {
key = 0;
xQueueSend(cli_handle, &key, 0);
}
} else {
sscanf(pkey, "%d", &key);
xQueueSend(cli_handle, &key, 0);
}
return 0;
}
int scli_receive_key(int *console_key)
{
return xQueueReceive(cli_handle, console_key, BLE_RX_TIMEOUT);
}
static esp_console_cmd_t cmds[] = {
{
.command = "key",
.help = "",
.func = enter_passkey_handler,
},
};
static int ble_register_cli(void)
{
int cmds_num = sizeof(cmds) / sizeof(esp_console_cmd_t);
int i;
for (i = 0; i < cmds_num; i++) {
esp_console_cmd_register(&cmds[i]);
}
return 0;
}
static void scli_task(void *arg)
{
int uart_num = (int) arg;
uint8_t linebuf[256];
int i, cmd_ret;
esp_err_t ret;
QueueHandle_t uart_queue;
uart_event_t event;
uart_driver_install(uart_num, 256, 0, 8, &uart_queue, 0);
/* Initialize the console */
esp_console_config_t console_config = {
.max_cmdline_args = 8,
.max_cmdline_length = 256,
};
esp_console_init(&console_config);
while (!stop) {
i = 0;
memset(linebuf, 0, sizeof(linebuf));
do {
ret = xQueueReceive(uart_queue, (void * )&event, (portTickType)portMAX_DELAY);
if (ret != pdPASS) {
if (stop == 1) {
break;
} else {
continue;
}
}
if (event.type == UART_DATA) {
while (uart_read_bytes(uart_num, (uint8_t *) &linebuf[i], 1, 0)) {
if (linebuf[i] == '\r') {
uart_write_bytes(uart_num, "\r\n", 2);
} else {
uart_write_bytes(uart_num, (char *) &linebuf[i], 1);
}
i++;
}
}
} while ((i < 255) && linebuf[i - 1] != '\r');
if (stop) {
break;
}
/* Remove the truncating \r\n */
linebuf[strlen((char *)linebuf) - 1] = '\0';
ret = esp_console_run((char *) linebuf, &cmd_ret);
if (ret < 0) {
break;
}
}
vTaskDelete(NULL);
}
int scli_init(void)
{
/* Register CLI "key <value>" to accept input from user during pairing */
ble_register_cli();
xTaskCreate(scli_task, "scli_cli", 4096, (void *) 0, 3, &cli_task);
if (cli_task == NULL) {
return ESP_FAIL;
}
cli_handle = xQueueCreate( 1, sizeof(int) );
if (cli_handle == NULL) {
return ESP_FAIL;
}
return ESP_OK;
}

View File

@ -0,0 +1,12 @@
# 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=
CONFIG_BTDM_CTRL_MODE_BTDM=
CONFIG_BLUEDROID_ENABLED=
CONFIG_NIMBLE_ENABLED=y

834
tools/ble/lib_ble_client.py Normal file
View File

@ -0,0 +1,834 @@
#!/usr/bin/env python
#
# Copyright 2019 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.
#
# DBus-Bluez BLE library
from __future__ import print_function
import sys
import time
import traceback
try:
from future.moves.itertools import zip_longest
import dbus
import dbus.mainloop.glib
from gi.repository import GLib
except ImportError as e:
if 'linux' not in sys.platform:
sys.exit("Error: Only supported on Linux platform")
print(e)
print("Install packages `libgirepository1.0-dev gir1.2-gtk-3.0 libcairo2-dev libdbus-1-dev libdbus-glib-1-dev` for resolving the issue")
print("Run `pip install -r $IDF_PATH/tools/ble/requirements.txt` for resolving the issue")
raise
import lib_gatt
import lib_gap
srv_added_old_cnt = 0
srv_added_new_cnt = 0
verify_signal_check = 0
blecent_retry_check_cnt = 0
verify_service_cnt = 0
verify_readchars_cnt = 0
blecent_adv_uuid = '1811'
gatt_app_obj_check = False
gatt_app_reg_check = False
adv_data_check = False
adv_reg_check = False
read_req_check = False
write_req_check = False
subscribe_req_check = False
ble_hr_chrc = False
DISCOVERY_START = False
SIGNAL_CAUGHT = False
TEST_CHECKS_PASS = False
ADV_STOP = False
SERVICES_RESOLVED = False
SERVICE_UUID_FOUND = False
BLUEZ_SERVICE_NAME = 'org.bluez'
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
ADAPTER_IFACE = 'org.bluez.Adapter1'
DEVICE_IFACE = 'org.bluez.Device1'
GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
LE_ADVERTISING_MANAGER_IFACE = 'org.bluez.LEAdvertisingManager1'
GATT_SERVICE_IFACE = 'org.bluez.GattService1'
GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1'
ADAPTER_ON = False
DEVICE_CONNECTED = False
GATT_APP_REGISTERED = False
ADV_REGISTERED = False
ADV_ACTIVE_INSTANCE = False
CHRC_VALUE_CNT = False
# Set up the main loop.
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
dbus.mainloop.glib.threads_init()
# Set up the event main loop.
event_loop = GLib.MainLoop()
def verify_signal_is_caught():
global verify_signal_check
verify_signal_check += 1
if (not SIGNAL_CAUGHT and verify_signal_check == 15) or (SIGNAL_CAUGHT):
if event_loop.is_running():
event_loop.quit()
return False # polling for checks will stop
return True # polling will continue
def set_props_status(props):
"""
Set Adapter status if it is powered on or off
"""
global ADAPTER_ON, SERVICES_RESOLVED, GATT_OBJ_REMOVED, GATT_APP_REGISTERED, \
ADV_REGISTERED, ADV_ACTIVE_INSTANCE, DEVICE_CONNECTED, CHRC_VALUE, CHRC_VALUE_CNT, \
SIGNAL_CAUGHT
is_service_uuid = False
# Signal caught for change in Adapter Powered property
if 'Powered' in props:
if props['Powered'] == 1:
SIGNAL_CAUGHT = True
ADAPTER_ON = True
else:
SIGNAL_CAUGHT = True
ADAPTER_ON = False
if 'ServicesResolved' in props:
if props['ServicesResolved'] == 1:
SIGNAL_CAUGHT = True
SERVICES_RESOLVED = True
else:
SIGNAL_CAUGHT = True
SERVICES_RESOLVED = False
if 'UUIDs' in props:
# Signal caught for add/remove GATT data having service uuid
for uuid in props['UUIDs']:
if blecent_adv_uuid in uuid:
is_service_uuid = True
if not is_service_uuid:
# Signal caught for removing GATT data having service uuid
# and for unregistering GATT application
GATT_APP_REGISTERED = False
lib_gatt.GATT_APP_OBJ = False
if 'ActiveInstances' in props:
# Signal caught for Advertising - add/remove Instances property
if props['ActiveInstances'] == 1:
ADV_ACTIVE_INSTANCE = True
elif props['ActiveInstances'] == 0:
ADV_ACTIVE_INSTANCE = False
ADV_REGISTERED = False
lib_gap.ADV_OBJ = False
if 'Connected' in props:
# Signal caught for device connect/disconnect
if props['Connected'] == 1:
SIGNAL_CAUGHT = True
DEVICE_CONNECTED = True
else:
SIGNAL_CAUGHT = True
DEVICE_CONNECTED = False
if 'Value' in props:
# Signal caught for change in chars value
if ble_hr_chrc:
CHRC_VALUE_CNT += 1
print(props['Value'])
if CHRC_VALUE_CNT == 10:
SIGNAL_CAUGHT = True
return False
return False
def props_change_handler(iface, changed_props, invalidated):
"""
PropertiesChanged Signal handler.
Catch and print information about PropertiesChanged signal.
"""
if iface == ADAPTER_IFACE:
set_props_status(changed_props)
if iface == LE_ADVERTISING_MANAGER_IFACE:
set_props_status(changed_props)
if iface == DEVICE_IFACE:
set_props_status(changed_props)
if iface == GATT_CHRC_IFACE:
set_props_status(changed_props)
class BLE_Bluez_Client:
def __init__(self, iface, devname=None, devaddr=None):
self.bus = None
self.device = None
self.devname = devname
self.devaddr = devaddr
self.iface = iface
self.ble_objs = None
self.props_iface_obj = None
self.adapter_path = []
self.adapter = None
self.services = []
self.srv_uuid = []
self.chars = {}
self.char_uuid = []
try:
self.bus = dbus.SystemBus()
om_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE)
self.ble_objs = om_iface_obj.GetManagedObjects()
except Exception as e:
print(e)
def __del__(self):
try:
print("Test Exit")
except Exception as e:
print(e)
sys.exit(1)
def set_adapter(self):
'''
Discover Bluetooth Adapter
Power On Bluetooth Adapter
'''
global verify_signal_check, SIGNAL_CAUGHT, ADAPTER_ON
verify_signal_check = 0
ADAPTER_ON = False
try:
print("discovering adapter...")
for path, interfaces in self.ble_objs.items():
adapter = interfaces.get(ADAPTER_IFACE)
if adapter is not None:
if path.endswith(self.iface):
self.adapter = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, path), ADAPTER_IFACE)
# Create Properties Interface object only after adapter is found
self.props_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, path), DBUS_PROP_IFACE)
self.adapter_path = [path, interfaces]
# Check adapter status - power on/off
set_props_status(interfaces[ADAPTER_IFACE])
break
if self.adapter is None:
raise Exception("Bluetooth adapter not found")
if self.props_iface_obj is None:
raise Exception("Properties interface not found")
print("bluetooth adapter discovered")
# Check if adapter is already powered on
if ADAPTER_ON:
print("Adapter already powered on")
return True
# Power On Adapter
print("powering on adapter...")
self.props_iface_obj.connect_to_signal('PropertiesChanged', props_change_handler)
self.props_iface_obj.Set(ADAPTER_IFACE, "Powered", dbus.Boolean(1))
SIGNAL_CAUGHT = False
GLib.timeout_add_seconds(5, verify_signal_is_caught)
event_loop.run()
if ADAPTER_ON:
print("bluetooth adapter powered on")
return True
else:
raise Exception("Failure: bluetooth adapter not powered on")
except Exception:
print(traceback.format_exc())
return False
def connect(self):
'''
Connect to the device discovered
Retry 10 times to discover and connect to device
'''
global DISCOVERY_START, SIGNAL_CAUGHT, DEVICE_CONNECTED, verify_signal_check
device_found = False
DEVICE_CONNECTED = False
try:
self.adapter.StartDiscovery()
print("\nStarted Discovery")
DISCOVERY_START = True
for retry_cnt in range(10,0,-1):
verify_signal_check = 0
try:
if self.device is None:
print("\nConnecting to device...")
# Wait for device to be discovered
time.sleep(5)
device_found = self.get_device()
if device_found:
self.device.Connect(dbus_interface=DEVICE_IFACE)
time.sleep(15)
SIGNAL_CAUGHT = False
GLib.timeout_add_seconds(5, verify_signal_is_caught)
event_loop.run()
if DEVICE_CONNECTED:
print("\nConnected to device")
return True
else:
raise Exception
except Exception as e:
print(e)
print("\nRetries left", retry_cnt - 1)
continue
# Device not found
return False
except Exception:
print(traceback.format_exc())
self.device = None
return False
def get_device(self):
'''
Discover device based on device name
and device address and connect
'''
dev_path = None
om_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE)
self.ble_objs = om_iface_obj.GetManagedObjects()
for path, interfaces in self.ble_objs.items():
if DEVICE_IFACE not in interfaces.keys():
continue
device_addr_iface = (path.replace('_', ':')).lower()
dev_addr = self.devaddr.lower()
if dev_addr in device_addr_iface and \
interfaces[DEVICE_IFACE].get("Name") == self.devname:
dev_path = path
break
if dev_path is None:
raise Exception("\nBLE device not found")
device_props_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, dev_path), DBUS_PROP_IFACE)
device_props_iface_obj.connect_to_signal('PropertiesChanged', props_change_handler)
self.device = self.bus.get_object(BLUEZ_SERVICE_NAME, dev_path)
return True
def srvc_iface_added_handler(self, path, interfaces):
'''
Add services found as lib_ble_client obj
'''
if self.device and path.startswith(self.device.object_path):
if GATT_SERVICE_IFACE in interfaces.keys():
service = self.bus.get_object(BLUEZ_SERVICE_NAME, path)
uuid = service.Get(GATT_SERVICE_IFACE, 'UUID', dbus_interface=DBUS_PROP_IFACE)
if uuid not in self.srv_uuid:
self.srv_uuid.append(uuid)
if path not in self.services:
self.services.append(path)
def verify_service_uuid_found(self, service_uuid):
'''
Verify service uuid found
'''
global SERVICE_UUID_FOUND
srv_uuid_found = [uuid for uuid in self.srv_uuid if service_uuid in uuid]
if srv_uuid_found:
SERVICE_UUID_FOUND = True
def get_services(self, service_uuid=None):
'''
Retrieve Services found in the device connected
'''
global SIGNAL_CAUGHT, SERVICE_UUID_FOUND, SERVICES_RESOLVED, verify_signal_check
verify_signal_check = 0
SERVICE_UUID_FOUND = False
SERVICES_RESOLVED = False
SIGNAL_CAUGHT = False
try:
om_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE)
self.ble_objs = om_iface_obj.GetManagedObjects()
for path, interfaces in self.ble_objs.items():
self.srvc_iface_added_handler(path, interfaces)
# If services not found, then they may not have been added yet on dbus
if not self.services:
GLib.timeout_add_seconds(5, verify_signal_is_caught)
om_iface_obj.connect_to_signal('InterfacesAdded', self.srvc_iface_added_handler)
event_loop.run()
if not SERVICES_RESOLVED:
raise Exception("Services not found...")
if service_uuid:
self.verify_service_uuid_found(service_uuid)
if not SERVICE_UUID_FOUND:
raise Exception("Service with uuid: %s not found..." % service_uuid)
# Services found
return self.srv_uuid
except Exception:
print(traceback.format_exc())
return False
def chrc_iface_added_handler(self, path, interfaces):
'''
Add services found as lib_ble_client obj
'''
global chrc, chrc_discovered, SIGNAL_CAUGHT
chrc_val = None
if self.device and path.startswith(self.device.object_path):
if GATT_CHRC_IFACE in interfaces.keys():
chrc = self.bus.get_object(BLUEZ_SERVICE_NAME, path)
chrc_props = chrc.GetAll(GATT_CHRC_IFACE,
dbus_interface=DBUS_PROP_IFACE)
chrc_flags = chrc_props['Flags']
if 'read' in chrc_flags:
chrc_val = chrc.ReadValue({}, dbus_interface=GATT_CHRC_IFACE)
uuid = chrc_props['UUID']
self.chars[path] = chrc_val, chrc_flags, uuid
SIGNAL_CAUGHT = True
def read_chars(self):
'''
Read characteristics found in the device connected
'''
global iface_added, chrc_discovered, SIGNAL_CAUGHT, verify_signal_check
verify_signal_check = 0
chrc_discovered = False
iface_added = False
SIGNAL_CAUGHT = False
try:
om_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE)
self.ble_objs = om_iface_obj.GetManagedObjects()
for path, interfaces in self.ble_objs.items():
self.chrc_iface_added_handler(path, interfaces)
# If chars not found, then they have not been added yet to interface
time.sleep(15)
if not self.chars:
iface_added = True
GLib.timeout_add_seconds(5, verify_signal_is_caught)
om_iface_obj.connect_to_signal('InterfacesAdded', self.chars_iface_added_handler)
event_loop.run()
return self.chars
except Exception:
print(traceback.format_exc())
return False
def write_chars(self, write_val):
'''
Write characteristics to the device connected
'''
chrc = None
chrc_val = None
char_write_props = False
try:
for path, props in self.chars.items():
if 'write' in props[1]: # check permission
char_write_props = True
chrc = self.bus.get_object(BLUEZ_SERVICE_NAME, path)
chrc.WriteValue(write_val,{},dbus_interface=GATT_CHRC_IFACE)
if 'read' in props[1]:
chrc_val = chrc.ReadValue({}, dbus_interface=GATT_CHRC_IFACE)
else:
print("Warning: Cannot read value. Characteristic does not have read permission.")
if not (ord(write_val) == int(chrc_val[0])):
print("\nWrite Failed")
return False
self.chars[path] = chrc_val, props[1], props[2] # update value
if not char_write_props:
raise Exception("Failure: Cannot perform write operation. Characteristic does not have write permission.")
return self.chars
except Exception:
print(traceback.format_exc())
return False
def hr_update_simulation(self, hr_srv_uuid, hr_char_uuid):
'''
Start Notifications
Retrieve updated value
Stop Notifications
'''
global ble_hr_chrc, verify_signal_check, SIGNAL_CAUGHT, CHRC_VALUE_CNT
srv_path = None
chrc = None
uuid = None
chrc_path = None
chars_ret = None
ble_hr_chrc = True
CHRC_VALUE_CNT = 0
try:
# Get HR Measurement characteristic
services = list(zip_longest(self.srv_uuid, self.services))
for uuid, path in services:
if hr_srv_uuid in uuid:
srv_path = path
break
if srv_path is None:
raise Exception("Failure: HR UUID:", hr_srv_uuid, "not found")
chars_ret = self.read_chars()
for path, props in chars_ret.items():
if path.startswith(srv_path):
chrc = self.bus.get_object(BLUEZ_SERVICE_NAME, path)
chrc_path = path
if hr_char_uuid in props[2]: # uuid
break
if chrc is None:
raise Exception("Failure: Characteristics for service: ", srv_path, "not found")
# Subscribe to notifications
print("\nSubscribe to notifications: On")
chrc.StartNotify(dbus_interface=GATT_CHRC_IFACE)
chrc_props_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, chrc_path), DBUS_PROP_IFACE)
chrc_props_iface_obj.connect_to_signal('PropertiesChanged', props_change_handler)
SIGNAL_CAUGHT = False
verify_signal_check = 0
GLib.timeout_add_seconds(5, verify_signal_is_caught)
event_loop.run()
chrc.StopNotify(dbus_interface=GATT_CHRC_IFACE)
time.sleep(2)
print("\nSubscribe to notifications: Off")
ble_hr_chrc = False
return True
except Exception:
print(traceback.format_exc())
return False
def create_gatt_app(self):
'''
Create GATT data
Register GATT Application
'''
global gatt_app_obj, gatt_manager_iface_obj, GATT_APP_REGISTERED
gatt_app_obj = None
gatt_manager_iface_obj = None
GATT_APP_REGISTERED = False
lib_gatt.GATT_APP_OBJ = False
try:
gatt_app_obj = lib_gatt.Application(self.bus, self.adapter_path[0])
gatt_manager_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME,self.adapter_path[0]), GATT_MANAGER_IFACE)
gatt_manager_iface_obj.RegisterApplication(gatt_app_obj, {},
reply_handler=self.gatt_app_handler,
error_handler=self.gatt_app_error_handler)
return True
except Exception:
print(traceback.format_exc())
return False
def gatt_app_handler(self):
'''
GATT Application Register success handler
'''
global GATT_APP_REGISTERED
GATT_APP_REGISTERED = True
def gatt_app_error_handler(self, error):
'''
GATT Application Register error handler
'''
global GATT_APP_REGISTERED
GATT_APP_REGISTERED = False
def start_advertising(self, adv_host_name, adv_iface_index, adv_type, adv_uuid):
'''
Create Advertising data
Register Advertisement
Start Advertising
'''
global le_adv_obj, le_adv_manager_iface_obj, ADV_ACTIVE_INSTANCE, ADV_REGISTERED
le_adv_obj = None
le_adv_manager_iface_obj = None
le_adv_iface_path = None
ADV_ACTIVE_INSTANCE = False
ADV_REGISTERED = False
lib_gap.ADV_OBJ = False
try:
print("Advertising started")
gatt_app_ret = self.create_gatt_app()
if not gatt_app_ret:
raise Exception
for path,interface in self.ble_objs.items():
if LE_ADVERTISING_MANAGER_IFACE in interface:
le_adv_iface_path = path
if le_adv_iface_path is None:
raise Exception('\n Cannot start advertising. LEAdvertisingManager1 Interface not found')
le_adv_obj = lib_gap.Advertisement(self.bus, adv_iface_index, adv_type, adv_uuid, adv_host_name)
le_adv_manager_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, le_adv_iface_path), LE_ADVERTISING_MANAGER_IFACE)
le_adv_manager_iface_obj.RegisterAdvertisement(le_adv_obj.get_path(), {},
reply_handler=self.adv_handler,
error_handler=self.adv_error_handler)
GLib.timeout_add_seconds(2, self.verify_blecent)
event_loop.run()
if TEST_CHECKS_PASS:
return True
else:
raise Exception
except Exception:
print(traceback.format_exc())
return False
def adv_handler(self):
'''
Advertisement Register success handler
'''
global ADV_REGISTERED
ADV_REGISTERED = True
def adv_error_handler(self, error):
'''
Advertisement Register error handler
'''
global ADV_REGISTERED
ADV_REGISTERED = False
def verify_blecent(self):
"""
Verify blecent test commands are successful
"""
global blecent_retry_check_cnt, gatt_app_obj_check, gatt_app_reg_check,\
adv_data_check, adv_reg_check, read_req_check, write_req_check,\
subscribe_req_check, TEST_CHECKS_PASS
# Get device when connected
if not self.device:
om_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, "/"), DBUS_OM_IFACE)
self.ble_objs = om_iface_obj.GetManagedObjects()
for path, interfaces in self.ble_objs.items():
if DEVICE_IFACE not in interfaces.keys():
continue
device_props_iface_obj = dbus.Interface(self.bus.get_object(BLUEZ_SERVICE_NAME, path), DBUS_PROP_IFACE)
device_props_iface_obj.connect_to_signal('PropertiesChanged', props_change_handler)
self.device = self.bus.get_object(BLUEZ_SERVICE_NAME, path)
# Check for failures after 10 retries
if blecent_retry_check_cnt == 10:
# check for failures
if not gatt_app_obj_check:
print("Failure: GATT Data could not be created")
if not gatt_app_reg_check:
print("Failure: GATT Application could not be registered")
if not adv_data_check:
print("Failure: Advertising data could not be created")
if not adv_reg_check:
print("Failure: Advertisement could not be registered")
if not read_req_check:
print("Failure: Read Request not received")
if not write_req_check:
print("Failure: Write Request not received")
if not subscribe_req_check:
print("Failure: Subscribe Request not received")
# Blecent Test failed
TEST_CHECKS_PASS = False
if subscribe_req_check:
lib_gatt.alert_status_char_obj.StopNotify()
else:
# Check for success
if not gatt_app_obj_check and lib_gatt.GATT_APP_OBJ:
print("GATT Data created")
gatt_app_obj_check = True
if not gatt_app_reg_check and GATT_APP_REGISTERED:
print("GATT Application registered")
gatt_app_reg_check = True
if not adv_data_check and lib_gap.ADV_OBJ:
print("Advertising data created")
adv_data_check = True
if not adv_reg_check and ADV_REGISTERED and ADV_ACTIVE_INSTANCE:
print("Advertisement registered")
adv_reg_check = True
if not read_req_check and lib_gatt.CHAR_READ:
read_req_check = True
if not write_req_check and lib_gatt.CHAR_WRITE:
write_req_check = True
if not subscribe_req_check and lib_gatt.CHAR_SUBSCRIBE:
subscribe_req_check = True
if read_req_check and write_req_check and subscribe_req_check:
# all checks passed
# Blecent Test passed
TEST_CHECKS_PASS = True
lib_gatt.alert_status_char_obj.StopNotify()
if (blecent_retry_check_cnt == 10 or TEST_CHECKS_PASS):
if event_loop.is_running():
event_loop.quit()
return False # polling for checks will stop
# Increment retry count
blecent_retry_check_cnt += 1
# Default return True - polling for checks will continue
return True
def verify_blecent_disconnect(self):
"""
Verify cleanup is successful
"""
global blecent_retry_check_cnt, gatt_app_obj_check, gatt_app_reg_check,\
adv_data_check, adv_reg_check, ADV_STOP
if blecent_retry_check_cnt == 0:
gatt_app_obj_check = False
gatt_app_reg_check = False
adv_data_check = False
adv_reg_check = False
# Check for failures after 10 retries
if blecent_retry_check_cnt == 10:
# check for failures
if not gatt_app_obj_check:
print("Warning: GATT Data could not be removed")
if not gatt_app_reg_check:
print("Warning: GATT Application could not be unregistered")
if not adv_data_check:
print("Warning: Advertising data could not be removed")
if not adv_reg_check:
print("Warning: Advertisement could not be unregistered")
# Blecent Test failed
ADV_STOP = False
else:
# Check for success
if not gatt_app_obj_check and not lib_gatt.GATT_APP_OBJ:
print("GATT Data removed")
gatt_app_obj_check = True
if not gatt_app_reg_check and not GATT_APP_REGISTERED:
print("GATT Application unregistered")
gatt_app_reg_check = True
if not adv_data_check and not adv_reg_check and not (ADV_REGISTERED or ADV_ACTIVE_INSTANCE or lib_gap.ADV_OBJ):
print("Advertising data removed")
print("Advertisement unregistered")
adv_data_check = True
adv_reg_check = True
# all checks passed
ADV_STOP = True
if (blecent_retry_check_cnt == 10 or ADV_STOP):
if event_loop.is_running():
event_loop.quit()
return False # polling for checks will stop
# Increment retry count
blecent_retry_check_cnt += 1
# Default return True - polling for checks will continue
return True
def disconnect(self):
'''
Before application exits:
Advertisement is unregistered
Advertisement object created is removed from dbus
GATT Application is unregistered
GATT Application object created is removed from dbus
Disconnect device if connected
Adapter is powered off
'''
try:
global blecent_retry_check_cnt, DISCOVERY_START, verify_signal_check, SIGNAL_CAUGHT
blecent_retry_check_cnt = 0
verify_signal_check = 0
print("\nexiting from test...")
self.props_iface_obj.connect_to_signal('PropertiesChanged', props_change_handler)
if ADV_REGISTERED:
# Unregister Advertisement
le_adv_manager_iface_obj.UnregisterAdvertisement(le_adv_obj.get_path())
# Remove Advertising data
dbus.service.Object.remove_from_connection(le_adv_obj)
if GATT_APP_REGISTERED:
# Unregister GATT Application
gatt_manager_iface_obj.UnregisterApplication(gatt_app_obj.get_path())
# Remove GATT data
dbus.service.Object.remove_from_connection(gatt_app_obj)
GLib.timeout_add_seconds(5, self.verify_blecent_disconnect)
event_loop.run()
if ADV_STOP:
print("Stop Advertising status: ", ADV_STOP)
else:
print("Warning: Stop Advertising status: ", ADV_STOP)
# Disconnect device
if self.device:
print("disconnecting device...")
self.device.Disconnect(dbus_interface=DEVICE_IFACE)
if self.adapter:
self.adapter.RemoveDevice(self.device)
self.device = None
if DISCOVERY_START:
self.adapter.StopDiscovery()
DISCOVERY_START = False
time.sleep(15)
SIGNAL_CAUGHT = False
GLib.timeout_add_seconds(5, verify_signal_is_caught)
event_loop.run()
if not DEVICE_CONNECTED:
print("device disconnected")
else:
print("Warning: device could not be disconnected")
except Exception:
print(traceback.format_exc())
return False

87
tools/ble/lib_gap.py Normal file
View File

@ -0,0 +1,87 @@
#!/usr/bin/env python
#
# Copyright 2019 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.
#
# Register Advertisement
from __future__ import print_function
import sys
try:
import dbus
import dbus.service
except ImportError as e:
if 'linux' not in sys.platform:
sys.exit("Error: Only supported on Linux platform")
print(e)
print("Install packages `libgirepository1.0-dev gir1.2-gtk-3.0 libcairo2-dev libdbus-1-dev libdbus-glib-1-dev` for resolving the issue")
print("Run `pip install -r $IDF_PATH/tools/ble/requirements.txt` for resolving the issue")
raise
ADV_OBJ = False
DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
LE_ADVERTISEMENT_IFACE = 'org.bluez.LEAdvertisement1'
class InvalidArgsException(dbus.exceptions.DBusException):
_dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
class Advertisement(dbus.service.Object):
PATH_BASE = '/org/bluez/hci0/advertisement'
def __init__(self, bus, index, advertising_type, uuid, name):
self.path = self.PATH_BASE + str(index)
self.bus = bus
self.ad_type = advertising_type
self.service_uuids = [uuid]
self.local_name = dbus.String(name)
dbus.service.Object.__init__(self, bus, self.path)
def __del__(self):
pass
def get_properties(self):
properties = dict()
properties['Type'] = self.ad_type
if self.service_uuids is not None:
properties['ServiceUUIDs'] = dbus.Array(self.service_uuids,
signature='s')
if self.local_name is not None:
properties['LocalName'] = dbus.String(self.local_name)
return {LE_ADVERTISEMENT_IFACE: properties}
def get_path(self):
return dbus.ObjectPath(self.path)
@dbus.service.method(DBUS_PROP_IFACE,
in_signature='s',
out_signature='a{sv}')
def GetAll(self, interface):
global ADV_OBJ
if interface != LE_ADVERTISEMENT_IFACE:
raise InvalidArgsException()
ADV_OBJ = True
return self.get_properties()[LE_ADVERTISEMENT_IFACE]
@dbus.service.method(LE_ADVERTISEMENT_IFACE,
in_signature='',
out_signature='')
def Release(self):
pass

412
tools/ble/lib_gatt.py Normal file
View File

@ -0,0 +1,412 @@
#!/usr/bin/env python
#
# Copyright 2019 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.
#
# Creating GATT Application which then becomes available to remote devices.
from __future__ import print_function
import sys
try:
import dbus
import dbus.service
except ImportError as e:
if 'linux' not in sys.platform:
sys.exit("Error: Only supported on Linux platform")
print(e)
print("Install packages `libgirepository1.0-dev gir1.2-gtk-3.0 libcairo2-dev libdbus-1-dev libdbus-glib-1-dev` for resolving the issue")
print("Run `pip install -r $IDF_PATH/tools/ble/requirements.txt` for resolving the issue")
raise
alert_status_char_obj = None
GATT_APP_OBJ = False
CHAR_READ = False
CHAR_WRITE = False
CHAR_SUBSCRIBE = False
DBUS_OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
DBUS_PROP_IFACE = 'org.freedesktop.DBus.Properties'
GATT_MANAGER_IFACE = 'org.bluez.GattManager1'
GATT_SERVICE_IFACE = 'org.bluez.GattService1'
GATT_CHRC_IFACE = 'org.bluez.GattCharacteristic1'
GATT_DESC_IFACE = 'org.bluez.GattDescriptor1'
class InvalidArgsException(dbus.exceptions.DBusException):
_dbus_error_name = 'org.freedesktop.DBus.Error.InvalidArgs'
class NotSupportedException(dbus.exceptions.DBusException):
_dbus_error_name = 'org.bluez.Error.NotSupported'
class Application(dbus.service.Object):
"""
org.bluez.GattApplication1 interface implementation
"""
def __init__(self, bus, path):
self.path = path
self.services = []
srv_obj = AlertNotificationService(bus, '0001')
self.add_service(srv_obj)
dbus.service.Object.__init__(self, bus, self.path)
def __del__(self):
pass
def get_path(self):
return dbus.ObjectPath(self.path)
def add_service(self, service):
self.services.append(service)
@dbus.service.method(DBUS_OM_IFACE, out_signature='a{oa{sa{sv}}}')
def GetManagedObjects(self):
global GATT_APP_OBJ
response = {}
for service in self.services:
response[service.get_path()] = service.get_properties()
chrcs = service.get_characteristics()
for chrc in chrcs:
response[chrc.get_path()] = chrc.get_properties()
descs = chrc.get_descriptors()
for desc in descs:
response[desc.get_path()] = desc.get_properties()
GATT_APP_OBJ = True
return response
def Release(self):
pass
class Service(dbus.service.Object):
"""
org.bluez.GattService1 interface implementation
"""
PATH_BASE = '/org/bluez/hci0/service'
def __init__(self, bus, index, uuid, primary=False):
self.path = self.PATH_BASE + str(index)
self.bus = bus
self.uuid = uuid
self.primary = primary
self.characteristics = []
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_SERVICE_IFACE: {
'UUID': self.uuid,
'Primary': self.primary,
'Characteristics': dbus.Array(
self.get_characteristic_paths(),
signature='o')
}
}
def get_path(self):
return dbus.ObjectPath(self.path)
def add_characteristic(self, characteristic):
self.characteristics.append(characteristic)
def get_characteristic_paths(self):
result = []
for chrc in self.characteristics:
result.append(chrc.get_path())
return result
def get_characteristics(self):
return self.characteristics
@dbus.service.method(DBUS_PROP_IFACE,
in_signature='s',
out_signature='a{sv}')
def GetAll(self, interface):
if interface != GATT_SERVICE_IFACE:
raise InvalidArgsException()
return self.get_properties()[GATT_SERVICE_IFACE]
class Characteristic(dbus.service.Object):
"""
org.bluez.GattCharacteristic1 interface implementation
"""
def __init__(self, bus, index, uuid, flags, service):
self.path = service.path + '/char' + str(index)
self.bus = bus
self.uuid = uuid
self.service = service
self.flags = flags
self.value = [0]
self.descriptors = []
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_CHRC_IFACE: {
'Service': self.service.get_path(),
'UUID': self.uuid,
'Flags': self.flags,
'Value': self.value,
'Descriptors': dbus.Array(self.get_descriptor_paths(), signature='o')
}
}
def get_path(self):
return dbus.ObjectPath(self.path)
def add_descriptor(self, descriptor):
self.descriptors.append(descriptor)
def get_descriptor_paths(self):
result = []
for desc in self.descriptors:
result.append(desc.get_path())
return result
def get_descriptors(self):
return self.descriptors
@dbus.service.method(DBUS_PROP_IFACE, in_signature='s', out_signature='a{sv}')
def GetAll(self, interface):
if interface != GATT_CHRC_IFACE:
raise InvalidArgsException()
return self.get_properties()[GATT_CHRC_IFACE]
@dbus.service.method(GATT_CHRC_IFACE, in_signature='a{sv}', out_signature='ay')
def ReadValue(self, options):
print('\nDefault ReadValue called, returning error')
raise NotSupportedException()
@dbus.service.method(GATT_CHRC_IFACE, in_signature='aya{sv}')
def WriteValue(self, value, options):
print('\nDefault WriteValue called, returning error')
raise NotSupportedException()
@dbus.service.method(GATT_CHRC_IFACE)
def StartNotify(self):
print('Default StartNotify called, returning error')
raise NotSupportedException()
@dbus.service.method(GATT_CHRC_IFACE)
def StopNotify(self):
print('Default StopNotify called, returning error')
raise NotSupportedException()
@dbus.service.signal(DBUS_PROP_IFACE,
signature='sa{sv}as')
def PropertiesChanged(self, interface, changed, invalidated):
print("\nProperties Changed")
class Descriptor(dbus.service.Object):
"""
org.bluez.GattDescriptor1 interface implementation
"""
def __init__(self, bus, index, uuid, flags, characteristic):
self.path = characteristic.path + '/desc' + str(index)
self.bus = bus
self.uuid = uuid
self.flags = flags
self.chrc = characteristic
dbus.service.Object.__init__(self, bus, self.path)
def get_properties(self):
return {
GATT_DESC_IFACE: {
'Characteristic': self.chrc.get_path(),
'UUID': self.uuid,
'Flags': self.flags,
}
}
def get_path(self):
return dbus.ObjectPath(self.path)
@dbus.service.method(DBUS_PROP_IFACE,
in_signature='s',
out_signature='a{sv}')
def GetAll(self, interface):
if interface != GATT_DESC_IFACE:
raise InvalidArgsException()
return self.get_properties()[GATT_DESC_IFACE]
@dbus.service.method(GATT_DESC_IFACE, in_signature='a{sv}', out_signature='ay')
def ReadValue(self, options):
print('Default ReadValue called, returning error')
raise NotSupportedException()
@dbus.service.method(GATT_DESC_IFACE, in_signature='aya{sv}')
def WriteValue(self, value, options):
print('Default WriteValue called, returning error')
raise NotSupportedException()
class AlertNotificationService(Service):
TEST_SVC_UUID = '00001811-0000-1000-8000-00805f9b34fb'
def __init__(self, bus, index):
global alert_status_char_obj
Service.__init__(self, bus, index, self.TEST_SVC_UUID, primary=True)
self.add_characteristic(SupportedNewAlertCategoryCharacteristic(bus, '0001', self))
self.add_characteristic(AlertNotificationControlPointCharacteristic(bus, '0002', self))
alert_status_char_obj = UnreadAlertStatusCharacteristic(bus, '0003', self)
self.add_characteristic(alert_status_char_obj)
class SupportedNewAlertCategoryCharacteristic(Characteristic):
SUPPORT_NEW_ALERT_UUID = '00002A47-0000-1000-8000-00805f9b34fb'
def __init__(self, bus, index, service):
Characteristic.__init__(
self, bus, index,
self.SUPPORT_NEW_ALERT_UUID,
['read'],
service)
self.value = [dbus.Byte(2)]
def ReadValue(self, options):
global CHAR_READ
CHAR_READ = True
val_list = []
for val in self.value:
val_list.append(dbus.Byte(val))
print("Read Request received\n", "\tSupportedNewAlertCategoryCharacteristic")
print("\tValue:", "\t", val_list)
return val_list
class AlertNotificationControlPointCharacteristic(Characteristic):
ALERT_NOTIF_UUID = '00002A44-0000-1000-8000-00805f9b34fb'
def __init__(self, bus, index, service):
Characteristic.__init__(
self, bus, index,
self.ALERT_NOTIF_UUID,
['read','write'],
service)
self.value = [dbus.Byte(0)]
def ReadValue(self, options):
val_list = []
for val in self.value:
val_list.append(dbus.Byte(val))
print("Read Request received\n", "\tAlertNotificationControlPointCharacteristic")
print("\tValue:", "\t", val_list)
return val_list
def WriteValue(self, value, options):
global CHAR_WRITE
CHAR_WRITE = True
print("Write Request received\n", "\tAlertNotificationControlPointCharacteristic")
print("\tCurrent value:", "\t", self.value)
val_list = []
for val in value:
val_list.append(val)
self.value = val_list
# Check if new value is written
print("\tNew value:", "\t", self.value)
if not self.value == value:
print("Failed: Write Request\n\tNew value not written\tCurrent value:", self.value)
class UnreadAlertStatusCharacteristic(Characteristic):
UNREAD_ALERT_STATUS_UUID = '00002A45-0000-1000-8000-00805f9b34fb'
def __init__(self, bus, index, service):
Characteristic.__init__(
self, bus, index,
self.UNREAD_ALERT_STATUS_UUID,
['read', 'write', 'notify'],
service)
self.value = [dbus.Byte(0)]
self.cccd_obj = ClientCharacteristicConfigurationDescriptor(bus, '0001', self)
self.add_descriptor(self.cccd_obj)
self.notifying = False
def StartNotify(self):
global CHAR_SUBSCRIBE
CHAR_SUBSCRIBE = True
if self.notifying:
print('\nAlready notifying, nothing to do')
return
self.notifying = True
print("\nNotify Started")
self.cccd_obj.WriteValue([dbus.Byte(1), dbus.Byte(0)])
self.cccd_obj.ReadValue()
def StopNotify(self):
if not self.notifying:
print('\nNot notifying, nothing to do')
return
self.notifying = False
print("\nNotify Stopped")
def ReadValue(self, options):
print("Read Request received\n", "\tUnreadAlertStatusCharacteristic")
val_list = []
for val in self.value:
val_list.append(dbus.Byte(val))
print("\tValue:", "\t", val_list)
return val_list
def WriteValue(self, value, options):
print("Write Request received\n", "\tUnreadAlertStatusCharacteristic")
val_list = []
for val in value:
val_list.append(val)
self.value = val_list
# Check if new value is written
print("\tNew value:", "\t", self.value)
if not self.value == value:
print("Failed: Write Request\n\tNew value not written\tCurrent value:", self.value)
class ClientCharacteristicConfigurationDescriptor(Descriptor):
CCCD_UUID = '00002902-0000-1000-8000-00805f9b34fb'
def __init__(self, bus, index, characteristic):
self.value = [dbus.Byte(0)]
Descriptor.__init__(
self, bus, index,
self.CCCD_UUID,
['read', 'write'],
characteristic)
def ReadValue(self):
print("\tValue on read:", "\t", self.value)
return self.value
def WriteValue(self, value):
val_list = []
for val in value:
val_list.append(val)
self.value = val_list
# Check if new value is written
print("New value on write:", "\t", self.value)
if not self.value == value:
print("Failed: Write Request\n\tNew value not written\tCurrent value:", self.value)

View File

@ -0,0 +1,3 @@
future
dbus-python
pygobject

View File

@ -18,3 +18,4 @@ components/mqtt/esp-mqtt @GENERAL_MIRROR_SERV
components/protobuf-c/protobuf-c @GENERAL_MIRROR_SERVER@/idf/protobuf-c.git ALLOW_TO_SYNC_FROM_PUBLIC
components/unity/unity @GENERAL_MIRROR_SERVER@/idf/Unity.git ALLOW_TO_SYNC_FROM_PUBLIC
examples/build_system/cmake/import_lib/main/lib/tinyxml2 @GENERAL_MIRROR_SERVER@/idf/tinyxml2.git ALLOW_TO_SYNC_FROM_PUBLIC
components/nimble/nimble @GENERAL_MIRROR_SERVER@/idf/esp-nimble.git ALLOW_TO_SYNC_FROM_PUBLIC