Creating Your First App¶
This guide covers the fundamentals of creating Domovoy apps.
App Structure¶
Every Domovoy app consists of three parts:
Configuration class - Defines the app’s configurable parameters. It is always accessible inside the app as
self.configApp class - Contains the automation logic
Registration - Tells Domovoy to run the app
Basic App Template¶
With Configuration¶
from dataclasses import dataclass
from domovoy.applications import AppBase, AppConfigBase
from domovoy.applications.registration import register_app
@dataclass
class MyAppConfig(AppConfigBase):
# Define your configuration parameters here
entity_id: str
interval_minutes: int = 5
class MyApp(AppBase[MyAppConfig]):
async def initialize(self) -> None:
# Setup code runs when the app starts
self.log.info("App initialized with entity: {entity}", entity=self.config.entity_id)
async def finalize(self) -> None:
# Cleanup code runs when the app stops (optional)
self.log.info("App shutting down")
# Register the app
register_app(
app_class=MyApp,
app_name="my_app",
config=MyAppConfig(
entity_id="light.living_room",
interval_minutes=10,
),
)
Without Configuration¶
For simple apps that don’t need configuration:
from domovoy.applications import AppBase, EmptyAppConfig
from domovoy.applications.registration import register_app
class SimpleApp(AppBase[EmptyAppConfig]):
async def initialize(self) -> None:
self.log.info("Simple app started")
register_app(
app_class=SimpleApp,
app_name="simple_app",
config=EmptyAppConfig(),
)
File Naming Convention¶
App files must end with the configured suffix (default: _apps.py).
apps/
├── lighting_apps.py # Valid - will be loaded
├── climate_apps.py # Valid - will be loaded
├── helpers.py # Ignored - not an app file
└── utils/
└── common.py # Ignored - can be imported by apps
Available Plugins¶
Every app has access to these plugins through self:
Plugin |
Purpose |
|---|---|
|
Home Assistant integration (states, services, triggers) |
|
Event listening and scheduling |
|
Create Home Assistant entities |
|
Logging with trace/debug/info/warning/error levels |
|
App metadata and control (restart, get name) |
|
Time utilities (now, parse dates, sleep) |
|
General utilities (parse numbers, run async) |
|
Access to your app’s configuration |
A Complete Example¶
Here’s a practical example that turns on a light at sunset:
from dataclasses import dataclass
from domovoy.applications import AppBase, AppConfigBase
from domovoy.applications.registration import register_app
from domovoy.plugins.hass.types import EntityID
@dataclass
class SunsetLightConfig(AppConfigBase):
light_entity: EntityID
class SunsetLight(AppBase[SunsetLightConfig]):
async def initialize(self) -> None:
# Schedule the light to turn on at sunset every day
self.callbacks.run_daily_on_sun_event(
self.turn_on_light,
"sunset",
)
self.log.info("Sunset light automation initialized")
async def turn_on_light(self) -> None:
self.log.info("Sunset! Turning on {light}", light=self.config.light_entity)
await self.hass.services.light.turn_on(
entity_id=self.config.light_entity,
brightness=200,
)
register_app(
app_class=SunsetLight,
app_name="sunset_light",
config=SunsetLightConfig(
light_entity="light.porch", # type: ignore
),
)
Registering Multiple Instances¶
To run the same app class with different configurations:
from domovoy.applications.registration import register_app_multiple
configs = {
"living_room_sunset": SunsetLightConfig(light_entity="light.living_room"),
"porch_sunset": SunsetLightConfig(light_entity="light.porch"),
"bedroom_sunset": SunsetLightConfig(light_entity="light.bedroom"),
}
register_app_multiple(
app_class=SunsetLight,
configs=configs,
)
App Lifecycle¶
Apps go through these states:
CREATED - App instance created
INITIALIZING -
initialize()is runningRUNNING - App is active and processing callbacks
FINALIZING -
finalize()is running (on shutdown or reload)TERMINATED - App has stopped
If initialize() raises an exception, the app enters the FAILED state.
Best Practices¶
Make initialize() Idempotent¶
Since apps can be reloaded (due to hot-reload), your initialize() should be safe to call multiple times. Callbacks generated with the callbacks plugin will be automatically cleaned-up during hot reload.
async def initialize(self) -> None:
# Good: Creates new listeners each time (old ones are cleaned up)
self.callbacks.listen_state(self.config.entity_id, self.on_state_change)
Use finalize() for Cleanup¶
Clean up resources that shouldn’t persist across reloads:
async def finalize(self) -> None:
# Close connections, cancel external tasks, etc.
if hasattr(self, 'session'):
await self.session.close()
Keep Callbacks Async¶
All I/O operations should use await:
async def on_state_change(self, new) -> None:
# Good: Uses await for Home Assistant call
await self.hass.services.light.turn_on(entity_id="light.hall")
Next Steps¶
Configuration Reference - All configuration options
Callbacks Guide - Scheduling and event listening
Examples - Real-world app examples