/* * 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 * @author Hendrik van Essen * * @} */ #include #include #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; }