Skip to content

States

Hassette maintains a local, real-time cache of all Home Assistant states. This is available as an instance of StateManager, accessible via self.states in your apps

Diagram

flowchart TD
    subgraph ha["Home Assistant"]
        HA["State change events"]
    end

    subgraph framework["Framework"]
        WS["WebsocketService"]
        SP["StateProxy<br/><i>in-memory cache</i>"]
        WS --> SP
    end

    subgraph app["Your App"]
        SM["self.states<br/><i>typed, sync access</i>"]
    end

    HA -- "WebSocket" --> WS
    SP --> SM

    style ha fill:#f0f0f0,stroke:#999
    style framework fill:#fff0e8,stroke:#cc8844
    style app fill:#e8f0ff,stroke:#6688cc
Hold "Ctrl" to enable pan & zoom

Using the StateManager

Use self.states instead of API calls to read entity states. It gives you:

  • Speed: Instant access from local memory.
  • Simplicity: Synchronous access without await.
  • Efficiency: No network overhead or rate limiting concerns.
  • Consistency: Event-driven updates ensure your app sees the latest state changes.
    • The StateManager event handler is prioritized over app event handlers to ensure you always have a consistent view of the latest states.

Domain Access

The easiest way to access states is via domain properties.

from hassette import App


class StateApp(App):
    async def on_initialize(self):
        # Access by domain
        light = self.states.light.get("light.kitchen")

        # Access attributes safely
        if light:
            self.logger.info("Brightness: %s", light.attributes.brightness)

        # if you know the entity exists you can access it
        # directly using dictionary-style access
        self.states.sensor["temperature"]

Notice how you do not need to use the domain in the entity ID - since you're already accessing the domain via self.states.sensor, you only need to provide the entity name.

Bracket access raises KeyError for missing entities

self.states.light["bedroom"] raises KeyError — not EntityNotFoundError — if the entity does not exist. Use .get("bedroom") for safe access that returns None when the entity is absent.

Direct Entity Access

Use self.states.get(entity_id) when you have a full entity ID and don't need to specify the domain or state class. It automatically resolves to the correct domain-specific type (e.g., LightState for light.*), or falls back to BaseState for unregistered domains.

from hassette import App


class DirectAccessApp(App):
    async def on_initialize(self):
        # Access any entity by full entity ID
        light = self.states.get("light.kitchen")
        if light:
            self.logger.info("State: %s", light.value)

        # Works for any domain, even unregistered ones
        custom = self.states.get("my_domain.some_entity")
        if custom:
            self.logger.info("Domain: %s, Value: %s", custom.domain, custom.value)

Generic Access

For domains that don't have a dedicated helper, or for dynamic access, provide the state class to the self.states dictionary-like interface:

from my_app import MyCustomState

from hassette import App


class GenericApp(App):
    async def on_initialize(self):
        # dictionary like access with state class
        my_instance = self.states[MyCustomState].get("work")
        if my_instance:
            self.logger.info("MyCustomState value: %s", my_instance.value)

Iteration

You can iterate over domains to find entities.

from hassette import App


class IteratorApp(App):
    async def on_initialize(self):
        # Find all low battery sensors
        for entity_id, sensor in self.states.sensor:
            # sensor.attributes is a plain Pydantic model; unrecognised fields are
            # not declared on the class, so access them via hasattr/getattr.
            if not hasattr(sensor.attributes, "battery_level"):
                continue
            if sensor.attributes.battery_level < 20:  # pyright: ignore[reportAttributeAccessIssue]
                self.logger.warning("Low battery: %s", entity_id)

DomainStates Collection Interface

Every domain accessor (e.g., self.states.light) returns a DomainStates object. Beyond iteration, it supports the following operations:

Operation Example Notes
Bracket access self.states.light["bedroom"] Raises KeyError if absent
Safe access self.states.light.get("bedroom") Returns None if absent
Containment "bedroom" in self.states.light
Length len(self.states.light) Number of entities in domain
Iteration (items) for entity_id, state in self.states.light Lazy; same as .items()
.items() self.states.light.items() Iterator of (entity_id, state) pairs
.keys() self.states.light.keys() Eager list of entity IDs
.iterkeys() self.states.light.iterkeys() Lazy iterator of entity IDs
.values() self.states.light.values() Eager list of states
.itervalues() self.states.light.itervalues() Lazy iterator of states
.to_dict() self.states.light.to_dict() Eager dict[entity_id, state]

Prefer lazy iteration for large domains

.items(), .iterkeys(), and .itervalues() are lazy and avoid validating every entity up front. .keys(), .values(), and .to_dict() are eager — they walk the entire domain immediately.

Built-in State Types

Hassette ships typed state classes for every standard Home Assistant domain. Import them from hassette.models.states (or via the states alias imported from hassette):

from hassette import states  # pyright: ignore[reportUnusedImport]

# e.g. states.LightState, states.SunState, states.BinarySensorState
Full list of built-in state classes
Domain Class
ai_task AiTaskState
air_quality AirQualityState
alarm_control_panel AlarmControlPanelState
assist_satellite AssistSatelliteState
automation AutomationState
binary_sensor BinarySensorState
button ButtonState
calendar CalendarState
camera CameraState
climate ClimateState
conversation ConversationState
counter CounterState
cover CoverState
date DateState
datetime DateTimeState
device_tracker DeviceTrackerState
event EventState
fan FanState
humidifier HumidifierState
image_processing ImageProcessingState
input_boolean InputBooleanState
input_button InputButtonState
input_datetime InputDatetimeState
input_number InputNumberState
input_select InputSelectState
input_text InputTextState
light LightState
lock LockState
media_player MediaPlayerState
notify NotifyState
number NumberState
person PersonState
remote RemoteState
scene SceneState
script ScriptState
select SelectState
sensor SensorState
siren SirenState
stt SttState
sun SunState
switch SwitchState
text TextState
time TimeState
timer TimerState
todo TodoState
tts TtsState
update UpdateState
vacuum VacuumState
valve ValveState
water_heater WaterHeaterState
weather WeatherState
zone ZoneState

For domains not in this list (custom integrations, third-party add-ons), see Custom State Classes.

Good to Know

Startup and staleness

The cache is populated once at startup via a full API fetch, then kept current by WebSocket state_changed events — so states are available as soon as your app's on_ready hook runs. A periodic background poll (default every 30 seconds) guards against any events that were missed. During a HA reconnect the cache is temporarily cleared; the StateProxy marks itself not-ready and retries reads automatically, so your code does not need to handle this case.

Missing entities

self.states.light.get("bedroom") returns None when the entity is absent. self.states.light["bedroom"] raises KeyError. If you are not certain an entity exists, prefer .get() and check the result before use.

See Also