Skip to content

Adaptive Cover Pro ⛅ v2.28.0-beta.6

Pre-release
Pre-release

Choose a tag to compare

@jrhubott jrhubott released this 11 Jun 21:00
· 161 commits to main since this release

ℹ Using release notes from: release_notes/v2.28.0-beta.6.md
Beta.6 is the build that genuinely ships Jinja2 template support in threshold fields and the optional occupancy condition template — the feature commit landed after the beta.5 tag was cut — plus two config-flow fixes that complete the create-flow persistence work from beta.2/beta.3.

🎯 Highlights

Beta — please test and report back.

  • Configure a temperature threshold (e.g. CONF_TEMP_HIGH) as a Jinja2 template — {{ states('sensor.outdoor_temp') | float }} — save, and confirm the cover responds to the rendered value rather than a hard-coded number.
  • Configure a lux or wind-speed threshold as a template and confirm it resolves each coordinator cycle.
  • Enter a malformed template and confirm the cover falls back to the field's default and that no error is logged on every cycle — only once per failure transition.
  • Open Diagnostics for a cover with templated thresholds and confirm the payload shows the raw template string alongside its last-resolved value.
  • Add an occupancy condition template to Motion Override; confirm a truthy render counts as presence and a falsy render correctly starts the motion timeout rather than holding the cover open.
  • Create a new cover, fill every field including lux threshold, wind direction tolerance, and custom positions, save, and confirm nothing is silently dropped.

✨ Features

Jinja2 templates in numeric threshold fields (#577, #578)

Nine numeric threshold config fields now accept either a plain number or a Home Assistant Jinja2 template: CONF_LUX_THRESHOLD, CONF_IRRADIANCE_THRESHOLD, CONF_CLOUD_COVERAGE_THRESHOLD, CONF_TEMP_LOW, CONF_TEMP_HIGH, CONF_OUTSIDE_THRESHOLD, CONF_WEATHER_WIND_SPEED_THRESHOLD, CONF_WEATHER_RAIN_THRESHOLD, and CONF_WEATHER_WIND_DIRECTION_TOLERANCE. All nine are collected in a single frozenset TEMPLATABLE_KEYS defined in config_fields.py; the config-flow selector builder, the service validators, and the runtime resolver all consume it — one source, no divergence.

In the config UI these fields render as a Jinja code editor (TemplateSelector) with entity autocomplete. Field units (°C, lux, W/m², etc.) moved into the translation description since the template selector carries no unit metadata.

At runtime, TemplateResolver in templates.py renders templated thresholds to floats once per coordinator cycle. Fast path: if no templatable key holds a string, the options dict is returned unchanged with no copy overhead. Render failures are non-fatal — a threshold whose template fails is dropped so the field falls back to its default. The failure is logged once per failure transition via the resolver's internal _failed set, not every cycle. is_template_string returns True only for strings containing {{ or {%, so a plain numeric string like "1000" is never treated as a template.

_num_or in config_types.py keeps the typed RuntimeConfig snapshot numeric: RuntimeConfig.from_options runs at setup/attach time on raw options before the first resolved cycle, so an unrendered template string falls back to the field default until TemplateResolver substitutes a real number.

DiagnosticsBuilder surfaces the template state via the new DiagnosticContext.resolved_options field — each key whose raw value is a template maps to an object containing the raw string and its last-resolved value. Plain-number configs produce an empty map.

All three threshold-template flavours ship English, German, and French translations.

Optional occupancy condition template on Motion Override (#577)

A new optional CONF_MOTION_TEMPLATE field on the Motion Override config accepts a Jinja2 condition template. When it renders truthy, it counts as occupancy and is OR'd with the configured motion sensors and media players. render_condition is the reusable primitive — it uses HA's result_as_boolean and returns the default on an empty string, a non-template value, or a render failure.

MotionManager.is_configured is now the single source for "is motion control active?" — it returns True when any sensor is set OR a template is configured. MotionManager.template_active exposes the rendered result. MotionManager.is_motion_detected OR's the template result with the sensor states. The coordinator wires the template as a state listener via async_check_motion_template_change, so a truthy render immediately refreshes the cover and a falsy render starts the motion timeout, matching existing sensor behaviour. The combine mode between template and sensors is controlled by TemplateCombineMode via combine_with_mode and exposed through CONF_MOTION_TEMPLATE_COMBINE_MODE.

🐛 Fixes

Hassfest: config_summary i18n relocated out of translations/ (#579)

Hassfest forbids custom top-level keys in translations/. The config_summary translation bundle moves to a new summary_i18n/ directory (_SUMMARY_I18N_DIR). Config flow now loads these labels via direct file I/O — _load_summary_labels_sync / _load_summary_labels / _flatten_summary_labels / _summary_label_overlay — instead of async_get_translations. Invisible to users; required for HACS/hassfest validation to pass.

Create-flow fields no longer silently dropped on first save (#565, #580)

When creating a new cover, all filled fields are now persisted. The previous implementation used a hardcoded ~73-key allowlist that silently dropped any key not on the list. The fix replaces the allowlist with full-dict persistence. A separate change fixes the FOV mode re-render to preserve a user-typed azimuth via add_suggested_values_to_schema, so toggling CONF_FOV_MODE no longer wipes the azimuth field.

🔧 Internal

  • Single-source TEMPLATABLE_KEYS: the frozenset in config_fields.py is the only place the templatable key set is defined; config-flow, service validators, and TemplateResolver all consume it — no parallel list to drift.
  • _num_or snapshot guard: config_types.py's _num_or helper coerces any non-numeric raw value (including an unrendered template string) to the field's numeric default at RuntimeConfig.from_options time, so the calculation engine always receives a float.
  • Once-per-transition failure logging: TemplateResolver tracks previously failing keys and logs only on the transition from success to failure (and on recovery), not every coordinator cycle.

🧪 Testing

4,420 tests passing. New coverage in tests/test_templates.py (418 lines — TemplateResolver, is_template_string, render_condition, MotionManager template integration, and end-to-end suppression tests).

Previously in this beta line

Configuration Summary translated to user's language (beta.4/beta.5, #258, #575, #576): The narrative Configuration Summary in the config/options flow renders in the user's HA language with English, German, and French shipped. The config_summary bundle (now under summary_i18n/) carries translated leaves for every summary fragment.

Set-position covers can reach true 0% in solar tracking (beta.3/beta.4, #569, #572): A geometry oversight prevented some set-position covers from closing fully during solar tracking. Covers that support arbitrary position commands now close to true 0%.

Same-position gate reverted to exact equality, restoring 1–3% tracking moves (beta.3, #567, #574): A tolerance band silently dropped small solar tracking adjustments. The gate reverts to exact equality; movement hysteresis is preserved in the reconcile path.

String entity_id in service targets now normalizes correctly (beta.3, #570, #571): Services that accept a string entity_id target normalize the value before resolution, fixing silent failures from case or spacing variations.

FOV mode persisted on re-render, fixing save loop (beta.2/beta.3, #565): The selected CONF_FOV_MODE was not written back into the stored options dict before re-render, causing an infinite re-render cycle.

Measurements-mode config-flow save and imperial shaded-area compounding fixes (beta.2, #565): FOV sliders were emitted as vol.Required, blocking the frontend from switching to Measurements mode. Imperial shaded-area values also compounded on each re-render.

Low-sun edge case returns closed position (beta.1, #559, #562): At very low sun elevations, a geometry edge case returned the fully-open position. The cover now closes when the sun is near the horizon.

Write-gating, geometry caching, and sun availability guard (beta.1, #543): _acp_render_signature() skips HA state writes when the calculated position matches the last written value. Pure geometry helpers use @lru_cache to eliminate repeated identical calculations. A guard on sun.sun suppresses solar tracking when the sun entity is unavailable.

Compatibility

Requires Home Assistant 2026.3.0+. No new coupled-component requirements.

References