Skip to content

Adaptive Cover Pro ⛅ v2.26.0

Choose a tag to compare

@jrhubott jrhubott released this 07 Jun 21:40
· 16 commits to main since this release

ℹ Using release notes from: release_notes/v2.26.0.md
2.26.0 is the stable release of the oscillating (drop-arm) awning cover type and the declarative config-field registry that made it possible. It also picks up six fixes found during beta: a timezone mismatch in sunset/sunrise boundary comparisons, a floor-clamp bug under manual override, motion detection gaps for media-player-only configs, a cover-type label derivation problem that caused oscillating saves to loop, a config summary omission, and an event re-anchoring issue. Schedule window start/end times are now published on the control_status sensor.


🎯 Highlights

  • New oscillating (drop-arm) awning cover type (#412): arm-sweep geometry, three new config fields, appears automatically in the cover-type picker.
  • Declarative config-field registry (config_fields.py + FieldSpec): every field's type, range, selector, and default defined once; OPTION_RANGES is derived from it rather than being defined separately in const.py.
  • CoverTypePolicy section API: build_section_schema(), section_order(), live_option_keys(), extra_field_keys(), disabled_config_keys(), plus __init_subclass__ auto-registration — cover types opt in with register=True and appear in SENSOR_TYPE_MENU without touching config_flow.py.
  • Schedule window exposed: schedule_start and schedule_end now published as attributes on the control_status sensor (#538).
  • Sunset/sunrise boundary comparisons fixed for non-UTC timezones, including a follow-up re-anchoring fix for next-event calculations (#531).

✨ Features

Oscillating (drop-arm) awning cover type (#412)

Drop-arm awnings extend downward at an angle rather than rolling straight out. The new engine in engine/covers/oscillating.py models this as an arm sweep: horizontal reach equals arm length times sin(sweep angle), and that reach maps linearly across the configured min/max angle range to an open percentage. AdaptiveOscillatingCover handles the forward and inverse transforms.

Three new config fields drive the geometry: CONF_ARM_LENGTH (physical arm length), CONF_AWNING_MIN_ANGLE and CONF_AWNING_MAX_ANGLE (the usable sweep range), and CONF_AWNING_HOUSING_OFFSET (distance from the wall mount to the window plane). The angle field is derived from position, so OscillatingAwningPolicy sets disabled_config_keys = {"angle"} to suppress it from the config UI automatically.

OscillatingAwningPolicy sets register=True, so CoverType.OSCILLATING_AWNING appears in the cover-type picker the moment the policy is imported. No manual entry in SENSOR_TYPE_MENU required.

  • engine/covers/oscillating.py — arm-sweep geometry and position mapping
  • cover_types/oscillating_awning.pyOscillatingAwningPolicy, OscillatingConfig
  • services/options_service.py — validators for the new fields
  • translations/en.json, translations/de.json, translations/fr.json — labels and descriptions for all four new fields

Declarative config-field registry and policy section API

Before this release, adding a cover type meant manually updating config_flow.py with new section builders, duplicating range definitions, and registering the type name in the picker by hand. Two changes close that loop.

config_fields.py introduces FieldSpec, a dataclass that captures type, range, selector, default, and validator for each config field. OPTION_RANGES is now built from the registry and re-exported to const.py rather than being defined separately there.

The CoverTypePolicy base class gains a section API: build_section_schema() generates the HA config-entry schema for a cover type's fields, section_order() declares the order those sections appear, live_option_keys() lists fields that should re-trigger a UI refresh, and extra_field_keys() and disabled_config_keys() let a policy include or suppress fields without subclassing for each variation. Policies set register=True in their class body to self-register in _POLICY_REGISTRY via __init_subclass__. The existing BlindPolicy, AwningPolicy, TiltPolicy, and VenetianPolicy are all migrated to this API.

config_dynamic.py holds the HA-aware section builders that config_flow.py previously defined inline. SENSOR_TYPE_MENU is now derived from _POLICY_REGISTRY, so the picker stays current as new policies register.

Schedule window attributes (#538, #539)

The control_status sensor now publishes schedule_start and schedule_end as attributes, exposing the automation window directly for automations and dashboards that need to know when the integration is active.


🐛 Fixes

Sunset/sunrise boundary compared in UTC (#531, #533): Entity-derived boundary times were compared in mismatched frames. compute_effective_default received a naive wall-clock time from the entity and compared it against a naive UTC time, producing an offset equal to the UTC offset — two hours in CEST, for example. _local_naive_to_utc_naive() in helpers.py converts the entity time before the comparison, so covers on non-UTC timezones activate sunset behavior at the correct time.

Next-event sunset/sunrise boundary re-anchored to today (#531, #541): A follow-up to the UTC fix. _read_time_entity() was computing the next-event boundary against a stale date rather than today, producing incorrect results after midnight. It now re-anchors to the current date before returning.

Floor position compared against actual position under manual override (#534, #536): The floor-clamp logic was comparing the floor against the tracked (last-commanded) position rather than the actual current position. Under manual override, where the actual and tracked positions diverge, this caused the floor check to pass when it should have blocked. The comparison now uses the actual cover position.

Motion detection enabled for media-player-only configs (#525): Instances configured with a media player but no binary motion sensors were skipping motion-detection setup entirely. Motion now initializes correctly in that configuration.

Cover-type label derived from policy, fixing oscillating save loop (#530, #532): _cover_type_label() in config_flow.py was not recognizing the oscillating awning type, causing the config save to cycle and reopen the form. The label is now derived from the policy registry, so all registered cover types resolve correctly.

Config summary surfaces dry-run, housing offset, and back-rotate lag (#528): The options summary shown at the end of config flow was missing three fields: the dry-run toggle, CONF_AWNING_HOUSING_OFFSET, and the venetian back-rotate lag. All three now appear.


🔧 Internal

OPTION_RANGES was previously defined as a plain dict in const.py, duplicating bounds already embedded in each field's selector definition. It now derives from the FieldSpec registry in config_fields.py and is re-exported to const.py for back-compat. Both the config-flow selectors and services/options_service.py validators now draw from the same source.


🧪 Testing

  • 3938 tests passing

Compatibility

  • Home Assistant 2026.3.0+

References