Migration Guide
This guide covers migrating AppDaemon automations to Hassette.
Quick Reference
Four areas change: configuration, app structure, event handlers, and API calls. Hassette splits AppDaemon's flat self.* surface into typed handles: self.bus (event subscriptions), self.scheduler (timed jobs), self.api (HA service calls), and self.states (entity state cache).
| Action | AppDaemon | Hassette | Guide |
|---|---|---|---|
| Define an app | class MyApp(Hass) |
class MyApp(App[MyConfig]) |
Configuration |
| Lifecycle hook | def initialize(self): |
async def on_initialize(self): |
Mental Model |
| Listen for state changes | self.listen_state(self.cb, "light.x", new="on") |
await self.bus.on_state_change("light.x", handler=self.cb, changed_to="on", name="...") |
Bus & Events |
| Listen for service calls | self.listen_event(self.cb, "call_service", domain="light") |
await self.bus.on_call_service(domain="light", handler=self.cb, name="...") |
Bus & Events |
| Cancel a listener | self.cancel_listen_state(handle) |
subscription.cancel() |
Bus & Events |
| Schedule a timer | self.run_in(self.cb, 60) |
await self.scheduler.run_in(self.cb, delay=60) |
Scheduler |
| Cancel a timer | self.cancel_timer(handle) |
job.cancel() |
Scheduler |
| Run daily at 07:30 | self.run_daily(self.cb, time(7, 30, 0)) |
await self.scheduler.run_daily(self.cb, at="07:30") |
Scheduler |
| Call a HA service | self.call_service("light/turn_on", entity_id="light.x") |
await self.api.call_service("light", "turn_on", target={"entity_id": "light.x"}) |
API Calls |
| Get entity state | self.get_state("light.x") |
self.states.light.get("light.x") or await self.api.get_state("light.x") |
API Calls |
| Access app config | self.args["entity"] |
self.app_config.entity |
Configuration |
| Logging | self.log("message") |
self.logger.info("message") |
Mental Model |
name= in the bus rows above is required — it identifies the listener in logs and the monitoring UI. Use a descriptive string like "kitchen_motion". Omitting it raises ListenerNameRequiredError at runtime. AppDaemon has no equivalent.
All Hassette bus, scheduler, and API calls are async and need await. In AppDaemon, self.listen_state registers immediately. In Hassette, forgetting await means nothing registers — no error, no warning, just silence.
Is Migration Worth It?
| Migrate if... | Stay with AppDaemon if... |
|---|---|
| You want IDE autocomplete and type errors at write time | Your apps work and you don't need type safety |
| You want to unit-test automations with a real test harness | You prefer synchronous code without async/await |
| You want Pydantic-validated config with clear error messages | Your team already knows AppDaemon well |
| You want dependency injection in event handlers | You rely on AppDaemon features not yet in Hassette |
| You want structured per-app logs with method and line context |
Known Gaps
| AppDaemon feature | Status in Hassette |
|---|---|
listen_log / log event subscriptions |
Not planned |
| HADashboard | Not planned |
Notification helpers (notify, call_action) |
Use await self.api.call_service("notify", ...) directly |
| MQTT plugin | Not yet supported. No workaround available. |
| Global variables / inter-app communication | Use await self.bus.emit(topic, data) for in-process broadcast |
If a feature you depend on is missing, open an issue or check GitHub discussions.
Common Pitfalls
name= is required on all bus subscriptions. Omitting it raises ListenerNameRequiredError at runtime. Every on_state_change, on_call_service, and on call needs a stable string name.
self.api.*, self.bus.on_*, and self.scheduler.* are async and must be awaited. Forgetting await returns a coroutine object. Nothing is registered or called.
AppSync apps use .sync facades. If you subclass AppSync for synchronous handlers, use self.bus.sync.on_state_change(...) and self.scheduler.sync.run_in(...). The async methods are not available in sync hooks.
Per-App Migration Checklist
The Migration Checklist walks through converting a single app from AppDaemon to Hassette. Work through it once for your first app, then use it as a reference for the rest.