mirror of
https://github.com/RIOT-OS/RIOT.git
synced 2024-12-29 04:50:03 +01:00
331 lines
10 KiB
C
331 lines
10 KiB
C
/*
|
|
* 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>
|
|
* @author Hendrik van Essen <hendrik.ve@fu-berlin.de>
|
|
*
|
|
* @}
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdint.h>
|
|
|
|
#include "assert.h"
|
|
#include "event/timeout.h"
|
|
#include "nimble_riot.h"
|
|
#include "nimble_autoadv.h"
|
|
#include "net/bluetil/ad.h"
|
|
#include "timex.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)
|
|
#define BPM_MIN (80U)
|
|
#define BPM_MAX (210U)
|
|
#define BPM_STEP (2)
|
|
#define BAT_LEVEL (42U)
|
|
|
|
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_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();
|
|
nimble_autoadv_start(NULL);
|
|
return 0;
|
|
}
|
|
_conn_handle = event->connect.conn_handle;
|
|
break;
|
|
|
|
case BLE_GAP_EVENT_DISCONNECT:
|
|
_stop_updating();
|
|
nimble_autoadv_start(NULL);
|
|
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_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);
|
|
(void)res;
|
|
|
|
/* schedule next update event */
|
|
event_timeout_set(&_update_timeout_evt, UPDATE_INTERVAL);
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
puts("NimBLE Heart Rate Sensor Example");
|
|
|
|
int res = 0;
|
|
(void)res;
|
|
|
|
/* setup local event queue (for handling heart rate updates) */
|
|
event_queue_init(&_eq);
|
|
_update_evt.handler = _hr_update;
|
|
event_timeout_ztimer_init(&_update_timeout_evt, ZTIMER_MSEC, &_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(CONFIG_NIMBLE_AUTOADV_DEVICE_NAME);
|
|
/* reload the GATT server to link our added services */
|
|
ble_gatts_start();
|
|
|
|
nimble_autoadv_cfg_t cfg = {
|
|
.adv_duration_ms = BLE_HS_FOREVER,
|
|
.adv_itvl_ms = BLE_GAP_ADV_ITVL_MS(100),
|
|
.flags = NIMBLE_AUTOADV_FLAG_CONNECTABLE | NIMBLE_AUTOADV_FLAG_LEGACY | \
|
|
NIMBLE_AUTOADV_FLAG_SCANNABLE,
|
|
.channel_map = 0,
|
|
.filter_policy = 0,
|
|
.own_addr_type = nimble_riot_own_addr_type,
|
|
.phy = NIMBLE_PHY_1M,
|
|
.tx_power = 0,
|
|
};
|
|
/* set advertise params */
|
|
nimble_autoadv_cfg_update(&cfg);
|
|
|
|
/* configure and set the advertising data */
|
|
uint16_t hrs_uuid = BLE_GATT_SVC_HRS;
|
|
nimble_autoadv_add_field(BLE_GAP_AD_UUID16_INCOMP, &hrs_uuid, sizeof(hrs_uuid));
|
|
|
|
nimble_autoadv_set_gap_cb(&gap_event_cb, NULL);
|
|
|
|
/* start to advertise this node */
|
|
nimble_autoadv_start(NULL);
|
|
|
|
/* run an event loop for handling the heart rate update events */
|
|
event_loop(&_eq);
|
|
|
|
return 0;
|
|
}
|