Your First Automation
This page adds two features to the app from the Quickstart:
- A sunset handler that turns on a light when the sun sets, with typed state data filled in automatically
- A heartbeat job that logs every minute
Subscribe to a State Change
from hassette import App, AppConfig, D, states
class MyAppConfig(AppConfig):
greeting: str = "Hello from Hassette!"
class MyApp(App[MyAppConfig]):
async def on_initialize(self):
self.logger.info(self.app_config.greeting)
await self.bus.on_state_change(
"sun.*", handler=self.on_sun_change, name="sun_change"
)
async def on_sun_change(self, new_state: D.StateNew[states.SunState]):
self.logger.info("Sun changed: %s", new_state.value)
if new_state.value == "below_horizon":
await self.api.turn_on("light.porch", domain="light")
self.logger.info("Porch light turned on")
self.bus.on_state_change() registers a handler that fires whenever an entity's state changes, like a light switching on or a sensor reporting a new reading. "sun.*" is a glob pattern that matches any entity in the sun domain. In practice, that's sun.sun. The name= parameter labels this handler in logs and the web UI (a monitoring dashboard Hassette includes).
The handler parameter new_state: D.StateNew[states.SunState] is a type annotation that tells Hassette: when this handler fires, pass in the new state as a SunState object. The bracket syntax works like list[str] in standard Python generics. D.StateNew[T] means "a new-state value, typed as T." Here is what the two imports do:
Dishassette.event_handling.dependencies, a module of type annotations.D.StateNew[T]means "give me the new state, converted to typeT."statesishassette.models.states, typed state classes for each HA domain.states.SunStatehas a.valueattribute holding"above_horizon"or"below_horizon".
This is a Hassette feature. Standard Python ignores type annotations at runtime, but Hassette inspects them to know what to pass into each handler. No event dict parsing needed. Your IDE knows the type, and Pyright (a Python type checker, optional but useful in VS Code) catches typos.
self.api.turn_on() calls the light.turn_on service in Home Assistant, the same action as toggling a light from the HA UI. The domain="light" parameter tells HA which service domain to use. You can find available services in Developer Tools → Services in your Home Assistant instance.
Schedule a Recurring Job
from hassette import App, AppConfig, D, states
class MyAppConfig(AppConfig):
greeting: str = "Hello from Hassette!"
class MyApp(App[MyAppConfig]):
async def on_initialize(self):
self.logger.info(self.app_config.greeting)
await self.bus.on_state_change(
"sun.*", handler=self.on_sun_change, name="sun_change"
)
await self.scheduler.run_minutely(self.log_heartbeat)
async def on_sun_change(self, new_state: D.StateNew[states.SunState]):
self.logger.info("Sun changed: %s", new_state.value)
if new_state.value == "below_horizon":
await self.api.turn_on("light.porch", domain="light")
self.logger.info("Porch light turned on")
async def log_heartbeat(self):
self.logger.info("Heartbeat")
self.scheduler.run_minutely() runs log_heartbeat every minute. The first run fires one minute after startup. Hassette tracks the job and cancels it automatically on shutdown.
log_heartbeat has no D.* annotations in its signature. Not every handler needs them. See Scheduler Methods for run_daily, run_cron, run_once, and more.
Run It
Replace your apps/main.py with the complete app from the previous snippet. Stop Hassette with Ctrl+C and run hassette run -e .env again. You see new log lines:
INFO hassette.MyApp.0 — Hello from Hassette!
INFO hassette.MyApp.0 — Heartbeat
The Sun changed and Porch light turned on lines appear at the next sunset. To test the handler now without waiting, trigger a state change manually:
- In Home Assistant, go to Settings → Developer Tools.
- Go to the States tab and find
sun.sun. - Change the state value to
below_horizonand click Set State.
The handler fires within milliseconds. You see Sun changed: below_horizon and Porch light turned on in the logs.
Next Steps
Bus& Handlers: attribute changes, service calls, glob patterns, predicates, and conditions- Dependency Injection: all the types you can extract into handler parameters
SchedulerMethods:run_daily,run_cron,run_once, and jitter- Testing Your Apps: unit tests using
AppTestHarness - Recipes: complete worked examples for motion lights, presence detection, and more
- Docker: run Hassette in production as a container