Skip to content

fix(macos): keep classic inquiry running and emit resolved names - #19

Merged
fotiDim merged 7 commits into
mainfrom
fix/macos-continuous-classic-scan
Oct 20, 2026
Merged

fotiDim merged 7 commits into
mainfrom
fix/macos-continuous-classic-scan

Conversation

@fotiDim

@fotiDim fotiDim commented Oct 19, 2026

Copy link
Copy Markdown
Contributor

Problem

On macOS, IOBluetoothDeviceInquiry only runs for inquiryLength seconds (3 s in this plugin). startScan() started a single round and never restarted it, while isScanning() reported the instantaneous inquiry state. Consumers that wait for a specific device (for example a camera in pairing mode) could miss the only scan window and then wait forever, even though the radio was idle.

A second race: deviceInquiryDeviceFound fires as soon as a device is found, before macOS resolves its name. Listeners that filter by name dropped that event, and deviceInquiryDeviceNameUpdated only printed the resolved name without re-emitting the device.

Changes

  • Track scanRequested and treat it as the source of truth for isScanning(), including the restart delay.
  • Restart the inquiry on the main queue after a 1 s pause when it completes while a scan is still requested, respecting Apple's inquiry throttling and leaving time for name resolution.
  • Stop scanning immediately and cancel any pending restart from stopScan().
  • Re-emit onDeviceDiscover from deviceInquiryDeviceNameUpdated so name-filtered listeners receive the device once its name is available.
  • Propagate inquiry.start() failures from startScan instead of silently doing nothing.
  • Add an Unreleased changelog entry.

No Dart API or Pigeon changes.

Verification

  • flutter pub get
  • flutter analyze
  • flutter test
  • flutter test --platform chrome
  • cd example && flutter build macos --debug
  • End-to-end in the BT Cam macOS app: scanning keeps cycling until the Nikon camera becomes classic-discoverable, and a device seen before its name resolves is paired on the name update.

IOBluetoothDeviceInquiry stops after inquiryLength seconds, so a single startScan only scanned for 3 seconds and then went silent until stopScan. Restart the inquiry while a scan is requested, report scan intent from isScanning, and surface start failures.

Also re-emit devices from deviceInquiryDeviceNameUpdated so listeners filtering by name see devices whose name was not resolved on first discovery.
macOS throttles back-to-back IOBluetoothDeviceInquiry starts. Retry a failed restart instead of ending the scan, and use a generation counter so stopScan or a new startScan cancels pending restarts.
@fotiDim

fotiDim commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Added a follow-up commit: macOS throttles back-to-back inquiries, so a failed restart is now retried instead of ending the scan. A generation counter cancels pending restarts on stopScan or a new startScan. Inquiry length is 5 s with a 1 s pause, reducing throttle pressure while keeping discovery latency low.

macOS can report a found device with its address as the name and never deliver a name update, so name-filtered consumers never see it. Disable automatic name updating, issue remote name requests after each inquiry stops, re-emit the device when the name resolves, and delay the next inquiry until requests finish (capped at 8s).
@fotiDim

fotiDim commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Final follow-up: macOS could keep reporting a device as name=3C:BE:E1:49:0C:66 (address) with no name update. The plugin now disables automatic name updating, issues remoteNameRequest after the inquiry stops, re-emits the device on remoteNameRequestComplete, and holds the next inquiry up to 8 s until name requests finish. Verified end-to-end in BT Cam: Nikon Z30 discovered and paired repeatedly on macOS.

openConnection can return I/O Timeout on the first page after classic discovery. Use a 10s page timeout and retry up to three times before reporting failure.
@fotiDim

fotiDim commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

One more macOS reliability fix: openConnection can return I/O Timeout on the first page right after classic discovery. pair now uses a 10 s page timeout and retries up to 3 times before failing. Verified in BT Cam with repeated Nikon Z30 pairings after this change.

Move inquiry lifecycle, name resolution, and restart policy into MacInquiryScanner, and bond retries into MacPairer. UniversalBluetoothPlugin now only adapts the Pigeon channels and SDP/HID lifecycle.
- Clear pending name requests on stop and restart, and ignore their late
  callbacks, so no onDeviceDiscover fires after stopScan.
- Report a device once per scan and again only when its name changes.
- Ignore inquiry completions from a previous scan session, so a late
  aborted completion cannot clear the state of a new inquiry.
- Bound remote name requests with an explicit page timeout so they finish
  before the inquiry restarts.
- Retry pairing only on a timeout or a busy controller; a rejected
  pairing now fails immediately instead of prompting again.
- Document the continuous macOS scan and its discovery semantics.
@fotiDim
fotiDim merged commit 553bf52 into main Oct 20, 2026
2 checks passed
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