The control socket is now the only way the web interface sends the display a
command. The display stops reading display_on_demand_request and
plugin_error_clear_request, and the web interface stops writing them.
- Display: no mailbox poll (MailboxWatch, the 1 s / 0.25 s cadence,
_consume_on_demand_request, the deprecation log) and no persisted
display_on_demand_processed_id guard; the error publisher reads no clear
request. CacheManager.file_signature and MailboxWatch are removed.
- A write to either retired key is dropped by CacheManager.save_cache and
logged once per writer, naming the plugin from the call stack (or the
request's plugin_id), with the API to move to.
- Web: on-demand start with no display listening starts the service (when
start_service) and sends the request again once the socket answers (45 s,
10 s for a running service without a socket yet); every other failure is
a 503 (400 for invalid_args). Stop answers 503 when no display listens,
unless stop_service. errors/clear answers 503 with a reason-specific
message instead of writing a request; clear_pending is always false.
src.ipc.client.should_fall_back is replaced by display_not_listening.
- Kept: display_current_state, display_on_demand_state,
plugin_runtime_snapshot and the heartbeat (read whenever the socket cannot
answer), and display_on_demand_config (the display's resume record).
Tests: mailbox-only tests removed (test_on_demand_mailbox.py, the mailbox
cadence, file_signature and MailboxWatch tests); tests that injected
requests through the mailbox now use the socket queue or a plugin's
in-process request. The run-loop harness sends on-demand requests over its
fake control socket, so four golden traces change: on-demand starts and
stops land at the request instant instead of the next 0.25 s mailbox look
(one frame fewer on the screen they end), and in vegas.json within one
frame instead of 263 ms, which shifts the later 1 s-throttled WiFi-notice
check by under a second.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- client: ControlError.sent says whether the display had the request;
should_fall_back() allows a mailbox write only when it did not, or when
the display is too old to know the command (upgrade case)
- on-demand start/stop: a display that had the request and failed it is
answered 503 (400 for invalid_args), no mailbox copy
- errors.clear: new socket command, answered on the connection thread by
a handler the display registers; applied and republished before the
answer; plugin_error_clear_request only on fallback
- display: on-demand mailbox looked at once a second while the socket is
up (0.25 s without), read only when its file changed (one stat via
CacheManager.file_signature / MailboxWatch); socket commands no longer
touch the mailbox; a processed duplicate is consumed; writers logged once
- error publisher: mailbox read only when changed; snapshot carries
applied_clear_cutoff so an older mailbox request is not shown pending
- docs and CHANGELOG (mailboxes kept for one release)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Control socket stage 2: the render thread wakes for queued commands (static screens ~1 ms, Vegas within one frame), brightness.set, and plugin.reload after a store update, with mailbox/restart fallbacks. Rig checks listed in the PR body are still to run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The display serves a control socket (/run/ledmatrix/control.sock) carrying versioned JSON commands, one per line, each answered. Stage 1 covers on-demand start, stop and status; commands are queued on the socket thread and applied on the render thread through the mailbox's own handler, and the web interface falls back to the file mailbox when the socket is unavailable. Protocol and security model: docs/IPC_CONTROL_SOCKET.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>