← AK-mee™ · Case study · Internal IP

Car Thing

Superbird resurrection — discontinued hardware, brought back as a fleet-aware Matrix display.

Spotify retired the Car Thing in late 2024. We turned ours into a 480×800 living UI with Spotify playback control, fleet messaging overlay, an OTA pipeline, and a full desktop simulator — all wired through a Bluetooth-PAN bridge to the lab network.

Python 3.7FastAPIWebSocketBlueZ / BT PANADBSpotify Web APIAstro

Why this was interesting

Most discontinued consumer hardware is bricked by one of three things: a dead cloud service, a sealed bootloader, or a network stack that can't be coerced into talking to anything else. The Car Thing — Spotify's Superbird — was hit by the first. The bootloader was open. The 480×800 capacitive panel was healthy. The Amlogic S905D2 was running a real kernel. There was nothing wrong with it that a different reason to exist couldn't fix.

The build

The first decision was the network path. The device has a USB gadget interface — useful for ADB and power, useless for IP. It has Bluetooth, but the kernel ships with the BNEP module built in and no user-space helpers. So I made the lab Linux box the internet bridge: BlueZ running with the --compat flag, a Python NAP server, dnsmasq on the bridge interface, and a tiny pairing agent that auto-accepts. Two things bit me here. First, the SDP NAP record (UUID 0x1116) has to be re-registered after every bluetoothd start, or the device's BlueZ never exposes its Network1 interface and the connect call silently fails. Second, modern systemd renames bnep0 to something like enxAA…, which breaks the bridge silently — fix is one /etc/systemd/network/90-bnep.link file.

On the device side, we don't have Python dbus bindings, but dbus-send is on the PATH, so connect is a shell-out: dbus-send … org.bluez.Network1.Connect string:nap. Static IP, default route, and a 30-second watchdog. From boot to playable Spotify on a cold device takes about 12 seconds.

The UI is a single-file HTML app served by a Python HTTP server running locally on the device. The webapp polls a /state endpoint and posts commands to /cmd — the same endpoints the rotary encoder, GPIO buttons, and capacitive touch all flow through. Aesthetic is Matrix rain over a portrait album-art card; the brand wordmark sits on the screen as a quiet watermark. We wrote a separate desktop simulator that runs the same HTML at the same 480×800 dimensions in a browser, against a stand-alone Spotify server using spotipy.SpotifyOAuth. Build-test-fix on the simulator, sideload to the device, done.

Spotify control is two-tier. On the device, an HTTP client refreshes an authorization-code token every hour and posts to /me/player/play, /me/player/pause, /me/player/next. On the workstation, a FastAPI service keeps a WebSocket open to any connected dashboard and republishes now-playing state at 2-second cadence. The fleet messaging overlay is the same channel — any machine on the lab network can POST /api/message and it lands as a green subtitle on the device within a frame.

System architecture

Car Thing system architecture Spotify cloud connects to a Mac mini OAuth proxy, which bridges to a Linux box providing BT PAN, which connects to the Car Thing display running a local UI. Spotify Cloud accounts.spotify.com api.spotify.com OAuth · Web API Workstation (Mac mini) FastAPI server.py :8888 spotipy SpotifyOAuth /api/message broadcast ADB · OTA trigger refresh-token cache · 1h cycle Bridge (Linux box) bluetoothd --compat sdptool add NAP (0x1116) bt-nap.py · pan0 bridge dnsmasq 10.0.0.2-10 iptables NAT → enp1s0 auto-accept pairing agent Car Thing Superbird · S905D2 · 480×800 dude_local.py :9999 webapp/index.html /state · /cmd · /announce supervisord chromium · backlight bt watchdog · 30s /dev/input/event0 GPIO /dev/input/event1 rotary /dev/input/event3 touch AK-mee HTTPS Tailscale · LAN BT PAN · bnep0 10.0.0.2/24 USB · ADB · OTA sideload Legend Bidirectional API / data LAN / Tailscale Out-of-band (debug · OTA) All traffic stays on owned hardware. No cloud broker. AK-mee™ fleet messaging uses the same WebSocket as Spotify state.

Lessons learned

  • Discontinued hardware is a system problem, not a firmware problem. The Superbird itself was the easy part. The hard part was the path between it and the internet — a Linux box running BlueZ in compat mode, an SDP record that has to be re-registered every boot, and a kernel that silently renames the bridge interface unless you nail it down with a systemd link file.
  • Build the simulator before you ship to the device. The 480×800 webapp runs in a desktop browser at the same dimensions, with the same touch and dial event model, against a stand-alone simulator server. Iteration loop dropped from minutes-per-cycle (sideload, restart, reconnect) to seconds.
  • Static IP everywhere the kernel is old enough to argue. udhcpc on Linux 4.9.113 is unreliable on Bluetooth bridge interfaces. We assign 10.0.0.2/24 and a default route by hand on every connect. A 30-second watchdog thread re-runs the connect sequence if the link drops.
  • OTA without an app store. A small shell script on the device pulls a manifest from the project repo, compares git SHAs, and rsyncs only the files that changed. Trigger is one ADB command from any machine on Tailscale. No build server, no CDN, no mystery.
  • Treat third-party APIs as a moving target. Mid-build, Spotify deprecated implicit-grant OAuth, banned localhost redirect hosts, and renamed half the playlist endpoints. We logged the changes, pinned to authorization-code-with-secret, and switched our redirect to 127.0.0.1. Worth keeping a per-device drift log for exactly this kind of upstream change.

What's next

The Car Thing is now the always-on display in the lab — Spotify control on the rotary, fleet status overlay across the bottom, AK-mee™ wordmark glowing under the Matrix rain. Next stops are a generalized phone-tethering profile so the same image runs untethered from box, and treating the device as a reference for our wider embedded display work — anything portrait, anything constrained, anything that needs a UI without an app store.

← Back to AK-mee™