Skip to content

Commit 05634c3

Browse files
seankyernashif
authored andcommitted
bluetooth: ANS: Add Alert Notification Service
Add alert notification service (ANS) to Bluetooth subsystem and accompanying sample. Signed-off-by: Sean Kyer <[email protected]>
1 parent 4d6dd7c commit 05634c3

File tree

10 files changed

+901
-0
lines changed

10 files changed

+901
-0
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright (c) 2025 Sean Kyer
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#ifndef ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_ANS_H_
8+
#define ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_ANS_H_
9+
10+
/**
11+
* @brief Alert Notification Service (ANS)
12+
* @defgroup bt_ans Alert Notification Service (ANS)
13+
*
14+
* @since 4.4
15+
* @version 0.1.0
16+
*
17+
* @ingroup bluetooth
18+
* @{
19+
*/
20+
21+
#include <stdint.h>
22+
23+
#ifdef __cplusplus
24+
extern "C" {
25+
#endif
26+
27+
/**
28+
* @brief Command not supported error code
29+
*/
30+
#define BT_ANS_ERR_CMD_NOT_SUP 0xa0
31+
32+
/**
33+
* @brief ANS max text string size in octets
34+
*
35+
* This is the max string size in octets to be saved in a New Alert. Text longer than the max is
36+
* truncated.
37+
*
38+
* section 3.165 of
39+
* https://btprodspecificationrefs.blob.core.windows.net/gatt-specification-supplement/GATT_Specification_Supplement.pdf
40+
*
41+
*/
42+
#define BT_ANS_MAX_TEXT_STR_SIZE 18
43+
44+
/**
45+
* @brief ANS Category ID Enum
46+
*
47+
* Enumeration for whether the category is supported.
48+
*/
49+
enum bt_ans_cat {
50+
BT_ANS_CAT_SIMPLE_ALERT, /**< Simple alerts (general notifications). */
51+
BT_ANS_CAT_EMAIL, /**< Email messages. */
52+
BT_ANS_CAT_NEWS, /**< News updates. */
53+
BT_ANS_CAT_CALL, /**< Incoming call alerts. */
54+
BT_ANS_CAT_MISSED_CALL, /**< Missed call alerts. */
55+
BT_ANS_CAT_SMS_MMS, /**< SMS/MMS text messages. */
56+
BT_ANS_CAT_VOICE_MAIL, /**< Voicemail notifications. */
57+
BT_ANS_CAT_SCHEDULE, /**< Calendar or schedule alerts. */
58+
BT_ANS_CAT_HIGH_PRI_ALERT, /**< High-priority alerts. */
59+
BT_ANS_CAT_INSTANT_MESSAGE, /**< Instant messaging alerts. */
60+
61+
/** @cond INTERNAL_HIDDEN */
62+
BT_ANS_CAT_NUM, /**< Marker for the number of categories. */
63+
/** @endcond */
64+
65+
/* 10–15 reserved for future use */
66+
};
67+
68+
/**
69+
* @brief Set the support for a given new alert category
70+
*
71+
* @param mask The bitmask of supported categories
72+
*
73+
* @return 0 on success
74+
* @return negative error codes on failure
75+
*/
76+
int bt_ans_set_new_alert_support_category(uint16_t mask);
77+
78+
/**
79+
* @brief Set the support for a given unread new alert category
80+
*
81+
* @param mask The bitmask of supported categories
82+
*
83+
* @return 0 on success
84+
* @return negative error codes on failure
85+
*/
86+
int bt_ans_set_unread_support_category(uint16_t mask);
87+
88+
/**
89+
* @brief Send a new alert to remote devices
90+
*
91+
* The new alert is transmitted to the remote devices if notifications are enabled. Each category
92+
* will save the latest call to this function in case an immediate replay is requested via the ANS
93+
* control point.
94+
*
95+
* @note This function waits on a Mutex with @ref K_FOREVER to ensure atomic updates to notification
96+
* structs. To avoid deadlocks, do not call this function in BT RX or System Workqueue threads.
97+
*
98+
* @param conn The connection object to send the alert to
99+
* @param category The category the notification is for
100+
* @param num_new Number of new alerts since last alert
101+
* @param text Text brief of alert, null terminated
102+
*
103+
* @return 0 on success
104+
* @return negative error codes on failure
105+
*/
106+
int bt_ans_notify_new_alert(struct bt_conn *conn, enum bt_ans_cat category, uint8_t num_new,
107+
const char *text);
108+
109+
/**
110+
* @brief Set the total unread count for a given category
111+
*
112+
* The unread count is transmitted to the remote devices if notifications are enabled. Each category
113+
* will save the latest call to this function in case an immediate replay is requested via the ANS
114+
* control point.
115+
*
116+
* @note This function waits on a Mutex with @ref K_FOREVER to ensure atomic updates to notification
117+
* structs. To avoid deadlocks, do not call this function in BT RX or System Workqueue threads.
118+
*
119+
* @param conn The connection object to send the alert to
120+
* @param category The category the unread count is for
121+
* @param unread Total number of unread alerts
122+
*
123+
* @return 0 on success
124+
* @return negative error codes on failure
125+
*/
126+
int bt_ans_set_unread_count(struct bt_conn *conn, enum bt_ans_cat category, uint8_t unread);
127+
128+
#ifdef __cplusplus
129+
}
130+
#endif
131+
132+
/**
133+
* @}
134+
*/
135+
136+
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_ANS_H_ */
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
3+
cmake_minimum_required(VERSION 3.20.0)
4+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
5+
project(peripheral_ans)
6+
7+
target_sources(app PRIVATE
8+
src/main.c
9+
)
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
.. zephyr:code-sample:: ble_peripheral_ans
2+
:name: Peripheral ANS
3+
:relevant-api: bluetooth
4+
5+
Send notification using Alert Notification Service (ANS).
6+
7+
Overview
8+
********
9+
10+
This sample demonstrates the usage of ANS by acting as a peripheral periodically sending
11+
notifications to the connected remote device.
12+
13+
Requirements
14+
************
15+
16+
* A board with Bluetooth LE support
17+
* Smartphone with Bluetooth LE app (ADI Attach, nRF Connect, etc.) or dedicated Bluetooth LE sniffer
18+
19+
Building and Running
20+
********************
21+
22+
To start receiving alerts over the connection, refer to
23+
`GATT Specification Supplement <https://btprodspecificationrefs.blob.core.windows.net/gatt-specification-supplement/GATT_Specification_Supplement.pdf>`_
24+
section 3.12 for byte array to enable/disable notifications and control the service.
25+
26+
See :zephyr:code-sample-category:`bluetooth` samples for details.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CONFIG_LOG=y
2+
CONFIG_UTF8=y
3+
CONFIG_BT=y
4+
CONFIG_BT_PERIPHERAL=y
5+
CONFIG_BT_HCI_ERR_TO_STR=y
6+
CONFIG_BT_DEVICE_NAME="Zephyr Peripheral ANS Sample"
7+
CONFIG_BT_ANS=y
8+
CONFIG_BT_ANS_LOG_LEVEL_DBG=y
9+
CONFIG_BT_ANS_NALRT_CAT_SIMPLE_ALERT=y
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
sample:
2+
name: Bluetooth Peripheral ANS
3+
description: Demonstrates the Alert Notification Service (ANS)
4+
tests:
5+
sample.bluetooth.peripheral_ans:
6+
harness: bluetooth
7+
platform_allow:
8+
- qemu_cortex_m3
9+
- qemu_x86
10+
- nrf52840dk/nrf52840
11+
integration_platforms:
12+
- qemu_cortex_m3
13+
- qemu_x86
14+
- nrf52840dk/nrf52840
15+
tags: bluetooth
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright (c) 2025 Sean Kyer
3+
*
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
#include <zephyr/bluetooth/bluetooth.h>
9+
#include <zephyr/bluetooth/hci.h>
10+
#include <zephyr/bluetooth/conn.h>
11+
#include <zephyr/bluetooth/uuid.h>
12+
#include <zephyr/bluetooth/gatt.h>
13+
#include <zephyr/bluetooth/services/ans.h>
14+
#include <zephyr/logging/log.h>
15+
16+
LOG_MODULE_REGISTER(peripheral_ans, CONFIG_LOG_DEFAULT_LEVEL);
17+
18+
/*
19+
* Sample loops forever, incrementing number of new and unread notifications. Number of new and
20+
* unread notifications will overflow and loop back around.
21+
*/
22+
static uint8_t num_unread;
23+
static uint8_t num_new;
24+
25+
static const struct bt_data ad[] = {
26+
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
27+
BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_ANS_VAL))};
28+
29+
static const struct bt_data sd[] = {
30+
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
31+
};
32+
33+
static void connected(struct bt_conn *conn, uint8_t err)
34+
{
35+
if (err != 0) {
36+
LOG_ERR("Connection failed, err 0x%02x %s", err, bt_hci_err_to_str(err));
37+
return;
38+
}
39+
40+
LOG_INF("Connected");
41+
}
42+
43+
static void disconnected(struct bt_conn *conn, uint8_t reason)
44+
{
45+
LOG_INF("Disconnected, reason 0x%02x %s", reason, bt_hci_err_to_str(reason));
46+
}
47+
48+
static void start_adv(void)
49+
{
50+
int err;
51+
52+
err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
53+
if (err != 0) {
54+
LOG_ERR("Advertising failed to start (err %d)", err);
55+
return;
56+
}
57+
58+
LOG_INF("Advertising successfully started");
59+
}
60+
61+
BT_CONN_CB_DEFINE(conn_callbacks) = {
62+
.connected = connected,
63+
.disconnected = disconnected,
64+
.recycled = start_adv,
65+
};
66+
67+
int main(void)
68+
{
69+
int ret;
70+
71+
LOG_INF("Sample - Bluetooth Peripheral ANS");
72+
73+
ret = bt_enable(NULL);
74+
if (ret != 0) {
75+
LOG_ERR("Failed to enable bluetooth: %d", ret);
76+
return ret;
77+
}
78+
79+
start_adv();
80+
81+
num_unread = 0;
82+
num_new = 0;
83+
84+
/* At runtime, enable support for given categories */
85+
uint16_t new_alert_mask = (1 << BT_ANS_CAT_SIMPLE_ALERT) | (1 << BT_ANS_CAT_HIGH_PRI_ALERT);
86+
uint16_t unread_mask = 1 << BT_ANS_CAT_SIMPLE_ALERT;
87+
88+
ret = bt_ans_set_new_alert_support_category(new_alert_mask);
89+
if (ret != 0) {
90+
LOG_ERR("Unable to set new alert support category mask! (err: %d)", ret);
91+
}
92+
93+
ret = bt_ans_set_unread_support_category(unread_mask);
94+
if (ret != 0) {
95+
LOG_ERR("Unable to set unread support category mask! (err: %d)", ret);
96+
}
97+
98+
while (true) {
99+
static const char test_msg[] = "Test Alert!";
100+
static const char high_pri_msg[] = "Prio Alert!";
101+
102+
num_new++;
103+
104+
ret = bt_ans_notify_new_alert(NULL, BT_ANS_CAT_SIMPLE_ALERT, num_new, test_msg);
105+
if (ret != 0) {
106+
LOG_ERR("Failed to push new alert! (err: %d)", ret);
107+
}
108+
k_sleep(K_SECONDS(1));
109+
110+
ret = bt_ans_notify_new_alert(NULL, BT_ANS_CAT_HIGH_PRI_ALERT, num_new,
111+
high_pri_msg);
112+
if (ret != 0) {
113+
LOG_ERR("Failed to push new alert! (err: %d)", ret);
114+
}
115+
k_sleep(K_SECONDS(1));
116+
117+
ret = bt_ans_set_unread_count(NULL, BT_ANS_CAT_SIMPLE_ALERT, num_unread);
118+
if (ret != 0) {
119+
LOG_ERR("Failed to push new unread count! (err: %d)", ret);
120+
}
121+
122+
num_unread++;
123+
124+
k_sleep(K_SECONDS(5));
125+
}
126+
127+
return 0;
128+
}

subsys/bluetooth/services/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# SPDX-License-Identifier: Apache-2.0
22

3+
zephyr_sources_ifdef(CONFIG_BT_ANS ans.c)
34

45
zephyr_sources_ifdef(CONFIG_BT_DIS dis.c)
56

subsys/bluetooth/services/Kconfig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
menu "GATT Services"
77
depends on BT_CONN
88

9+
rsource "Kconfig.ans"
10+
911
rsource "Kconfig.dis"
1012

1113
rsource "Kconfig.cts"

0 commit comments

Comments
 (0)