Skip to content

fix: shield switch commands from stale poll state - #36

Merged
dalyem merged 1 commit into
masterfrom
fix/issue-34-stale-state
Jul 14, 2026
Merged

fix: shield switch commands from stale poll state#36
dalyem merged 1 commit into
masterfrom
fix/issue-34-stale-state

Conversation

@dalyem

@dalyem dalyem commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes #34 — turning a diffuser off from HA showed On again ~60 seconds later while the physical device stayed off (reported with 2× A7 + 1× A5).

Plan / root-cause write-up: https://plan.dalem.dev/p/iwuvjac2

Root cause

Per docs/API.md: the device takes 15–20s to acknowledge a switch command server-side, and the list endpoints (the integration's poll source) can serve stale cached snapshots long after that — statisticsUpdateTime is sometimes hours old while onlineStatus=1, and an idle device rarely pushes fresh state. The rows carry no real onOff; it's derived from workStatus.

Meanwhile the optimistic shield in _apply_recent_switch_state only protected a command for 8–15s. Timeline: off at T+0 → T+3s delayed refresh (stale, shielded, still Off) → T+60s scheduled poll: shield expired, server still serving the pre-command workStatus=1/2 snapshot → HA flips back to On. The ~60s in the report is exactly the default poll interval.

(The other half of this failure mode — a leftover armed 24/7 schedule genuinely re-activating the device — is issue #31, fixed separately in #35. This PR reproduces and fixes the pure-staleness half.)

Change

A poll that contradicts the last HA switch command is now only trusted when the server's own snapshot proves it postdates that command:

  • Poll agrees with the command → passes through untouched (server caught up).
  • Contradicting poll with statisticsUpdateTime newer than the command (+5s clock-skew margin) → accepted immediately (genuine change, e.g. app use).
  • Contradicting poll with an older snapshot → provably stale, commanded state kept. Capped at 30 min so HA can never diverge indefinitely.
  • No snapshot timestamp available → time-based fallback shield of 180s (device ack + 2–3 polls), up from 8–15s.
  • While shielding, workStatus is kept coherent with the commanded state in both directions (previously an ON command shielded against a stale OFF could show switch On + Work Status "Off").
  • A second delayed refresh fires +25s after every switch command (past the ack window) so HA converges quickly.

Verification

Stubbed-coordinator scenario tests, all passing:

  1. no-command polls pass through; 2. the reported bug: off@T, poll@T+60s claiming On with an hour-old snapshot → stays Off; 3. fresh contradicting snapshot accepted; 4. agreement passthrough; 5./6. no-stamp shield holds at 60s, defers at 300s; 7. 30-min cap defers; 8. reverse direction (On command vs stale Off); 9. end-to-end v1+v2 row merge preserves statisticsUpdateTime and keeps commanded Off.

All files compile; hassfest + HACS on this PR.

Hardware test for @dalyem: turn a diffuser off from HA → wait 3+ min (2–3 polls) → should stay Off; then turn it on from the Aroma-Link app → HA should show On within a couple of polls once the server publishes a fresh snapshot.

Rollback

Single revertable commit, coordinator-only, no entity/config migrations.

🤖 Generated with Claude Code

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@dalyem, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 30 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fac98e1e-dc62-45c2-8e48-c8b7977d710c

📥 Commits

Reviewing files that changed from the base of the PR and between 8ae0cbd and c4b3be4.

📒 Files selected for processing (1)
  • custom_components/aromalink_ha_integration/AromaLinkDeviceCoordinator.py
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/issue-34-stale-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@dalyem dalyem mentioned this pull request Jul 7, 2026
Turning a diffuser off from HA flipped back to On one poll (~60s) later
while the device stayed off (issue #34). The device takes 15-20s to
acknowledge a switch server-side and the list endpoints keep serving the
pre-command snapshot long after (statisticsUpdateTime can lag by hours
while onlineStatus stays 1), but the optimistic-state shield only lasted
8-15s, so the first scheduled poll reinstated the stale state.

A contradicting poll is now only trusted when its statisticsUpdateTime
postdates the command (5s skew margin), with a 30-minute cap so HA can
never diverge from the server indefinitely; polls without a snapshot
timestamp fall back to a 180s shield. Agreeing polls always pass through
untouched, and a second delayed refresh fires 25s after each command so
HA converges as soon as the device ack lands.

Fixes #34

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dalyem
dalyem force-pushed the fix/issue-34-stale-state branch from 1dfe2a2 to c4b3be4 Compare July 7, 2026 05:29
@dalyem
dalyem merged commit 2dc0ecd into master Jul 14, 2026
6 checks passed
@dalyem
dalyem deleted the fix/issue-34-stale-state branch July 14, 2026 02:48
dalyem added a commit that referenced this pull request Jul 14, 2026
Covers the configurable SSL bypass (#32), the Power/Fan control split
and API reference (#33), schedule disarming after momentary runs (#35),
the stale-poll shield (#36), the restored count sensors with long-term
statistics (#37), and the MIT license adoption.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

Sensor on off

1 participant