Skip to content

Republish Tuya mesh status when the wall clock steps - #256

Merged
bbangert merged 4 commits into
mainfrom
claude/tuya-last-seen-clock-step
Nov 3, 2026
Merged

bbangert merged 4 commits into
mainfrom
claude/tuya-last-seen-clock-step

Conversation

@bbangert

@bbangert bbangert commented Nov 3, 2026 •

Copy link
Copy Markdown
Owner

Summary

Found on hardware: the Pi boots with its clock at the system image's build date (2026-08-23), and nerves_time steps it forward shortly after boot. A boot trace (polled every 2 s) showed the OS clock already correct by 17 s of uptime. The Resolver's last_seen, though, stayed on August until about 62 s. ERTS updates System.time_offset only when it detects the step, about 45 s later, and last_seen was converted with that offset. The Bluetooth page showed "Seen 71 d ago" for about a minute after every boot, and HA's "Last seen" sensor got the same wrong time.

  • A new UniversalProxy.TuyaMesh.WallClock converts monotonic to wall clock with System.os_time - System.monotonic_time, the same clock DateTime.utc_now/0 uses. The offset is rounded to whole seconds so read jitter can't cause republishes. The Resolver (last_seen) and MeshSync (live_of, for the HA timestamp sensor) both use it, so the 5 s tick corrects last_seen within one tick of the step.
  • The Resolver also monitors time-offset changes (:erlang.monitor(:time_offset, :clock_service)) and republishes on them. That covers an idle mesh, where nothing ticks.
  • A :time_offset_fun option makes the conversion testable.

Test plan

  • mise run test: 2399 passed. New: a tick-only test (the OS offset steps and the next tick republishes the corrected last_seen), a :CHANGE-only test (periodic publishing disabled; fails with the handler removed), and WallClock unit tests.
  • format, compile --warnings-as-errors, credo --strict, dialyzer, Argus (0 new)
  • HW: reboot the Pi and poll; last_seen is never more than one tick behind the OS clock

🤖 Generated with Claude Code

last_seen is derived from the monotonic clock plus the time offset, so a bulb heard before nerves_time set the clock kept a stale date until another event rebuilt the status. The Resolver now monitors time-offset changes and republishes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The test’s recurring timer can mask a missing :CHANGE handler or race with status flushing and cause timeouts, so it does not reliably verify notification-triggered publication.

1 open issue
What changed in this PR

Updates the Tuya mesh Resolver to refresh wall-clock last_seen timestamps after clock adjustments, correcting stale values shown in the Bluetooth page and Home Assistant after boot.

Changes:

  • Monitors Erlang time-offset changes and republishes updated mesh status.
  • Adds an injectable time-offset function for testing.
  • Adds a regression test simulating a 71-day clock step.
File Description
test/​universal_proxy/​tuya_mesh/​supervisor_test.exs Tests timestamp correction and status publication after a simulated clock step.
lib/​universal_proxy/​tuya_mesh/​resolver.ex Handles time-offset notifications and makes timestamp conversion testable.

Review effort: Balanced

bbangert and others added 2 commits November 3, 2026 02:38
The periodic tick also republishes, so the test could pass without the handler. It now starts with a far-off tick, waits on the broadcast, and drains before changing the offset.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ERTS notices an OS clock step only some seconds after it happens, so System.time_offset stayed pre-step while DateTime.utc_now already showed the new date. A shared WallClock helper derives the offset from os_time minus monotonic_time, used by the Resolver and MeshSync; the 5 s tick republish now corrects last_seen.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Stateless offset rounding can turn boundary jitter into alternating last_seen values, triggering unnecessary broadcasts and Home Assistant updates without a new report or clock step.

1 open issue
1 resolved since last review

Review effort: Balanced

Rounding each sample independently could flip last_seen by a second when the raw offset jittered across a rounding boundary. WallClock now keeps the accepted offset in persistent_term and adopts a new reading only when it differs by more than 1 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The remaining Pi reboot check must validate hardware-specific OS/ERTS clock divergence and confirm Bluetooth UI and Home Assistant timestamps recover within one tick after the actual nerves_time clock step.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

@bbangert
bbangert merged commit a1f308c into main Nov 3, 2026
8 checks passed
@bbangert
bbangert deleted the claude/tuya-last-seen-clock-step branch November 3, 2026 03:07
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.

2 participants