An unofficial, single-file web app for controlling a BUSY Bar® over its local HTTP API.
Everything lives in one barpilot.html - no install, no server, no build step, no dependencies.
Double-click it on Windows or macOS and it talks straight to your bar.
Not affiliated. BarPilot is an independent community project. It is not affiliated with, endorsed by, or sponsored by Flipper Devices Inc. "BUSY Bar" is a product of Flipper Devices; it is referenced here only to describe compatibility.
The official apps are limited (and there's no Windows app at all), but the device ships a full local HTTP API with permissive CORS - which means a plain HTML file opened from disk can drive every feature. BarPilot covers all 53 API endpoints, then keeps going: meeting mode, pixel art, a 92-animation gallery, playlists, and a raw API console.
- Download
barpilot.html(or grab it from the latest release). - Double-click it - it opens in your browser (Chrome, Edge, Firefox, Safari).
- Put your bar's IP in the Device field (USB networking or Wi-Fi both work). The dot goes green when it's connected.
If your browser is picky about file:// pages, serve the folder instead:
python3 -m http.server → http://localhost:8000/barpilot.html
Light and dark themes and two colour-blind palettes are in the top bar. BarPilot checks for new releases daily - a green ⬆ chip appears in the top bar when one is out, and clicking it updates in place (Chrome/Edge) or downloads the new file. Settings live in your browser and survive updates.
| Section | Covers |
|---|---|
| Quick Status | Messages - one list, two ways to use it: send a single line or rotate the whole list (live crypto prices, per-line colours, inline stamps + animated stamps placed where you type them), weekly busy schedule (recurring auto-meeting blocks), night mode (dim / display-off / away-message on a timer or nightly schedule), fully configurable one-click presets (text, icon, colour, flashing, LED, duration, on-bar icon - ON AIR / FOCUS / DND / AVAILABLE / COME IN / ON A CALL / BACK AT…), scrolling messages, meeting mode (flashing red banner + live countdown + blinking LED, pickable banner text), END BUSY reset, desktop shortcut generator (.cmd / .command) |
| Calendar | ICS calendar feeds → automatic meeting mode: Google, Outlook / Microsoft 365 (incl. Teams meetings), iCloud, Fastmail, Calendly, 8x8/Zoom-synced events - the bar goes BUSY when events start (configurable lead, event title as banner, duration cap; daily/weekly recurrence). On-call hook: auto-BUSY while you're actually in a call - on Windows two independent detectors (the OS mic/camera-usage records in both registry hives, plus live audio capture sessions - so it works even where newer Teams builds stop writing the records), on macOS ControlCenter's sensor attributions (any app using mic/camera), and a local webhook (curl 127.0.0.1:8765/hook?state=on) wires up everything else (Stream Deck, Shortcuts, watchers). Banner text, display flash and LED blink are configurable (changing the banner mid-call re-labels the live banner), the count-up matches the real call length even when the banner starts mid-call, and a "what does Windows see?" debug view shows the raw detection data when something misbehaves. Feeds and call state go through the local helper - providers block direct browser fetches |
| Dashboard | device / firmware / system / power status, log dump |
| Timer | start/pause/resume/stop sessions (simple, infinite, interval), both timer profiles, raw snapshot JSON |
| Draw | full canvas composer: text, rectangles, images, animations, countdowns; priority, LED blink, JSON mode; audio; asset upload |
| Pixel Art | paint on a 72×16 or 80×80 grid, import & auto-fit images (animated GIFs become animation frames automatically on Chrome/Edge), 231 stamps in 10 packs, a 5×7 pixel-font text stamper, ASCII-art import/export |
| Animations | 92 built-in animations with live-preview gallery - 64 full-bar scenes (space, aquarium, city, weather, cats, dogs, lizards, memes…) + 28 sprite loops. Play/stop, 2-8 fps, clock overlay, periodic clock breaks, rotation playlists with seamless switches and skip controls (buttons, mouse wheel on the now-playing chip, arrow keys). Worker-driven, so background tabs keep animating. Prepare the bar stores every frame on the bar itself, so playback sends only tiny commands (rotation switches and skips cost nothing), animations are deterministic (what you preview is what plays) and the one that was playing resumes after a reload. Physical-switch aware: flip the bar's mode switch off OFF and overlays yield instantly; flip back and they resume |
| Games | Snake, Pong, Tetris, Breakout, Space Invaders, Frogger and Simon played live on the bar with your keyboard (priority 70), score shown on the bar itself (snake: top-right apple count · pong: digits flanking the net), local preview canvas |
| Sounds | the bar has a speaker: a soundboard of built-in sounds (chimes, alerts, the Imperial March opening...) plus your own - record from the microphone or drop in audio files, converted to the bar's .snd format in the browser. Sounds are stored on the bar itself by Prepare the bar, so playing one is a single command |
| Claude | Ask Claude - questions from the app, answers scroll on the bar (claude-opus-5, your own API key). Usage monitor - gauges on the bar in your color (default Claude orange) from three sources: this app's own usage, your Claude Pro/Max plan (a tiny downloadable Python helper serves rolling 5-hour / weekly totals from Claude Code's local transcripts - and if you optionally hand it your Claude Code OAuth token, your official plan percentages; browsers can't call claude.ai cross-origin, hence the helper), or the Anthropic Admin API (org-wide) |
| Scenes | save Draw payloads as named scenes, replay/export/import, daily scheduler |
| Screen | live view of both displays, PNG snapshots, big-screen LED mirror, raw status WebSocket monitor |
| Input | virtual remote (all 10 keys) + keyboard control - entering the section auto-pauses overlays so the bar's own menus are visible |
| Storage | browse /ext, upload/download/rename/delete, mkdir, usage |
| Settings / Time / Wi-Fi / BLE | name, brightness, volume, API access mode, clock sync (automatic on connect - a drifted bar clock skews device-rendered countdowns), timezone picker, Wi-Fi scan/connect with radio-state guidance (currently broken by a firmware Wi-Fi wedge - see Known issues), Bluetooth (experimental - see Known issues) |
| Smart Home | Matter pairing with scannable QR (self-contained QR encoder) + manual code - auto-retries the Matter stack's 503 warm-up, shows live commissioning status, diagnoses Wi-Fi-related blocks; emulated switch (pairing currently blocked by the firmware Wi-Fi wedge - see Known issues) |
| Account / Updater | cloud link with PIN, MQTT backend config, firmware check/install/upload with progress - bundles are inspected before upload (target, security flags, radio-firmware replacement) and refused if incompatible |
| Console | raw request builder for every endpoint, request log, copy-as-curl |
| Troubleshooter | guided checks - connection, API access, power, firmware, display control, Wi-Fi, local helper - with the fix for each and buttons that apply it |
Destructive actions always ask for confirmation first.
One fix, worth its own release: on-call detection on macOS works again. The
1.25 rewrite misread how macOS reports microphone use - the system logs a line
only when the set of apps using the mic changes, not continuously - so long
Zoom and Teams calls were cleared minutes in, or never detected at all. The
proven event-driven behaviour is restored: detect on the start event, hold
through quiet, clear on the end event. If you use the on-call hook on a Mac,
update the helper (barpilot-usage-helper.py) - that is where the fix lives.
Everything since 1.18, tested against a real bar over USB, Wi-Fi and Bluetooth:
- Sounds (new section): a soundboard for the bar's speaker - built-in sounds plus your
own recordings and audio files, converted to the bar's raw
.sndformat in the browser. - Messages: the custom message and the ticker merged into one card - a single list where Send this line puts one message up and Rotate all cycles the list, with the same syntax everywhere (inline stamps, animated stamps, per-line colours, crypto prices).
- Prepare the bar (Bluetooth section, step 1): sends every sound, all 1,469 animation frames, every stamp and icon, and your settings to the bar's own storage over USB/Wi-Fi. After that, playing any of it is a single small command - which is what makes the slow links usable, makes rotation skips free, and survives page reloads.
- Your settings live on the bar and follow it: any copy of BarPilot on any machine reads them back on first connect. The helper keeps an offline copy too.
- Bluetooth control (experimental): drive the bar over BLE through the helper - no
Web Bluetooth, so it works from a plain
file://page and on managed machines. Pairing, bonded reconnect, link-paced playback and self-healing after drops are all in, and it is still buggy - treat it as a work in progress (the app says so on the panel too). - On-call detection rebuilt and field-tested on Windows: two independent detectors (registry usage records in both hives, plus live audio capture sessions - newer Teams builds stop writing the registry records, the audio engine can't hide), a "what does Windows see?" debug view, the count-up timer backdated to the call's real start, live banner re-labelling, and a stale-meeting bug fixed that silently disabled the automation after an unclean page close.
- Calendar correctness: deleted occurrences stay deleted (comma-list
EXDATE), moved occurrences show only at their new time (RECURRENCE-ID), and duplicates from overlapping feeds/imports are folded - the ghost-event parade is over. - Troubleshooter fix buttons re-check themselves: press a fix and the row re-runs its own check and turns green, instead of leaving you wondering. The helper row shows the whole webhook/microphone chain state.
- Screen panel: front and back brightness side by side, "blank the back screen" no longer blanks both, and the live preview pauses over Bluetooth (with a refresh-once button) instead of silently eating the link.
- Quick messages diagnose their own 409s: the toast names what actually owns the screen - and if it is your own night mode, the message wins and night lifts.
- Helper v28 (
barpilot-usage-helper.py): serves the app at127.0.0.1:8765/app, syncs settings across origins and to the bar, proxies calendar feeds, runs the webhook and both call detectors, installs its own optional add-ons, and is hardened against the crashes and silent failures found in the field. BarPilot checks the helper version and says so when it is too old.
Earlier releases: see the release notes.
- Bluetooth is a work in progress and marked as such in the app: Windows pairing can take several attempts (the bar requires an authenticated bond and does not resume old ones), the link can drop and re-pair, and playback is paced by a link that carries a few KB/s. Everything works over USB and Wi-Fi; treat Bluetooth as a bonus, not the primary transport, until this notice goes away.
- The bar's Wi-Fi stack can wedge (stock fw 1.1.1): stuck on "connecting" even from a cold
boot,
Command timed outon scan/disconnect, and the Matter stack then answers 503 (it can't bind while the radio churns). One observed wedge survived forgetting the network, multiple power cycles, a full Wi-Fi clear and a bootloader recovery. Root-caused to an unbounded wait in the firmware's Wi-Fi service - fixed in BarMetal, which also adds the software reboot the stock firmware lacks. On stock firmware there is no reboot API, so recovery is physical (see the unbricking cheatsheet). - Even on BarMetal, a radio that has failed may still not scan: that fix lives in the Wi-Fi co-processor firmware, which requires vendor signing. BarMetal keeps the device responsive and self-healing rather than bricked.
- Matter commissioning additionally requires the phone and the bar on the same network segment (mDNS discovery - VLANs block it without a reflector).
- The scroll wheel and up/down keys emit nothing observable to clients - only the mode switch and OK/BACK/START buttons are streamed. BarPilot offers on-page skip controls instead; a firmware feature request is filed.
The bar is battery-backed, so unplugging it never resets it - a crashed or bootloader-stuck bar just sits there. The physical controls that actually matter:
| Action | How |
|---|---|
| Power off | hold the OK / spin-dial button ~15 s |
| Power on | hold BACK ~5 s |
| Enter the bootloader (STM32 DFU) | hold OK + BACK together |
| Exit the bootloader without flashing | hold OK for 10-15 s - or, from a computer: dfu-util -d 0483:df11 -a 0 -s 0x08000000:leave (it may print a get_status error - the bar still boots seconds later) |
If the screen says "System error - restart device": power off (OK 15 s), then power on (BACK 5 s). There is no reboot API.
Real frames captured from the device over the API (/api/screen):
Learned the hard way, verified against a real device:
- While any timer session runs, the bar rejects all canvas drawing (every priority,
even 100) with
Not drawn due to low priority. - Sessions/cards started on the device itself don't appear in
/api/busy/snapshotand can't be stopped via the API. Open on-device menus also block drawing. - Turning the emulated smart-home switch ON makes the bar start its own timer-profile session - which then blocks drawing (see above).
- An active canvas overlay blocks other draws at the same or lower priority - only strictly higher wins (the spec's "equal priority overrides" was not observed).
- Re-sending a scrolling text element restarts its scroll - send it once and animate other elements around it.
/api/screenreturns base64 of raw pixels despite itsimage/bmpcontent type: front = 72×16 RGB888 (BGR byte order!), back = 80×80 8-bit grayscale.- The brightness setting (
/api/display/brightness) is accepted and stored but visibly does nothing - dimming has to be done by drawing darker pixels (night mode owns the screen and does exactly that). - Apps don't composite: whichever app has the highest priority owns the whole display; anything below it is fully hidden (not blended). A translucent overlay can never dim another app's content.
- BarPilot layers its overlays deliberately: animations 55 < quick messages 60 < meeting 95.
- Settings persist per browser via
localStorage(device address, API key, playlists…). - The API key (only needed in "key" access mode) is stored in plain text - fine for a LAN gadget, don't reuse a secret you care about.
- Built and live-tested against firmware 1.1.1 (API 25.0.0). The Console can call anything a future firmware adds.
BarPilot is deliberately one dependency-free HTML file: nothing to install, nothing to supply-chain-attack. If you want to script the bar from real code, the official clients are the right tool - pin exact versions and verify checksums:
| package | pinned, hash-verified install | |
|---|---|---|
| Python | busylib 1.0.0 (docs) - sync/async clients, pydantic element models, WS state stream |
printf 'busylib==1.0.0 --hash=sha256:7f7a4079e028d9f6280f859ec155cb724f049bf473abf14303b26b539dbb312c --hash=sha256:b0a91f7f66015a9f7f91dc180203c28056e7b354cc7b44b3598840b8b0b07a2b\n' > requirements.txt && pip install --require-hashes -r requirements.txt |
| JS / TS | @busy-app/busy-lib 0.17.0 - typed openapi-fetch client, WS state stream, WebGL2 renderer, browser-capable ESM |
npm install --save-exact @busy-app/busy-lib@0.17.0 (lockfile integrity sha512-N8x54N0sgrxKa0YSnA7BTBB4Hg0EXBH+TBtRgoHXJJTP+88QUw+nLwVaJL2PYRWIVxZZneKKmif+21twjskAOQ==) |
The Draw section can export your composed canvas as a ready-to-run barpilot-draw.py
pinned to busylib 1.0.0. BarPilot itself never imports these libraries at runtime - a local
page pulling remote code would undo the zero-dependency security model.
MIT - free to use, modify, and share with attribution. If you build something on top of it, a credit and a link back here are appreciated, and contributions (PRs, firmware quirk reports, new stamps and scenes) are very welcome.






