ANCS User Guide

Introduction to ANCS

ANCS is a notification service protocol for iOS devices, allowing Bluetooth accessories (such as watches) to receive and manage notifications from iOS, achieving cross-device notification alerts and interaction. For more details, please refer to: Apple Notification Center Service (ANCS) Specification

ANCS comprises three core characteristics, each responsible for different data interaction functions:

  • Notification Source Primarily used to push basic information about notifications, including events such as arrival, update, and removal. The push content includes (in order):

    • EventID: Event type (new notification, modified, removed, etc.)

    • EventFlags: Notification status flags (e.g., high priority, silent, etc.)

    • CategoryID: Message category (e.g., incoming call, SMS, calendar, etc.)

    • CategoryCount: Count of similar notifications

    • NotificationUID: Unique identifier for the notification (used for subsequent detail queries)

  • Control Point The client (watch) interacts with the iOS device by writing commands, serving as the entry point for obtaining notification attributes, app attributes, and executing actions.

  • Data Source The channel through which the iOS device proactively sends detailed content or operation results, commonly including:

    • Detailed notification attributes: After the watch initiates a 'Get Notification Details' command through the Control Point, iOS returns the required field content via the Data Source.

    • App attribute information: After the watch initiates a command through the Control Point, iOS returns the app's attributes (such as the name) via the Data Source.

    • Notification operation feedback: After the watch initiates an operation command (such as clear, open) through the Control Point, iOS returns the operation result via the Data Source.

SDK-based ANCS Call Process

  1. Add ANCS service initialization in app_ble_service_init()

    • Call ancs_init() and client_init() to complete the ANCS client initialization.

      client_init(1);
      ancs_init(1);
      
  2. Add ANCS message type handling in watch_handle_io_message()

    • Support ANCS message dispatch by calling ancs_handle_msg().

      #if F_BT_ANCS_CLIENT_SUPPORT
         case IO_MSG_TYPE_ANCS:
            {
                  ancs_handle_msg(p_watch_msg);
            }
            break;
      #endif
      
  3. Add ANCS service discovery in app_handle_authen_state_evt()

    • After pairing/authentication is complete, trigger ANCS service discovery via ancs_start_discovery().

      #if F_BT_ANCS_CLIENT_SUPPORT
            ancs_start_discovery(conn_id);
      #endif
      
  4. After discovering the service, subscribe to Notification Source and Data Source in ancs_client_cb()

    • Subscribe to 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;
      
    • Subscribe to 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. After successful subscription, parse Notification Source data.

    • The mobile phone pushes Notification Source, use app_parse_notification_source_data() to parse the content.

      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;
      
    • In the parsing function, message filtering and categorization can be implemented based on 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: silent %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. Parse the Data Source content.

    • After receiving the required Data Source data, use app_parse_data_soucre_notifications() for further processing.

      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_soucre_notifications(conn_id, p_cb_data->cb_content.notify_data.p_value,
                                             p_cb_data->cb_content.notify_data.value_size);
         break;
      
    • The complete data can be obtained in app_parse_data_soucre_notifications(), and the processing flow can be chosen based on the parsing state.

      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
      
      
      // Determine whether to parse notification_attribute or app_attribute based on m_parse_state
         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() distinguishes between two parsing processes based on m_parse_state:

      1. Notification Attribute Parsing

        • Calls app_parse_notification_attribute(), parsing command_id, notification_uid, attribute_id, attribute_len, and attribute_data step by step.

          //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;
          
        • Use app_handle_notification_attribute_data() to further parse and display content, such as title, body, etc.

          // Different attributes can display different data information
          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;
          

        Note

        The type of data information displayed can be determined based on attribute_id.

      2. App Attribute Parsing

        • Call app_parse_app_attribute() to parse the app attribute data in the Data Source, similar logic to app_parse_notification_attribute().