Architecture¶
ScrollKit is one library that runs unchanged on the Adafruit MatrixPortal S3 (CircuitPython) and on a desktop pygame simulator. This page shows how the pieces fit together — from where the library sits in a running device down to the subsystem dependencies. For class-level detail, follow the links to each guide.
System context¶
Your application subclasses ScrollKitApp and talks to a single
display abstraction, UnifiedDisplay. The library picks its backend
at import time and brokers every external system the sign touches:
flowchart TB
app["Your app<br/>(subclasses ScrollKitApp)"] --> core
subgraph sk["ScrollKit library"]
core["ScrollKitApp · UnifiedDisplay<br/>ContentQueue · effects · config"]
end
core -->|"sys.implementation.name == 'circuitpython'"| hw["MatrixPortal S3<br/>displayio → RGBMatrix panel"]
core -->|desktop| sim["pygame simulator<br/>(hardware-timing model)"]
core <-->|"WiFiManager"| ap(["Wi-Fi AP"])
core <-->|"HttpClient — synchronous, blocks the loop"| api(["HTTP data API"])
core <-->|"SettingsWebServer"| browser(["Browser<br/>config UI"])
core -->|"raw.githubusercontent.com"| gh(["GitHub<br/>OTA manifest + files"])
The one detail that shapes everything else: HTTP is synchronous
(adafruit_requests), so a data fetch blocks the whole cooperative event loop —
including rendering. The library paints a loading frame and suspends rendering
around the blocking call, and — when the app opts in (enable_watchdog=True) —
the hardware watchdog resets the board if a fetch truly wedges. See
Performance and the run loop for how
that plays out frame-by-frame.
Subsystem dependencies¶
Each box is a package under src/scrollkit/. Solid arrows are eager (module
top-level) imports; dashed arrows are lazy imports done inside a function —
the RAM-saving pattern that keeps the device boot path light. your app uses only
app and display; everything else is an internal or app-wired detail.
flowchart LR
yourapp["your app"] --> app
yourapp --> display
app["app<br/>ScrollKitApp"] --> display["display<br/>UnifiedDisplay"]
app --> config["config<br/>SettingsManager"]
app -.->|lazy| utils["utils"]
app -.->|lazy| effects["effects<br/>Transition"]
app -.->|lazy| web["web<br/>SettingsWebServer"]
app -.->|lazy · desktop| simulator
effects --> display
display -.->|desktop · lazy| simulator["simulator<br/>displayio emulation<br/>+ hardware model"]
config -.->|lazy| utils
config --> names["config.transition_names<br/>(imports nothing)"]
network["network<br/>WiFiManager · HttpClient"] --> config
network --> utils
network --> exceptions["exceptions"]
ota["ota<br/>OTAClient"] --> exceptions
simulator -.->|lazy| exceptions
web --> utils
dev["dev<br/>run_headless · validate<br/>(desktop only)"] --> display
dev --> effects
dev --> simulator
dev --> utils
classDef desktop stroke-dasharray:6 4;
class dev,simulator desktop;
Dashed-outlined boxes (dev, simulator) are desktop-only — importing them
on CircuitPython raises ImportError by design.
The rules this graph enforces
These invariants are load-bearing on a RAM-constrained device; a change that breaks one is a regression, not a refactor:
- The top-level
scrollkit/__init__.pydoes no eager submodule imports. Every import costs RAM; the package stays a thin namespace. - Nothing in the core library imports
dev/. It pulls in numpy/pygame and is verification-only. config/settings never importseffects/.config.transition_namesimports nothing, so selecting a transition in the settings UI doesn't drag theeffects/package onto the boot path.web/imports only the sharedutils.url_utils.url_decodehelper — itssettingsandappare dependency-injected, which is what keeps the web server from ever touching display/queue state (see Web Interface).appdoes not ownnetwork/ota. Apps construct those themselves and call them fromsetup()/update_data()(see Networking, OTA Updates).
Where to go deeper¶
| Concern | Diagram | Guide |
|---|---|---|
| One display, two backends | class hierarchy + backend selection | Display |
| What you put on screen | content class hierarchy | Display |
| Content-swap transitions | Transition class diagram |
Transitions |
| The async lifecycle | run-loop sequence | App Framework |
| Web config safety | settings-handoff sequence | Web Interface |
| Over-the-air updates | OTA flow | OTA Updates |
| Building & proving an app | verification workflow | Simulator |