Adaptive Cover Pro ⛅ v2.28.0-beta.6
Pre-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 inconfig_fields.pyis the only place the templatable key set is defined; config-flow, service validators, andTemplateResolverall consume it — no parallel list to drift. _num_orsnapshot guard:config_types.py's_num_orhelper coerces any non-numeric raw value (including an unrendered template string) to the field's numeric default atRuntimeConfig.from_optionstime, so the calculation engine always receives a float.- Once-per-transition failure logging:
TemplateResolvertracks 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.