#include #include #include "ans.h" #include "host/ble_hs.h" #include "host/ble_gap.h" /* Max length of new alert info string */ #define BLE_SVC_ANS_INFO_STR_MAX_LEN 18 /* Max length of a new alert notification, max string length + 2 bytes * for category ID and count. */ #define BLE_SVC_ANS_NEW_ALERT_MAX_LEN (BLE_SVC_ANS_INFO_STR_MAX_LEN + 2) /* Supported categories bitmasks */ static uint8_t ble_svc_ans_new_alert_cat; static uint8_t ble_svc_ans_unr_alert_cat; /* Characteristic values */ static uint8_t ble_svc_ans_new_alert_val[BLE_SVC_ANS_NEW_ALERT_MAX_LEN]; static uint16_t ble_svc_ans_new_alert_val_len; static uint8_t ble_svc_ans_unr_alert_stat[2]; static uint8_t ble_svc_ans_alert_not_ctrl_pt[2]; /* Alert counts, one value for each category */ static uint8_t ble_svc_ans_new_alert_cnt[BLE_SVC_ANS_CAT_NUM]; static uint8_t ble_svc_ans_unr_alert_cnt[BLE_SVC_ANS_CAT_NUM]; /* Characteristic value handles */ static uint16_t ble_svc_ans_new_alert_val_handle; static uint16_t ble_svc_ans_unr_alert_val_handle; /* Connection handle * * TODO: In order to support multiple connections we would need to save * the handles for every connection, not just the most recent. Then * we would need to notify each connection when needed. * */ static uint16_t ble_svc_ans_conn_handle; /* Access function */ static int ble_svc_ans_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg); /* Notify new alert */ static int ble_svc_ans_new_alert_notify(uint8_t cat_id, const char * info_str); /* Notify unread alert */ static int ble_svc_ans_unr_alert_notify(uint8_t cat_id); /* Save written value to local characteristic value */ static int ble_svc_ans_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, void *dst, uint16_t *len); static const struct ble_gatt_svc_def ble_svc_ans_defs[] = { { /*** Alert Notification Service. */ .type = BLE_GATT_SVC_TYPE_PRIMARY, .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_UUID16), .characteristics = (struct ble_gatt_chr_def[]) { { /** Supported New Alert Catagory * * This characteristic exposes what categories of new * alert are supported in the server. */ .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_SUP_NEW_ALERT_CAT), .access_cb = ble_svc_ans_access, .flags = BLE_GATT_CHR_F_READ, }, { /** New Alert * * This characteristic exposes information about * the count of new alerts (for a given category). */ .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_NEW_ALERT), .access_cb = ble_svc_ans_access, .val_handle = &ble_svc_ans_new_alert_val_handle, .flags = BLE_GATT_CHR_F_NOTIFY, }, { /** Supported Unread Alert Catagory * * This characteristic exposes what categories of * unread alert are supported in the server. */ .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_SUP_UNR_ALERT_CAT), .access_cb = ble_svc_ans_access, .flags = BLE_GATT_CHR_F_READ, }, { /** Unread Alert Status * * This characteristic exposes the count of unread * alert events existing in the server. */ .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_UNR_ALERT_STAT), .access_cb = ble_svc_ans_access, .val_handle = &ble_svc_ans_unr_alert_val_handle, .flags = BLE_GATT_CHR_F_NOTIFY, }, { /** Alert Notification Control Point * * This characteristic allows the peer device to * enable/disable the alert notification of new alert * and unread event more selectively than can be done * by setting or clearing the notification bit in the * Client Characteristic Configuration for each alert * characteristic. */ .uuid = BLE_UUID16_DECLARE(BLE_SVC_ANS_CHR_UUID16_ALERT_NOT_CTRL_PT), .access_cb = ble_svc_ans_access, .flags = BLE_GATT_CHR_F_WRITE, }, { 0, /* No more characteristics in this service. */ } }, }, { 0, /* No more services. */ }, }; /** * ANS access function */ static int ble_svc_ans_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg) { uint16_t uuid16; int rc; /* ANS Control point command and catagory variables */ uint8_t cmd_id; uint8_t cat_id; uint8_t cat_bit_mask; int i; uuid16 = ble_uuid_u16(ctxt->chr->uuid); assert(uuid16 != 0); switch (uuid16) { case BLE_SVC_ANS_CHR_UUID16_SUP_NEW_ALERT_CAT: assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); rc = os_mbuf_append(ctxt->om, &ble_svc_ans_new_alert_cat, sizeof ble_svc_ans_new_alert_cat); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; case BLE_SVC_ANS_CHR_UUID16_NEW_ALERT: if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { rc = ble_svc_ans_chr_write(ctxt->om, 0, sizeof ble_svc_ans_new_alert_val, ble_svc_ans_new_alert_val, &ble_svc_ans_new_alert_val_len); return rc; } else if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) { rc = os_mbuf_append(ctxt->om, &ble_svc_ans_new_alert_val, sizeof ble_svc_ans_new_alert_val); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } assert(0); return BLE_ATT_ERR_UNLIKELY; case BLE_SVC_ANS_CHR_UUID16_SUP_UNR_ALERT_CAT: assert(ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR); rc = os_mbuf_append(ctxt->om, &ble_svc_ans_unr_alert_cat, sizeof ble_svc_ans_unr_alert_cat); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; case BLE_SVC_ANS_CHR_UUID16_UNR_ALERT_STAT: if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { rc = ble_svc_ans_chr_write(ctxt->om, sizeof ble_svc_ans_unr_alert_stat, sizeof ble_svc_ans_unr_alert_stat, &ble_svc_ans_unr_alert_stat, NULL); return rc; } else { rc = os_mbuf_append(ctxt->om, &ble_svc_ans_unr_alert_stat, sizeof ble_svc_ans_unr_alert_stat); return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES; } case BLE_SVC_ANS_CHR_UUID16_ALERT_NOT_CTRL_PT: if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) { rc = ble_svc_ans_chr_write(ctxt->om, sizeof ble_svc_ans_alert_not_ctrl_pt, sizeof ble_svc_ans_alert_not_ctrl_pt, &ble_svc_ans_alert_not_ctrl_pt, NULL); if (rc != 0) { return rc; } /* Get command ID and category ID */ cmd_id = ble_svc_ans_alert_not_ctrl_pt[0]; cat_id = ble_svc_ans_alert_not_ctrl_pt[1]; /* Set cat_bit_mask to the appropriate bitmask based on cat_id */ if (cat_id < BLE_SVC_ANS_CAT_NUM) { cat_bit_mask = (1 << cat_id); } else if (cat_id == 0xff) { cat_bit_mask = cat_id; } else { /* invalid category ID */ return BLE_ATT_ERR_UNLIKELY; } switch (cmd_id) { case BLE_SVC_ANS_CMD_EN_NEW_ALERT_CAT: ble_svc_ans_new_alert_cat |= cat_bit_mask; break; case BLE_SVC_ANS_CMD_EN_UNR_ALERT_CAT: ble_svc_ans_unr_alert_cat |= cat_bit_mask; break; case BLE_SVC_ANS_CMD_DIS_NEW_ALERT_CAT: ble_svc_ans_new_alert_cat &= ~cat_bit_mask; break; case BLE_SVC_ANS_CMD_DIS_UNR_ALERT_CAT: ble_svc_ans_unr_alert_cat &= ~cat_bit_mask; break; case BLE_SVC_ANS_CMD_NOT_NEW_ALERT_IMMEDIATE: if (cat_id == 0xff) { /* If cat_id is 0xff, notify on all enabled categories */ for (i = BLE_SVC_ANS_CAT_NUM - 1; i > 0; --i) { if ((ble_svc_ans_new_alert_cat >> i) & 0x01) { ble_svc_ans_new_alert_notify(i, NULL); } } } else { ble_svc_ans_new_alert_notify(cat_id, NULL); } break; case BLE_SVC_ANS_CMD_NOT_UNR_ALERT_IMMEDIATE: if (cat_id == 0xff) { /* If cat_id is 0xff, notify on all enabled categories */ for (i = BLE_SVC_ANS_CAT_NUM - 1; i > 0; --i) { if ((ble_svc_ans_unr_alert_cat >> i) & 0x01) { ble_svc_ans_unr_alert_notify(i); } } } else { ble_svc_ans_unr_alert_notify(cat_id); } break; default: return BLE_SVC_ANS_ERR_CMD_NOT_SUPPORTED; } return 0; } else { rc = BLE_ATT_ERR_UNLIKELY; } return rc; default: assert(0); return BLE_ATT_ERR_UNLIKELY; } } /** * This function must be called with the connection handlewhen a gap * connect event is received in order to send notifications to the * client. * * @param conn_handle The connection handle for the current connection. */ void ble_svc_ans_on_gap_connect(uint16_t conn_handle) { ble_svc_ans_conn_handle = conn_handle; } /** * Adds a new alert to the given category then notifies the client * if the given category is valid and enabled. * * @param cat_flag The id for the category which should should be incremented and notified * @param info_str The info string to be sent to the client with the notification. * @return 0 on success, non-zero error code otherwise. */ int ble_svc_ans_new_alert_add(uint8_t cat_id, const char * info_str) { uint8_t cat_bit_mask; if (cat_id < BLE_SVC_ANS_CAT_NUM) { cat_bit_mask = (1 << cat_id); } else { return BLE_HS_EINVAL; } if ((cat_bit_mask & ble_svc_ans_new_alert_cat) == 0) { return BLE_HS_EINVAL; } ble_svc_ans_new_alert_cnt[cat_id] += 1; return ble_svc_ans_new_alert_notify(cat_id, info_str); } /** * Adds an unread alert to the given category then notifies the client * if the given category is valid and enabled. * * @param cat_flag The flag for the category which should should be incremented and notified * @return 0 on success, non-zero error code otherwise. */ int ble_svc_ans_unr_alert_add(uint8_t cat_id) { uint8_t cat_bit_mask; if (cat_id < BLE_SVC_ANS_CAT_NUM) { cat_bit_mask = 1 << cat_id; } else { return BLE_HS_EINVAL; } if ((cat_bit_mask & ble_svc_ans_unr_alert_cat) == 0) { return BLE_HS_EINVAL; } ble_svc_ans_unr_alert_cnt[cat_id] += 1; return ble_svc_ans_unr_alert_notify(cat_id); } /** * Send a new alert notification to the given category with the * given info string. * * @param cat_id The ID of the category to send the notification to. * @param info_str The info string to send with the notification * @return 0 on success, non-zero error code otherwise. */ static int ble_svc_ans_new_alert_notify(uint8_t cat_id, const char * info_str) { int info_str_len; /* Clear notification to remove old infomation that may persist */ memset(&ble_svc_ans_new_alert_val, '\0', BLE_SVC_ANS_NEW_ALERT_MAX_LEN); /* Set ID and count values */ ble_svc_ans_new_alert_val[0] = cat_id; ble_svc_ans_new_alert_val[1] = ble_svc_ans_new_alert_cnt[cat_id]; if (info_str) { info_str_len = strlen(info_str); if (info_str_len > BLE_SVC_ANS_INFO_STR_MAX_LEN) { /* If info_str is longer than the max string length only * write up to the maximum length */ memcpy(&ble_svc_ans_new_alert_val[2], info_str, BLE_SVC_ANS_INFO_STR_MAX_LEN); } else { memcpy(&ble_svc_ans_new_alert_val[2], info_str, info_str_len); } } return ble_gatts_notify(ble_svc_ans_conn_handle, ble_svc_ans_new_alert_val_handle); } /** * Send an unread alert notification to the given category. * * @param cat_id The ID of the category to send the notificaiton to. * @return 0 on success, non-zer0 error code otherwise. */ static int ble_svc_ans_unr_alert_notify(uint8_t cat_id) { ble_svc_ans_unr_alert_stat[0] = cat_id; ble_svc_ans_unr_alert_stat[1] = ble_svc_ans_unr_alert_cnt[cat_id]; return ble_gatts_notify(ble_svc_ans_conn_handle, ble_svc_ans_unr_alert_val_handle); } static int ble_svc_ans_chr_write(struct os_mbuf *om, uint16_t min_len, uint16_t max_len, void *dst, uint16_t *len) { uint16_t om_len; int rc; om_len = OS_MBUF_PKTLEN(om); if (om_len < min_len || om_len > max_len) { return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN; } rc = ble_hs_mbuf_to_flat(om, dst, max_len, len); if (rc != 0) { return BLE_ATT_ERR_UNLIKELY; } return 0; } /** * Initialize the ANS with initial values for enabled categories * for new and unread alert characteristics. Bitwise or the * catagory bitmasks to enable multiple catagories. * * XXX: We should technically be able to change the new alert and * unread alert catagories when we have no active connections. */ void ble_svc_ans_init(void) { int rc; rc = ble_gatts_count_cfg(ble_svc_ans_defs); assert(rc == 0); rc = ble_gatts_add_svcs(ble_svc_ans_defs); assert(rc == 0); ble_svc_ans_new_alert_cat = 0; ble_svc_ans_unr_alert_cat = 0; }