Documentation Index
Fetch the complete documentation index at: https://mintlify.com/home-assistant/core/llms.txt
Use this file to discover all available pages before exploring further.
The state machine is the heart of Home Assistant. It tracks the current state of all entities and fires events when states change. Understanding state management is crucial for building reliable integrations.
The State Machine
The StateMachine class manages all entity states in Home Assistant:
from homeassistant.core import HomeAssistant
def example(hass: HomeAssistant):
# Access the state machine
state_machine = hass.states
# Get current state
state = state_machine.get("light.living_room")
# Set state
state_machine.async_set(
"sensor.temperature",
"23.5",
{"unit_of_measurement": "°C"}
)
Reference: homeassistant/core.py:2058
State Objects
State Structure
The State object contains complete entity information:
from homeassistant.core import State
state = hass.states.get("light.living_room")
# Core properties
entity_id: str = state.entity_id # "light.living_room"
domain: str = state.domain # "light"
object_id: str = state.object_id # "living_room"
state_value: str = state.state # "on", "off", etc.
# Metadata
attributes: dict = state.attributes # Extra data
context: Context = state.context # Who/what caused change
# Timestamps
last_changed: datetime = state.last_changed # When state changed
last_updated: datetime = state.last_updated # When state/attrs updated
last_reported: datetime = state.last_reported # When last written
Reference: homeassistant/core.py:1718
State vs Attributes
- State: The primary status (on/off, open/closed, numeric value)
- Attributes: Additional context (brightness, color, temperature)
# Light state
state = "on" # State is simple string
attributes = {
"brightness": 255,
"color_temp": 370,
"friendly_name": "Living Room Light",
"supported_features": 43
}
Reading State
Get Single State
from homeassistant.core import State
# Get state (returns None if not found)
state = hass.states.get("light.bedroom")
if state is None:
_LOGGER.warning("Entity not found")
else:
_LOGGER.info(f"Light is {state.state}")
brightness = state.attributes.get("brightness")
Reference: homeassistant/core.py:2142
Check State Value
# Quick check if entity has specific state
if hass.states.is_state("light.bedroom", "on"):
_LOGGER.info("Bedroom light is on")
Reference: homeassistant/core.py:2151
Get All States
# Get all states
all_states = hass.states.async_all()
# Filter by domain
all_lights = hass.states.async_all("light")
all_sensors = hass.states.async_all(["sensor", "binary_sensor"])
# Count entities
light_count = hass.states.async_entity_ids_count("light")
Reference: homeassistant/core.py:2124
Get Entity IDs
# Get all entity IDs
all_entity_ids = hass.states.async_entity_ids()
# Filter by domain
light_ids = hass.states.async_entity_ids("light")
climate_switch_ids = hass.states.async_entity_ids(["climate", "switch"])
Reference: homeassistant/core.py:2081
Setting State
Basic State Update
from homeassistant.core import HomeAssistant
@callback
def update_sensor(hass: HomeAssistant):
"""Update sensor state."""
hass.states.async_set(
"sensor.my_sensor",
"42",
{
"unit_of_measurement": "units",
"device_class": "temperature",
"friendly_name": "My Sensor"
}
)
Reference: homeassistant/core.py:2247
State Update with Context
Context tracks what triggered the state change:
from homeassistant.core import Context
# Create context (e.g., for automation)
context = Context(user_id="automation.turn_on_lights")
hass.states.async_set(
"light.living_room",
"on",
{"brightness": 255},
context=context
)
Reference: homeassistant/core.py:1213
Force Update
Force a state change event even if state hasn’t changed:
# Force update - fires event even if state/attributes unchanged
hass.states.async_set(
"sensor.refresh",
state.state,
state.attributes,
force_update=True # Always fire EVENT_STATE_CHANGED
)
Reference: homeassistant/core.py:2247
State Change Events
EVENT_STATE_CHANGED
Fired when state or attributes change:
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import Event, EventStateChangedData
@callback
def handle_state_change(event: Event[EventStateChangedData]) -> None:
"""Handle state change."""
entity_id = event.data["entity_id"]
old_state = event.data["old_state"] # None if new entity
new_state = event.data["new_state"] # None if removed
if old_state is None:
_LOGGER.info(f"New entity: {entity_id}")
elif new_state is None:
_LOGGER.info(f"Removed entity: {entity_id}")
else:
_LOGGER.info(
f"{entity_id}: {old_state.state} -> {new_state.state}"
)
hass.bus.async_listen(EVENT_STATE_CHANGED, handle_state_change)
Reference: homeassistant/core.py:137
EVENT_STATE_REPORTED
Fired when state is updated but unchanged:
from homeassistant.const import EVENT_STATE_REPORTED
from homeassistant.core import Event, EventStateReportedData
@callback
def handle_state_reported(event: Event[EventStateReportedData]) -> None:
"""Handle state report (no change)."""
entity_id = event.data["entity_id"]
new_state = event.data["new_state"]
last_reported = event.data["last_reported"]
old_last_reported = event.data["old_last_reported"]
_LOGGER.debug(
f"{entity_id} reported at {last_reported}, still {new_state.state}"
)
Reference: homeassistant/core.py:147
State Reservations
Reserve Entity ID
Reserve an entity ID before creating the entity:
@callback
def reserve_entity(hass: HomeAssistant, entity_id: str):
"""Reserve entity ID to prevent conflicts."""
# Reserve the ID
hass.states.async_reserve(entity_id)
# Now safe to create entity
# ... entity setup code ...
# Finally set initial state
hass.states.async_set(entity_id, "unknown", {})
Reference: homeassistant/core.py:2222
Check Availability
if hass.states.async_available("light.new_light"):
# Entity ID is available
hass.states.async_reserve("light.new_light")
else:
_LOGGER.error("Entity ID already in use")
Reference: homeassistant/core.py:2239
Removing State
Remove Entity State
# Remove entity from state machine
removed = hass.states.async_remove("sensor.old_sensor")
if removed:
_LOGGER.info("Entity removed")
else:
_LOGGER.warning("Entity not found")
This fires EVENT_STATE_CHANGED with new_state=None.
Reference: homeassistant/core.py:2169
State Persistence
States are internally compressed for efficiency:
# Compressed state format (used in websocket and recorder)
compressed = {
"s": "on", # state
"a": {"brightness": 255}, # attributes
"c": "context_id", # context (or full dict)
"lc": 1234567890.0, # last_changed timestamp
"lu": 1234567891.0, # last_updated (optional if == lc)
}
Reference: homeassistant/core.py:1708
Advanced State Patterns
State Validation
from homeassistant.exceptions import InvalidStateError
from homeassistant.core import validate_state
try:
# State must be <= 255 characters
validated = validate_state(user_input)
hass.states.async_set("sensor.test", validated, {})
except InvalidStateError as err:
_LOGGER.error("Invalid state: %s", err)
Reference: homeassistant/core.py:200
Entity ID Validation
from homeassistant.core import valid_entity_id, split_entity_id
# Validate format
if not valid_entity_id("light.living_room"):
raise ValueError("Invalid entity ID")
# Split into domain and object_id
domain, object_id = split_entity_id("light.living_room")
# domain = "light", object_id = "living_room"
Reference: homeassistant/core.py:192, homeassistant/core.py:171
Tracking Multiple Entities
from homeassistant.helpers.event import async_track_state_change_event
@callback
def handle_changes(event: Event) -> None:
"""Handle state changes for tracked entities."""
entity_id = event.data["entity_id"]
new_state = event.data["new_state"]
_LOGGER.info(f"{entity_id} changed to {new_state.state}")
# Track multiple entities efficiently
remove = async_track_state_change_event(
hass,
["light.living_room", "light.bedroom", "light.kitchen"],
handle_changes
)
# Clean up
remove()
Reference: homeassistant/helpers/event.py:309
State Attribute Updates
Update only attributes without changing state:
# Get current state
current = hass.states.get("climate.thermostat")
if current:
# Update attributes while keeping same state
new_attrs = dict(current.attributes)
new_attrs["temperature"] = 22
hass.states.async_set(
current.entity_id,
current.state, # Same state
new_attrs # Updated attributes
)
# last_changed stays same, last_updated updates
State Machine Indexes
The state machine maintains indexes for performance:
Domain Index
# Internally indexed by domain for fast lookups
class States(UserDict):
def __init__(self):
self._domain_index: dict[str, dict[str, State]] = {}
# Fast domain filtering
def domain_states(self, domain: str):
return self._domain_index.get(domain, {}).values()
Reference: homeassistant/core.py:2016
Best Practices
1. Use Async Methods
Always use async methods in the event loop:
# GOOD
@callback
def my_callback():
state = hass.states.get("light.test") # Synchronous read OK
hass.states.async_set("light.test", "on", {}) # Async write
# BAD
def blocking_function():
hass.states.set("light.test", "on", {}) # Blocks event loop
2. Check for None
Always handle missing states:
state = hass.states.get("sensor.might_not_exist")
if state is None:
_LOGGER.warning("Sensor not found")
return
value = float(state.state)
3. Use Appropriate Tracking
Use helpers instead of manual event listening:
# GOOD - Optimized tracking
from homeassistant.helpers.event import async_track_state_change_event
remove = async_track_state_change_event(
hass, ["light.test"], callback
)
# BAD - Manual tracking is slower
remove = hass.bus.async_listen(
EVENT_STATE_CHANGED,
lambda event: callback(event) if event.data["entity_id"] == "light.test" else None
)
4. Minimize State Updates
Only update when necessary:
# Check before updating
current = hass.states.get("sensor.test")
if current is None or current.state != new_value:
hass.states.async_set("sensor.test", new_value, attrs)
5. Use Context
Provide context for state changes:
context = Context(
user_id=automation.entity_id,
parent_id=trigger_context.id
)
hass.states.async_set(
"light.automated",
"on",
{},
context=context
)
- State reads are fast - Direct dictionary lookup
- State writes fire events - Consider batch updates
- Domain filtering is optimized - Uses internal index
- Attributes are immutable - Uses ReadOnlyDict for safety
Common Pitfalls
- Don’t modify
state.attributes directly (it’s read-only)
- Don’t assume entities exist (always check for None)
- Don’t create excessive state updates (batch when possible)
- Don’t store large data in attributes (use data registry)
- Clean up entity states when removing entities
State Machine Lifecycle
# 1. Reserve entity ID (optional, prevents races)
hass.states.async_reserve("sensor.new")
# 2. Create initial state
hass.states.async_set("sensor.new", "unknown", {})
# 3. Update state over time
hass.states.async_set("sensor.new", "23.5", {"unit": "°C"})
# 4. Remove when done
hass.states.async_remove("sensor.new")