Hi! Sorry for my English, I’m French ![]()
I run a set of Velux covers behind a KLF200 gateway, plus a handful of Zigbee devices
sitting right at the edge of usable range. Both fail the same way, and it took me longer
than I’d like to admit to see it: Home Assistant reports the service call as successful,
because the call was successful. Nobody ever checks that anything actually happened.
The KLF200 is the worst offender. It accepts commands perfectly happily and then does
nothing — no error, no timeout, the covers simply don’t move. The only fix I found that
works reliably is power-cycling the gateway. And then re-sending the command, because the
one that triggered the whole mess is long gone.
The Zigbee ones fail more quietly. At the edge of range a command just gets lost, HA
optimistically shows the light as on, and the room stays dark. Sending it a second time
almost always works — the mesh routes around it. But “send it again” is precisely the
thing nobody is doing.
So I wrote two automations: one retrying lights and switches until the state matched,
another restarting the KLF200 when the covers stopped responding. They worked. They were
also long, brittle, and impossible to reuse for anything else. Action Control is those
two automations generalised into an integration.
What it does
It listens to the internal call_service event — every service call, whatever triggered
it: you, an automation, a script, a voice assistant, another integration. For each rule
you configure, it resolves the targeted entities, works out what the call should have
produced, and verifies it. If reality doesn’t match, it re-issues the command, and if it
keeps failing it can run a recovery action and tell you about it.
Two verification modes, because my two problems needed different answers:
- Delay — wait, then compare state and attributes, with per-attribute tolerance
(brightness ±5,rgb_colorelement-wise, and so on). This is the Zigbee case: the
light either reached the requested state or it didn’t. - Movement — for things that travel. Comparing a cover’s position after a fixed delay
is useless, since you don’t know how long it should take. Instead it waits for
current_positionto actually start changing. A cover that never even begins to move
is the failure — and that is exactly the KLF200 symptom.
The KLF200 rule, concretely
- Domain
cover, entity patterncover.volet_* - Verify by Movement on
current_position, 45 s timeout - On persistent failure, run a recovery action: turn on the gateway’s restart switch
- Verify the recovery action worked — check the switch really came back
on, and
re-run the restart if it didn’t - Wait for the gateway to come back, then replay the original cover command
That verification step on the recovery action matters more than it sounds. Recovery
actions fail too, and replaying a cover command after a reboot that never happened just
adds noise to the log. My original automation assumed the restart always worked; it
didn’t always.
Everything else
- Retries with configurable delay growth: constant, linear or exponential
- Escalation cooldown, shared across a rule’s entities and persisted across restarts —
so ten covers failing together reboot the gateway once, not ten times - Notifications: persistent notification and/or any
notify.*service, per rule - A diagnostic sensor per rule (
ok/retrying/escalated/failed) carrying
expected vs. observed values and aresponse_durationmeasurement - Optional per-rule
info-level log line summarising each entity’s outcome and response
time, without turning on debug for the whole component - No retry loops: every command the integration re-issues carries its own tracked
Context, and the resulting event is recognised and ignored before anything happens.
No guard entity, noinput_boolean, no configuration - Services
action_control.run_ruleto test a rule on demand, and
reset_escalation_cooldown - Diagnostics download, and a repair issue if a rule targets an area/label/device you
deleted - Entirely configured through the UI. The wizard asks which capabilities you want first,
then only shows the settings those choices need — no YAML anywhere - English and French
Targeting
A rule can watch any domain and service, not just light/switch/cover — filtered by
entity_id glob, friendly-name glob, areas, labels and/or devices. light, switch,
cover and scene come with sensible defaults pre-filled.
Install
Not in the HACS default store yet — the submission is in their queue. For now, add it as
a custom repository in HACS:
https://github.com/cddu33/ha-action_control — category Integration
Then Settings → Devices & services → Add integration → Action Control.
Requires Home Assistant 2025.3.0 or newer.
Links
- Repository: GitHub - cddu33/ha-action_control: Home Assistant watchdog for service calls: verifies your commands actually took effect, retries them, escalates and notifies when they don't. · GitHub
- Full documentation, with a field-by-field reference, ready-to-use recipes and diagrams:
ha-action_control/docs/documentation.md at ad904843b7e837f917205ee174c628e2d995a5f3 · cddu33/ha-action_control · GitHub
Known limitations
I’d rather state these up front:
- One status sensor per rule, so a command hitting many entities leaves only the last
outcome on the sensor — the log has the per-entity detail - The post-escalation replay is not itself verified; it’s the last action of the run
- Editing rules reloads the integration, which cancels checks in flight
Feedback and bug reports very welcome.