Skip to content

Adaptive Cover Pro ⛅ v2.30.0

Choose a tag to compare

@jrhubott jrhubott released this 28 Jun 15:52
· 7 commits to main since this release

ℹ Using release notes from: release_notes/v2.30.0.md

Highlights

v2.30.0 promotes four beta cycles of work since v2.29.0. The two headline additions are a new roof/skylight window cover type (closing the oldest open feature request, #212) and a building profile system that consolidates shared weather and climate sensors across all covers in a building without duplicating them per entry. Alongside those, blind-spot configuration expands to three slots with per-slot elevation-mode filtering, custom-position slots grow from five to ten, and a new Sun Tracking switch pauses solar tracking at runtime while keeping glare-zone protection active. The venetian engine gains a drift-reset cycle with configurable direction and threshold, endpoint-aware open/close dispatch, a live solar_calculation diagnostic sensor, and a fix for slat back-rotation on motors that drag slats shut during a close. An external input-sensor manual override, a weather override master toggle, an opt-in summer-close sun-floor bypass, and a set_tilt service for automations complete the set. Thanks to @Zhephyr54 for contributing the Sun Tracking switch (#678) and to @vaind for tracking down and fixing the venetian back-rotation bug (#694).

Added

  • Roof/skylight window cover type (#212, #696): New cover type "cover_roof_window" with pitch-aware sun geometry. Configure CONF_ROOF_PITCH and CONF_ROOF_HEIGHT_ABOVE to match the physical installation; RoofSunGeometry handles the tilted-plane math, RoofWindowCover drives position calculation, and RoofWindowPolicy provides the policy layer. The type is registered in ALL_COVER_TYPES alongside the existing blind/awning/tilt types. Closes the oldest open feature request.

  • Building profile (#693, #700, #706): A new virtual entry type, created via its own config-flow steps (async_step_create_building_profile(), async_step_building_profile_sensors()), that holds shared building-level weather and climate sensors without controlling any covers. Linked covers receive live sensor propagation via _async_profile_propagate(); _copy_profile_to_cover() applies profile values and _covers_linked_to() tracks which entries reference a given profile. BuildingProfilePolicy and SensorSource define the data model. On deletion, async_remove_entry() cleans the dangling profile link from all linked covers. The standalone weather-retraction toggle was removed in the same pass (#706); the migration handles existing data transparently.

  • Building Profile overview of linked covers: async_step_profile_overview in building_overview.py presents a comparison table of every cover linked to the profile. Rows are modeled with _CoverRecord, _DiffSpec, and _Entry dataclasses; diff logic uses _comparison_as_list, _comparison_as_table, and _format_diff_line, with local overrides rendered by _local_override_repr. Number normalization via _num ensures numeric values compare correctly regardless of representation.

  • Inherit/override model for Building Profile sensors: async_step_profile_overrides lets a linked cover override any inherited profile sensor locally. Each override is tracked as an OverrideRecord dataclass, giving the options flow a structured representation of which fields are inherited and which are locally set.

  • Sun Tracking switch; glare-zone protection without sun tracking (#498, #678): A new Sun Tracking switch (CONF_ENABLE_SUN_TRACKING, option key enable_sun_tracking) exposes runtime control over whether the solar position engine drives cover movement. The switch defaults to on (True), so all existing installs continue to track the sun. When toggled off, async_apply_sun_tracking_update rebuilds the pipeline without a reload; CONF_ENABLE_SUN_TRACKING is listed in _RUNTIME_APPLICABLE_OPTIONS so the update listener applies it without triggering a config-entry reload. GlareZoneHandler is now aware of the tracking state: when enable_sun_tracking is off, glare-zone protection can still activate if its computed position is more protective than the default result, as determined by more_protective_position; apply_snapshot_limits enforces sun-dependent limits only when tracking is on. Thanks to @Zhephyr54 for filing #498 and contributing the implementation.

  • Multiple blind-spot slots with elevation modes (#701, #702, #708): Blind-spot configuration expands from one slot to three (BLIND_SPOT_SLOTS). Each slot carries left/right azimuth bounds plus an elevation threshold and an elevation mode: BLIND_SPOT_ELEV_MODE_BELOW blocks low-sun intrusion, BLIND_SPOT_ELEV_MODE_ABOVE blocks high-sun intrusion. BlindSpot is a frozen dataclass; _make_blind_spot() and _extra_blind_spots_from() build the slot list from config, _blind_spot_slot_keys() drives options-flow validation, _blind_spot_step_errors() validates bounds, and _sun_in_blind_spot() does the single containment check for both modes. Slot 1 reuses the legacy unsuffixed keys so existing configurations carry forward unchanged; slots 2 and 3 are optional and suffixed. Diagnostics loop over all active slots dynamically.

  • Ten custom-position slots (#703, #704): CUSTOM_POSITION_SLOT_NUMBERS now covers slots 1-10, up from 1-5. Slots 6-10 behave identically to existing slots and respect the same priority range (1-100, default 77).

  • Endpoint-aware open/close (#697, #699): When a cover supports set_position, open and close commands now drive the carriage to its physical endpoints rather than issuing only a generic service call. apply_user_position_endpoint_open() and apply_user_position_endpoint_close() handle the axis-aware dispatch; _POSITION_AXIS_SERVICES maps each axis to its service.

  • On/off master toggle for weather override (#719): CONF_WEATHER_ENABLED gates the entire weather handler. New installs default to off (DEFAULT_WEATHER_ENABLED = False); the v3.5 to v3.6 migration sets CONF_WEATHER_ENABLED = True for all existing entries, so upgrading changes nothing for installs that already have weather sensors configured.

  • Opt-in summer-close sun-floor bypass (#689): CONF_SUMMER_CLOSE_BYPASS_SUN_FLOOR lets summer climate mode close covers regardless of sun position. Default off; enabling it is additive and changes nothing for installs that leave it unset.

  • Venetian drift-reset threshold (#663, #681): A new per-instance option CONF_VENETIAN_TILT_RESET_THRESHOLD sets an accumulated commanded-tilt-% threshold that triggers a mechanical drift-reset cycle. When the accumulated counter in DualAxisSequencer reaches the threshold, the sequencer drives the slats to POSITION_OPEN (bypassing any configured tilt max) and back to the original target, flushing mechanical actuator drift. A _reset_in_progress guard prevents the re-zero moves from accumulating against the counter. The default DEFAULT_VENETIAN_TILT_RESET_THRESHOLD is 0 (feature disabled); range is 0-5000 accumulated %. The threshold applies live without a reload. Thanks to @x4N70pHyLL (#663) for surfacing this.

  • Configurable venetian drift-reset direction (#686, #690): CONF_VENETIAN_TILT_RESET_DIRECTION (select: open / close) controls which endpoint the drift-reset drives through before re-targeting. The default VENETIAN_TILT_RESET_OPEN (DEFAULT_VENETIAN_TILT_RESET_DIRECTION) preserves prior behavior; VENETIAN_TILT_RESET_CLOSE suits hardware that rests near-closed during sun tracking or re-zeroes the actuator on a close command. Valid values are listed in VENETIAN_TILT_RESET_DIRECTIONS. The direction applies live without a reload. Thanks to @elmakus (#686) for requesting this.

  • Opt-in endpoint delta enforcement for coupled covers (#679, #680): CONF_ENFORCE_DELTA_AT_ENDPOINTS (default DEFAULT_ENFORCE_DELTA_AT_ENDPOINTS = False) brings the min-change delta gate to endpoint commands. By default the always-send-to-0/100 guarantee is preserved: build_special_positions returns [0, 100] and the tilt special-position bypass is in effect. When opted in, build_special_positions returns an empty list and check_position_delta runs for endpoint targets too, which suits mechanically coupled covers where the hardware parks at 3 rather than 0 and full-open/close commands disturb the tilt axis.

  • Manual override detection from external input sensors (#688, #691): CONF_MANUAL_OVERRIDE_INPUT_ENTITIES (multi-entity selector, default empty) accepts a list of binary sensors whose off-to-on edge engages manual override on every cover in the instance. Intended for physical wall switches such as a Shelly input sensor: pressing the switch pauses automatic control for the configured override duration without waiting for the cover to report a position change. The edge filter is strict; on-to-on, None-to-on, and unavailable/unknown states do nothing, and each press re-arms the full duration. Thanks to @elmakus (#688) for requesting this.

  • set_tilt service (#684, #685): A new adaptive_cover_pro.set_tilt service lets automations drive the venetian slat axis without touching the carriage. The service delegates to async_apply_user_tilt, which routes through VenetianPolicy to drive only the slats via DualAxisSequencer.update_tilt_only. A force parameter (default false) governs manual-override engagement, matching set_position semantics. Implemented in set_tilt_service.py via async_handle_set_tilt.

  • Live solar_calculation diagnostic sensor (#682, #683): Each cover now gets a solar_calculation sensor whose state is the per-cycle computed position_pct and whose attributes carry the full geometric trace: sun elevation, gamma, engine-specific intermediates, and for venetian covers a nested tilt sub-trace. The calc engines record raw inputs and outputs in _last_calc_details each cycle. DiagnosticsBuilder._round_trace and _round_trace_value round every numeric leaf at the presentation boundary and stamp cover_type and status before the data reaches the sensor. The sensor reads unknown outside the solar window; DiagnosticsBuilder._build_position_calc_details emits a minimal fallback trace so attributes always carry a status reason.

  • Safety-slot warning (#711): The options flow warns when a custom-position slot is set to safety priority (100), which bypasses all gates including the time window and delta checks.

Fixed

  • Venetian tilt back-rotated after close (#694): On hardware where the motor drags the slats shut during a close, the integration previously left the verified-tilt flag set and never re-asserted the commanded angle. _tilt_targets_verified.discard() is now called after any position move so the sequencer re-reads actual tilt on the next update and re-asserts the correct angle. Thanks to @vaind for reporting and fixing this edge case.

  • Venetian coupled-tilt recovery after set_cover_position (#679, #680): On mechanically coupled venetians, a set_cover_position command back-drives the tilt actuator, leaving the stored tilt target stale. The previous stored-target dedup silently discarded the next intended tilt command even when the actuator had drifted. _target_already_satisfied now compares the stored target against the live actuator reading via _resolve_tilt_anchor; it returns True only when the stored target matches the live reading within VENETIAN_TILT_VERIFY_TOLERANCE. When the live reading has drifted, the dedup returns False and the tilt command is re-sent. This fix is always on and is independent of CONF_ENFORCE_DELTA_AT_ENDPOINTS. Thanks to @elmakus (#679) for reporting this.

  • Proxy tilt drove the carriage, not the slats (#684, #685): The venetian proxy's async_set_cover_tilt_position was calling async_apply_user_position with the requested tilt value, which moved the carriage and left the slats at whatever angle they held. It now calls async_apply_user_tilt, which dispatches through VenetianPolicy.apply_user_tilt to DualAxisSequencer.update_tilt_only with force=True.

  • solar_calculation attributes blank outside the solar window (#682): When the sun was outside the tracking window, _last_calc_details was None and the sensor read unknown with empty attributes. DiagnosticsBuilder._build_position_calc_details now emits a minimal fallback trace (sol_elev_deg, gamma_deg, position_pct=None) whenever no engine trace was recorded, with a status field drawn from control_state_reason.

  • Roof-window FOV gate now accounts for cover tilt (#212, #728): The field-of-view gate for roof/skylight covers was computing sun containment from the raw azimuth without accounting for how the cover's own tilt affects the effective field of view. The gate now applies tilt-aware geometry, preventing false exclusions where a tilted roof cover would incorrectly discard sun positions that should be tracked.

  • Per-day predicted sun window now accounts for roof cover tilt (#729, #731): The per-day sun-window prediction used for scheduling was computing the sun-in-FOV interval without adjusting for a roof cover's pitch, leading to incorrectly wide or narrow tracking windows. The prediction now uses the same tilt-aware geometry as the real-time gate.

  • Building Profile step missing from cover create flow (#727): The Building Profile setup step was not appearing when creating a new cover entry. The create flow now correctly routes to the profile step.

  • Building Profile options flow routed to sensor-only step (#715, #717): The Building Profile options flow now routes to async_step_profile_sensors rather than the full cover options flow, so profile entries can only configure shared sensors.

  • Building Profile template fields scoped and surfaced in UI (#720, #722): Template fields in the Building Profile options flow are now correctly scoped to the profile entry and visible in the UI; previously they could leak across entries or not appear.

  • Building Profile entries skipped in loaded_coordinators (#712): Building profile entries are now excluded from loaded_coordinators so coordinator lookups intended for cover entries don't inadvertently resolve to profile entries.

  • async_unload_entry guarded for building profile entries (#712, #714): async_unload_entry now guards against non-cover building profile entries, preventing an unhandled path when unloading a profile entry.

  • German translation drift resolved (#709, #710): Several config and options flow strings were out of sync between English and German. No string keys changed.

Upgrade Notes

Upgrading from v2.29.0 requires no manual reconfiguration. All new options default to off or empty, so behavior is unchanged unless you enable them. The config schema advances from v3.4 to v3.6 across two minor-version bumps; both migrations are transparent.

Weather override toggle: The v3.5 to v3.6 migration sets CONF_WEATHER_ENABLED = True for all pre-existing entries, preserving existing behavior. The off-by-default applies only to new installs.

Venetian options: CONF_VENETIAN_TILT_RESET_THRESHOLD defaults to 0 (feature disabled), CONF_VENETIAN_TILT_RESET_DIRECTION defaults to VENETIAN_TILT_RESET_OPEN (prior behavior), and CONF_ENFORCE_DELTA_AT_ENDPOINTS defaults to False. Upgrading from v2.29.0 changes nothing without reconfiguration.

Blind-spot slots: Slot 1 reuses the legacy unsuffixed keys. Slots 2-3 are optional and additive; existing single-slot configurations are unaffected.

Custom-position slots: Slots 6-10 are additive; existing slots 1-5 are unchanged.

Testing

5288 tests passing.

Compatibility

Requires Home Assistant 2026.3.0+. The companion Lovelace card (jrhubott/adaptive-cover-pro-card) is a separate repo with its own release cycle.