Skip to content

Bluetooth pairing overlay, transport badge, smaller artwork - #31

Open
edward-rosado wants to merge 6 commits into
ItsRiprod:mainfrom
edward-rosado:feature/bluetooth-transport-client
Open

Bluetooth pairing overlay, transport badge, smaller artwork#31
edward-rosado wants to merge 6 commits into
ItsRiprod:mainfrom
edward-rosado:feature/bluetooth-transport-client

Conversation

@edward-rosado

Copy link
Copy Markdown

Bluetooth pairing overlay, transport badge, smaller artwork

Companion to the server-side Bluetooth transport PR (ItsRiprod/DeskThing#152 — the RFCOMM tunnel that lets a Car Thing run on wall power alone).

What this does

  • Pairing code overlay: when the computer initiates Bluetooth pairing, the device's screen shows the 6-digit numeric-comparison code fullscreen — the original Car Thing pairing experience — then flashes the outcome. State comes from the on-device pairing agent's local endpoint (127.0.0.1:8892/pairing); on anything that is not a provisioned Car Thing the endpoint doesn't exist, the first two polls fail, and the overlay permanently stands down.
  • Transport badge: while connected, the connection overlay names the link actually carrying data (BT / USB). Probed, not assumed: GET /__bt is answered locally by the Bluetooth mux when it owns the client port, so a JSON reply means Bluetooth while a 404 means the request travelled to the server over USB. Degrades gracefully without the server-side mux (probe 404s → badge reads USB).
  • Smaller album art: prefers Spotify's 300×300 artwork variant over 640×640 on the proxied thumbnail path. The panel is 800×480, so the larger asset is wasted bytes — about 0.7s of a saturated ~155 KB/s Bluetooth link on every track change. Non-Spotify URLs are untouched.

🤖 Generated with Claude Code

Edward Rosado and others added 3 commits August 4, 2026 23:31
Adds a small badge to the connection overlay naming the link that is
actually carrying data. The transport is probed, not assumed: /__bt is
answered locally by the Bluetooth mux when it owns the client port, so
a JSON reply means Bluetooth while a 404 means the request travelled to
the server over USB — no server support required, the badge simply
reads USB without it.

Also prefers Spotify's 300x300 artwork variant over the 640x640 one on
the proxied thumbnail path. The panel is 800x480, so the larger asset
is wasted bytes — about 0.7s of a saturated ~155KB/s Bluetooth link on
every track change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
When the computer initiates pairing, the on-device agent exposes the
6-digit numeric-comparison code at 127.0.0.1:8892/pairing; this overlay
polls it and draws the code fullscreen, then flashes the outcome — the
original Car Thing pairing experience. On anything that is not a
provisioned Car Thing the endpoint does not exist, the first two polls
fail, and the overlay permanently stands down.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A pairing exchange can resolve in a couple of seconds; at a 2s poll the
overlay could miss the whole window (observed on a real pairing — the
device agent captured the code but the screen never showed it). Poll
every 500ms so the code is on screen the entire time the person is
comparing it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
closeExisting only closed a socket in the OPEN state, so a socket stuck
in CONNECTING — which is what happens when the server is unreachable at
dial time, the norm on a Bluetooth link that comes up ~80s after the
device boots — was abandoned without closing. Those half-open sockets
lingered, held a tunnel stream, and kept the client from cleanly
reconnecting once the link was finally up. Over USB the server is
reachable instantly so this never surfaced.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edward-rosado

Copy link
Copy Markdown
Author

Companion PRs:

The size preference was only applied in the startsWith('http') branch,
but that branch is not the one Spotify artwork takes. The DeskThing
server rewrites thumbnails to a relative /proxy/v1?url=... before
sending them (songCache.ts:124), so the client lands in the
startsWith('/') branch, which only prefixed the host. The optimization
was dead code and the device kept fetching 640x640.

Caught by screenshotting the device and reading the actual <img> src:
ab67616d0000b273 (640px) where 00001e02 (300px) was expected.

Two changes: match the bare Spotify id prefix instead of '/image/<id>',
because by the time we see the URL the slashes are usually percent-
encoded inside the proxy query while the id is not; and apply it in the
relative branch too.

Verified on hardware over Bluetooth: the element now loads
ab67616d00001e02... at 300x276 instead of 640x588, and the art renders.
That is ~110KB down to ~36KB per track change on a link that saturates
at 155KB/s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edward-rosado

Copy link
Copy Markdown
Author

Pushed a correction: the artwork optimization in this PR was dead code. It never ran.

The size preference was only applied in the startsWith('http') branch of setSong, but that is not the branch Spotify artwork takes. The DeskThing server rewrites thumbnails to a relative /proxy/v1?url=... before sending them (songCache.ts:124), so the client lands in the startsWith('/') branch, which only prefixed the host — leaving the 640×640 URL untouched.

Found by screenshotting the device and reading the actual <img> src, which showed ab67616d0000b273 (640px) where 00001e02 (300px) was expected. Nothing in the logs indicated a problem.

Two changes: match the bare Spotify id prefix rather than /image/<id>, since by the time the client sees the URL the slashes are percent-encoded inside the proxy query while the id is not; and apply the preference in the relative branch as well.

Verified on hardware over Bluetooth — the element now loads ab67616d00001e02... at 300×276 instead of 640×588, and the art renders correctly. That is ~110KB down to ~36KB per track change, on a link that saturates at 155KB/s.

Measured on hardware: 11.9s from page load until the track and artwork
were on screen. Two independent causes, both fixed.

The client never asked for song state. requestMusicData was only called
from inside the handler for an incoming song message, so the only path
to data was an unsolicited push — after any (re)connect the screen sat
on "Waiting For Track…" until the track happened to change or the
server's refresh interval came round. It now requests the current track
as soon as the socket reports connected.

Reconnect took a flat 15s before even trying. onclose waited 5s before
calling reconnect(), which waited a further 10s before dialling, with no
fast first attempt — so a link that dropped and returned immediately
still cost 15 seconds. Now 250ms to the first attempt and exponential
backoff from 500ms to a 10s ceiling, reset on a successful open.

Both are general client behaviour, not Bluetooth-specific, but Bluetooth
makes them constantly visible: the link legitimately drops when the
device is power-cycled, and it has no battery, so every power change is
a reconnect.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@edward-rosado

Copy link
Copy Markdown
Author

Pushed 961de70, which fixes something the artwork change made easy to see: after any reconnect the screen sat on "Waiting For Track…" until the track happened to change.

The client never asked for the current track. The only path to song data was an unsolicited push, so a freshly-connected client waited for whatever came first — a track change, or the server's refresh cadence. Measured on a Car Thing over Bluetooth: 11,938ms from navigation to a rendered track.

Two changes:

  • Ask for the current track as soon as the socket connects (websocketListener.tsx). This is the actual fix.
  • Reconnect promptly. The old path waited 5s in onclose and a further flat 10s in reconnect(), so the first retry was 15s after a drop however briefly the link had gone away. Now 250ms, then exponential backoff from 500ms to a 10s ceiling, reset on connect.

Same measurement after: 2,631ms, and repeated runs cluster tightly at 2,460 / 2,495 / 2,525ms. Of what remains, ~2.1s is the page load itself on the device's Chromium 69 — only ~400ms is data fetch. Getting below that means attacking bundle parse time, which is separate work.

This matters most over Bluetooth, where the link legitimately drops and returns, but it is not Bluetooth-specific — any client that reconnects hits it.

Worth reading alongside ItsRiprod/DeskThing#153, which fixes the server-side half: a track change took a median of ~11s to be noticed at all. Together, a track change now reaches the screen in about a second. I verified the server→device leg separately and it is essentially zero (−0.27s, within clock skew), so the delay was never the radio link.

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