From 79c9771e0279a93f6bf307a36d29aaf750b5e98d Mon Sep 17 00:00:00 2001 From: Alex Russell Date: Thu, 30 Jul 2026 22:08:13 +0200 Subject: [PATCH] fix(status): refresh on restart and periodically, not only at init Closes #5. The status was read from the retained topic in exactly one place -- init_machine -- so anything that cleared the machine's shared state left the page showing `UNKNOWN` with an empty status text until someone printed or the pods restarted. Two ways to hit that, both seen on the cluster: a manual restart from the Admin Center, and a Redis restart, which drops every `machine:*` key at once because this deployment runs Redis without persistence by design. The framework already offered both hooks and this driver implemented neither: restart_machine called on a manual restart ping_machines called periodically when MACHINE_PING_ENABLED is set, which is on by default and confirmed on here That last one is the real fix -- it is what recovers the page after a cache wipe with nobody printing, which is the case the issue was actually about. The issue text claimed the framework "does not obviously offer a hook" for periodic work; that was wrong, and it is corrected there. ping_machines guards each machine separately so one broker problem does not stop the others refreshing. _refresh_status already turns an unreachable broker into an UNKNOWN status rather than an exception, so the guard is for everything else. print_labels is deliberately unchanged. With AWAIT_RESULT_S=0 the page sits at PRINTING after a dispatch, and the periodic refresh now corrects that within a ping interval -- which is better than reading the retained topic immediately, when the agent has not yet published anything about the job. --- README.md | 16 +++++++++++++++ inventree_label_dispatch/driver.py | 31 +++++++++++++++++++++++++++++ inventree_label_dispatch/version.py | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 99834bd..f7f1464 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,22 @@ InvenTree imports so it is unit-tested here: | any other fault | `ERROR`, carrying the reason | | agent will / shutdown | `DISCONNECTED` | +### When it refreshes + +The status is read from the retained topic in three places, which between them keep the +page honest without polling the printer itself: + +| when | hook | +|---|---| +| machine initialisation | `init_machine` | +| a manual restart from the Admin Center | `restart_machine` | +| periodically | `ping_machines`, driven by InvenTree's `MACHINE_PING_ENABLED` (on by default) | + +The periodic one is not a nicety. Machine status lives in the Django cache, so clearing +that cache drops every `machine:*` key at once — and this deployment runs Redis without +persistence, so a restart does exactly that. With only `init_machine` the page then sat +at `UNKNOWN` with an empty status text until someone printed or the pods restarted. + Two deliberate distinctions. **Unreported media is not healthy media**: `media_ok` is a tri-state, and a printer that has said nothing shows "media unreported" rather than "media ok". And **battery percentage alone is not enough** — it pins at 100% whenever diff --git a/inventree_label_dispatch/driver.py b/inventree_label_dispatch/driver.py index 892e2da..00305f5 100644 --- a/inventree_label_dispatch/driver.py +++ b/inventree_label_dispatch/driver.py @@ -110,6 +110,37 @@ def _apply(self, machine, member: str, text: str) -> None: machine.set_status(getattr(LabelPrinterStatus, member)) machine.set_status_text(text) + def restart_machine(self, machine) -> None: + """Re-read the printer's state after a manual restart. + + A restart clears the machine's shared state, and without this nothing puts it + back: the page then shows UNKNOWN with an empty status text -- the defaults + `get_shared_state` returns for missing keys -- while the machine itself + reports `initialized`, no errors and a driver available. An empty status text + is diagnostic on its own, since no path in this driver produces one. + """ + self._refresh_status(machine) + + def ping_machines(self) -> None: + """Periodic refresh, driven by InvenTree's MACHINE_PING_ENABLED (default on). + + This is what keeps the page honest between prints. It matters more than it + looks: machine status lives in the Django cache, so when that cache is cleared + -- a Redis restart, and this deployment runs Redis deliberately without + persistence -- every `machine:*` key vanishes at once. Before this hook the + only thing that re-read the retained topic was machine initialisation, so the + page stayed blank until someone printed or the pods restarted. + + One machine's broker trouble must not stop the others being refreshed, hence + the per-machine guard. `_refresh_status` already turns an unreachable broker + into an UNKNOWN status rather than an exception; this catches anything else. + """ + for machine in self.get_machines(): + try: + self._refresh_status(machine) + except Exception as exc: # noqa: BLE001 - one machine must not break the loop + machine.handle_error(exc) + def _refresh_status(self, machine) -> None: printer_id = machine.get_setting("PRINTER_ID", "D") try: diff --git a/inventree_label_dispatch/version.py b/inventree_label_dispatch/version.py index 91ce6a5..77d41e4 100644 --- a/inventree_label_dispatch/version.py +++ b/inventree_label_dispatch/version.py @@ -1,3 +1,3 @@ """Single source of truth for the plugin version.""" -__version__ = "0.2.0" +__version__ = "0.2.1"