Merge pull request #11739 from haukepetersen/add_nimble_hrs
Add NimBLE heart rate sensor example (GATT notifications)
This commit is contained in:
commit
03ab95b2fa
29
examples/nimble_heart_rate_sensor/Makefile
Normal file
29
examples/nimble_heart_rate_sensor/Makefile
Normal file
@ -0,0 +1,29 @@
|
||||
# name of your application
|
||||
APPLICATION = nimble_heart_rate_sensor
|
||||
|
||||
# If no BOARD is found in the environment, use this default:
|
||||
BOARD ?= nrf52dk
|
||||
|
||||
# This has to be the absolute path to the RIOT base directory:
|
||||
RIOTBASE ?= $(CURDIR)/../..
|
||||
|
||||
# Some RIOT modules needed for this example
|
||||
USEMODULE += event_timeout
|
||||
|
||||
# Include NimBLE
|
||||
USEPKG += nimble
|
||||
USEMODULE += nimble_svc_gap
|
||||
USEMODULE += nimble_svc_gatt
|
||||
|
||||
# We also use the AD part of the BLE helper module
|
||||
USEMODULE += bluetil_ad
|
||||
|
||||
# Comment this out to disable code in RIOT that does safety checking
|
||||
# which is not needed in a production environment but helps in the
|
||||
# development process:
|
||||
DEVELHELP ?= 1
|
||||
|
||||
# Change this to 0 show compiler invocation lines by default:
|
||||
QUIET ?= 1
|
||||
|
||||
include $(RIOTBASE)/Makefile.include
|
||||
13
examples/nimble_heart_rate_sensor/README.md
Normal file
13
examples/nimble_heart_rate_sensor/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
NimBLE Heart Rate Sensor Example
|
||||
================================
|
||||
This example demonstrates how to implement asynchronous data transfer using GATT
|
||||
notifications by implementing a mock-up BLE heart rate sensor. Once flashed on
|
||||
a node, you can connect to it using any phone or device capable of using such a
|
||||
heart rate sensor, e.g. Nordics `nRF Toolbox` for Android
|
||||
(https://play.google.com/store/apps/details?id=no.nordicsemi.android.nrftoolbox).
|
||||
|
||||
The heart rate sensor is simulated by simply providing an up- and down counter
|
||||
for its BPM value.
|
||||
|
||||
On top of the heart rate service, this example implements the device information
|
||||
as well as the battery service.
|
||||
332
examples/nimble_heart_rate_sensor/main.c
Normal file
332
examples/nimble_heart_rate_sensor/main.c
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Freie Universität Berlin
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU Lesser
|
||||
* General Public License v2.1. See the file LICENSE in the top level
|
||||
* directory for more details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @ingroup examples
|
||||
* @{
|
||||
*
|
||||
* @file
|
||||
* @brief (Mock-up) BLE heart rate sensor example
|
||||
*
|
||||
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
|
||||
*
|
||||
* @}
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "assert.h"
|
||||
#include "event/timeout.h"
|
||||
#include "nimble_riot.h"
|
||||
#include "net/bluetil/ad.h"
|
||||
|
||||
#include "host/ble_hs.h"
|
||||
#include "host/ble_gatt.h"
|
||||
#include "services/gap/ble_svc_gap.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
|
||||
#define HRS_FLAGS_DEFAULT (0x01) /* 16-bit BPM value */
|
||||
#define SENSOR_LOCATION (0x02) /* wrist sensor */
|
||||
#define UPDATE_INTERVAL (250U * US_PER_MS)
|
||||
#define BPM_MIN (80U)
|
||||
#define BPM_MAX (210U)
|
||||
#define BPM_STEP (2)
|
||||
#define BAT_LEVEL (42U)
|
||||
|
||||
static const char *_device_name = "RIOT Heart Rate Sensor";
|
||||
static const char *_manufacturer_name = "Unfit Byte Inc.";
|
||||
static const char *_model_number = "2A";
|
||||
static const char *_serial_number = "a8b302c7f3-29183-x8";
|
||||
static const char *_fw_ver = "13.7.12";
|
||||
static const char *_hw_ver = "V3B";
|
||||
|
||||
static struct __attribute__((packed)) {
|
||||
uint8_t flags;
|
||||
uint16_t bpm;
|
||||
} _hr_data = { HRS_FLAGS_DEFAULT, (BPM_MIN + BPM_STEP) };
|
||||
|
||||
static event_queue_t _eq;
|
||||
static event_t _update_evt;
|
||||
static event_timeout_t _update_timeout_evt;
|
||||
|
||||
static uint16_t _conn_handle;
|
||||
static uint16_t _hrs_val_handle;
|
||||
|
||||
static int step = BPM_STEP;
|
||||
|
||||
static int _hrs_handler(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
static int _devinfo_handler(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
static int _bas_handler(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg);
|
||||
|
||||
static void _start_advertising(void);
|
||||
static void _start_updating(void);
|
||||
static void _stop_updating(void);
|
||||
|
||||
/* GATT service definitions */
|
||||
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
|
||||
{
|
||||
/* Heart Rate Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_SVC_HRS),
|
||||
.characteristics = (struct ble_gatt_chr_def[]) { {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_HEART_RATE_MEASURE),
|
||||
.access_cb = _hrs_handler,
|
||||
.val_handle = &_hrs_val_handle,
|
||||
.flags = BLE_GATT_CHR_F_NOTIFY,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_BODY_SENSE_LOC),
|
||||
.access_cb = _hrs_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
0, /* no more characteristics in this service */
|
||||
}, }
|
||||
},
|
||||
{
|
||||
/* Device Information Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_SVC_DEVINFO),
|
||||
.characteristics = (struct ble_gatt_chr_def[]) { {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_MANUFACTURER_NAME),
|
||||
.access_cb = _devinfo_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_MODEL_NUMBER_STR),
|
||||
.access_cb = _devinfo_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_SERIAL_NUMBER_STR),
|
||||
.access_cb = _devinfo_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_FW_REV_STR),
|
||||
.access_cb = _devinfo_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_HW_REV_STR),
|
||||
.access_cb = _devinfo_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
0, /* no more characteristics in this service */
|
||||
}, }
|
||||
},
|
||||
{
|
||||
/* Battery Level Service */
|
||||
.type = BLE_GATT_SVC_TYPE_PRIMARY,
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_SVC_BAS),
|
||||
.characteristics = (struct ble_gatt_chr_def[]) { {
|
||||
.uuid = BLE_UUID16_DECLARE(BLE_GATT_CHAR_BATTERY_LEVEL),
|
||||
.access_cb = _bas_handler,
|
||||
.flags = BLE_GATT_CHR_F_READ,
|
||||
}, {
|
||||
0, /* no more characteristics in this service */
|
||||
}, }
|
||||
},
|
||||
{
|
||||
0, /* no more services */
|
||||
},
|
||||
};
|
||||
|
||||
static int _hrs_handler(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
(void)conn_handle;
|
||||
(void)attr_handle;
|
||||
(void)arg;
|
||||
|
||||
if (ble_uuid_u16(ctxt->chr->uuid) != BLE_GATT_CHAR_BODY_SENSE_LOC) {
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
puts("[READ] heart rate service: body sensor location value");
|
||||
|
||||
uint8_t loc = SENSOR_LOCATION;
|
||||
int res = os_mbuf_append(ctxt->om, &loc, sizeof(loc));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
static int _devinfo_handler(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
(void)conn_handle;
|
||||
(void)attr_handle;
|
||||
(void)arg;
|
||||
const char *str;
|
||||
|
||||
switch (ble_uuid_u16(ctxt->chr->uuid)) {
|
||||
case BLE_GATT_CHAR_MANUFACTURER_NAME:
|
||||
puts("[READ] device information service: manufacturer name value");
|
||||
str = _manufacturer_name;
|
||||
break;
|
||||
case BLE_GATT_CHAR_MODEL_NUMBER_STR:
|
||||
puts("[READ] device information service: model number value");
|
||||
str = _model_number;
|
||||
break;
|
||||
case BLE_GATT_CHAR_SERIAL_NUMBER_STR:
|
||||
puts("[READ] device information service: serial number value");
|
||||
str = _serial_number;
|
||||
break;
|
||||
case BLE_GATT_CHAR_FW_REV_STR:
|
||||
puts("[READ] device information service: firmware revision value");
|
||||
str = _fw_ver;
|
||||
break;
|
||||
case BLE_GATT_CHAR_HW_REV_STR:
|
||||
puts("[READ] device information service: hardware revision value");
|
||||
str = _hw_ver;
|
||||
break;
|
||||
default:
|
||||
return BLE_ATT_ERR_UNLIKELY;
|
||||
}
|
||||
|
||||
int res = os_mbuf_append(ctxt->om, str, strlen(str));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
static int _bas_handler(uint16_t conn_handle, uint16_t attr_handle,
|
||||
struct ble_gatt_access_ctxt *ctxt, void *arg)
|
||||
{
|
||||
(void)conn_handle;
|
||||
(void)attr_handle;
|
||||
(void)arg;
|
||||
|
||||
puts("[READ] battery level service: battery level value");
|
||||
|
||||
uint8_t level = BAT_LEVEL; /* this battery will never drain :-) */
|
||||
int res = os_mbuf_append(ctxt->om, &level, sizeof(level));
|
||||
return (res == 0) ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
|
||||
}
|
||||
|
||||
static int gap_event_cb(struct ble_gap_event *event, void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
|
||||
switch (event->type) {
|
||||
case BLE_GAP_EVENT_CONNECT:
|
||||
if (event->connect.status) {
|
||||
_stop_updating();
|
||||
_start_advertising();
|
||||
return 0;
|
||||
}
|
||||
_conn_handle = event->connect.conn_handle;
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_DISCONNECT:
|
||||
_stop_updating();
|
||||
_start_advertising();
|
||||
break;
|
||||
|
||||
case BLE_GAP_EVENT_SUBSCRIBE:
|
||||
if (event->subscribe.attr_handle == _hrs_val_handle) {
|
||||
if (event->subscribe.cur_notify == 1) {
|
||||
_start_updating();
|
||||
}
|
||||
else {
|
||||
_stop_updating();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void _start_advertising(void)
|
||||
{
|
||||
struct ble_gap_adv_params advp;
|
||||
int res;
|
||||
|
||||
memset(&advp, 0, sizeof advp);
|
||||
advp.conn_mode = BLE_GAP_CONN_MODE_UND;
|
||||
advp.disc_mode = BLE_GAP_DISC_MODE_GEN;
|
||||
advp.itvl_min = BLE_GAP_ADV_FAST_INTERVAL1_MIN;
|
||||
advp.itvl_max = BLE_GAP_ADV_FAST_INTERVAL1_MAX;
|
||||
res = ble_gap_adv_start(nimble_riot_own_addr_type, NULL, BLE_HS_FOREVER,
|
||||
&advp, gap_event_cb, NULL);
|
||||
assert(res == 0);
|
||||
(void)res;
|
||||
}
|
||||
|
||||
static void _start_updating(void)
|
||||
{
|
||||
event_timeout_set(&_update_timeout_evt, UPDATE_INTERVAL);
|
||||
puts("[NOTIFY_ENABLED] heart rate service");
|
||||
}
|
||||
|
||||
static void _stop_updating(void)
|
||||
{
|
||||
event_timeout_clear(&_update_timeout_evt);
|
||||
puts("[NOTIFY_DISABLED] heart rate service");
|
||||
}
|
||||
|
||||
static void _hr_update(event_t *e)
|
||||
{
|
||||
(void)e;
|
||||
struct os_mbuf *om;
|
||||
|
||||
/* our mock-up heart rate is going up and down */
|
||||
if ((_hr_data.bpm == BPM_MIN) || (_hr_data.bpm == BPM_MAX)) {
|
||||
step *= -1;
|
||||
}
|
||||
_hr_data.bpm += step;
|
||||
|
||||
printf("[NOTIFY] heart rate service: measurement %i\n", (int)_hr_data.bpm);
|
||||
|
||||
/* send heart rate data notification to GATT client */
|
||||
om = ble_hs_mbuf_from_flat(&_hr_data, sizeof(_hr_data));
|
||||
assert(om != NULL);
|
||||
int res = ble_gattc_notify_custom(_conn_handle, _hrs_val_handle, om);
|
||||
assert(res == 0);
|
||||
|
||||
/* schedule next update event */
|
||||
event_timeout_set(&_update_timeout_evt, UPDATE_INTERVAL);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
puts("NimBLE Heart Rate Sensor Example");
|
||||
|
||||
int res = 0;
|
||||
|
||||
/* setup local event queue (for handling heart rate updates) */
|
||||
event_queue_init(&_eq);
|
||||
_update_evt.handler = _hr_update;
|
||||
event_timeout_init(&_update_timeout_evt, &_eq, &_update_evt);
|
||||
|
||||
/* verify and add our custom services */
|
||||
res = ble_gatts_count_cfg(gatt_svr_svcs);
|
||||
assert(res == 0);
|
||||
res = ble_gatts_add_svcs(gatt_svr_svcs);
|
||||
assert(res == 0);
|
||||
|
||||
/* set the device name */
|
||||
ble_svc_gap_device_name_set(_device_name);
|
||||
/* reload the GATT server to link our added services */
|
||||
ble_gatts_start();
|
||||
|
||||
/* configure and set the advertising data */
|
||||
uint8_t buf[BLE_HS_ADV_MAX_SZ];
|
||||
bluetil_ad_t ad;
|
||||
bluetil_ad_init_with_flags(&ad, buf, sizeof(buf), BLUETIL_AD_FLAGS_DEFAULT);
|
||||
uint16_t hrs_uuid = BLE_GATT_SVC_HRS;
|
||||
bluetil_ad_add(&ad, BLE_GAP_AD_UUID16_INCOMP, &hrs_uuid, sizeof(hrs_uuid));
|
||||
bluetil_ad_add_name(&ad, _device_name);
|
||||
ble_gap_adv_set_data(ad.buf, ad.pos);
|
||||
|
||||
/* start to advertise this node */
|
||||
_start_advertising();
|
||||
|
||||
/* run an event loop for handling the heart rate update events */
|
||||
event_loop(&_eq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -85,6 +85,39 @@ extern "C" {
|
||||
#define BLE_DESC_VALUE_TRIGGER_SETTING (0x290a)
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Selected GATT service UUIDs (16-bit)
|
||||
*
|
||||
* @see https://www.bluetooth.com/specifications/gatt/services
|
||||
* @{
|
||||
*/
|
||||
#define BLE_GATT_SVC_GAP (0x1800) /**< GAP service */
|
||||
#define BLE_GATT_SVC_GATT (0x1801) /**< GATT service */
|
||||
#define BLE_GATT_SVC_DEVINFO (0x180a) /**< device info */
|
||||
#define BLE_GATT_SVC_HRS (0x180d) /**< heart rate service */
|
||||
#define BLE_GATT_SVC_BAS (0x180f) /**< battery service */
|
||||
#define BLE_GATT_SVC_IPSS (0x1820) /**< IP protocol support */
|
||||
/* add more on demand */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Selected GATT characteristic UUIDs (16-bit)
|
||||
*
|
||||
* @see https://www.bluetooth.com/specifications/gatt/characteristics/
|
||||
* @{
|
||||
*/
|
||||
#define BLE_GATT_CHAR_BATTERY_LEVEL (0x2a19) /**< battery level */
|
||||
#define BLE_GATT_CHAR_SYSTEM_ID (0x2a23) /**< system ID */
|
||||
#define BLE_GATT_CHAR_MODEL_NUMBER_STR (0x2a24) /**< model number */
|
||||
#define BLE_GATT_CHAR_SERIAL_NUMBER_STR (0x2a25) /**< serial number */
|
||||
#define BLE_GATT_CHAR_FW_REV_STR (0x2a26) /**< firmware revision */
|
||||
#define BLE_GATT_CHAR_HW_REV_STR (0x2a27) /**< hardware revision */
|
||||
#define BLE_GATT_CHAR_SW_REV_STR (0x2a28) /**< software revision */
|
||||
#define BLE_GATT_CHAR_MANUFACTURER_NAME (0x2a29) /**< manufacturer name */
|
||||
#define BLE_GATT_CHAR_HEART_RATE_MEASURE (0x2a37) /**< heart rate measurement */
|
||||
#define BLE_GATT_CHAR_BODY_SENSE_LOC (0x2a38) /**< body sensor location */
|
||||
/** @} */
|
||||
|
||||
/**
|
||||
* @name Characteristic format types (8-bit)
|
||||
*
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user