Merge branch 'nimble/misc_host_flow_ctrl_changes_v3.3' into 'release/v3.3'
NimBLE: Misc fixes in NimBLE host (backport v3.3) See merge request espressif/esp-idf!7960
This commit is contained in:
commit
13d4ab5afb
|
@ -1338,6 +1338,36 @@ menu Bluetooth
|
|||
help
|
||||
This enables NimBLE host stack
|
||||
|
||||
choice NIMBLE_MEM_ALLOC_MODE
|
||||
prompt "Memory allocation strategy"
|
||||
default NIMBLE_MEM_ALLOC_MODE_INTERNAL
|
||||
depends on NIMBLE_ENABLED
|
||||
help
|
||||
Allocation strategy for NimBLE host stack, essentially provides ability to
|
||||
allocate all required dynamic allocations from,
|
||||
|
||||
- Internal DRAM memory only
|
||||
- External SPIRAM memory only
|
||||
- Either internal or external memory based on default malloc()
|
||||
behavior in ESP-IDF
|
||||
|
||||
Recommended mode here is always internal, since that is most preferred
|
||||
from security perspective. But if application requirement does not
|
||||
allow sufficient free internal memory then alternate mode can be
|
||||
selected.
|
||||
|
||||
config NIMBLE_MEM_ALLOC_MODE_INTERNAL
|
||||
bool "Internal memory"
|
||||
|
||||
config NIMBLE_MEM_ALLOC_MODE_EXTERNAL
|
||||
bool "External SPIRAM"
|
||||
depends on ESP32_SPIRAM_SUPPORT
|
||||
|
||||
config NIMBLE_MEM_ALLOC_MODE_DEFAULT
|
||||
bool "Default alloc mode"
|
||||
|
||||
endchoice
|
||||
|
||||
config NIMBLE_MAX_CONNECTIONS
|
||||
int "Maximum number of concurrent connections"
|
||||
range 1 9
|
||||
|
@ -1526,6 +1556,56 @@ menu Bluetooth
|
|||
low-priority event buffers, then an incoming advertising report will
|
||||
get dropped
|
||||
|
||||
config NIMBLE_MSYS1_BLOCK_COUNT
|
||||
int "MSYS_1 Block Count"
|
||||
depends on NIMBLE_ENABLED
|
||||
default 12
|
||||
help
|
||||
MSYS is a system level mbuf registry. For prepare write & prepare
|
||||
responses MBUFs are allocated out of msys_1 pool. For NIMBLE_MESH
|
||||
enabled cases, this block count is increased by 8 than user defined
|
||||
count.
|
||||
|
||||
config NIMBLE_HS_FLOW_CTRL
|
||||
bool "Enable Host Flow control"
|
||||
depends on NIMBLE_ENABLED
|
||||
default y
|
||||
help
|
||||
Enable Host Flow control
|
||||
|
||||
config NIMBLE_HS_FLOW_CTRL_ITVL
|
||||
int "Host Flow control interval"
|
||||
depends on NIMBLE_HS_FLOW_CTRL
|
||||
default 1000
|
||||
help
|
||||
Host flow control interval in msecs
|
||||
|
||||
config NIMBLE_HS_FLOW_CTRL_THRESH
|
||||
int "Host Flow control threshold"
|
||||
depends on NIMBLE_HS_FLOW_CTRL
|
||||
default 2
|
||||
help
|
||||
Host flow control threshold, if the number of free buffers are at or
|
||||
below this threshold, send an immediate number-of-completed-packets
|
||||
event
|
||||
|
||||
config NIMBLE_HS_FLOW_CTRL_TX_ON_DISCONNECT
|
||||
bool "Host Flow control on disconnect"
|
||||
depends on NIMBLE_HS_FLOW_CTRL
|
||||
default y
|
||||
help
|
||||
Enable this option to send number-of-completed-packets event to
|
||||
controller after disconnection
|
||||
|
||||
config NIMBLE_RPA_TIMEOUT
|
||||
int "RPA timeout in seconds"
|
||||
range 1 41400
|
||||
depends on NIMBLE_ENABLED
|
||||
default 900
|
||||
help
|
||||
Time interval between RPA address change. This is applicable in case of
|
||||
Host based RPA
|
||||
|
||||
menuconfig NIMBLE_MESH
|
||||
bool "Enable BLE mesh functionality"
|
||||
select NIMBLE_SM_SC
|
||||
|
|
|
@ -70,6 +70,7 @@ if(CONFIG_NIMBLE_ENABLED)
|
|||
"./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/src/ble_hs_resolv.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"
|
||||
|
@ -85,7 +86,8 @@ if(CONFIG_NIMBLE_ENABLED)
|
|||
"./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")
|
||||
"./esp-hci/src/esp_nimble_hci.c"
|
||||
"./port/src/esp_nimble_mem.c")
|
||||
|
||||
if(NOT CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS)
|
||||
|
||||
|
|
|
@ -37,7 +37,8 @@ COMPONENT_SRCDIRS += nimble/nimble/host/src \
|
|||
nimble/nimble/host/util/src \
|
||||
nimble/nimble/host/store/ram/src \
|
||||
nimble/nimble/host/store/config/src \
|
||||
esp-hci/src
|
||||
esp-hci/src \
|
||||
port/src
|
||||
|
||||
ifndef CONFIG_NIMBLE_CRYPTO_STACK_MBEDTLS
|
||||
COMPONENT_SRCDIRS += nimble/ext/tinycrypt/src
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "nimble/nimble_port.h"
|
||||
#include "nimble/nimble_port_freertos.h"
|
||||
#include "esp_nimble_hci.h"
|
||||
#include "esp_nimble_mem.h"
|
||||
#include "esp_bt.h"
|
||||
#include "freertos/semphr.h"
|
||||
|
||||
|
@ -48,30 +49,23 @@ static struct os_mempool_ext ble_hci_acl_pool;
|
|||
+ 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 os_membuf_t *ble_hci_acl_buf;
|
||||
|
||||
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 os_membuf_t *ble_hci_cmd_buf;
|
||||
|
||||
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 os_membuf_t *ble_hci_evt_hi_buf;
|
||||
|
||||
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))
|
||||
];
|
||||
static os_membuf_t *ble_hci_evt_lo_buf;
|
||||
|
||||
static SemaphoreHandle_t vhci_send_sem;
|
||||
const static char *TAG = "NimBLE";
|
||||
|
||||
int os_msys_buf_alloc(void);
|
||||
void os_msys_buf_free(void);
|
||||
|
||||
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,
|
||||
|
@ -378,24 +372,75 @@ static const esp_vhci_host_callback_t vhci_host_cb = {
|
|||
.notify_host_recv = host_rcv_pkt,
|
||||
};
|
||||
|
||||
static void ble_buf_free(void)
|
||||
{
|
||||
os_msys_buf_free();
|
||||
|
||||
nimble_platform_mem_free(ble_hci_evt_hi_buf);
|
||||
ble_hci_evt_hi_buf = NULL;
|
||||
nimble_platform_mem_free(ble_hci_evt_lo_buf);
|
||||
ble_hci_evt_lo_buf = NULL;
|
||||
nimble_platform_mem_free(ble_hci_cmd_buf);
|
||||
ble_hci_cmd_buf = NULL;
|
||||
nimble_platform_mem_free(ble_hci_acl_buf);
|
||||
ble_hci_acl_buf = NULL;
|
||||
}
|
||||
|
||||
static esp_err_t ble_buf_alloc(void)
|
||||
{
|
||||
if (os_msys_buf_alloc()) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
ble_hci_evt_hi_buf = (os_membuf_t *) nimble_platform_mem_calloc(1,
|
||||
(sizeof(os_membuf_t) * OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_HI_BUF_COUNT),
|
||||
MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE))));
|
||||
|
||||
ble_hci_evt_lo_buf = (os_membuf_t *) nimble_platform_mem_calloc(1,
|
||||
(sizeof(os_membuf_t) * OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_HCI_EVT_LO_BUF_COUNT),
|
||||
MYNEWT_VAL(BLE_HCI_EVT_BUF_SIZE))));
|
||||
|
||||
ble_hci_cmd_buf = (os_membuf_t *) nimble_platform_mem_calloc(1,
|
||||
(sizeof(os_membuf_t) * OS_MEMPOOL_SIZE(1, BLE_HCI_TRANS_CMD_SZ)));
|
||||
|
||||
ble_hci_acl_buf = (os_membuf_t *) nimble_platform_mem_calloc(1,
|
||||
(sizeof(os_membuf_t) * OS_MEMPOOL_SIZE(MYNEWT_VAL(BLE_ACL_BUF_COUNT),
|
||||
ACL_BLOCK_SIZE)));
|
||||
|
||||
if (!ble_hci_evt_hi_buf || !ble_hci_evt_lo_buf || !ble_hci_cmd_buf || !ble_hci_acl_buf) {
|
||||
ble_buf_free();
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_nimble_hci_init(void)
|
||||
{
|
||||
esp_err_t ret;
|
||||
|
||||
ret = ble_buf_alloc();
|
||||
if (ret != ESP_OK) {
|
||||
goto err;
|
||||
}
|
||||
if ((ret = esp_vhci_host_register_callback(&vhci_host_cb)) != ESP_OK) {
|
||||
return ret;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ble_hci_transport_init();
|
||||
|
||||
vhci_send_sem = xSemaphoreCreateBinary();
|
||||
if (vhci_send_sem == NULL) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
ret = ESP_ERR_NO_MEM;
|
||||
goto err;
|
||||
}
|
||||
|
||||
xSemaphoreGive(vhci_send_sem);
|
||||
|
||||
return ESP_OK;
|
||||
return ret;
|
||||
err:
|
||||
ble_buf_free();
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
esp_err_t esp_nimble_hci_and_controller_init(void)
|
||||
|
@ -444,7 +489,14 @@ esp_err_t esp_nimble_hci_deinit(void)
|
|||
vSemaphoreDelete(vhci_send_sem);
|
||||
vhci_send_sem = NULL;
|
||||
}
|
||||
return ble_hci_transport_deinit();
|
||||
esp_err_t ret = ble_hci_transport_deinit();
|
||||
if (ret != ESP_OK) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ble_buf_free();
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t esp_nimble_hci_and_controller_deinit(void)
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 5250d712a5454b2623bf92343c027d77477ca804
|
||||
Subproject commit ecb1e9e5e3efb830d0b04dc3382be7b1012fdeaa
|
|
@ -15,9 +15,9 @@
|
|||
/*** kernel/os */
|
||||
#ifndef MYNEWT_VAL_MSYS_1_BLOCK_COUNT
|
||||
#ifdef CONFIG_NIMBLE_MESH
|
||||
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (20)
|
||||
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (CONFIG_NIMBLE_MSYS1_BLOCK_COUNT + 8)
|
||||
#else
|
||||
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT (12)
|
||||
#define MYNEWT_VAL_MSYS_1_BLOCK_COUNT CONFIG_NIMBLE_MSYS1_BLOCK_COUNT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
@ -444,19 +444,23 @@
|
|||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_HS_FLOW_CTRL
|
||||
#ifdef CONFIG_NIMBLE_HS_FLOW_CTRL
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL (1)
|
||||
#else
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL (0)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL (1000)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_ITVL CONFIG_NIMBLE_HS_FLOW_CTRL_ITVL
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH (2)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_THRESH CONFIG_NIMBLE_HS_FLOW_CTRL_THRESH
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT (0)
|
||||
#define MYNEWT_VAL_BLE_HS_FLOW_CTRL_TX_ON_DISCONNECT CONFIG_NIMBLE_HS_FLOW_CTRL_TX_ON_DISCONNECT
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_HS_PHONY_HCI_ACKS
|
||||
|
@ -531,8 +535,12 @@
|
|||
#define MYNEWT_VAL_BLE_MONITOR_UART_DEV ("uart0")
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_HOST_BASED_PRIVACY
|
||||
#define MYNEWT_VAL_BLE_HOST_BASED_PRIVACY (1)
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_RPA_TIMEOUT
|
||||
#define MYNEWT_VAL_BLE_RPA_TIMEOUT (300)
|
||||
#define MYNEWT_VAL_BLE_RPA_TIMEOUT (CONFIG_NIMBLE_RPA_TIMEOUT)
|
||||
#endif
|
||||
|
||||
#ifndef MYNEWT_VAL_BLE_SM_BONDING
|
||||
|
|
39
components/nimble/port/include/esp_nimble_mem.h
Normal file
39
components/nimble/port/include/esp_nimble_mem.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2020 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_MEM_H__
|
||||
#define __ESP_NIMBLE_MEM_H__
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void *nimble_platform_mem_malloc(size_t size);
|
||||
void *nimble_platform_mem_calloc(size_t n, size_t size);
|
||||
void nimble_platform_mem_free(void *ptr);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __ESP_NIMBLE_MEM_H__ */
|
52
components/nimble/port/src/esp_nimble_mem.c
Normal file
52
components/nimble/port/src/esp_nimble_mem.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* Copyright 2020 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 "esp_attr.h"
|
||||
#include "esp_heap_caps.h"
|
||||
#include "sdkconfig.h"
|
||||
#include "esp_nimble_mem.h"
|
||||
|
||||
IRAM_ATTR void *nimble_platform_mem_malloc(size_t size)
|
||||
{
|
||||
#ifdef CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL
|
||||
return heap_caps_malloc(size, MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
|
||||
#elif CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL
|
||||
return heap_caps_malloc(size, MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT);
|
||||
#else
|
||||
return malloc(size);
|
||||
#endif
|
||||
}
|
||||
|
||||
IRAM_ATTR void *nimble_platform_mem_calloc(size_t n, size_t size)
|
||||
{
|
||||
#ifdef CONFIG_NIMBLE_MEM_ALLOC_MODE_INTERNAL
|
||||
return heap_caps_calloc(n, size, MALLOC_CAP_INTERNAL|MALLOC_CAP_8BIT);
|
||||
#elif CONFIG_NIMBLE_MEM_ALLOC_MODE_EXTERNAL
|
||||
return heap_caps_calloc(n, size, MALLOC_CAP_SPIRAM|MALLOC_CAP_8BIT);
|
||||
#else
|
||||
return calloc(n, size);
|
||||
#endif
|
||||
}
|
||||
|
||||
IRAM_ATTR void nimble_platform_mem_free(void *ptr)
|
||||
{
|
||||
heap_caps_free(ptr);
|
||||
}
|
|
@ -319,6 +319,7 @@ blecent_should_connect(const struct ble_gap_disc_desc *disc)
|
|||
static void
|
||||
blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc)
|
||||
{
|
||||
uint8_t own_addr_type;
|
||||
int rc;
|
||||
|
||||
/* Don't do anything if we don't care about this advertiser. */
|
||||
|
@ -333,16 +334,23 @@ blecent_connect_if_interesting(const struct ble_gap_disc_desc *disc)
|
|||
return;
|
||||
}
|
||||
|
||||
/* Figure out address to use for connect (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;
|
||||
}
|
||||
|
||||
/* 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,
|
||||
rc = ble_gap_connect(own_addr_type, &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));
|
||||
"addr=%s; rc=%d\n",
|
||||
disc->addr.type, addr_str(disc->addr.val), rc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,9 +10,11 @@ This example aims at understanding GATT database configuration, advertisement an
|
|||
|
||||
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.
|
||||
|
||||
For RPA feature (currently Host based privacy feature is supported), use API `ble_hs_pvcy_rpa_config` to enable/disable host based privacy, `own_addr_type` needs to be set to `BLE_ADDR_RANDOM` to use this feature. Please include `ble_hs_pvcy.h` while using this API. As `ble_hs_pvcy_rpa_config` configures host privacy and sets address in controller, it is necessary to call this API after host-controller are synced (e.g. in `bleprph_on_sync` callback).
|
||||
|
||||
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.
|
||||
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 :
|
||||
|
||||
|
|
Loading…
Reference in a new issue