feat(agent): refresh device truth on demand and on an interval - #14
Merged
Conversation
0.3.0 gave the status topic a memory. Two ways to refresh what it remembers, both reusing probe_device rather than inventing a second path to the printer. `probe` (or `status`) on the cmd topic surveys the printer immediately. That topic already exists and already carries `flush`, the agent already reads it, and the broker ACL already lets InvenTree write it -- so a refresh button in a consumer UI needs no new topic, no ACL change and no new credential. Like flush, it only enqueues a sentinel: the cmd handler runs on paho's network thread and nothing there may touch the printer. device.probe_interval_s re-surveys once the reading goes stale, off by default. Two clocks here and conflating them is the trap. Staleness is measured on seen_at, what the printer last *said*, so a printer that is being used is never probed -- printing keeps it fresh already. The rate limit is measured on the last *attempt*, because a failed probe deliberately leaves seen_at alone; without a separate attempt clock the stale condition would still hold a tick later and a sleeping printer would be dialled once a second, each attempt blocking the print loop for the connect timeout. A missed probe stays silent rather than publishing `disconnected`. That is reserved for a printer that could not take a job, which is news a producer needs. A background probe finding the D30 asleep is the normal state of a printer nobody is using, and flipping the page between connected and disconnected as it naps would make the distinction worthless. The reading ages instead, which is what device_seen_at is for.
…Ping' controls An operator who turns off InvenTree's Machine Ping expecting the printer to stop being contacted would be half right: that setting stops InvenTree re-reading the retained topic, a cheap read that never touches Bluetooth, and cannot reach this timer, which is the one that actually dials the printer. Says so where the knob is, since neither side is guessable from the other. Also records the ping's 5-minute cadence as the sensible floor for this value.
…s not a shortcut Partly answers the open question on wake behaviour: it answers BR/EDR inquiry continuously while powered, not only for a window after wake, and disappears within about a minute of being switched off. Measured on fw 2.1.2 with auto_power=0. Three things worth writing down because each would otherwise be discovered the hard way. l2ping gets no response at all -- including while the unit was demonstrably up and RFCOMM connected 3.3s later -- so the usual liveness trick does not exist here. Presence must be judged from an RSSI update inside a scan window, not from the cached devices list. That list happens to self-clear today only because the printer is unpaired and BlueZ expires temporary devices; pairing it would make the entry permanent and a naive check would then report the printer online forever. And presence detection is not a cheap substitute for connecting, which was the reason to want it: a 3-8s inquiry window against a 5.2s failed connect is no saving, and the connect returns real device state when it succeeds while a scan returns a boolean.
The docstring said 'called from the run loop at startup, and only there', which this same branch made false by adding the idle tick and the probe command. Reworded around the constraint that actually matters and still holds: every caller is on the print loop, and nothing may call it from paho's network thread. That is also why the probe command arrives as a queue sentinel rather than being serviced in the cmd handler -- the sentinel exists to move the work onto the loop.
impuls42
added a commit
to sengine-cloud/inventree-label-dispatch
that referenced
this pull request
Aug 2, 2026
…ing memory (#9) Pairs with sengine-cloud/labelfab#14, which adds the `probe` command this sends. ## The problem with the button The retained status is what the agent last *heard*. Re-reading it on a manual refresh returned the same thing the page was already showing — pressing the button could not tell you anything you did not already know. It now publishes `probe` on the `cmd` topic and waits for the agent to actually go and look at the printer. ## Why this needed nothing new The `cmd` topic already existed and already carries `flush`, the agent already subscribes to it, and the broker ACL already grants `inventree` `topic write se/v1/print/+/cmd`. So: **no new topic, no ACL change, no new credential.** ## On "custom actions" Worth recording, because it is the first thing anyone will ask. InvenTree's machine framework has **no generic custom-action mechanism** — I checked the running pod. `restart_machine` is the single hardcoded action, with its own `MachineRestart` endpoint and a fixed place in the Machines menu. Adding a *new* menu item means patching InvenTree. So "Restart Machine" **is** the refresh button whether or not it is called that, and it was being wasted on a re-read. ## Degradation `probe_status` subscribes *before* publishing, because the answer is a retained message and a subscription set up afterwards would race the agent. The message already on the topic arrives immediately and is kept as the baseline, so a probe the printer cannot answer — the normal case for a D30 that has powered itself down — degrades to exactly what `read_status` would have returned. The page keeps the old reading and its `seen Xh ago`, rather than going blank. The automatic paths (`init_machine`, `ping_machines`) deliberately keep reading rather than probing: neither should dial a sleeping printer on a schedule nobody asked for. ## Verified 54 tests on 3.12 and on a real 3.9.25 in Docker, ruff clean. The new tests cover the subscribe-before-publish ordering, the fall-back-to-remembered path, a malformed retained payload, and nothing-retained-and-no-answer.
impuls42
added a commit
that referenced
this pull request
Aug 3, 2026
Two ways to refresh what the status topic remembers (#14). `probe` on the cmd topic surveys the printer on demand, which is what a refresh button in a consumer should send; device.probe_interval_s re-surveys once the reading goes stale, off by default. Both route through the existing probe_device, so a miss still cannot wake a sleeping printer and still leaves the remembered reading and its timestamp alone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
0.3.0 gave the status topic a memory. This adds two ways to refresh what it remembers, both routed through the existing
probe_devicerather than inventing a second path to the printer.On demand —
probeon thecmdtopicThe nice part is how little it needs. That topic already exists and already carries
flush, the agent already subscribes to it, and the broker ACL already grantsinventreetopic write se/v1/print/+/cmd. So a refresh button in a consumer UI needs no new topic, no ACL change, no new credential.Accepts
probeorstatus, case- and whitespace-insensitive — it arrives from a human pressing a button, and being fussy about which synonym they typed buys nothing.Like
flush, it only enqueues a sentinel. The cmd handler runs on paho's network thread, and nothing there may touch the printer.On an interval —
device.probe_interval_sOff by default. Two clocks, and conflating them is the trap:
seen_at— what the printer last said — so a printer that is being used is never probed at all, because printing keeps that fresh on its ownseen_atuntouched. Without a separate attempt clock the stale condition would still hold on the next tick, and a sleeping printer would be dialled once a second, each attempt blocking the print loop for the connect timeout.That second one has a test of its own (
test_a_sleeping_printer_is_not_dialled_every_tick), because it is the kind of bug that looks fine in review and eats a print loop in production.A missed probe stays quiet
It does not publish
disconnected. That is reserved for a printer that could not take a job, which is news a producer needs. A background probe finding the D30 asleep is not news — it is the normal state of an idle printer, and flipping the page between connected and disconnected as it naps would make the distinction worthless. The reading ages instead, which is exactly whatdevice_seen_atwas added for.Verified
274 tests, ruff clean. Pairs with a consumer change that makes InvenTree's existing "Restart Machine" action send
probe, turning it into a real refresh instead of a re-read of memory.Worth noting for anyone looking for a new button: InvenTree's machine framework has no generic custom-action mechanism —
restart_machineis the single hardcoded action, with its own endpoint and a fixed UI menu. Reusing it is the whole reason this lands without patching InvenTree.