Conversation
📝 WalkthroughWalkthroughA 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
Sequence DiagramsequenceDiagram
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
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
examples/ANCS/ANCS.ino
| 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]) { |
There was a problem hiding this comment.
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.
| } 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; |
There was a problem hiding this comment.
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.
| } 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.
| void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) { | ||
| Serial.printf("Client disconnected: %s, reason: %d\n", connInfo.getAddress().toString().c_str(), reason); | ||
| } |
There was a problem hiding this comment.
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.
Summary by CodeRabbit