Skip to content

pb-service: aio-rfcomm 1.1, and say when macOS is asking for Bluetooth - #8

Merged
dlech merged 4 commits into
mainfrom
claude/aio-rfcomm-1-1-f3f0e1
Oct 18, 2026
Merged

dlech merged 4 commits into
mainfrom
claude/aio-rfcomm-1-1-f3f0e1

Conversation

@dlech

@dlech dlech commented Oct 18, 2026

Copy link
Copy Markdown
Member

Moves pb-service to aio-rfcomm 1.1.0, released today, and handles the one change in it that users see. On macOS, opening Bluetooth no longer waits for the user to answer the permission dialog. Until they answer, the availability is AWAITING_PERMISSION.

Before this change, a pb scan or pb connect started while that dialog was open waited ten seconds. It then said "No hubs found. Is the hub paired with this computer?" or told the user to pair the hub, although the hub was paired.

Three commits:

  • Require aio-rfcomm 1.1: the floor and uv.lock. First, so that each commit passes on its own.
  • Say a Bluetooth permission dialog is open, and wait for its answer:
    • Reason as a word: transports name why an adapter can't be used in one word, one per aio-rfcomm Availability (UnavailableDevice.availability). The system's own sentence is kept alongside it.
    • New hub.adapters notification: while a scan or connect is looking, the service reports the adapters it can't use. Adding a notification doesn't move the protocol version.
    • Held time limit: while permission is being asked and nothing has been found yet, the service holds the search's time limit still, for at most 2 minutes. A Mac with nobody at it leaves the dialog open for good.
    • Errors: BLUETOOTH_UNAVAILABLE errors carry the word in data.
    • pb-client: Service.watching_adapters().
    • CLI: pb scan and pb connect say at once that macOS is asking, under the name the dialog shows, and carry on when the dialog is answered. When Bluetooth can't be used, the suggestion now depends on the reason, rather than one suggestion covering every reason.
    • Smaller fixes:
      • A failed open of Bluetooth keeps its reason instead of always reading "unsupported".
      • The RFCOMM transport replaces what it reports at the end of each pass, so a watching client doesn't see it flicker.
      • Each scan starts without the previous scan's state.
    • Fake radio: it gains a reason, so tests can open it already awaiting permission.
  • Ask for Bluetooth as "Pybricks RFCOMM Service": plain "Pybricks" in Privacy & Security reads as the Pybricks app or Pybricks Code. macOS remembers the answer against the name, so everyone is asked once more, and the old "Pybricks" entry stays until it is removed there. The CLI repeats the name, and a test keeps the two copies equal.

Tested on macOS 26.6.2 with a Pybricks EV3, firmware 4.1.0:

  • Dialog: under the never-used new name, the real dialog appeared. pb scan --transport rfcomm printed "macOS is asking whether Pybricks RFCOMM Service may use Bluetooth. Answer the dialog to carry on." 2 s in, and carried on after Allow.
  • Scan to disconnect: scan, connect, status, run and disconnect all work against aio-rfcomm's final main, which is what 1.1.0 was released from.
  • Test suite: each commit passes against 1.1.0 from PyPI, with ruff and pyright clean.

Known, and not addressed here:

  • After bluetoothd restarts, macOS forgets every paired device's stored services. pb then still says "Is the hub paired?" until something connects to the hub. A separate change, being worked on, lists bricks by name in that case.
  • The changelogs are not updated here.

dlech added 3 commits October 18, 2026 17:19
1.1 stops waiting for the answer to macOS's Bluetooth permission dialog and
reports AWAITING_PERMISSION meanwhile, says NOT_ALLOWED rather than
UNSUPPORTED for a refused permission, and names the program in both
messages. What follows depends on the first: until now a scan started while
the dialog was open sat waiting and then told the user to pair the hub.
…ts answer

On macOS the first use of Bluetooth Classic asks the user whether the
service may use it, and with aio-rfcomm 1.1 nothing can be listed until
they answer. A scan or connect started meanwhile ran out after ten seconds
and told the user to pair a hub that was paired.

Transports now name why an adapter cannot be used in a word, one per
aio-rfcomm Availability, alongside the system's sentence. While a search
runs, the service reports the adapters it cannot use as hub.adapters, and
while permission is being asked and nothing has been found it holds the
search's time limit still, for up to two minutes. Bluetooth errors carry
the word too.

pb-client follows those with watching_adapters(). pb scan and pb connect
say at once that macOS is asking, under the name the dialog shows, and
carry on when the dialog is answered. When Bluetooth cannot be used, the
suggestion now fits the reason rather than covering every reason at once.

A failed open keeps its reason instead of always reading unsupported. The
RFCOMM transport replaces what it reports at the end of each pass, so a
client watching does not see it flicker, and starts each scan without the
last one's.
macOS shows this name in the permission dialog and under Privacy &
Security. Plain "Pybricks" reads there as the Pybricks app or Pybricks
Code, and says nothing about what is asking or why. What asks is the part
of the service that reaches EV3 and NXT hubs over Bluetooth Classic.

macOS remembers the answer against the name, so everyone is asked once
more, and the old "Pybricks" entry stays in Privacy & Security until it is
removed there.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

Queued searches can inherit another search’s adapter state, causing unrelated permission prompts and incorrect timeout extensions.

1 open issue
What changed in this PR

Updates Bluetooth discovery for aio-rfcomm 1.1 so users receive permission guidance while scans and connections wait for an answer.

Changes:

  • Adds adapter availability notifications and bounded permission-related timeout extensions.
  • Exposes adapter watching in the client and reason-specific guidance in the CLI.
  • Renames the macOS permission identity and adds regression coverage.
File Description
uv.lock Locks aio-rfcomm 1.1.0.
tests/​e2e/​test_hub_lifecycle.py Tests permission waiting and recovery.
tests/​e2e/​test_commands.py Tests CLI permission guidance.
packages/​pb-service/​tests/​test_rfcomm_transport.py Tests availability reasons and recovery.
packages/​pb-service/​tests/​test_rfcomm_lazy.py Tests preserved opening errors and permission transitions.
packages/​pb-service/​src/​pb_service/​methods/​hub.py Reports adapter state and extends search deadlines.
packages/​pb-service/​src/​pb_service/​hub/​transport.py Adds structured availability reasons.
packages/​pb-service/​src/​pb_service/​hub/​rfcomm/​transport.py Publishes stable adapter availability state.
packages/​pb-service/​src/​pb_service/​hub/​rfcomm/​lazy.py Preserves Bluetooth opening failure reasons.
packages/​pb-service/​src/​pb_service/​hub/​rfcomm/​fake.py Simulates specific availability states.
packages/​pb-service/​src/​pb_service/​backend.py Sets the macOS permission identity.
packages/​pb-service/​pyproject.toml Requires aio-rfcomm 1.1.
packages/​pb-core/​src/​pb_core/​protocol.py Defines adapter notifications.
packages/​pb-client/​src/​pb_client/​service.py Adds adapter watching.
packages/​pb-client/​src/​pb_client/​models.py Adds the unusable-adapter model.
packages/​pb-client/​src/​pb_client/​__init__.py Exports the adapter model.
packages/​pb-cli/​tests/​test_errors.py Tests availability advice and permission naming.
packages/​pb-cli/​src/​pb_cli/​errors.py Provides reason-specific Bluetooth advice.
packages/​pb-cli/​src/​pb_cli/​commands/​hub.py Displays permission prompts during searches.

Review effort: Balanced

Comment thread packages/pb-service/src/pb_service/methods/hub.py
Only one scan runs at a time, so a search arriving during another waits
for the radio. Until it has it, what the transports report is the other
search's. A USB-only search queued behind an RFCOMM one held by the
permission dialog was told about that dialog, had its own time limit held
by it, and, if its time ran out in the queue, failed with that search's
Bluetooth error.

HubManager.discover() now says when a scan has the radio. Until then the
search follows nothing, holds nothing, and explains an empty result only as
nothing found.
@dlech
dlech requested a balanced review from Copilot October 18, 2026 22:49

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@dlech
dlech merged commit 324a941 into main Oct 18, 2026
23 checks passed
@dlech
dlech deleted the claude/aio-rfcomm-1-1-f3f0e1 branch October 18, 2026 22:51
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.

2 participants