-
Notifications
You must be signed in to change notification settings - Fork 47
Event based triggers
Here's more detail on event triggers and some examples of using them.
Pyscript now supports Jupyter frontends. There is a Jupyter notebook tutorial that you can read, or you can download and run interactively in Jupyter notebook connected to your live HASS with pyscript.
Here's a list of standard HASS events. The names are constants that are defined in homeassistant.const
; for example the name EVENT_CALL_SERVICE
is defined to be "call_service"
.
Event name | What it means |
---|---|
EVENT_CALL_SERVICE |
A service is being called |
EVENT_COMPONENT_LOADED |
A component has been loaded |
EVENT_CORE_CONFIG_UPDATE |
A configuration update has happened |
EVENT_HOMEASSISTANT_CLOSE |
HASS is closing |
EVENT_HOMEASSISTANT_START |
HASS is starting |
EVENT_HOMEASSISTANT_STARTED |
HASS has started |
EVENT_HOMEASSISTANT_STOP |
HASS is stopping |
EVENT_HOMEASSISTANT_FINAL_WRITE |
HASS is about to stop |
EVENT_LOGBOOK_ENTRY |
A logbook entry is added |
EVENT_PLATFORM_DISCOVERED |
Platform discovered |
EVENT_SERVICE_REGISTERED |
A service has been registered |
EVENT_SERVICE_REMOVED |
A service has been removed |
EVENT_STATE_CHANGED |
A state has changed |
EVENT_THEMES_UPDATED |
Themes are updated |
EVENT_TIMER_OUT_OF_SYNC |
Time is out of sync |
EVENT_TIME_CHANGED |
A 1 second heartbeat |
Only a handful of these will typically be of interest. The EVENT_HOMEASSISTANT_START
and EVENT_HOMEASSISTANT_STARTED
events happen before pyscript starts. The startup @time_trigger
(with no arguments) is actually triggered by the EVENT_HOMEASSISTANT_STARTED
event. Similarly, most components will be loaded and services will be registered during startup before EVENT_HOMEASSISTANT_STARTED
, so those corresponding events won't occur after pyscript is started. The EVENT_TIME_CHANGED
is just a 1 second heartbeat and you are better off using a periodic @time_trigger
.
If you want to eavesdrop on service calls, you could have a trigger on EVENT_CALL_SERVICE
. The event parameters are:
domain
service
service_data
This trigger will log a message on every service call:
from homeassistant.const import EVENT_CALL_SERVICE
@event_trigger(EVENT_CALL_SERVICE)
def monitor_service_events(domain=None, service=None, service_data=None):
log.info(f"{domain}.{service} called with service_data={service_data}")
Trigger conditions can be added using a Python string expression in the 2nd argument. For example, to only trigger when domain
is light
:
@event_trigger("call_service", "domain == 'light'")
def monitor_lights_service_events(service=None, service_data=None):
log.info(f"light.{service} called with service_data={service_data}")
In this example the domain
function argument was removed since it will always be light
, and the event name was replaced by its value instead of importing it (although best practices would be to import and use the constant name).
You could also trigger on state change events. However, it's much better and more efficient to use @state_trigger
instead. But if you want to see some or all state changes (rather than specific ones) you could trigger on EVENT_STATE_CHANGED
. The parameters are:
-
entity_id
- the string name of the state variable -
new_state
- the new value and attributes -
old_state
- the old value and attributes
Here's an example that triggers on all state changes for state variables whose names starts with "light."
:
from homeassistant.const import EVENT_STATE_CHANGED
@event_trigger(EVENT_STATE_CHANGED, "entity_id.startswith('light.')")
def monitor_state_change(entity_id=None, new_state=None, old_state=None):
log.info(f"entity {entity_id} changed from {old_state} to {new_state}")
If you run this test you will see that old_state
and new_state
are objects. The actual string value is new_state.state
and attributes are new_state.attributes
, which is a dict
. So you could update the log message to
from homeassistant.const import EVENT_STATE_CHANGED
@event_trigger(EVENT_STATE_CHANGED, "entity_id.startswith('light.')")
def monitor_state_change(entity_id=None, new_state=None, old_state=None):
old_value = old_state.state if old_state else None
log.info(f"entity {entity_id} changed from {old_value} to {new_state.state} and attributes are now {new_state.attributes}")
Note that old_state
could be None
, which the code checks for.
Be careful about what event triggers you add, since every event of that type across HASS will cause your conditions to be checked and potentially your function to be run. A large HASS implementation will have a lot of state changes and service calls. Also, please don't set a state variable inside a function that is trigged by EVENT_STATE_CHANGED
, or make a service call that is triggered by EVENT_CALL_SERVICE
, unless the trigger condition is False
in those cases. Otherwise bad things will happen...
Finally, if you are not sure what data is attached to an event, create a trigger with no additional conditions, and use the kwargs
form to capture all the arguments in a dict
:
from homeassistant.const import EVENT_STATE_CHANGED
@event_trigger(EVENT_STATE_CHANGED)
def monitor_state_change(**kwargs):
log.info(f"got EVENT_STATE_CHANGED with kwargs={kwargs}")
HASS allows user-defined events to be fired. They have a string name, and optional parameters. You can use @event_trigger
in just the same way as the examples above. When you create your own trigger name, you should make sure it isn't the same as any of the built-in triggers.
Here's an example:
@event_trigger("my_event")
def got_my_event(**kwargs):
log.info(f"got my_event: kwargs={kwargs}")
You can fire the event and trigger the function with:
event.fire("my_event", my_param1=100, fruit="apple")
This will produce this output:
got my_event: kwargs={'trigger_type': 'event', 'event_type': 'my_event', 'my_param1': 100, 'fruit': 'apple'}
You can see the function is called with the two event parameters, and also a handful of other parameters that tell the function the type of trigger and name of the event.
Rather than use the catch-all **kwargs, you could explicitly specify the parameters. In this example we'll also add a trigger condition, so that the function only runs when my_param1 is between 50 and 150 (you could also include state variables in the condition if you want):
@event_trigger("my_event", "50 <= my_param1 <= 150")
def got_my_event(my_param1=None, fruit=None):
log.info(f"got my_event: my_param1={my_param1}, fruit={fruit}")
Now fire the event three times; you should see that only one of the events causes the function to run because of the additional condition:
event.fire("my_event", my_param1=40, fruit="apple")
event.fire("my_event", my_param1=110, fruit="orange")
event.fire("my_event", my_param1=180, fruit="banana")
The output is:
got my_event: my_param1=110, fruit=orange