| name | ha-architecture |
| description | Home Assistant core internals — event bus, state machine, service registry, and integration loading. Use when asking about the hass object, event propagation, state management, service registration, or how integrations load. |
Home Assistant Core Architecture
Home Assistant runs on a single-threaded asyncio event loop. All code shares this loop — blocking it freezes automations, the UI, and entity updates.
The hass Object
Every integration receives HomeAssistant instance (hass) — the central hub for all core systems:
from homeassistant.core import HomeAssistant
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
...
Event Bus
The nervous system of Home Assistant. All component communication flows through events.
from homeassistant.core import callback, Event
hass.bus.async_fire("my_custom_event", {"key": "value"})
@callback
def handle_event(event: Event) -> None:
entity_id = event.data.get("entity_id")
new_state = event.data.get("new_state")
unsub = hass.bus.async_listen("state_changed", handle_event)
entry.async_on_unload(unsub)
Key events: state_changed, homeassistant_start, homeassistant_stop, call_service, automation_triggered.
State Machine
Tracks current state of every entity. States are immutable snapshots.
state = hass.states.get("sensor.temperature")
if state is not None:
value = state.state
attrs = state.attributes
last_changed = state.last_changed
Special state values (STATE_UNAVAILABLE / STATE_UNKNOWN from homeassistant.const): "unavailable" (entity cannot be reached — device offline, push connection lost, or coordinator update failed), "unknown" (entity exists but has no value yet).
Service Registry (Actions)
Services (now called "actions" in UI) control devices. Register integration-wide services once — in async_setup if they must exist independent of any config entry, otherwise in async_setup_entry guarded so they register only once (e.g. via hass.services.has_service). Unregister in async_unload_entry only when removing the last entry.
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def handle_action(call: ServiceCall) -> None:
entity_id = call.data.get("entity_id")
await do_something(entity_id)
hass.services.async_register(DOMAIN, "my_action", handle_action)
return True
Integration Loading Order
- Core starts, loads
configuration.yaml
- Dependencies from
manifest.json loaded first
async_setup(hass, config) called (if present)
- For each config entry:
async_setup_entry(hass, entry) called
- Platform forwarding loads each platform's
async_setup_entry
- Entities registered and begin receiving updates
Key Design Principles
Single-threaded event loop: All async code shares one loop. Never block it.
Snapshot state: hass.states.get() returns a point-in-time snapshot — treat it as read-only; the state machine replaces State objects rather than mutating them.
Integration isolation: Each integration manages its lifecycle via config entries. Store coordinator in entry.runtime_data (not hass.data).