Decides when the panel is redrawn, and with what.
It owns none of the panel: BB.NSK.Display.Viewport renders and
BB.NSK.Display.FrameSink draws. What lives here is the policy, which is
the part that is specific to this robot.
A refresh takes seconds, so redraws are driven by change rather than by a clock: the controller watches the robot's safety state and the WiFi interface, and coalesces a burst of changes into one redraw. A slower periodic redraw keeps uptime, memory and load from going stale and does the full refresh that clears the ghosting partial refreshes leave behind.
Arbitrary screens can be drawn over the top with render/3; the next redraw
replaces them.
When there is no viewport — on the host, or if the renderer failed to start — the controller runs anyway and its redraws go nowhere. That keeps the robot's topology the same everywhere rather than making an entity conditional on hardware.
Summary
Functions
@spec refresh(module()) :: :ok
Redraw the dashboard now, without waiting for a change.
@spec render(module(), Emerge.tree(), keyword()) :: :ok
Draw a screen onto the panel.
Takes an Emerge tree the size of the panel. The frame reaches the panel
some time after this returns — rendering is a push, and the panel takes
seconds — so this reports that the screen was accepted, not that it is up.