Skip to content

Adaptive Cover Pro ⛅ v2.27.0

Latest

Choose a tag to compare

@jrhubott jrhubott released this 09 Jun 04:08

ℹ Using release notes from: release_notes/v2.27.0.md
2.27.0 brings forecast/runtime parity, opt-in movement minimization for sun-tracking covers, and sun-state classification exposed through diagnostics and the decision_trace sensor. A false manual override that fired when a cover returned from unavailable is fixed. The release tooling also gains a ZIP-structure guard to prevent a double-nested HACS install path.


🎯 Highlights

  • Forecast/runtime parity (#556): the position forecast now calls the same snapshot-free primitives as the live pipeline, so min/max limits, floor-at-1%, rounding, and movement minimization all match runtime by construction.
  • Opt-in movement minimization (#555): a new CONF_MINIMIZE_MOVEMENTS option quantizes sun-tracking positions to a configurable number of coverage steps, reducing small cover movements without changing average coverage.
  • Sun-state classification (#552, #553, #554): a SunState enum (HITTING, IN_FOV_NOT_VALID, OUTSIDE_FOV) is computed per cycle, surfaced in diagnostics, and exposed as sun_state on the decision_trace sensor attributes.
  • False manual override suppressed (#546, #551): covers returning from unavailable no longer trigger a spurious manual override when no prior command target exists.

✨ Features

Forecast/runtime parity (#556)

The position forecast in forecast.py previously re-implemented a subset of the live position math independently. It never applied min/max position limits, did not floor solar samples at 1% or round them, and used the raw default height rather than the sunset position during the sunset window. The result was a forecast that visually diverged from what the live pipeline would produce.

Three snapshot-free primitives now own the shared math in pipeline/helpers.py: solar_position_from_geometry computes the sun-tracking position from geometry alone, default_position_with_limits computes the default (sunset-aware) position with min/max limits applied, and apply_config_limits applies the configured position bounds. The live pipeline helpers in pipeline/helpers.py are thin adapters over these primitives. The forecast calls the same functions, so limits, floor-at-1, rounding, and movement minimization match runtime by construction.

compute_effective_default gained an eval_time keyword argument so the forecast can compute the sunset-aware default at each sample's projected time rather than at the current moment. A parity test locks compute_solar_position and compute_default_position against future drift between the two paths.

  • forecast.py — now calls shared primitives instead of duplicating position math
  • pipeline/helpers.pysolar_position_from_geometry, default_position_with_limits, apply_config_limits extracted here

Opt-in movement minimization (#555)

Small positional corrections as the sun moves across the sky cause covers to jog frequently. The new CONF_MINIMIZE_MOVEMENTS option (default off) quantizes the computed sun-tracking position to a configurable number of evenly-spaced coverage steps via quantize_to_coverage_steps() in position_utils.py. Step count is controlled by CONF_MAX_COVERAGE_STEPS (default DEFAULT_MAX_COVERAGE_STEPS). Quantization applies after the raw solar position is computed, so it interacts correctly with min/max limits and the floor-at-1% rounding already in place.

The feature applies to tilt, blind, and awning cover types. It is surfaced in the config flow options UI, in services.yaml for service calls, and in all three translation files.

  • position_utils.pyquantize_to_coverage_steps()
  • pipeline/handlers/solar.py — applies quantization when CONF_MINIMIZE_MOVEMENTS is enabled
  • services.yamlCONF_MINIMIZE_MOVEMENTS, CONF_MAX_COVERAGE_STEPS exposed
  • translations/en.json, translations/de.json, translations/fr.json — labels and descriptions

Sun-state classification in diagnostics and decision trace (#552, #553, #554)

Each update cycle now classifies the sun's relationship to the cover window using the new SunState StrEnum in engine/sun_geometry.py, with three values: HITTING (sun is directly hitting the cover), IN_FOV_NOT_VALID (sun is within the field of view but not at a valid elevation or angle), and OUTSIDE_FOV (sun is outside the configured field of view entirely). The in_fov() method on sun geometry classes drives the classification.

diagnostics/builder.py reads the classification from the pipeline result and includes it in the diagnostics payload, making it visible in the HA diagnostics download. sensor.py surfaces sun_state as an attribute on the decision_trace sensor, so dashboards and automations can read the classification directly without downloading diagnostics. The attribute degrades gracefully when the value is absent.

  • engine/sun_geometry.pySunState enum, in_fov() method
  • diagnostics/builder.py — classification included in diagnostics output
  • sensor.pysun_state attribute on decision_trace sensor

🐛 Fixes

False manual override on cover returning from unavailable (#546, #551): The override detector in managers/manual_override/ treated any position delta as a manual override, even when the integration had never recorded a command target — for example, when a cover came back online after being unavailable. The fix threads has_recorded_target through the detection path: when no target has been recorded, the position-delta branch does not mark an override. A user-context change (explicit user action) still triggers the override regardless. The new constant _NON_POSITION_COVER_STATES centralizes the set of cover states excluded from position-delta detection.


🔧 Internal

The release script in scripts/release now verifies the built ZIP structure before publishing, guarding against a double-nested directory layout that causes HACS to install the integration at the wrong path (#544, #545). This is a distribution-only change with no runtime effect.


🧪 Testing

  • 4214 tests passing

Compatibility

  • Home Assistant 2026.3.0+

References