Ancs ble fixes - #20
Conversation
Delay BLE service discovery until the link is encrypted instead of starting it immediately after connection. Some phones appear to reject or ignore ANCS setup while pairing/security is still in progress, leaving notifications stuck until Bluetooth is toggled on the phone. Also make ANCS descriptor setup more robust by discovering CCCDs one at a time, using each characteristic's actual handle range, subscribing sequentially, and finishing discovery only once subscription state is known.
Start BLE service discovery only after the connection has reached an encrypted state instead of using the generic BleConnected delay. iOS only exposes and authorizes ANCS after pairing/security is complete, so the old timer-based path could race the pairing flow and miss ANCS entirely. In that case the notification permission dialog would not appear until a later Bluetooth reconnect. Also tighten the ANCS setup path so descriptor discovery and CCCD writes are serialized and scoped to the actual Notification Source and Data Source characteristics. This removes the bogus write to the ANCS service end handle and avoids treating the Control Point as a subscribable characteristic. Finally, fix the advertising data to describe ANCS correctly. InfiniTime is an ANCS client, not an ANCS server, so the ANCS UUID should not be advertised as a local service. Keep DFU in the local 128-bit service UUID list and place ANCS in the scan response as a 128-bit Service Solicitation UUID. This keeps the legacy advertising payload within size limits and should make LE scanners such as bluetoothctl behave more reliably.
|
@cyberneel can you look over this :)) |
mark9064
left a comment
There was a problem hiding this comment.
Really appreciate you being transparent with using LLM assistance
InfiniTime does not have a formal AI contribution policy. Therefore I will review this as normal for now
Importantly, you must understand the code you're submitting. If you couldn't have written it yourself, please learn how and why it actually works, and fix the inevitable codex bugs that get revealed by doing this (or decide not to continue with the PR)
I will try and review this properly soon but I'm short on time to go over BLE minutiae right now :)
| // Services discovery is deferred from 3 seconds to avoid the conflicts between the host communicating with the | ||
| // target and vice-versa. I'm not sure if this is the right way to handle this... | ||
| isBleDiscoveryStarted = true; | ||
| // Service discovery is started after link security is established. Keep a short defer so the security callback can unwind. |
There was a problem hiding this comment.
This sounds pretty dubious.. could you explain?
There was a problem hiding this comment.
Codex suggests keeping the 5 tick delay even though we defer discovery until after a secure link had been established. Seems unnecessary to me, so in the minimal change I have removed the timer entirely.
|
Completely understandable. AI-generated code can be troublesome. I could not have written this myself. I have quite a bit of experience with the Arduino ecosystem and its libraries but I have not used freertos before and I have no experience with nimble. Just as I have no experience with Swift so I could not have suggested the Ancs enabled yes/no fix for InfiniLink without AI assistance. I have a personal interest in getting this PR to a mergable state (would be nice to not maintain the patch locally forever) as well as a non-personal interest because I think the community would benefit from having it fixed. I’ll just need some time (probably a few weeks) to get sufficiently deep into the stack. I’ll post follow-ups whenever I have something to share. |
|
Sounds great :) feel free to drop any questions here / in the chat channels if I can be of help |
|
I'm a relative outsider here; I know C++ and I have done many reviews (though mostly for Python and JavaScript projects), but I have no experience with InfiniTime, firmware in general or Apple APIs. I'm following this PR regardless because @liamcharger suggested that it might still add value if I did a review. I just started an attempt at doing a review, but quickly realized that the code churn in I would in principle be able to do that effort and that might even enable us to condense the patch to a smaller diff. However, I won't have time to do it until the end of June at the earliest. @mark9064 did you already do this as part of your review? |
|
Yeah, the bonus refactoring is a codex classic :D I haven't done more than glance at it so far. Looks like I'll be pretty busy for a while too And yes, review is by far the bottleneck in InfiniTime so anyone happy to help makes a huge difference :) whatever thoughts you have, it'd be great to hear them |
|
I think you can hold off on reviewing, I’ll se what I can do to remove some of the noise first. |
|
Alright, I've been digging into this a bit further. The ble fixes contain 3 elements.
This is the large, hard to review change:
I'm tempted to only keep (1) for now, since it looks like that addresses the immediate problem and keeps the change small. We can always do the other ones later if we want to. I have tested with only (1). When I pair in InfiniLink, I am prompted for the pairing code. A few seconds after entering the pairing code, I get the notification to allow the watch to read notifications. After accepting that, notifications work. So all good. Any opinions? And if I proceed, would you prefer that I create a new PR with the new change? For now, I have reverted the prior commits in this PR and only kept (1) from above, to keep all the history in this PR but it feels a bit messy. |
|
Kudos on figuring this out and doing it so soon! Maybe create a second branch that has all three changes (basically a copy of your original branch without the reverts), then simply reset/rebase the current branch to only contain d52638d and force-push. That way, you can keep the history clean without losing the other changes. You can also keep it in the current PR in that way, which is good for keeping all discussion in one place. To me, change 2 looks a bit as if codex might be just inventing a problem. Regardless, I would suggest creating separate issues for changes 2 and 3, referencing the code changes that codex generated in order to address them, and letting time tell whether those are actual problems that actually need addressing. 13+/15- looks like a very manageable diff, I should be able to review that soon. If everyone agrees that we're going with just that change. |
|
So TIL Apple provides a "Require ANCS" connection flag when connecting to the watch. From my understanding, when iOS initiates the connection and this flag is enabled, it prioritizes bringing up ancs right off the bat. Without it, it may only expose well after the bond has been established I can confirm with the flag set ancs is enabled immediately and notifications come through on the first connection without this pr. The only thing I'm concerned about is if connections will hang for infinitime versions without ancs support. Because with this change, iOS only hands the peripheral off to the app after the connection is fully completed and ancs is up and running. It seems to handle it fine, but I've pushed a commit to InfiniLink that includes a switch in developer settings to toggle this flag so others can test |
|
I’ve done some more testing. Without (3) I cannot discover (and therefore not pair) the watch using bluetoothctl on my Linux laptop. And without pairing no dfu so I had to boot into the recovery firmware. |
Does it work fine on a build without ANCS? |
|
This is what I have observed:
|
jgonggrijp
left a comment
There was a problem hiding this comment.
Keeping in mind my previously mentioned caveats (familiar with C++ and reviews but not with ANCS, InfiniTime or embedded software), here is my review of the condensed changes.
I find it credible that these changes will improve/enable successful ANCS discovery. I'm somewhat concerned that the code is not yet robust against unsuccessful pairings (see second code comment below). However, I don't think this would be a regression compared to the previous situation, so as far as I'm concerned, that is not a reason not to merge this.
| connectionHandle = event->connect.conn_handle; | ||
| bleController.Connect(); | ||
| systemTask.PushMessage(Pinetime::System::Messages::BleConnected); | ||
| // Service discovery is deferred via systemtask |
| isBleDiscoveryTimerRunning = true; | ||
| bleDiscoveryTimer = 5; | ||
| isBleDiscoveryStarted = false; | ||
| break; | ||
| case Messages::BleStartDiscovery: | ||
| if (!isBleDiscoveryStarted) { | ||
| isBleDiscoveryStarted = true; | ||
| nimbleController.StartDiscovery(); | ||
| } |
There was a problem hiding this comment.
My one concern with this change is that only the success case is explicitly covered in the code. When the nimbleController starts discovering, the isBleDiscoveryStarted member is set to true; when the discovery is successful and a connection is made, it is set back to false. I can imagine that discovery might time out or that a connection might fail, but neither of those conditions is represented in the Messages enum or in members of the SystemTask class. NimbleController might have this information (I did not check whether this is the case), but SystemTask does not seem to access the information if it exists. As far as I can tell, that means that SystemTask will keep the isBleDiscoveryStarted == true state indefinitely when things don't go as planned.
|
Bug thanks for reviewing. I’ll look deeper i to your comments when I’m at a computer. Might be a few days. Will report back here. |
|
I haven’t forgotten about this, but finding time while the kids are home is proving difficult. I’ll report as soon as I can. |
Sorry, there were two commits to fix the ble ancs issues. The patch isn't that small unfortunately. But it works, and maybe it is possible to make it smaller before merging by removing changes that aren't strictly required.
Disclosure: I used Codex to find the problem and implement the patch.