Adaptive Cover Pro ⛅ v2.22.0
ℹ Using release notes from: release_notes/v2.22.0.md
v2.22.0
🎯 Highlights
v2.22.0 closes a seven-beta cycle that delivered two user-facing features, a large internal refactor, and a concentrated run of venetian-blind reliability fixes. The headline additions are pipeline-aware "My Position" entities — a button and a runtime-configurable number that expose the Somfy "My" preset through the managed cover with full pipeline preemption — and custom sunrise/sunset time entity options, so integrations like Sun2 can supply seasonally-adjusted window boundaries that the fixed-offset sliders cannot match. Venetian users on KNX, Shelly 2PM, FGR223, and Somfy IO hardware should see the end of false manual-override events: issue #33 received three cooperating fixes across betas 2–5 that tighten the three-tier suppression model to handle fast-settle, slow-start, and slow-bus republish scenarios. Two pipeline correctness fixes round out the release: cloud suppression no longer fires when the sun is outside the window's field of view, and user commands that already satisfy a min-mode custom-position floor are no longer preempted.
✨ Features
-
Pipeline-aware My Position button and number (#409, PRs #410, #413):
Two new entities appear on each configured managed cover:
button.<managed_cover>_my_position("Managed My Position"): Pressing the button readsCONF_MY_POSITION_VALUEfrom config options and callscoordinator.async_apply_user_positionwithtrigger="my_position_recall"andforce=False. The pipeline preemption path runs first: if a higher-priority handler is active —force_override(priority 100),weather(90), or any custom-position slot configured aboveManualOverrideHandlerpriority — the move is dropped and recorded as a preempted skip. When the move proceeds, min-mode floor clamping applies andmark_user_commandengages manual override, so the cover holds the recalled position until the next coordinator cycle releases it.number.<managed_cover>_my_position_value("Managed My Position Value",EntityCategory.CONFIG): A runtime slider (1–99 %, step 1) that exposesCONF_MY_POSITION_VALUE. Writing a new value goes throughvalidate_options_patchandapply_options_patch, so thesunset_use_my ⇒ my_position_valuecross-field validation still fires. The entity readsCONF_MY_POSITION_VALUEfromconfig_entry.optionsdirectly so the value survives options-form saves without resetting.Both entities carry translation keys
"my_position"(button) and"my_position_value"(number) inen.json,de.json, andfr.json. German and French translations keep"My"as a verbatim brand-name token matching the physical label on Somfy RTS remotes, rather than translating it as the possessive pronoun. -
Custom sunrise/sunset time entities (#411, PR #415):
Two new optional entity options —
Sunset Time Entity(CONF_SUNSET_TIME_ENTITY) andSunrise Time Entity(CONF_SUNRISE_TIME_ENTITY) — accept asensororinput_datetimewhose state is an ISO-8601 datetime string. When set, the entity value replaces the astral-computed boundary insidecompute_effective_default()inhelpers.py. ExistingSunset Offset/Sunrise Offsetsliders remain additive on top of the entity value. The integration falls back to astral silently when the entity is unavailable, unknown, or unparseable.A module-level helper
_read_time_entityincoordinator.pyreads the entity state viaget_safe_state, parses it withget_datetime_from_str, and returns a naive-local datetime orNoneon any failure. Typical use case: a Sun2 "sunset at −4° elevation" sensor delivers a seasonally-adjusted boundary that the static astral value cannot match.Translations updated for DE and FR.
🐛 Bug Fixes
Venetian blinds (issue #33)
Three cooperating fixes address false manual-override on venetian covers across different actuator hardware:
-
Post-settle cap grace — position axis (#33, beta.2):
DualAxisSequencer.is_in_suppression_with_capholds the cap bypass active for 5 seconds after a position command stamp, preventing KNX, Shelly 2PM, FGR223, and similar actuators from tripping false manual-override whencover.statesettles before the tilt-walk burst finishes publishing. -
Tilt-axis command-grace suppression (#33, PR #406): After the position-axis fix, the tilt axis still lacked a command-grace gate.
VenetianPolicy.secondary_axis_checknow builds a suppression callback that OR-composes the existing back-rotate window (is_in_tilt_suppression) with a new_is_in_tilt_command_gracehelper that delegates toGracePeriodManager.is_in_command_grace_period. Motor drift and verification-poll republishes inside the command-grace window no longer reach the manual-override path. -
Publish-lag window anchored to settle transition + startup grace (#33, PR #408): Two cooperating defects on slow-start actuators like Somfy IO:
-
Premature stall declaration:
_wait_for_position_settlewas counting consecutive unchanged-position samples as a stall before the motor had started moving (Somfy IO takes 3–5 s to begin physical travel). The fix addsVENETIAN_POSITION_SETTLE_STARTUP_GRACE_SECONDS = 6.0: stall declaration is blocked until the cover has been observed in a moving state at least once, or the startup-grace window has elapsed. -
Wrong clock for publish-lag:
is_in_suppression_with_capwas starting its 5 s cap-grace clock fromstamp_position_command. On Somfy IO, the premature settle stamp started that clock 22 s early, so the real back-rotate burst arrived after the cap had already reasserted. The fix adds a third tier —VENETIAN_BACKROTATE_PUBLISH_LAG_SECONDS = 45.0— anchored to the actualmoving → settledtransition recorded by_stamp_settled. The resulting three-tier suppression model: (a) carriage mid-travel: any delta suppressed; (b) command-grace tail (5 s after stamp): large deltas suppressed; (c) publish-lag window (45 s after settle): large deltas suppressed. Outside all three tiers, the geometry-bounded cap (VENETIAN_BACKROTATE_MAX_DELTA_PERCENT = 30%) applies normally.
-
Pipeline correctness
-
Cloud suppression skips when sun is outside window FOV (#417, PR #420):
CloudSuppressionHandlerwas evaluating its cloud-trigger conditions regardless ofdirect_sun_valid. When the sun was geometrically outside the window's field of view, the handler could still win and send the cover tocloudy_positionordefault_position. The fix adds adirect_sun_validguard at the top of the handler; when it is false the handler returnsNoneand the reason string reads"cloud suppression skipped (sun outside window FOV)". -
User commands above custom-position floor pass through (#416, PR #419): In
async_apply_user_position, the pipeline was rebuilt without conveying the user's requested target. ACustomPositionHandlerat higher priority thanManualOverrideHandlerwould win and preempt any user move — even requests that already satisfied the min-mode floor. The fix adds_winner_is_satisfied_custom_floorincoordinator.py: when the winning handler is a min-modeCustomPositionHandlerand the clamped request is at or above the configured floor, the coordinator falls through to dispatch the user command. Min-mode now means what the UI describes: a floor that allows higher positions, not a forced-exact target. -
Auto-control re-enable dispatches fresh solar position (#352, PR #414): Setting the automatic-control switch OFF→ON no longer sends the stale prior-cycle position before the pipeline runs. The coordinator now sets
state_change = Trueand letsasync_refreshroute dispatch through the normal state-change path, so the first position after re-enable reflects current sun geometry. -
"My Position Value" number survived options saves (#409, PR #413):
AdaptiveCoverMyPositionNumberhadasync_set_native_value(the write path) but nonative_valueproperty. HA's base class returned_attr_native_value = None, so every config-entry reload (triggered by options-form saves) reset the displayed value tounknown. Fixed by adding anative_valueproperty that readsCONF_MY_POSITION_VALUEdirectly fromconfig_entry.options.
Config flow
-
"Is Sunny" sensor can be cleared in the options flow (#377, PR #393): After setting a cloud-suppression "Is Sunny" binary sensor, a blank submission silently kept the prior value. Root cause:
CONF_BINARY_SENSORwas absent from the optional-entities list the schema layer uses to recognise cleared fields. Adding it makes blank submissions remove the value as expected. -
MODE2 tilt climate handler: sun-side awareness +
min_positionwarning (#373, PR #405): The climate handler'stilt_with_presenceandtilt_without_presencemethods computed tilt percentages using MODE1-only formulas that ignored the sign of the sun's azimuth offset (gamma). In MODE2, picking the wrong hemisphere produces a position on the open side rather than the blocking side. The fix addsTiltPolicy.climate_tilt_percentageas a single conversion helper that picks the correct hemisphere from the sign of gamma and routes all four call sites through it. Separately, the config-flow summary now surfaces a⚠️ warning when MODE2 is active andmin_position≥ 50 — a configuration that silently collapses every below-centre climate result to the open-slat position.
🧹 Internal / Refactor
-
Nine-phase coordinator refactor (Phases A–I, betas 1–3): Coordinator responsibility distributed into
managers/,state/,pipeline/, andcover_types/. The coordinator is ~640 lines lighter vs v2.21.5. Manual-override detection moved intomanagers/cover_command/state_classifier.py(preserving all five prior issue-specific fixes: #147, #172, #186, #271, #285). Cover-type-specific behaviour consolidated ontoCoverTypePolicysubclasses; coordinator and pipeline handlers no longer branch on cover-type strings or hardcoded capability keys.DualAxisSequencerrelocated tocover_types/venetian/alongsideVenetianPolicy.StubSingleAxisPolicyandStubDualAxisPolicyadded as canary types with ~60 regression tests parametrised across all real and stub policies. Per-type label and wiki-URL mappings consolidated ontoCoverTypePolicy.wiki_anchor()anddisplay_label()hooks. Net diff for the refactor: 62 files changed, +3,682 / −1,251 lines. -
Config-flow plumbing refactor (Phase I, beta.3, PRs #399, #400): Duplicated schema reads, step routing, and options-step boilerplate collapsed into shared helpers, reducing
config_flow.pyby 168 lines. Deadasync_step_climate/CLIMATE_SCHEMAcode removed; optional-key constants extracted adjacent to their schemas to prevent future #377-class drift.
🧪 Tests
- ✅ 3449 tests passing (up from 3239 in v2.21.5).
Compatibility
- Home Assistant 2026.3.0+
- Python 3.11+
- No breaking changes