ANCS 使用说明

ANCS 简介

ANCS 是 iOS 设备的消息通知服务协议,允许蓝牙配件(如手表)接收和管理 iOS 端的通知,实现跨设备的通知提醒与交互。详细信息可参见: Apple 通知中心服务(ANCS)规范

ANCS 包含三个核心特征,分别负责不同的数据交互功能:

  • Notification Source 主要用于推送通知的基本信息,包括通知的到达、更新与移除等事件。推送内容包括(按顺序):

    • EventID:事件类型(新通知、已修改、已移除等)

    • EventFlags:通知状态位(如是否高优先、静音等)

    • CategoryID:消息分类(如来电、短信、日历等)

    • CategoryCount:同类通知计数

    • NotificationUID:通知唯一标识(用以后续查询详情)

  • Control Point 客户端(手表)通过写命令方式与 iOS 设备交互,是获取通知属性、App 属性和执行操作的入口。

  • Data Source iOS 设备用于主动下发详细内容或操作结果的通道,常见包括:

    • 通知属性详细内容:手表通过 Control Point 发起“获取通知详情”命令后,iOS 通过 Data Source 返回所需字段内容。

    • App 属性信息:手表通过 Control Point 发起命令后,iOS 通过 Data Source 返回 App 的属性(如名称)。

    • 通知操作反馈:手表向 Control Point 发起执行操作命令(如清除、打开)后,iOS 通过 Data Source 返回操作结果。

基于 SDK 的 ANCS 调用流程

  1. app_ble_service_init() 中添加 ANCS 服务初始化

    • 调用 ancs_init()client_init() 完成 ANCS 客户端初始化。

      client_init(1);
      ancs_init(1);
      
  2. watch_handle_io_message() 中添加 ANCS 消息类型处理

    • 支持 ANCS 消息分发,调用 ancs_handle_msg()

      #if F_BT_ANCS_CLIENT_SUPPORT
         case IO_MSG_TYPE_ANCS:
            {
                  ancs_handle_msg(p_watch_msg);
            }
            break;
      #endif
      
  3. app_handle_authen_state_evt() 中添加 ANCS 服务发现

    • 配对/认证完成后,通过 ancs_start_discovery() 触发 ANCS 服务发现。

      #if F_BT_ANCS_CLIENT_SUPPORT
            ancs_start_discovery(conn_id);
      #endif
      
  4. 发现服务后,在 ancs_client_cb() 订阅 Notification Source 和 Data Source

    • 订阅 Notification Source:

      case ANCS_WRITE_DATA_SOURCE_NOTIFY_ENABLE:
         APP_PRINT_INFO0("ANCS_WRITE_DATA_SOURCE_NOTIFY_ENABLE");
         ancs_set_notification_source_notify(conn_id, true);//Used by application, to set the notification flag of notification source.
         break;
      
    • 订阅 Data Source:

      case DISC_ANCS_DONE:
         APP_PRINT_INFO0("ANCS BLE Client CB: discover procedure done.");
         ancs_set_data_source_notify(conn_id, true);//Used by application, to set the notification flag of data source.
         break;
      
  5. 订阅成功后,解析 Notification Source 数据

    • 手机端推送 Notification Source,使用 app_parse_notification_source_data() 解析内容。

      case ANCS_FROM_NOTIFICATION_SOURCE:
         APP_PRINT_INFO2("ANCS_FROM_NOTIFICATION_SOURCE: conn_id %d, length  %d",
                        conn_id, p_cb_data->cb_content.notify_data.value_size);
         app_parse_notification_source_data(conn_id, p_cb_data->cb_content.notify_data.p_value,
                                             p_cb_data->cb_content.notify_data.value_size);
         break;
      
    • 在解析函数内可根据 CategoryID 实现消息筛选与分类。

      APP_PRINT_INFO5("app_parse_notification_source_data: event_id %d, event_flags 0x%02x, category_id %d, category_count %d, notification_uid 0x%08x",
                        ns_data.event_id,
                        ns_data.event_flags,
                        ns_data.category_id,
                        ns_data.category_count,
                        ns_data.notification_uid
                     );
      APP_PRINT_INFO5("event_flags: slient %d, important %d, pre existing %d, positive action %d, negative action %d ",
                        ns_data.event_flags & NS_EVENT_FLAG_SILENT,
                        ns_data.event_flags & NS_EVENT_FLAG_IMPORTANT,
                        ns_data.event_flags & NS_EVENT_FLAG_PRE_EXISTING,
                        ns_data.event_flags & NS_EVENT_FLAG_POSITIVE_ACTION,
                        ns_data.event_flags & NS_EVENT_FLAG_NEGATIVE_ACTION
                     );
      
  6. 解析 Data Source 内容

    • 收到所需 Data Source 数据后,使用 app_parse_data_source_notifications() 进一步处理。

      case ANCS_FROM_DATA_SOURCE:
         APP_PRINT_INFO2("ANCS_FROM_DATA_SOURCE: conn_id %d, length  %d",
                        conn_id, p_cb_data->cb_content.notify_data.value_size);
         app_parse_data_source_notifications(conn_id, p_cb_data->cb_content.notify_data.p_value,
                                             p_cb_data->cb_content.notify_data.value_size);
         break;
      
    • 可在 app_parse_data_source_notifications() 获取完整数据并根据解析状态选择处理流程。

      APP_PRINT_INFO2("ANCS_FROM_DATA_SOURCE: conn_id %d, len =%d", conn_id, len);
      #if F_BT_ANCS_CLIENT_DEBUG
         APP_PRINT_INFO1("data = %b", TRACE_BINARY(len, p_data));
      #endif
         T_APP_ANCS_LINK *p_ancs_link = &ancs_link_table[conn_id];
      
      #if F_BT_ANCS_CLIENT_DEBUG
         APP_PRINT_INFO1("m_parse_state %d", p_ancs_link->m_parse_state);
      #endif
      
      
      //根据 m_parse_state 来判断是进行 notification_attribute 的解析,还是进行 app_attribute 解析
         if (p_ancs_link->m_parse_state < DS_PARSE_GET_APP_COMMAND_ID)
         {
            app_parse_notification_attribute(p_ancs_link, p_data, len);
         }
      #if F_BT_ANCS_GET_APP_ATTR
         else
         {
            app_parse_app_attribute(p_ancs_link, p_data, len);
         }
      #endif
      
    • app_parse_data_source_notifications() 会根据 m_parse_state 区分两类解析流程:

      1. Notification Attribute 解析

        • 调用 app_parse_notification_attribute(),分步骤解析 command_id、notification_uid、attribute_id、attribute_len 及 attribute_data。

          //command id
                case DS_PARSE_GET_NOTIFICATION_COMMAND_ID:
                      p_ancs_link->notification_attr.command_id = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_UID1;
                      APP_PRINT_INFO1("parse notify attr: command_id %d", p_ancs_link->notification_attr.command_id);
                      break;
          //notification uid
                case DS_PARSE_UID1:
                      p_ancs_link->notification_attr.notification_uid[0] = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_UID2;
                      APP_PRINT_INFO1("parse notify attr: notification_uid[0] %d", p_ancs_link->notification_attr.notification_uid[0]);
                      break;
          
                case DS_PARSE_UID2:
                      p_ancs_link->notification_attr.notification_uid[1] = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_UID3;
                      APP_PRINT_INFO1("parse notify attr: notification_uid[1] %d", p_ancs_link->notification_attr.notification_uid[1]);
                      break;
          
                case DS_PARSE_UID3:
                      p_ancs_link->notification_attr.notification_uid[2] = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_UID4;
                      APP_PRINT_INFO1("parse notify attr: notification_uid[2] %d", p_ancs_link->notification_attr.notification_uid[2]);
                      break;
          
                case DS_PARSE_UID4:
                      p_ancs_link->notification_attr.notification_uid[3] = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_ATTRIBUTE_ID;
                      APP_PRINT_INFO1("parse notify attr: notification_uid[3] %d", p_ancs_link->notification_attr.notification_uid[3]);
                      break;
          //attribute id/attribute len
                case DS_PARSE_ATTRIBUTE_ID:
                      p_ancs_link->notification_attr.attribute_id = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_ATTRIBUTE_LEN1;
                      break;
          
                case DS_PARSE_ATTRIBUTE_LEN1:
                      p_ancs_link->notification_attr.attribute_len = p_data[i];
                      p_ancs_link->m_parse_state = DS_PARSE_ATTRIBUTE_LEN2;
                      break;
          
                case DS_PARSE_ATTRIBUTE_LEN2:
                      p_ancs_link->notification_attr.attribute_len |= (p_data[i] << 8);
                      p_ancs_link->m_parse_state = DS_PARSE_ATTRIBUTE_READY;
                      p_ancs_link->ptr = p_ancs_link->notification_attr.data;
                      p_ancs_link->current_len = 0;
          #if F_BT_ANCS_CLIENT_DEBUG
                      APP_PRINT_INFO2("parse notify attr: attribute_id %d, attribute_len %d",
                                     p_ancs_link->notification_attr.attribute_id,
                                     p_ancs_link->notification_attr.attribute_len
                                     );
          #endif
          
          //attribute data
                case DS_PARSE_ATTRIBUTE_READY:
                      *p_ancs_link->ptr++ = p_data[i];
                      p_ancs_link->current_len++;
          
                      if (p_ancs_link->current_len == p_ancs_link->notification_attr.attribute_len)
                      {
                         /*An attribute is always a string whose length in bytes is provided in the tuple but that is not NULL-terminated.*/
                         *p_ancs_link->ptr++ = 0;
          #if F_BT_ANCS_CLIENT_DEBUG
                         APP_PRINT_INFO1("parse notify attr: data %b",
                                        TRACE_BINARY(p_ancs_link->notification_attr.attribute_len,
                                                       p_ancs_link->notification_attr.data));
          #endif
                         app_handle_notification_attribute_data (p_ancs_link);
                      }
                      break;
          
        • app_handle_notification_attribute_data() 进一步解析展示内容,如标题、正文等。

          //不同 attribute 可以展示出不同的数据信息
          typedef enum
          {
             DS_NOTIFICATION_ATTR_ID_APP_IDENTIFIER = 0, /**< Format: UTF-8 strings */
             DS_NOTIFICATION_ATTR_ID_TITLE = 1, /**<Format: UTF-8 strings. Needs to be followed by a 2-bytes max length parameter */
             DS_NOTIFICATION_ATTR_ID_SUB_TITLE = 2, /**<Format: UTF-8 strings. Needs to be followed by a 2-bytes max length parameter */
             DS_NOTIFICATION_ATTR_ID_MESSAGE = 3, /**<Format: UTF-8 strings. Needs to be followed by a 2-bytes max length parameter */
             DS_NOTIFICATION_ATTR_ID_MESSAGE_SIZE = 4, /**<The format of the DS_NOTIFICATION_ATTR_ID_MESSAGE_SIZE constant is a string
             that represents the integral value of the message size. */
             DS_NOTIFICATION_ATTR_ID_DATE = 5, /**<The format of the DS_NOTIFICATION_ATTR_ID_DATE constant is a string
             that uses the Unicode Technical Standard (UTS) #35 date format pattern yyyyMMdd'T'HHmmSS. */
             DS_NOTIFICATION_ATTR_ID_POSITIVE_ACTION_LABEL = 6, /**<Format: UTF-8 strings. The label used to describe
             the positive action that can be performed on the iOS notification. */
             DS_NOTIFICATION_ATTR_ID_NEGATIVE_ACTION_LABEL = 7, /**<Format: UTF-8 strings. The label used to describe
             the negative action that can be performed on the iOS notification. */
             DS_NOTIFICATION_ATTR_ID_RESERVED = 255
          } T_DS_NOTIFICATION_ATTR_ID;
          

        备注

        可根据 attribute_id 判断展示的数据信息类型。

      2. App Attribute 解析

        • 调用 app_parse_app_attribute() 对 Data Source 中的 app 属性数据进行解析,逻辑与 app_parse_notification_attribute() 类似。