Skip to content

Add ANCS example#1133

Merged
h2zero merged 1 commit intomasterfrom
ancs
Mar 29, 2026
Merged

Add ANCS example#1133
h2zero merged 1 commit intomasterfrom
ancs

Conversation

@h2zero
Copy link
Copy Markdown
Owner

@h2zero h2zero commented Mar 29, 2026

Summary by CodeRabbit

  • New Features
    • Added a new ANCS (Apple Notification Center Service) example demonstrating Bluetooth Low Energy communication with Apple devices for receiving real-time notifications.
    • Retrieve comprehensive notification details including message title, content, and timestamp information.
    • Interactive incoming call management with serial input controls for accepting or rejecting calls.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 29, 2026

📝 Walkthrough

Walkthrough

A new Arduino sketch implementing an ANCS (Apple Notification Center Service) BLE client for ESP32 has been added. The sketch initializes BLE advertising, handles device connections, subscribes to ANCS notification characteristics, parses incoming notification packets, and processes user input for call accept/reject actions via serial commands.

Changes

Cohort / File(s) Summary
ANCS BLE Implementation
examples/ANCS/ANCS.ino
New sketch implementing ANCS protocol with BLE server callbacks, notification source/data source subscriptions, control point command writing, and serial-driven call response handling. Includes UUID configuration, client state management, and notify callback functions.

Sequence Diagram

sequenceDiagram
    participant Device as ESP32 Device
    participant BLEServer as BLE Server
    participant RemoteClient as Remote BLE Client
    participant Serial as Serial Input
    participant ControlPoint as ANCS Control Point

    Device->>BLEServer: Initialize & Start Advertising
    RemoteClient->>BLEServer: Connect
    BLEServer->>Device: onConnect Callback
    Device->>RemoteClient: Subscribe to Notification Source
    Device->>RemoteClient: Subscribe to Data Source
    
    RemoteClient->>Device: Send Notification (0x00 = New)
    Device->>Device: Parse & Store Message ID
    Device->>Device: Set pendingNotification Flag
    
    Device->>ControlPoint: Write Attributes Request (title/message/date)
    RemoteClient->>Device: Data Source Notify with Attributes
    Device->>Device: Log Notification Data
    
    alt Incoming Call Detected
        Serial->>Device: User Input ('1' or '0')
        Device->>ControlPoint: Write Call Response Command
        Device->>Device: Clear incomingCall Flag
    end
    
    Device->>Device: Reset pendingNotification
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

A sketch takes flight on wireless wings,
Notifications dance through BLE rings,
Calls arrive, decisions made,
Apple's service, well displayed! 📱✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add ANCS example' directly and clearly summarizes the main change: addition of a new ANCS (Apple Notification Center Service) example sketch to the repository.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ancs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@examples/ANCS/ANCS.ino`:
- Around line 36-47: In NotificationSourceNotifyCallback, guard accesses into
pData by first checking pData != nullptr and that length is at least the number
of bytes you read (use length >= 8 since you read pData[0], pData[2], and
pData[4..7]); if the buffer is too short, log/ignore and return early. Update
the start of NotificationSourceNotifyCallback to validate these conditions
before using pData or writing into latestMessageID to avoid out-of-bounds reads.
- Around line 88-99: The code raises pendingNotification for every event but
only updates latestMessageID in the "new notification" branch, causing
control-point requests to use a stale/all-zero UID for modified/removed events
and never clearing incomingCall on removal. Fix by extracting and copying the
UID from the incoming pData into latestMessageID in the pData[0] == 1 (modified)
and pData[0] == 2 (removed) branches before setting pendingNotification or
issuing control-point actions; for the removed (pData[0] == 2) branch also clear
incomingCall when pData[2] indicates a call gone; finally, only set
pendingNotification when a valid (non-zero) UID was extracted to avoid using a
stale UID. Ensure you reference and update variables latestMessageID,
pendingNotification, and incomingCall in the modified branches.
- Around line 109-111: The session loop that uses while(1) never exits when the
peer drops because onDisconnect only logs; update onDisconnect(NimBLEServer*
pServer, NimBLEConnInfo& connInfo, int reason) to signal the main code (e.g.,
set a volatile/global flag like deviceConnected = false or call
pServer->removePeer(connInfo.getConnHandle()) if appropriate) and then change
the infinite while(1) session loop to check that signal or the server connection
count (use NimBLEServer::getConnCount() or the deviceConnected flag) and
break/return when the connection is lost so the sketch can clean up and return
to the reconnect path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5eec18ca-f86e-4094-a08b-c5bbd36e2fc9

📥 Commits

Reviewing files that changed from the base of the PR and between d161f35 and 7db9eaa.

📒 Files selected for processing (1)
  • examples/ANCS/ANCS.ino

Comment on lines +36 to +47
static void NotificationSourceNotifyCallback(NimBLERemoteCharacteristic* pNotificationSourceCharacteristic,
uint8_t* pData,
size_t length,
bool isNotify) {
if (pData[0] == 0) {
Serial.println("New notification!");
latestMessageID[0] = pData[4];
latestMessageID[1] = pData[5];
latestMessageID[2] = pData[6];
latestMessageID[3] = pData[7];

switch (pData[2]) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Arrr, guard the notification packet before ye index fixed offsets.

pData[0], pData[2], and pData[4..7] are read without checking length. A short or malformed callback payload turns this into an out-of-bounds read in the BLE path.

🛠️ Patch fer this
 static void NotificationSourceNotifyCallback(NimBLERemoteCharacteristic* pNotificationSourceCharacteristic,
                                              uint8_t*                    pData,
                                              size_t                      length,
                                              bool                        isNotify) {
+    if (length < 8) {
+        Serial.printf("Unexpected Notification Source length: %u\n", static_cast<unsigned>(length));
+        return;
+    }
+
     if (pData[0] == 0) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ANCS/ANCS.ino` around lines 36 - 47, In
NotificationSourceNotifyCallback, guard accesses into pData by first checking
pData != nullptr and that length is at least the number of bytes you read (use
length >= 8 since you read pData[0], pData[2], and pData[4..7]); if the buffer
is too short, log/ignore and return early. Update the start of
NotificationSourceNotifyCallback to validate these conditions before using pData
or writing into latestMessageID to avoid out-of-bounds reads.

Comment on lines +88 to +99
} else if (pData[0] == 1) {
Serial.println("Notification Modified!");
if (pData[2] == 1) {
Serial.println("Call Changed!");
}
} else if (pData[0] == 2) {
Serial.println("Notification Removed!");
if (pData[2] == 1) {
Serial.println("Call Gone!");
}
}
pendingNotification = true;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Arrr, don’t reuse a stale UID for modified/removed events.

pendingNotification is raised for every event, but latestMessageID is only refreshed in the pData[0] == 0 branch. If a modified/removed event arrives first, Lines 172-180 send control-point requests for a stale or all-zero UID; the removed-call branch also never drops incomingCall.

🛠️ Patch fer this
     } else if (pData[0] == 1) {
+        latestMessageID[0] = pData[4];
+        latestMessageID[1] = pData[5];
+        latestMessageID[2] = pData[6];
+        latestMessageID[3] = pData[7];
         Serial.println("Notification Modified!");
         if (pData[2] == 1) {
             Serial.println("Call Changed!");
         }
     } else if (pData[0] == 2) {
         Serial.println("Notification Removed!");
         if (pData[2] == 1) {
             Serial.println("Call Gone!");
+            incomingCall = false;
         }
+        pendingNotification = false;
+        return;
     }
     pendingNotification = true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (pData[0] == 1) {
Serial.println("Notification Modified!");
if (pData[2] == 1) {
Serial.println("Call Changed!");
}
} else if (pData[0] == 2) {
Serial.println("Notification Removed!");
if (pData[2] == 1) {
Serial.println("Call Gone!");
}
}
pendingNotification = true;
} else if (pData[0] == 1) {
latestMessageID[0] = pData[4];
latestMessageID[1] = pData[5];
latestMessageID[2] = pData[6];
latestMessageID[3] = pData[7];
Serial.println("Notification Modified!");
if (pData[2] == 1) {
Serial.println("Call Changed!");
}
} else if (pData[0] == 2) {
Serial.println("Notification Removed!");
if (pData[2] == 1) {
Serial.println("Call Gone!");
incomingCall = false;
}
pendingNotification = false;
return;
}
pendingNotification = true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ANCS/ANCS.ino` around lines 88 - 99, The code raises
pendingNotification for every event but only updates latestMessageID in the "new
notification" branch, causing control-point requests to use a stale/all-zero UID
for modified/removed events and never clearing incomingCall on removal. Fix by
extracting and copying the UID from the incoming pData into latestMessageID in
the pData[0] == 1 (modified) and pData[0] == 2 (removed) branches before setting
pendingNotification or issuing control-point actions; for the removed (pData[0]
== 2) branch also clear incomingCall when pData[2] indicates a call gone;
finally, only set pendingNotification when a valid (non-zero) UID was extracted
to avoid using a stale UID. Ensure you reference and update variables
latestMessageID, pendingNotification, and incomingCall in the modified branches.

Comment on lines +109 to +111
void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
Serial.printf("Client disconnected: %s, reason: %d\n", connInfo.getAddress().toString().c_str(), reason);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Arrr, this session loop never leaves a dead connection.

while (1) ignores link state, and onDisconnect() only logs. After the peer drops, the sketch stays marooned in the old session instead of returning to the normal reconnect path.

🛠️ Patch fer this
     void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) {
         Serial.printf("Client disconnected: %s, reason: %d\n", connInfo.getAddress().toString().c_str(), reason);
+        pendingNotification = false;
+        incomingCall        = false;
+        acceptCall          = 0;
+        pClient             = nullptr;
     }
-        while (1) {
+        while (pClient != nullptr && pClient->isConnected()) {
-                while (incomingCall) {
+                while (incomingCall && pClient != nullptr && pClient->isConnected()) {

Also applies to: 166-208

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@examples/ANCS/ANCS.ino` around lines 109 - 111, The session loop that uses
while(1) never exits when the peer drops because onDisconnect only logs; update
onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) to
signal the main code (e.g., set a volatile/global flag like deviceConnected =
false or call pServer->removePeer(connInfo.getConnHandle()) if appropriate) and
then change the infinite while(1) session loop to check that signal or the
server connection count (use NimBLEServer::getConnCount() or the deviceConnected
flag) and break/return when the connection is lost so the sketch can clean up
and return to the reconnect path.

@h2zero h2zero merged commit 1b7a21d into master Mar 29, 2026
42 checks passed
@h2zero h2zero deleted the ancs branch March 29, 2026 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant