Skip to content

Hint at a stale Bluetooth serial port when the machine does not respond - #2228

Open
edwin-truthsearch-io wants to merge 1 commit into
artisan-roaster-scope:masterfrom
edwin-truthsearch-io:macos-stale-bluetooth-port
Open

edwin-truthsearch-io wants to merge 1 commit into
artisan-roaster-scope:masterfrom
edwin-truthsearch-io:macos-stale-bluetooth-port

Conversation

@edwin-truthsearch-io

Copy link
Copy Markdown

Addresses the second aspect of #2226 (and the long-standing #1705): recovering from a stale macOS Bluetooth serial port. This does not fix the stale port — it makes the state recognizable and tells the user what to do about it.

What happens today

After the Mac suspends with a Bluetooth serial machine connected, macOS can leave the serial device behind as a stale object. /dev/cu.<machine> still exists and open() still succeeds — instantly — but there is no serial connection behind it any more, so nothing is sent or received. The Kaleido connect loop then repeats

INFO   : connecting to /dev/cu.Roaster_SRSv6@57600 ...
WARNING: connection timeout (/dev/cu.Roaster_SRSv6)

every seven seconds, forever, while the user sits in front of empty readings with no indication of the cause or the cure.

What was measured

On macOS 26.6.2 with a Kaleido on Bluetooth SPP, in the stale state:

attempted recovery result
reopening the port opens in 0.00s, no data at any baud rate, passively or after a ping
Bluetooth off/on the device node survives with the same minor number
power-cycling the machine no change
IOBluetoothDevice.closeConnection() returns success, isConnected() stays true
IOBluetoothDevice.openConnection() no-op, returns instantly
restarting bluetoothd refused: "Operation not permitted while System Integrity Protection is engaged"
removing the pairing and pairing again works — unpairing deletes the device node, and the next connect succeeds in ~2s

So the pairing record owns the serial binding, and re-pairing is the only remedy available to a user. Worth noting for anyone reading #1705: toggling Bluetooth is not what fixes it — it only makes the unpaired machine visible again so it can be paired.

The change

The Kaleido serial transport counts connect attempts that opened the port but never received a response. After three, it logs a hint and reports it through a new optional unresponsive_handler, which the canvas uses to tell the user:

Kaleido does not respond. If connected via Bluetooth, remove the machine in the system Bluetooth settings and pair it again.

The hint is raised once per unresponsive phase and re-armed by a successful connect, so a machine that is simply switched off does not repeat the message on every reconnect attempt. An unresponsive machine and a stale port are indistinguishable from the application's side, which is why the wording is conditional rather than assertive.

Notes

  • Only the Kaleido serial transport is covered, as that is where the behaviour was reproduced and measured. The same pattern would fit AsyncComm, which has the identical failure mode, and I am happy to extend it if wanted.
  • This touches serial_connect(), as does Keep the machine connection alive across system standby #2225, so depending on merge order a small conflict resolution may be needed. The two changes are otherwise independent.
  • Tests cover the threshold, that the hint is raised only once, and that both a successful connect and start() re-arm it. Full suite 2376 passed / 7 skipped; ruff, pylint (10.00/10), mypy, pyright and codespell clean.

🤖 Generated with Claude Code

On macOS a Bluetooth serial port can survive a system suspension as a stale
device: /dev/cu.<machine> still exists and opens successfully, but there is no
serial connection behind it any longer, so nothing is sent or received. The
connect loop then repeats "connection timeout" forever while the user is left
with empty readings and no indication of what to do. The port can only be
re-established by removing the machine in the system Bluetooth settings and
pairing it again (neither toggling Bluetooth nor reconnecting the device
rebuilds it, and the Bluetooth daemon cannot be restarted under SIP).

- counts the connect attempts of the Kaleido serial transport that opened the
  port but received no response from the machine and, after three of them,
  logs a hint and reports it to the user via the new unresponsive_handler
- the hint is raised only once per unresponsive phase and re-armed by a
  successful connect, so a machine that is simply switched off does not repeat
  the message on every reconnect attempt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant