-
Notifications
You must be signed in to change notification settings - Fork 1
Compatibility
Goal: any app folder from
splitflap-os drops into this project's
apps/ and works unmodified. That inbound direction is the contract this
document holds the companion to: its engine and UI are original, but the plugin
ABI is a faithful, behavior-identical port.
The reverse direction is not promised. An app authored here loads on a stock
splitflap-os — every companion helper is opt-in by signature, so nothing breaks —
but it runs without them (no get_weather, no i18n, no caps), unoptimized,
and we make no guarantees of it.
Pinned upstream: splitflap-os commit
12df2773cbbe9890a7d6f92fdc60d2be920129bd (VERSION 0.3.0).
-
Layout & loading:
apps/<id>/{manifest.json, app.py}(+ bundled data files).app.pyis loaded as a single standalone module — the app's directory is not put onsys.path, so relative or sibling-file imports do not resolve; keep the code in one file and read bundled data relative to__file__. Bothfunctional(hasapp.py) andchannel(data-only) app types load. -
fetch(settings, format_lines, get_rows, get_cols) -> list[str]— same argument order and semantics.format_lines(*lines, cols=None),get_rows(),get_cols()are behaviour copies (padding/centering/width; each page isrows × colschars), with one deliberate divergence:Vertical centering. When an app returns fewer lines than the wall is tall, splitflap-os pads only at the bottom; we pad above and below. On a 3-row wall — what splitflap-os targets — the two are identical, because apps fill it. On a taller wall (a 5×15 MatrixPortal, say) bottom-padding strands a 3-line app at the top with two dead rows beneath it. An app is unaffected either way: it still returns the same lines, and
get_rows()still tells it how much room it has.Case is the display's business, not the app's. The vendored apps no longer call
.upper()on the text they show.normalize()folds a non-raw page for a wall that cannot show lowercase — which every split-flap is — so the flaps are unchanged; a Matrix Portal (whose index-addressed API can show lowercase) gets the text as written. A splitflap-os app that still uppercases its own strings is unaffected: it is simply folding text that was already folded.The divergence is opt-out. An app that wants the original behaviour — or that builds its own layout and needs its rows left where it put them — declares it in its manifest:
{ "vertical_align": "top" } // "center" (default) | "top" | "bottom""top"is byte-for-byte splitflap-os padding. The key is additive: absent means"center", so every existing app — and every unmodified splitflap-os app — behaves exactly as it does today without being touched.format_lines's signature does not change: the runtime hands each app aformat_linesalready bound to that app's alignment, so apps keep callingformat_lines(*lines).Declaring
"top"is what an app needs if it places its own blank rows. Without it, an app that centres its own block gets centred a second time and drifts below the middle — which is exactly what happened to three vendored apps in 1.9.0-beta.5. -
settingsdict — manifest defaults merged with saved user values, same key resolution, including any global keys apps read (e.g.currency_symbol). -
Optional
capsargument — likei18nandget_location, injected by parameter name and only when the app asks for it. It reports what the wall can show (lowercase,pictographs,named_colours,instant), so an app can offer↑where the wall has a real arrow andHIGHwhere it does not. Additive and defaulted toNone, so an app that uses it still runs on stock splitflap-os — whereNonecorrectly means "a plain reel". -
Triggers —
trigger(settings, conditions) -> boolon atrigger_interval, withtrigger_display_seconds/trigger_cooldown/trigger_conditions, and thesetattr(fetch, '_state', …)state pattern. -
Manifest schema / settings fields —
text, number, password, datetime-local, textarea, select, toggle, search_chips, computed, inline_toggle, plusvisible_when,sync_values/sync_parent,stepper, string/objectoptions, and theLUCIDE_APP_ICONSopt-in. -
search_chipssearchUrlendpoints — served at the same paths with the same response shapes:/location_search,/location_timezone,/timezones,/stocks_search,/crypto_search,/sports_search(leagues and teams in one call — the endpoint the vendored sports app declares). -
Rendering — the emoji→colour-code map and all transition orderings (apps emit colour codes and expect the same animation styles).
-
Caching / paging — results cached per
refresh_interval; each page shownloop_delayseconds.
Final character normalization is companion-specific and deliberately not a
port of splitflap-os. Because the gateway and modules support the full
Windows-1252 set, the companion sends accented letters, € and punctuation
through verbatim (upper-cased in a cp1252-aware way, so ß and accents survive)
instead of policing them against a fixed FLAP_CHARS set or substituting the
currency $/"→q. This changes only the final glyphs on the wire, never what
an app sees — the inbound ABI above is untouched, so splitflap-os apps stay
drop-in compatible here.
-
backend/tests/test_renderer.pyguards normalization (cp1252-aware upper-casing + verbatim Windows-1252 passthrough) and the animation orderings (a drift here is a compatibility regression). -
backend/tests/test_plugins.py::test_every_app_loadsimports everyapps/*and asserts its manifest +fetch/datasatisfy this contract. The manual drop-in check still holds: copy a not-pre-vendored splitflap-os app intoapps/and confirm it appears, configures, and runs with no changes.
If splitflap-os adds a field type, helper, or endpoint, bump the pinned commit above, port the addition, and extend the conformance test. Never diverge the ABI silently.
Start
Build it
- Hardware
- Module Firmware
- Provisioning
- Calibration
- Flaps & Character Sets
- SplitFlap Gateway
- Matrix Gateway
- LCD Gateway
Drive it
- Companion
- Built-in Apps
- Standalone & Docker
- Multiple Displays
- Home Assistant
- Vestaboard API
- MCP Server
- Using splitflap-os
Extend it
Reference
Hardware © Adam G Makes