-
Notifications
You must be signed in to change notification settings - Fork 28
Template Self References
Every option template β the numeric thresholds and the boolean condition fields β renders with an extra namespace called acp that points at this instance's own entities. It lets a template say "when my Sun Infront sensor is on" without typing the install-specific entity ID, and without that template breaking the day someone renames the entity or the instance.
Before this existed, the only way to self-reference was to hardcode the ID:
{# fragile: breaks on any rename #}
{{ is_state('binary_sensor.dining_room_dining_room_shade_sun_infront', 'on') }}With the namespace:
{{ is_state(acp.sun_infront, 'on') }}The namespace is always available. There is nothing to enable and no new setting.
| Form | Gives you | Available in |
|---|---|---|
acp.<key> |
the entity ID as a string | every template field |
acp_entity('<key>') |
the same entity ID, spelled as a call | every template field |
acp_state.<key> |
the entity's state as a string | numeric threshold fields only |
acp.<key> and acp_entity('<key>') are two spellings of one lookup β use whichever reads better. The bracket form acp['<key>'] works too, which is handy when the key comes from a variable.
acp_state is deliberately restricted; see Why acp_state is numeric-only below.
One update per second, per template. Any tracked template that uses the namespace has the update cycles it can drive capped at one a second. The first change after a quiet second still runs immediately, so a single flip is as fast as it ever was; changes that arrive inside that second are collapsed into one run when it closes. In practice you notice this only when something flips twice in quick succession β a Custom Position slot gating on
acp.manual_override, for instance, will act on the first flip at once and on the second up to a second later. The cap is per template, not per instance: ten namespace templates can still drive ten updates a second between them. Templates that don't use the namespace are untouched. Details in Don't gate a template on a key that template's own outcome changes.
A boolean field β a Custom Position slot template. Trigger the slot only while the sun is actually on this window and the room's climate mode agrees it is summer:
{{ is_state(acp.sun_infront, 'on')
and is_state(acp.climate_status, 'summer_mode') }}A numeric field β the Lux Threshold on the Climate screen. Cloud suppression fires when the lux reading is at or below this threshold, which sends the cover to its cloudy or default position instead of tracking the sun. Move the bar with the season: in summer the shade is earning its keep on heat rejection, so hold out until it is genuinely overcast; the rest of the year, give up sooner and let the room have its daylight back.
{{ 400 if acp_state.climate_status == 'summer_mode' else 2500 }}The same thing written with the entity form, which works in numeric fields too:
{{ 400 if is_state(acp.climate_status, 'summer_mode') else 2500 }}climate_status is a safe key to gate on here: it is derived from your temperature readings, not from which handler won the last cycle, so nothing this template does can change it. Both spellings need Climate Mode configured β without it the entity doesn't exist and the render fails to the built-in default.
A Weather Safety Is Windy template that retracts earlier while the sun is on this window β that is when an awning is actually out, so the same gust matters more:
{{ states('sensor.wind_speed') | float(0) > (25 if is_state(acp.sun_infront, 'on') else 40) }}Don't gate weather safety on
manual_override. It is tempting to writeand is_state(acp.manual_override, 'off')so a manual position isn't overruled β but weather override sits at priority 90 precisely so it outranks manual override at 80. That ordering is the reason a manually-parked awning still comes in when the wind gets up. A template that ANDs the override away silently deletes the protection, and it will read as working right up until the day it matters.
Keys are grouped by the entity's domain. Aliases are alternative spellings of the same key β usually the word you see in the entity ID, where that differs from the internal name.
Every key below behaves the same way, whichever table it is in. What changes is the field you reference it from, and there are three cases:
- Tracked fields β sixteen of them. Motion Occupancy, the Daytime Gate, the Manual Override Input, the three Weather Safety condition templates, and the ten Custom Position slot templates. A referenced entity flipping re-runs the pipeline the instant it happens. These are also the fields the one-second coalescing applies to.
-
Numeric threshold fields. Not tracked. Re-rendered once per update cycle, so a referenced entity changing takes effect on the next cycle. These are the only fields that also get
acp_state. - Is Sunny and Presence (Climate). Neither tracked nor numeric β they are rendered part-way through each update cycle, as inputs to the climate read. A namespace reference works there, but it gets no instant reaction and no coalescing; like the numeric fields, it lands on the next cycle.
| Key | Aliases | States | Meaning |
|---|---|---|---|
sun_motion |
sun_infront |
on / off
|
Sun is within this window's sun acceptance angle and direct sun is valid. |
manual_override |
β |
on / off
|
Manual override is engaged on at least one of this instance's covers. |
glare_active |
β |
on / off
|
A glare zone is currently shielding. Only exists when Glare Zones are enabled, and only on the two cover types that support them: Vertical Blind and Dual Panel Shade. |
position_mismatch |
β |
on / off
|
A cover is not sitting where it was commanded. Disabled by default β enable the entity in Home Assistant before referencing it. |
| Key | Aliases | States | Meaning |
|---|---|---|---|
enabled_toggle |
integration_enabled |
on / off
|
The master kill switch. |
automatic_control |
β |
on / off
|
Solar-tracking toggle. |
sun_tracking |
β |
on / off
|
Sun tracking enabled for this instance. |
manual_toggle |
manual_override_detection |
on / off
|
Whether manual-override detection is armed. |
return_to_default_toggle |
return_to_default_when_disabled |
on / off
|
On every cover type except Venetian / Tilt Blind, Venetian Blind (Dual-Axis), Louvered Roof and Day/Night Shade. |
motion_control |
β |
on / off
|
Only when motion sensors or occupancy media players are configured. |
switch_mode |
climate_mode |
on / off
|
Only when Climate Mode is configured. |
temp_toggle |
outside_temperature |
on / off
|
Only with Climate Mode and a weather or outdoor-temperature entity. Disabled by default. |
lux_toggle |
lux |
on / off
|
Only with a lux entity and either Climate Mode or Cloud Suppression on. Disabled by default. |
irradiance_toggle |
irradiance |
on / off
|
Only with an irradiance entity and either Climate Mode or Cloud Suppression on. Disabled by default. |
glare_zone_0 β¦ glare_zone_3
|
β |
on / off
|
One per named glare zone, numbered from zero in configuration order. Same availability as glare_active. |
| Key | Aliases | States | Meaning |
|---|---|---|---|
control_status |
β |
active, outside_time_window, manual_override, automatic_control_off, sun_not_visible, weather_override_active, motion_timeout
|
Why automatic control is (or isn't) driving the cover. |
motion_status |
occupancy_status |
not_configured, motion_detected, timeout_pending, no_motion, holding, waiting_for_data
|
Occupancy state machine. |
climate_status |
β |
summer_mode, winter_mode, intermediate
|
Only when Climate Mode is configured. |
All three are discrete-valued. Their full attribute tables live on Diagnostic Sensors.
These are the sensor states as the integration produces them β the raw values, not the friendly names Home Assistant renders in the UI. Compare against
summer_mode, notSummer Mode.
Every continuously-varying sensor is excluded: Target Position, Target Tilt, Solar Calculation, Sun Position, Position Forecast, Start Sun, End Sun, Position Verification, Last Cover Action, Last Skipped Action, Manual Override End Time, Decision Trace, the My Position number and buttons, and the opt-in proxy cover entity β its position tracks the cover it mirrors, so it is the same moving target as the sensors above.
The reason is the feedback loop. Those sensors change on essentially every update cycle. A tracked template that reads one would re-render every cycle, and a changed render triggers a refresh, which recomputes the sensor, which re-renders the template. The coordinator itself is entirely event-driven β no periodic timer β so nothing in it damps that. The one-per-second coalescing described below caps the rate, but a loop that never settles is still a loop. Restricting the namespace to discrete, mostly-stable values removes the whole class of problem.
If you genuinely need one of the excluded values, reference it by its literal entity ID and accept that the template will re-render often. Better still, use the value from the source it came from (the lux sensor, the weather entity) rather than from ACP's echo of it.
Discrete values are not automatically loop-free. The canonical trap is control_status:
{# DON'T: this template's own result is one of the inputs to control_status #}
{{ is_state(acp.control_status, 'active') }}A weather or motion template written that way changes which handler wins, which changes control_status, which re-renders the template. The same applies to a motion occupancy template that inverts motion_status, or a Custom Position slot template that keys on the slot's own effect.
Two things stop that from becoming a busy loop:
-
The update cycles it triggers are coalesced. When a tracked template uses the namespace, the pipeline runs it can drive are capped at one per second. The first flip after a quiet second runs straight away β a single change keeps exactly the same immediacy as any other template β and further flips inside that second collapse into one run when the window closes. So a template feeding its own input ticks about once a second instead of spinning as fast as the machine allows. Templates that don't use the namespace are untouched and stay completely unthrottled.
Deferred flips are normally run rather than discarded. The one exception is narrow: Home Assistant's debouncer skips the deferred run if the previous run is somehow still going when the second closes β it treats a run already in flight as good enough. That needs a single update cycle to take longer than a second, which is not the normal case, and the next flip schedules a fresh run anyway. Worth knowing about, not worth designing around.
-
Home Assistant only invokes the tracker when the rendered result changes, and ACP's entities only write state when their rendered value changes. A self-reference that settles on a stable value stops after one extra cycle.
The second one ends the loop; the first only caps its cost. A template gating on a key its own outcome flips has no stable value to settle on, so it will keep ticking for as long as the integration is loaded β a wasted refresh every second, and a cover whose decision oscillates with it. Reference a key whose value your template does not itself determine.
The namespace resolves through the entity registry, matching on each entity's translation key β an internal identifier that Home Assistant never changes. What that survives:
- Renaming an entity. The entity ID changes; the translation key does not. The next render picks up the new ID with no reload and no edit to your template. Nothing is cached between renders, so this happens on the very next evaluation.
- Renaming the instance or its device. Neither is consulted at all.
- Moving the device to a different area. Same β areas only affect the entity ID that HA originally assigned.
What does not survive a rename, for contrast:
-
A hardcoded entity ID in a template. Renaming the entity silently turns it into
unknown, and the condition quietly reads false. Nothing errors; you just get wrong behaviour. -
integration_entities('<title>'), the built-in Home Assistant escape hatch that worked before this feature existed. It matches on the config entry's title, which is not the device name you see on the entity β the title is"<cover type> <name>", so a shade named "Dining Room Shade" on a Vertical Blind instance has the title "Vertical Blind Dining Room Shade". Renaming the entry in the UI changes the title and breaks the template, while leaving the device name (and therefore the entity IDs) exactly as they were, so there is no visible cause.
The namespace follows the same fail-soft contract as the rest of the template system: a bad reference never raises out of a cycle and never stalls a cover.
Unknown key. acp.not_a_real_key fails the render with an error naming the key and the instance. A numeric threshold then falls back to its built-in default and logs a warning once, not every cycle, until it recovers. What a condition field does depends on which field it is β see the next paragraph.
A failed render is not always "no opinion". Most condition fields abstain: the daytime gate, the manual-override input, Is Raining, Is Windy, Severe Weather, Is Sunny, Presence and the Custom Position slot templates all fall through to their companion entity, and then to that screen's built-in logic, exactly as if you had left the template blank.
The Motion Occupancy Template is the exception. It reads a failed render as not occupied β a real verdict, not a shrug. What that verdict costs you depends on how the rest of the field is set up:
- Template only, no motion sensors or occupancy media players. Nothing moves. Adaptive Cover Pro only lets the no-motion timeout drive a cover when motion sensors are configured, so the unoccupied verdict never reaches the motion-timeout handler. You lose the occupancy signal the template was there to provide, and that is all.
- Combine mode OR, alongside sensors. The template stops contributing occupancy and the sensors decide on their own β the same behaviour as leaving the field blank.
- Combine mode AND, alongside sensors. This is the one that bites. AND lets the failed render override an active sensor, so the instance reads unoccupied no matter what the room is doing, the no-motion timeout runs to completion, and the cover is parked at its default position until the template renders again.
Check a typo'd key against the key table above before saving, especially in AND mode. Developer Tools β Template will not help you here: acp only exists inside Adaptive Cover Pro's own renders, so paste the literal entity ID there instead when you want to test the rest of the expression.
Key exists but the entity doesn't. Several entities only exist when their feature is on β glare_active needs glare zones enabled on a cover type that supports them, climate_status needs Climate Mode, motion_control needs a motion sensor or an occupancy media player, glare_zone_2 needs a third named zone. Referencing one that this instance doesn't publish fails the same way as an unknown key. That is intentional: a silent false would be indistinguishable from "the condition isn't met", which is exactly the bug that makes template debugging miserable.
Disabled entities. position_mismatch and the temp_toggle / lux_toggle / irradiance_toggle switches are registered but disabled by default. The namespace resolves them (they have registry entries), but Home Assistant publishes no state for a disabled entity, so is_state(acp.position_mismatch, 'on') will read false forever. Enable the entity in Home Assistant first.
First boot. On the very first setup of a brand-new cover, the trackers are registered after the instance's own entities exist, so a self-reference is wired up correctly from the start. During Home Assistant startup a referenced entity may briefly have no state yet β the template reads it as unknown, and the tracker re-fires the moment the entity appears. Nothing needs restarting.
Everywhere a template is accepted. The right-hand column is how quickly a referenced entity's change reaches the cover β see Every exposed key for what the three cases mean.
| Field | Screen | Reacts |
|---|---|---|
| The numeric threshold fields | Templated Thresholds | next update cycle |
| Motion Occupancy Template | Automation | immediately, capped at 1/s |
| Daytime Gate Template | Automation | immediately, capped at 1/s |
| Manual Override Input Template | Automation | immediately, capped at 1/s |
| Is Sunny, Presence | Climate | next update cycle |
| Is Raining, Is Windy, Severe Weather | Weather Safety | immediately, capped at 1/s |
| Condition Template 1β10 | Custom Position | immediately, capped at 1/s |
Only the numeric threshold fields get acp_state; everything else gets the entity forms only.
Home Assistant works out which entities a template depends on by watching which entities it reads during a render. is_state(acp.sun_infront, 'on') reads a real entity, so the dependency is recorded and the template re-fires the instant that entity flips.
acp_state.sun_infront hands back a plain string. Home Assistant sees no entity access at all, records no dependency, and the template would never re-fire β a tracked field would silently degrade from "reacts immediately" to "reacts whenever something unrelated happens to trigger a cycle". The numeric threshold fields are re-rendered once per cycle anyway and are not tracked, so acp_state costs them nothing; in a tracked field it would quietly delete the immediacy the field exists to provide.
Rather than offer it and hope, it is given only to the numeric fields. Every other field β the sixteen tracked ones, and Is Sunny and Presence, which are untracked but would gain nothing from it β gets the entity forms only, and {{ acp_state.sun_infront }} there fails outright instead of quietly losing immediacy. On a numeric field that failure is a logged warning; on a condition field it is a debug-level log and the field's default β either way the template stops doing something subtly wrong and starts doing nothing.
The practical rule: in a condition field, always use acp.<key> inside is_state() / state_attr(). Use acp_state only where you want the raw string in a numeric expression.
- Templated Thresholds: which fields take templates, and how rendering and fallback work
- Entities: what each entity does and its full attribute set
-
Diagnostic Sensors: the complete state vocabulary and attributes for
control_status,motion_status, andclimate_status - Custom Position: the ten slot condition templates and their combine modes
π Home Β· β¨ Features Β· π° What's New
π Getting Started
- Installation
- Migrating from Custom Repository
- Migrating from Adaptive Cover
- First-Time Setup
- Building Profile β start here if you have multiple covers
- Cover Groups
- Copy Settings to Other Covers
- Cover Types
π§ Core Concepts
π Cover Types
- Vertical
- Horizontal
- Oscillating Awning
- Roof / Skylight Window
- Tilt
- Louvered Roof
- Sliding Curtain
- Day/Night Shade
- Dual Panel
- Venetian (Dual-Axis)
βοΈ Configuration
- Sun Tracking
- Position
- Position Matching
- Travel Time Calibration
- Glare Zones
- Automation
- Manual Override
- Command Queue
- Custom Position
- Force Override
- Weather Safety
- Climate
- Templated Thresholds
- Template Self-References
- Blindspot
- Summary Screen
- Debug & Diagnostics
π Entities & Services
- Entities
- Proxy Cover Entity
- Position Verification
- My Position Support (Somfy RTS)
- Runtime Configuration Services
π οΈ Operations
- Known Limitations
- Hardware Compatibility
- Troubleshooting
- Troubleshooting Findings
- Diagnostic Sensors
- Tips and Tricks
π§ Advanced Use Cases
- Overview
- Dynamic Temperature Thresholds
- Dynamic Tracking Window
- Bedroom Sleep Mode
- Handling Variable Cloud Cover
- Indoor Lux Sensor Behind the Cover
- Venetian Tilt-Only on Overcast Days
- Forecast-Based Shading
- Custom Position When Sun in FOV
- Suppress Closing While a Door Is Open
- Keep a Blind Clear of the Sill
π¨ Dashboard
- Dashboard Cards
- Panel Card
- Tile Card
- Sky Compass Card
- Decision Strip Card
- Solar Chart Card
- History Card
π§ͺ Testing & Simulation
π Reference
π©βπ» For Developers