States
How the state machine keeps the current state of every entity, what a state object holds, and why integrations write states through entities.
The state machine on hass.states holds the current state of every entity in Home Assistant. Dashboards read it, automations trigger on it, and the recorder stores every change. It is a simple store with few rules, and that is on purpose.
Before anything else: as an integration developer, you almost never call the state machine yourself. You create entities, and the entity base class writes their state with async_write_ha_state(). It takes care of the entity ID, the name, the unit, the icon, the device class, availability, and the attributes every entity has. Writing states by hand skips all of that, and the user cannot rename, hide, or configure what you wrote.
Read on to understand what happens underneath.
Every state is a State object from homeassistant.core. States are immutable: a change creates a new object.
| Attribute | Description |
|---|---|
entity_id |
The entity ID, like light.kitchen_ceiling: the domain, a dot, and the object ID. |
domain |
The part before the dot, like light. |
object_id |
The part after the dot, like kitchen_ceiling. |
state |
The state itself, always a string of at most 255 characters, like on or 21.5. |
attributes |
A read-only dictionary with extra information. Keep it JSON serializable. |
last_changed |
When state last changed. |
last_updated |
When state or any attribute last changed. |
last_reported |
When the state was last written, even if nothing changed. |
context |
The Context that caused this state. |
name |
The friendly name, from the friendly_name attribute, or else the object ID with spaces for underscores. |
The state object page on home-assistant.io describes the same object from the user's side.
state = hass.states.get("sun.sun")
if state is not None:
elevation = state.attributes.get("elevation")hass.states.get(entity_id)returns theState, orNoneif the entity does not exist. Always handleNone: entities come and go.hass.states.async_all(domain)returns all states, or all states of one or more domains.hass.states.async_entity_ids(domain)returns just the entity IDs.hass.states.is_state(entity_id, "on")is a shortcut to compare.
Remember that the state can also be unavailable or unknown. The constants STATE_UNAVAILABLE and STATE_UNKNOWN live in homeassistant/const.py.
hass.states.async_set(entity_id, state, attributes) creates or replaces a state. What happens next depends on what changed:
- The state or attributes changed: the state machine stores a new
Stateand fires astate_changedevent withentity_id,old_state, andnew_statein its data.last_changedonly moves whenstateitself changed. - Nothing changed: the state machine updates
last_reportedand fires astate_reportedevent instead. Listening tostate_reportedrequires anevent_filter, because it fires a lot. force_update=True: astate_changedevent is fired even if nothing changed, andlast_changedmoves too.
A state longer than 255 characters is not stored. The state machine logs an error and stores unknown instead.
hass.states.async_remove(entity_id) removes a state and fires a state_changed event with new_state set to None. Listeners must expect that, and the same goes for old_state being None when an entity is new.
Like everything starting with async_, these must run in the event loop. From another thread, use hass.states.set and hass.states.remove.
On an entity, extra information goes in the extra_state_attributes property. It returns a dictionary that ends up in attributes.
@property
def extra_state_attributes(self) -> dict[str, Any]:
"""Return extra attributes of the entity."""
return {"filter_life": self._filter_life}Do not override state_attributes: entity base classes, like the light, use it for the attributes of their domain. And think twice before you add attributes at all. Every attribute is stored with every state change, and a value that changes often belongs in its own entity. See entities for the details.