Skip to content

Record the clock the device stamps its own messages with - #34

Merged
matuszeg merged 1 commit into
mainfrom
capture-device-timestamps
Aug 24, 2026
Merged

matuszeg merged 1 commit into
mainfrom
capture-device-timestamps

Conversation

@matuszeg

Copy link
Copy Markdown
Owner

Towards #25, and a prerequisite for #17.

Every timestamp we hold is arrival time. That is why a backup run reported at
the instant the device reconnects after an outage cannot be told apart from one
that actually happened then — the ambiguity #25 exists to record, now seen
twice (2026-08-21 00:30, 12 s after re-auth; 2026-08-22 22:45, 11 s after).
#17 is built entirely on run timing, so if the device replays queued events
its arithmetic is fed fiction precisely when the system is under stress.

The device has been telling us all along: utcunixtime rides on every message,
in milliseconds, and no production code read it. Only test fixtures contained
it.

What this does

  • BbsReading.device_time — utcunixtime parsed to an aware UTC datetime.
  • DeviceState.clock_offset_seconds — arrival minus that, updated per message.
  • sensor.<device>_device_clock_offset (diagnostic) — what is normal for this
    device.
  • clock_offset_seconds on the pump-run event — the offset of the message that
    carried the run.

The raw number settles nothing on its own, which is why it is exposed as a
pair. Device clocks drift, so a steady offset of any size is ordinary; a run
whose figure sits far from the device's usual one is a message that waited. And
if the device restamps on send rather than keeping the original time, the offset
stays flat through the next outage — which answers the question in the other
direction.

The sensor is deliberately named device_clock_offset rather than
clock_error: it is clock drift plus transit time, the two cannot be
separated from here, and for "was this made now or made earlier and held" they
do not need to be.

Safety

Nothing depends on the field. A message without utcunixtime, or with one that
cannot be read, parses exactly as before — and an unreadable value leaves the
last known offset in place rather than fabricating a zero. Tests cover both.

12 new tests, TDD, each seen failing first; one of them caught a wrong epoch
conversion in the test I had written. 209 pass. core/ still imports no
homeassistant.

Not yet verified live

The parser tests run against real captured bodies, so the field and its units
are confirmed on the wire. What is not confirmed is what the live device's
offset actually looks like day to day — that needs a deploy, and the real answer
to #25 needs the next vendor outage.

Every timestamp the integration holds is arrival time, so a pump run
reported the instant a device reconnects after an outage is
indistinguishable from one that happened at that moment. That has now
been seen twice, both times within a dozen seconds of the device getting
a working token back, and #17 wants to compute inflow and pump health
from run timing.

The device sends utcunixtime on every message and it was being dropped.
Parse it into BbsReading, and record the gap between it and arrival on
the device state.

The gap alone proves nothing -- device clocks drift, so a constant
offset is ordinary. What discriminates is a jump: the sensor shows what
is normal for the device, and the pump-run event carries the offset of
the message that reported the run. A run whose figure sits far from the
usual one waited somewhere before it was delivered.

If the device restamps on send instead of keeping the original time,
the offset will stay flat through the next outage and that answers the
question too.

Nothing depends on the field: a message without it, or with one that
cannot be read, parses as before and leaves the last known offset
alone rather than fabricating a zero.
@matuszeg
matuszeg merged commit 7e8d20e into main Aug 24, 2026
3 checks passed
@matuszeg
matuszeg deleted the capture-device-timestamps branch August 24, 2026 00:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant