|
#include <assert.h>
|
#include <string.h>
|
|
#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;
|
}
|