Skip to content

Commit

Permalink
Merge branch 'feature/nimble_gatt_server' into 'master'
Browse files Browse the repository at this point in the history
NimBLE: Added custom gatt server functionality

See merge request espressif/esp-idf!20910
  • Loading branch information
rahult-github committed Feb 6, 2023
2 parents 5754e29 + 3361718 commit 2538270
Show file tree
Hide file tree
Showing 8 changed files with 489 additions and 85 deletions.
23 changes: 20 additions & 3 deletions examples/bluetooth/nimble/blecent/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,27 @@

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:
After connection it enables bonding and link encryprion if the `Enable Link Encryption` flag is set in the example config.

It performs six 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.

* After the subscribe operation is completed, it subscribes to notifications for a user defined characteristic.

* After this subscribe operation is completed, it writes to the user defined characteristic.

* After the write operation is completed, it reads from the user defined 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.
This example aims at understanding BLE service discovery, connection, encryption 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.

Expand All @@ -40,7 +48,7 @@ idf.py set-target <chip_name>

### Hardware Required

* A development board with ESP32/ESP32-C3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A development board with ESP32/ESP32-C2/ESP32-C3/ESP32-S3 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.)
* A USB cable for Power supply and programming

See [Development Boards](https://www.espressif.com/en/products/devkits) for more information about it.
Expand Down Expand Up @@ -79,6 +87,8 @@ 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
Connection secured
encryption change event; status=0
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
Expand All @@ -92,6 +102,13 @@ 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
GATT procedure initiated: write; att_handle=26 len=2
GATT procedure initiated: write; att_handle=25 len=1
GATT procedure initiated: read; att_handle=25
Subscribe to the custom subscribable characteristic complete; status=0 conn_handle=1 attr_handle=26 value=
Write to the custom subscribable characteristic complete; status=0 conn_handle=1 attr_handle=25
received notification; conn_handle=1 attr_handle=25 attr_len=4
Read complete for the subscribable characteristic; status=0 conn_handle=1 attr_handle=25 value=0x19
```

This is the console output on failure (or peripheral does not support New Alert Service category):
Expand Down
6 changes: 6 additions & 0 deletions examples/bluetooth/nimble/blecent/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ menu "Example Configuration"
prompt "Perform init deinit of nimble stack in a loop"
help
Enable this flag, to perform only stack Init and Deinit in a loop.

config EXAMPLE_ENCRYPTION
bool
prompt "Enable Link Encryption"
help
This enables bonding and encryption after connection has been established.
endmenu
215 changes: 211 additions & 4 deletions examples/bluetooth/nimble/blecent/main/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,183 @@
#include "services/gap/ble_svc_gap.h"
#include "blecent.h"

/*** The UUID of the service containing the subscribable characterstic ***/
static const ble_uuid_t * remote_svc_uuid =
BLE_UUID128_DECLARE(0x2d, 0x71, 0xa2, 0x59, 0xb4, 0x58, 0xc8, 0x12,
0x99, 0x99, 0x43, 0x95, 0x12, 0x2f, 0x46, 0x59);

/*** The UUID of the subscribable chatacteristic ***/
static const ble_uuid_t * remote_chr_uuid =
BLE_UUID128_DECLARE(0x00, 0x00, 0x00, 0x00, 0x11, 0x11, 0x11, 0x11,
0x22, 0x22, 0x22, 0x22, 0x33, 0x33, 0x33, 0x33);

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 custom subscribable chatacteristic
* in the remote GATT server is read.
* Expect to get the recently written data.
**/
static int
blecent_on_custom_read(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
MODLOG_DFLT(INFO,
"Read complete for the subscribable characteristic; "
"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");

return 0;
}

/**
* Application Callback. Called when the custom subscribable characteristic
* in the remote GATT server is written to.
* Client has previously subscribed to this characeteristic,
* so expect a notification from the server.
**/
static int
blecent_on_custom_write(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
const struct peer_chr *chr;
const struct peer *peer;
int rc;

MODLOG_DFLT(INFO,
"Write to the custom subscribable characteristic complete; "
"status=%d conn_handle=%d attr_handle=%d\n",
error->status, conn_handle, attr->handle);

peer = peer_find(conn_handle);
chr = peer_chr_find_uuid(peer,
remote_svc_uuid,
remote_chr_uuid);
if (chr == NULL) {
MODLOG_DFLT(ERROR,
"Error: Peer doesn't have the custom subscribable characteristic\n");
goto err;
}

/*** Performs a read on the characteristic, the result is handled in blecent_on_new_read callback ***/
rc = ble_gattc_read(conn_handle, chr->chr.val_handle,
blecent_on_custom_read, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR,
"Error: Failed to read the custom subscribable 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 custom subscribable characteristic
* is subscribed to.
**/
static int
blecent_on_custom_subscribe(uint16_t conn_handle,
const struct ble_gatt_error *error,
struct ble_gatt_attr *attr,
void *arg)
{
const struct peer_chr *chr;
uint8_t value;
int rc;
const struct peer *peer;

MODLOG_DFLT(INFO,
"Subscribe to the custom subscribable characteristic 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");

peer = peer_find(conn_handle);
chr = peer_chr_find_uuid(peer,
remote_svc_uuid,
remote_chr_uuid);
if (chr == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer doesn't have the subscribable characteristic\n");
goto err;
}

/* Write 1 byte to the new characteristic to test if it notifies after subscribing */
value = 0x19;
rc = ble_gattc_write_flat(conn_handle, chr->chr.val_handle,
&value, sizeof(value), blecent_on_custom_write, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR,
"Error: Failed to write to the subscribable 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 3 operations on the remote GATT server.
* 1. Subscribes to a characteristic by writing 0x10 to it's CCCD.
* 2. Writes to the characteristic and expect a notification from remote.
* 3. Reads the characteristic and expect to get the recently written information.
**/
static void
blecent_custom_gatt_operations(const struct peer* peer)
{
const struct peer_dsc *dsc;
int rc;
uint8_t value[2];

dsc = peer_dsc_find_uuid(peer,
remote_svc_uuid,
remote_chr_uuid,
BLE_UUID16_DECLARE(BLE_GATT_DSC_CLT_CFG_UUID16));
if (dsc == NULL) {
MODLOG_DFLT(ERROR, "Error: Peer lacks a CCCD for the subscribable characterstic\n");
goto err;
}

/*** Write 0x00 and 0x01 (The subscription code) to the CCCD ***/
value[0] = 1;
value[1] = 0;
rc = ble_gattc_write_flat(peer->conn_handle, dsc->dsc.handle,
value, sizeof(value), blecent_on_custom_subscribe, NULL);
if (rc != 0) {
MODLOG_DFLT(ERROR,
"Error: Failed to subscribe to the subscribable characteristic; "
"rc=%d\n", rc);
goto err;
}

return;
err:
/* Terminate the connection */
ble_gap_terminate(peer->conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}

/**
* Application callback. Called when the attempt to subscribe to notifications
* for the ANS Unread Alert Status characteristic has completed.
Expand All @@ -44,10 +215,20 @@ blecent_on_subscribe(uint16_t conn_handle,
struct ble_gatt_attr *attr,
void *arg)
{
struct peer *peer;

MODLOG_DFLT(INFO, "Subscribe complete; status=%d conn_handle=%d "
"attr_handle=%d\n",
error->status, conn_handle, attr->handle);

peer = peer_find(conn_handle);
if (peer == NULL) {
MODLOG_DFLT(ERROR, "Error in finding peer, aborting...");
ble_gap_terminate(conn_handle, BLE_ERR_REM_USER_CONN_TERM);
}
/* Subscribe to, write to, and read the custom characteristic*/
blecent_custom_gatt_operations(peer);

return 0;
}

Expand Down Expand Up @@ -216,7 +397,7 @@ blecent_on_disc_complete(const struct peer *peer, int status, void *arg)
"conn_handle=%d\n", status, peer->conn_handle);

/* Now perform three GATT procedures against the peer: read,
* write, and subscribe to notifications.
* write, and subscribe to notifications for the ANS service.
*/
blecent_read_write_subscribe(peer);
}
Expand Down Expand Up @@ -467,13 +648,30 @@ blecent_gap_event(struct ble_gap_event *event, void *arg)
return 0;
}

/* Perform service discovery. */
rc = peer_disc_all(event->connect.conn_handle,
blecent_on_disc_complete, NULL);
#if CONFIG_EXAMPLE_ENCRYPTION
/** Initiate security - It will perform
* Pairing (Exchange keys)
* Bonding (Store keys)
* Encryption (Enable encryption)
* Will invoke event BLE_GAP_EVENT_ENC_CHANGE
**/
rc = ble_gap_security_initiate(event->connect.conn_handle);
if (rc != 0) {
MODLOG_DFLT(INFO, "Security could not be initiated, rc = %d\n", rc);
return ble_gap_terminate(event->connect.conn_handle,
BLE_ERR_REM_USER_CONN_TERM);
} else {
MODLOG_DFLT(INFO, "Connection secured\n");
}
#else
/* 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;
}
#endif
} else {
/* Connection attempt failed; resume scanning. */
MODLOG_DFLT(ERROR, "Error: Connection failed; status=%d\n",
Expand Down Expand Up @@ -508,6 +706,15 @@ blecent_gap_event(struct ble_gap_event *event, void *arg)
rc = ble_gap_conn_find(event->enc_change.conn_handle, &desc);
assert(rc == 0);
print_conn_desc(&desc);
#if CONFIG_EXAMPLE_ENCRYPTION
/*** Go for service discovery after encryption has been successfully enabled ***/
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;
}
#endif
return 0;

case BLE_GAP_EVENT_NOTIFY_RX:
Expand Down
11 changes: 9 additions & 2 deletions examples/bluetooth/nimble/bleprph/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ This example creates GATT server and then starts advertising, waiting to be conn

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.
This example aims at understanding GATT database configuration, handling GATT reads and writes, handling subscribe events, understanding 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.
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, Enabling Link Encryption 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).

Expand Down Expand Up @@ -83,6 +83,13 @@ peer_id_addr=xx:xx:xx:xx:xx:xx conn_itvl=6 conn_latency=0 supervision_timeout=50
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
subscribe event; conn_handle=1 attr_handle=19 reason=1 prevn=0 curn=1 previ=0 curi=0
Subscribe to attribute (19) successful
subscribe event; conn_handle=1 attr_handle=25 reason=1 prevn=0 curn=1 previ=0 curi=0
Subscribe to attribute (25) successful
GATT procedure initiated: notify; att_handle=25
Notification sent succesfully
```

## Running Python Utility
Expand Down
6 changes: 6 additions & 0 deletions examples/bluetooth/nimble/bleprph/main/Kconfig.projbuild
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,10 @@ menu "Example Configuration"
prompt "Advertise RANDOM Address"
help
Use this option to advertise a random address instead of public address

config EXAMPLE_ENCRYPTION
bool
prompt "Enable Link Encryption"
help
This adds Encrypted Read and Write permissions in the custom GATT server.
endmenu
2 changes: 2 additions & 0 deletions examples/bluetooth/nimble/bleprph/main/bleprph.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ struct ble_gatt_register_ctxt;

void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
int gatt_svr_init(void);
int gatt_svr_subscribe(uint16_t);
void gatt_svr_subscription_delete(void);

#ifdef __cplusplus
}
Expand Down
Loading

0 comments on commit 2538270

Please sign in to comment.