Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ republishes what it last heard rather than going quiet, and the age of that read
shown alongside it.

```
CONNECTED fw 2.1.2 · 100% (4.17V) · media ok · Q223P4C31420105 · seen just now
CONNECTED fw 2.1.2 · 100% (4.17V) · media ok · Q223P4C31420105 · seen 6h ago
```

Expand All @@ -61,6 +62,15 @@ rather than overruling it — a printer that had tape six hours ago is far likel
still have tape than to have anything else — so the status mapping is unchanged. An
agent too old to publish the field renders exactly as it did before.

One case the age cannot cover: the agent's MQTT will is fixed when its connection opens
and the broker holds it, so a printer fault learned *during* that session cannot reach
it. If the agent is then killed, the broker publishes the older, healthier reading over
the newer one, and this page renders it — correctly marked `DISCONNECTED`, but with a
stale-healthy detail line. It is self-correcting rather than sticky: the agent restarts
(`Restart=always`, `RestartSec=5`), seeds from its spool, which does have the fault, and
republishes. Nothing here can detect the window, since retained MQTT v3.1.1 messages
carry no publish timestamp.

## Print outcomes

`AWAIT_RESULT_S` makes the driver wait for the job's `JobResult` after dispatching, so
Expand Down
12 changes: 11 additions & 1 deletion inventree_label_dispatch/status.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,14 @@
_GOOD_JOB_STATES = frozenset({"completed"})


def _ago(seen, now: datetime) -> str:
def _ago(seen: object, now: datetime) -> str:
"""How long ago the device fields were true, as a short suffix. ``""`` if unknown.

``seen`` is typed as ``object`` rather than ``str | None`` on purpose: it comes
straight out of ``json.loads`` on a payload this process did not produce, so it can
be any JSON type at all, and pretending otherwise would just move the lie into the
annotation.

Empty rather than a guess in three cases, all of which mean the same thing: nobody
said. An agent older than the field simply omits it, and the page should look
exactly as it did before rather than sprouting a fake age.
Expand All @@ -52,6 +57,11 @@ def _ago(seen, now: datetime) -> str:
# Clamped at zero: the agent's clock and this one are not the same clock, and a
# small skew must read as "just now", never as a negative age.
delta = max(0, int((now - stamp).total_seconds()))
# A reading taken while the operator was watching the print happen. "seen 0s ago"
# is technically right and reads like a machine; this is the common case, since the
# page is usually looked at right after printing something.
if delta < 10:
return "seen just now"
if delta < 60:
return f"seen {delta}s ago"
if delta < 3600:
Expand Down
6 changes: 4 additions & 2 deletions tests/test_status.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ def _seen(delta: timedelta) -> dict:
@pytest.mark.parametrize(
("delta", "expected"),
[
(timedelta(seconds=0), "seen 0s ago"),
(timedelta(seconds=0), "seen just now"),
(timedelta(seconds=9), "seen just now"),
(timedelta(seconds=42), "seen 42s ago"),
(timedelta(minutes=4), "seen 4m ago"),
(timedelta(hours=6), "seen 6h ago"),
Expand Down Expand Up @@ -188,7 +189,8 @@ def test_a_naive_timestamp_is_read_as_utc():
def test_a_clock_ahead_of_ours_reads_as_just_now_not_a_negative_age():
"""The agent's clock and InvenTree's are not the same clock."""
_member, text = classify_status(_seen(timedelta(minutes=-5)), now=NOW)
assert "seen 0s ago" in text
assert "seen just now" in text
assert "-" not in text.split("seen")[-1]


# -- job outcomes ----------------------------------------------------------- #
Expand Down
Loading