Skip to content

Commit 690ef3e

Browse files
committed
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 9d10d67 commit 690ef3e

File tree

10 files changed

+899
-0
lines changed

10 files changed

+899
-0
lines changed
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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.3
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+
BT_ANS_CAT_NUM, /**< Marker for the number of categories. */
62+
63+
/* 10–15 reserved for future use */
64+
};
65+
66+
/**
67+
* @brief Set the support for a given new alert category
68+
*
69+
* @param mask The bitmask of supported categories
70+
*
71+
* @return 0 on success
72+
* @return negative error codes on failure
73+
*/
74+
int bt_ans_set_new_alert_support_category(uint16_t mask);
75+
76+
/**
77+
* @brief Set the support for a given unread new alert category
78+
*
79+
* @param mask The bitmask of supported categories
80+
*
81+
* @return 0 on success
82+
* @return negative error codes on failure
83+
*/
84+
int bt_ans_set_unread_support_category(uint16_t mask);
85+
86+
/**
87+
* @brief Send a new alert to remote devices
88+
*
89+
* The new alert is transmitted to the remote devices if notifications are enabled. Each category
90+
* will save the latest call to this function in case an immediate replay is requested via the ANS
91+
* control point.
92+
*
93+
* @note This function waits on a Mutex with @ref K_FOREVER to ensure atomic updates to notification
94+
* structs. To avoid deadlocks, do not call this function in BT RX or System Workqueue threads.
95+
*
96+
* @param conn The connection object to send the alert to
97+
* @param category The category the notification is for
98+
* @param num_new Number of new alerts since last alert
99+
* @param text Text brief of alert, null terminated
100+
*
101+
* @return 0 on success
102+
* @return negative error codes on failure
103+
*/
104+
int bt_ans_notify_new_alert(struct bt_conn *conn, enum bt_ans_cat category, uint8_t num_new,
105+
const char *text);
106+
107+
/**
108+
* @brief Set the total unread count for a given category
109+
*
110+
* The unread count is transmitted to the remote devices if notifications are enabled. Each category
111+
* will save the latest call to this function in case an immediate replay is requested via the ANS
112+
* control point.
113+
*
114+
* @note This function waits on a Mutex with @ref K_FOREVER to ensure atomic updates to notification
115+
* structs. To avoid deadlocks, do not call this function in BT RX or System Workqueue threads.
116+
*
117+
* @param conn The connection object to send the alert to
118+
* @param category The category the unread count is for
119+
* @param unread Total number of unread alerts
120+
*
121+
* @return 0 on success
122+
* @return negative error codes on failure
123+
*/
124+
int bt_ans_set_unread_count(struct bt_conn *conn, enum bt_ans_cat category, uint8_t unread);
125+
126+
#ifdef __cplusplus
127+
}
128+
#endif
129+
130+
/**
131+
* @}
132+
*/
133+
134+
#endif /* ZEPHYR_INCLUDE_BLUETOOTH_SERVICES_BT_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: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
.. zephyr:code-sample:: ble_peripheral_ans
2+
:name: Peripheral ANS
3+
:relevant-api: bluetooth
4+
5+
Sample application for 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 BLE app (ADI Attach, nRF Connect, etc.) or dedicated BLE sniffer
18+
19+
Building and Running
20+
********************
21+
22+
This sample can be found under :zephyr_file:`samples/bluetooth/peripheral_ans` in the
23+
Zephyr tree.
24+
25+
To start receiving alerts over the connection, refer to
26+
`GATT Specification Supplement <https://btprodspecificationrefs.blob.core.windows.net/gatt-specification-supplement/GATT_Specification_Supplement.pdf>`_
27+
section 3.12 for byte array to enable/disable notifications and control the service.
28+
29+
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: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
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+
/* Sample loops forever, incrementing number of new and unread notifications. Number of new and unread notifications will overflow and loop back around. */
19+
static uint8_t num_unread;
20+
static uint8_t num_new;
21+
22+
static const struct bt_data ad[] = {
23+
BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
24+
BT_DATA_BYTES(BT_DATA_UUID16_ALL, BT_UUID_16_ENCODE(BT_UUID_ANS_VAL))};
25+
26+
static const struct bt_data sd[] = {
27+
BT_DATA(BT_DATA_NAME_COMPLETE, CONFIG_BT_DEVICE_NAME, sizeof(CONFIG_BT_DEVICE_NAME) - 1),
28+
};
29+
30+
static void connected(struct bt_conn *conn, uint8_t err)
31+
{
32+
if (err != 0) {
33+
LOG_ERR("Connection failed, err 0x%02x %s", err, bt_hci_err_to_str(err));
34+
return;
35+
}
36+
37+
LOG_INF("Connected");
38+
}
39+
40+
static void disconnected(struct bt_conn *conn, uint8_t reason)
41+
{
42+
LOG_INF("Disconnected, reason 0x%02x %s", reason, bt_hci_err_to_str(reason));
43+
}
44+
45+
static void start_adv(void)
46+
{
47+
int err;
48+
49+
err = bt_le_adv_start(BT_LE_ADV_CONN_FAST_1, ad, ARRAY_SIZE(ad), sd, ARRAY_SIZE(sd));
50+
if (err != 0) {
51+
LOG_ERR("Advertising failed to start (err %d)", err);
52+
return;
53+
}
54+
55+
LOG_INF("Advertising successfully started");
56+
}
57+
58+
BT_CONN_CB_DEFINE(conn_callbacks) = {
59+
.connected = connected,
60+
.disconnected = disconnected,
61+
.recycled = start_adv,
62+
};
63+
64+
int main(void)
65+
{
66+
int ret;
67+
68+
LOG_INF("Sample - Bluetooth Peripheral ANS");
69+
70+
ret = bt_enable(NULL);
71+
if (ret != 0) {
72+
LOG_ERR("Failed to enable bluetooth: %d", ret);
73+
return ret;
74+
}
75+
76+
start_adv();
77+
78+
num_unread = 0;
79+
num_new = 0;
80+
81+
/* At runtime, enable support for given categories */
82+
uint16_t new_alert_mask = (1 << BT_ANS_CAT_SIMPLE_ALERT) | (1 << BT_ANS_CAT_HIGH_PRI_ALERT);
83+
uint16_t unread_mask = 1 << BT_ANS_CAT_SIMPLE_ALERT;
84+
85+
ret = bt_ans_set_new_alert_support_category(new_alert_mask);
86+
if (ret != 0) {
87+
LOG_ERR("Unable to set new alert support category mask! (err: %d)", ret);
88+
}
89+
90+
ret = bt_ans_set_unread_support_category(unread_mask);
91+
if (ret != 0) {
92+
LOG_ERR("Unable to set unread support category mask! (err: %d)", ret);
93+
}
94+
95+
while (true) {
96+
static const char test_msg[] = "Test Alert!";
97+
static const char high_pri_msg[] = "Prio Alert!";
98+
99+
num_new++;
100+
101+
ret = bt_ans_notify_new_alert(NULL, BT_ANS_CAT_SIMPLE_ALERT, num_new, test_msg);
102+
if (ret != 0) {
103+
LOG_ERR("Failed to push new alert! (err: %d)", ret);
104+
}
105+
k_sleep(K_SECONDS(1));
106+
107+
ret = bt_ans_notify_new_alert(NULL, BT_ANS_CAT_HIGH_PRI_ALERT, num_new,
108+
high_pri_msg);
109+
if (ret != 0) {
110+
LOG_ERR("Failed to push new alert! (err: %d)", ret);
111+
}
112+
k_sleep(K_SECONDS(1));
113+
114+
ret = bt_ans_set_unread_count(NULL, BT_ANS_CAT_SIMPLE_ALERT, num_unread);
115+
if (ret != 0) {
116+
LOG_ERR("Failed to push new unread count! (err: %d)", ret);
117+
}
118+
119+
num_unread++;
120+
121+
k_sleep(K_SECONDS(5));
122+
}
123+
124+
return 0;
125+
}

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)