fix(ble): drop the hand-rolled CCCD that made the command characteristic unsubscribable - #92
Merged
Merged
Conversation
…tic unsubscribable
A browser could connect to a Blox, read it and write to it, but never subscribe
for replies -- and replies are delivered only on the command characteristic, so
no browser could receive any answer at all. Every command appeared to be sent and
then silently timed out.
startNotifications() failed with NotSupportedError, every time, on four
consecutive attempts. Isolating each GATT step from Chrome 151 on Android against
a live Blox:
connect ok
getPrimaryService ok
getCharacteristic ok
writeValueWithResponse ok
startNotifications NotSupportedError
The cause is visible in the descriptors the client sees:
command (00000003-...): 00002902-... + 00002902-... <- two CCCDs
broadcast (00000002-...): 00000003-... + 00002902-... <- one, and it works
BlueZ creates and owns the 0x2902 Client Characteristic Configuration Descriptor
for any characteristic flagged notify or indicate. CommandDescriptor declared a
second one. Subscribing IS a CCCD write, so the client hit the ambiguity; the
broadcast characteristic escaped it because its own descriptor uses a custom UUID.
Verified on hardware before committing: with the descriptor removed and
bluetooth.py restarted, the phone subscribes and the reply arrives.
The mobile app was unaffected throughout, which is why this survived so long --
Android's CCCD write goes through a different path and evidently tolerates the
duplicate.
CommandDescriptor is deleted rather than left unreferenced so that reattaching it
takes a deliberate act. Its WriteValue drove StartNotify/StopNotify by hand and
BlueZ now calls those on the characteristic itself. It was also the only caller of
StartIndicate/StopIndicate, so nothing sets indicating any more and the
indicate flag is now vestigial -- worth removing as a follow-up, left here
because the configuration proven on hardware kept it.
Closes #91.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJyqpFReP1wbz86nsqsoXq
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A browser could connect to a Blox, read it and write to it — but never subscribe for replies. Replies are delivered only on the command characteristic, so no browser could receive any answer at all: every command appeared to send, then silently timed out.
The failure
startNotifications()threwNotSupportedErrorfour times out of four. Isolating each GATT step from Chrome 151 on Android against a live Blox:The cause, from the client's own view of the descriptors
BlueZ creates and owns the 0x2902 Client Characteristic Configuration Descriptor for any characteristic flagged
notifyorindicate.CommandDescriptordeclared a second one. Subscribing is a CCCD write, so the client hit the ambiguity — and the broadcast characteristic escaped it only because its own descriptor uses a custom UUID.Verified on hardware before committing
With the descriptor removed and
bluetooth.pyrestarted on a live Blox, the phone subscribes successfully and the reply arrives. This is not a reasoned fix.The mobile app was unaffected throughout, which is why this survived so long — Android's CCCD write goes through a different path and evidently tolerates the duplicate.
Notes on the diff
CommandDescriptoris deleted rather than left unreferenced, so reattaching it takes a deliberate act. ItsWriteValuedroveStartNotify/StopNotifyby hand; BlueZ now calls those on the characteristic itself. It was also the only caller ofStartIndicate/StopIndicate, so nothing setsindicatingany more and theindicateflag is vestigial — worth removing as a follow-up, left alone here because the configuration proven on hardware kept it.Still outstanding, separately
Subscribing now works, but the log fetch still returns an incomplete reply: the
systemhalf alone is over 15 KB, andprepare_responsehalves its own chunk size with a// 2margin on top of a loop that already measures each encoded chunk and shrinks it on overflow. That doubles the frame count for nothing, and replies travel as unacknowledged notifications. Dropping the margin roughly halves the frames. Happy to open that separately.Closes #91.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YJyqpFReP1wbz86nsqsoXq