Skip to content

Opt-in verified delivery: readback confirmation and cross-zone stagger - #24

Draft
Cloudore wants to merge 2 commits into
cgonfer:mainfrom
Cloudore:feat/verified-delivery
Draft

Opt-in verified delivery: readback confirmation and cross-zone stagger#24
Cloudore wants to merge 2 commits into
cgonfer:mainfrom
Cloudore:feat/verified-delivery

Conversation

@Cloudore

@Cloudore Cloudore commented Sep 4, 2026

Copy link
Copy Markdown

Summary

  • Per zone, opt-in delivery.confirm (with confirm_timeout_seconds 5–120, default 25, and confirm_attempts 1–5, default 3): after a delivery's call sequence is accepted, Velair watches the climate entity (state listener plus one timer, no polling) until it converges — hvac_mode equals the requested mode (or any non-off mode when the block keeps the current mode) and each requested target is within half the entity's target_temp_step; turn_off converges when the state is off. On timeout the delivery is re-run through the existing recovery resolver (so a newer intent is what gets re-sent) as the next attempt; after the last attempt the outcome is unconfirmed. Commit timing and climate_target_applied are unchanged; confirmation is an additional, later outcome.
  • Global settings.delivery_stagger_seconds (0–30, default 0): when set, the starts of physical call sequences are serialized across the entry's entities with that gap; superseded waiters are dropped, zero keeps today's behavior.
  • Observability: runtime diagnostics entries per attempt and outcome, sensor.velair_diagnostics_status attributes for unconfirmed deliveries, zone_runtime.delivery in the schedule response, and a new public event delivery_outcome. Settings and Diagnostics show the per-zone state.
  • Why: a Midea CCM15 gateway here silently drops single set_temperature calls (Home Assistant reports success, the entity never changes) and drops all five when five zones are written back-to-back; [Bug]: Daikin local integration ignores target temperature when climate commands are sent in rapid succession #11 describes the same behavior on a local Daikin integration. This adds evidence-based confirmation rather than blind delays, and it is off by default.

Checks

  • Backend tests pass with python -m unittest discover -s tests (adds tests/backend/test_verified_delivery.py, 44 tests)
  • Frontend TypeScript check passes with npx tsc --noEmit
  • Frontend build passes with npm run build (bundle committed)
  • Documentation was updated when behavior changed (docs/user/resilient-climate-delivery.md, docs/user/automation-events.md, docs/user/diagnostics.md, docs/developer/climate-delivery.md, docs/developer/api.md, README)

Manual testing

Draft: installed on Home Assistant 2026.8.2 with five Midea CCM15 zones and one LocalTuya unit; confirmation enabled on the five Midea zones with a 3 s stagger. The live run that exercises a dropped write and the re-delivery on the CCM15 is scheduled next; the PR will be marked ready with the observed attempt/outcome trace. Opening it now for design feedback.

🤖 Generated with Claude Code

https://claude.ai/code/session_01F2KBugsLZkL6yR7BB95pKK

Cloudore and others added 2 commits September 4, 2026 03:31
Some gateways (Midea CCM15, local Daikin per upstream cgonfer#11) accept a climate
call and silently drop it. Instead of blind delays, this adds evidence-based,
opt-in readback confirmation per zone plus an optional global stagger.

Model: `ZoneData.delivery` {confirm, confirm_timeout_seconds 5-120 (25),
confirm_attempts 1-5 (3)} via normalize_zone_delivery(), and
`settings.delivery_stagger_seconds` 0-30 (0). Both are exported/imported as
optional keys; the portable model version is unchanged because the import
validator tolerates them.

Coordinator: `Delivery.confirm` carries a DeliveryConfirmation. After the
accepted sequence and its unchanged commit, one state-change listener plus one
async_call_later timeout watch the entity (no polling). Convergence: turn_off
needs `off`; otherwise the mode must match (any non-off mode when no mode was
requested) and each requested scalar/range end must be within half the target
step. On timeout the eligible recovery resolver is re-run as attempt N+1 so a
newer intent is what gets re-sent; after the last attempt the outcome is
`unconfirmed`. Superseding generations, cancel and stop tear the watch down.
A positive stagger serializes sequence starts through one coordinator lock
taken after the per-entity lock; superseded waiters are woken and dropped.

Observability: diagnostics history entries per attempt and outcome, unit
`delivery.confirmation`, a `delivery_unconfirmed` warning issue, sensor
attributes unconfirmed_deliveries / confirmed_deliveries_today /
unconfirmed_deliveries_today, `delivery` in zone runtime statuses, and the
new public `delivery_outcome` event. WebSocket: velair/update_zone_delivery
and delivery_stagger_seconds on velair/update_settings.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJhD4sYeWQvoikjYP2CxUr
Settings: per-climate "Confirm delivery" switch with timeout and attempts
inputs inside each managed climate row, and a global "Delivery stagger"
seconds input. Diagnostics: confirmation outcome, timestamp and attempt count
in the status and delivery group, new delivery status chips, the
delivery_unconfirmed issue text, and attempt-of-N descriptions in the runtime
log. Adds updateZoneDelivery to the API client, saveZoneDelivery to the
settings controller, DeliverySettings/DeliveryConfirmationStatus types,
translations for all ten languages plus the template, vitest coverage, and
the regenerated integration bundle.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WJhD4sYeWQvoikjYP2CxUr
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