Skip to content

Adaptive Cover Pro ⛅ v2026.8.2

Choose a tag to compare

@jrhubott jrhubott released this 12 Aug 17:08
· 6 commits to main since this release

Highlights

2026.8.2 is an in-month patch: five bug fixes back-ported from develop, no new features, no configuration changes and nothing to migrate. Three of them cost users real cover movement. Manual overrides survive a Home Assistant restart again, after a regression that destroyed them on every reboot for every user (#1232). A blind with a blank start time and a configured end time no longer drives to its daytime default at local midnight (#1256). And the end-of-window reposition now actually fires instead of mistaking an ordinary in-window winner for an active override and skipping the move entirely (#1241). The remaining two fix a venetian drift-reset scope leak and a set_position_limits field that the Actions form never rendered.

Fixed

  • An unset manual-override toggle no longer wipes restored state (#1232): _update_manager_and_covers truthiness-tested manual_toggle, which is a tri-state bool | None. That conflated the None meaning "no switch entity has restored yet" with the False meaning "the user disabled manual-override detection". During platform setup the Integration Enabled switch fires a coordinator refresh three switch specs before Manual Override restores the flag, so the reset loop ran while the toggle was still None and destroyed every override the sensor had just rehydrated from the restore cache. Every user hit this on every reboot.

    Gating the reset on an explicit is False fixes it. This is a follow-on to #1019 and PR #1021, whose fix (populating manager.covers early) is correct and untouched.

  • A blank start time anchors to sunrise, not midnight (#1256): With no start time configured and an end time set, the operational window re-opened at local midnight. after_start_time mapped "no start configured" to True, putting the window's lower bound at midnight while _normalize_to_today pinned the end onto the new date. is_active flipped inactive to active at 00:00, check_transition fired on_window_open, and with every override handler declining in darkness the priority-0 default handler drove the cover to its daytime default.

    The night-hold branch added for #492 never reached these installs, because compute_effective_default short-circuits when sunset_pos is None and they had never configured a night position. TimeWindowManager now takes an optional sunrise_provider, and only the blank-start branch of after_start_time changed: when an end bound is configured it consults sunrise and fails open to True when none is available, so the window runs from sunrise to the configured end and never flips at midnight. The coordinator wires the provider as a closure over live SunData, converting astral's UTC-aware sunrise to naive local. The config summary renders "from sunrise" for that shape, with DE and FR synced. _resolve_start_datetime, _start_has_passed, window_explicitly_started and compute_effective_default are untouched. The mirror-image case, a start time set with the end left blank, is not fixed here; the shared _bound_is_configured helper prepares for it.

  • The pipeline refreshes before the end-time override guard (#1241): The end-of-window reposition is a one-shot fired from the reconciliation tick, and its override guard read whatever _pipeline_result the last coordinator cycle happened to write. This coordinator has no periodic update interval, so that result can predate end_time by up to a full reconciliation interval, and any ordinary in-window winner such as SOLAR, CLOUD or GLARE_ZONE then looked like an active override. The entire end-time move was skipped. managers/time_window.py latches the close edge before invoking the callback, so nothing retried, and outside the clock window no other dispatch path will move the cover.

    I refresh the pipeline immediately before consulting the guard. Re-evaluating with the clock window already closed makes every window-gated handler decline, so the winner is DEFAULT and the end-time position is sent. A genuinely active CUSTOM_POSITION, MANUAL or WEATHER slot still wins and is still deferred to, preserving the behaviour added in #895.

  • Sun-tracking minimum position is visible in set_position_limits (#1242): min_position_sun_tracking was already wired into FIELD_VALIDATORS and the _SECTION_POSITION_LIMITS frozenset the service is registered with, and it carried translation entries in all three languages, but services.yaml never declared the field. Home Assistant builds the Actions form strictly from services.yaml, so the option was invisible in the set_position_limits action even though calling the service with it worked. It now has a field block with a slider selector matching the backend range (0-99, step 1, %).

    The same service had the mirror-image gap: enable_position_matching was declared in services.yaml but had no translation entry in any language, so it rendered as raw English inside translated UIs. Both directions are now locked by a parity test modelled on the set_blind_spot guard from #247. The same class of drift in five other services is tracked separately in #1251 and is not fixed here.

  • Drift-reset ineligibility is inherited on the tilt retry (#1234): The sun_tracking_only drift-reset scope gate works correctly on the primary tilt send, but the #500 drift-retry re-send inside _verify_and_record_tilt called _send_tilt_command without threading drift_reset_eligible. The flag fell back to its True default and re-armed drift accumulation on a send the scope had already ruled ineligible, so a DEFAULT-controlled venetian tilt ran a full reset cycle. The scope gate itself was never wrong: it reads ControlMethod off PositionContext rather than the command's trigger label, which is why the original diagnosis landed one frame short of the real seam.

    _verify_and_record_tilt now takes drift_reset_eligible keyword-only and forwards it on the retry. Both verify call sites pass the raw parameter rather than the derived drift_reset_enabled, because the retry's own _send_tilt_command re-derives the _reset_in_progress and threshold folds itself. apply_user_tilt leaked the same flag and now passes _drift_reset_eligible(None), so a card or service tilt is eligible only under the all_tilt_commands scope. The retry still fires; only its accumulation is gated. This is a no-op under the default all_tilt_commands scope and for every non-venetian cover type.

Testing

12,584 tests passing.

Compatibility

Home Assistant 2026.3.0+ and Python 3.11+. No migration runs for this release, and no new or changed configuration options. The companion Lovelace card (jrhubott/adaptive-cover-pro-card) is a separate repo with its own release cycle.