Centralized management and storage of all entity states in Home Assistant
The State Machine is Home Assistant’s central storage system for all entity states. It maintains a complete picture of your home automation system’s current state and efficiently tracks changes over time.
The StateMachine class provides thread-safe access to entity states:
homeassistant/core.py
class StateMachine: """Helper class that tracks the state of different entities.""" def __init__(self, bus: EventBus, loop: asyncio.events.AbstractEventLoop) -> None: """Initialize state machine.""" self._states = States() self._states_data = self._states.data self._reservations: set[str] = set() self._bus = bus self._loop = loop
The StateMachine maintains two references to state storage: _states (the States container) and _states_data (direct dict access) for performance optimization.
Each entity’s state is represented by a State object:
homeassistant/core.py
class State: """Object to represent a state within the state machine.""" entity_id: str # Entity identifier (e.g., "light.living_room") domain: str # Domain portion (e.g., "light") object_id: str # Object ID portion (e.g., "living_room") state: str # Current state value (e.g., "on", "23.5", "home") attributes: ReadOnlyDict[str, Any] # Additional state information last_changed: datetime.datetime # When state value last changed last_reported: datetime.datetime # When state was last reported last_updated: datetime.datetime # When state or attributes last changed context: Context # Context that created this state
@callbackdef async_set( self, entity_id: str, new_state: str, attributes: Mapping[str, Any] | None = None, force_update: bool = False, context: Context | None = None, state_info: StateInfo | None = None, timestamp: float | None = None,) -> None: """Set the state of an entity, add entity if it does not exist. If you just update the attributes and not the state, last_changed will not be affected. """ state = str(new_state) validate_state(state) self.async_set_internal( entity_id.lower(), state, attributes or {}, force_update, context, state_info, timestamp or time.time(), )
# Fired when state value or attributes change{ "entity_id": "light.living_room", "old_state": State(entity_id="light.living_room", state="off", ...), "new_state": State(entity_id="light.living_room", state="on", ...)}
EVENT_STATE_REPORTED was introduced to allow tracking of entity updates even when the state value doesn’t change. This is useful for detecting that a device is still communicating.
# Get state (returns None if not found)state = hass.states.get("light.living_room")if state: print(f"State: {state.state}") print(f"Attributes: {state.attributes}") print(f"Last changed: {state.last_changed}")
# Get all states (async context)all_states = hass.states.async_all()# Get states for a specific domainlight_states = hass.states.async_all("light")# Get states for multiple domains states = hass.states.async_all(["light", "switch"])
# Get all entity IDsall_entity_ids = hass.states.async_entity_ids()# Get entity IDs for a domainlight_entity_ids = hass.states.async_entity_ids("light")# Get count of entitiestotal_entities = hass.states.async_entity_ids_count()light_count = hass.states.async_entity_ids_count("light")
Reserve an entity_id before creating it to prevent race conditions:
from homeassistant.exceptions import HomeAssistantErrortry: # Reserve the entity_id hass.states.async_reserve("sensor.new_sensor") # Perform setup that might take time await setup_sensor() # Set the initial state hass.states.async_set( "sensor.new_sensor", "initializing", {"friendly_name": "New Sensor"} )except HomeAssistantError: _LOGGER.error("Entity ID already in use")
Reservations prevent multiple components from trying to create the same entity_id simultaneously, which could cause data corruption or unexpected behavior.
The States class provides efficient domain-based indexing:
homeassistant/core.py
class States(UserDict[str, State]): """Container for states, maps entity_id -> State. Maintains an additional index: - domain -> dict[str, State] """ def __init__(self) -> None: super().__init__() self._domain_index: defaultdict[str, dict[str, State]] = defaultdict(dict) def __setitem__(self, key: str, entry: State) -> None: """Add an item.""" self.data[key] = entry self._domain_index[entry.domain][entry.entity_id] = entry def domain_entity_ids(self, key: str) -> KeysView[str] | tuple[()]: """Get all entity_ids for a domain.""" if key not in self._domain_index: return () return self._domain_index[key].keys()
The domain index enables O(1) lookups when retrieving all entities of a specific domain, making operations like hass.states.async_all("light") very fast even with thousands of entities.
Use last_reported to detect if a device is still communicating, even when its state hasn’t changed. This is particularly useful for detecting unavailable devices.
States can be serialized for storage or API responses:
# Get as dictionarystate_dict = state.as_dict()# Returns ReadOnlyDict with ISO-formatted timestamps# Get as JSON bytesstate_json = state.as_dict_json# Get compressed state (for WebSocket API)compressed = state.as_compressed_state# Omits last_updated if equal to last_changed# Compresses context if simple
Direct dict access: Internal code uses _states_data to bypass container overhead Cached properties: Expensive computations are cached with @cached_property Domain indexing: O(1) lookups for domain-filtered queries Timestamp caching: Timestamp conversions are cached on State objects