A type-safe Kotlin DSL for defining Home Assistant configurations programmatically. Write your automations, scripts, dashboards, and entities in Kotlin and generate YAML configurations automatically.
- Type-safe: Leverage Kotlin's type system to catch errors at compile time
- Modular: Organize your configurations into reusable components
- DRY: Create abstractions and share common patterns across your automations
- IDE Support: Get autocomplete, refactoring, and navigation in your IDE
- Version Control Friendly: Better diff and merge experience with programmatic configs
See the example template repository for a complete starter project.
homeassistant-kotlin-dsl/
├── BUILD.bazel # Bazel build configuration
├── MODULE.bazel # Bazel module dependencies
├── kt/
│ └── dsl_core/
│ └── kotlin/
│ ├── base/ # Core DSL classes (Automation, HAProject, etc.)
│ ├── actions/ # Home Assistant actions
│ ├── conditions/ # Automation conditions
│ ├── triggers/ # Automation triggers
│ ├── script/blocks/ # Script building blocks
│ ├── entities/ # Entity definitions
│ └── ui/ # Dashboard and UI components
│ ├── cards/ # Card types (entities, conditional, etc.)
│ ├── Dashboard.kt
│ └── View.kt
└── py/ # Python utilities
The HAProject is the root container for all your Home Assistant configurations:
fun main(args: Array<String>) = HAProjectCommand {
// Add automations
automation { ... }
// Add scripts
script { ... }
// Add dashboards
dashboard { ... }
// Add entities
inputBoolean { ... }
counter { ... }
binarySensor { ... }
}.main(args)Automations consist of three parts: triggers, conditions (optional), and actions.
automation {
id = "turn_on_lights_at_sunset"
alias = "Turn on lights at sunset"
description = "Automatically turn on living room lights when sun sets"
triggers {
stateTrigger {
entity("sun.sun")
transitionTo = "below_horizon"
}
}
conditions {
state {
entity("input_boolean.auto_lights")
state = "on"
}
}
actions {
service {
service = ActionNames.Light.TurnOn
target("light.living_room")
data["brightness"] = 255
}
}
}Common trigger types:
stateTrigger {
entities("binary_sensor.motion_sensor")
transitionTo = "on"
forDuration {
seconds = 30
}
}timePatternTrigger {
hours = "/1" // Every hour
}numericStateTrigger {
entity("sensor.temperature")
above = 25
}nfcTag {
tag("your_tag_id")
}Conditions determine whether automation actions should run:
conditions {
// Simple state condition
state {
entity("input_boolean.guest_mode")
state = "off"
}
// Numeric state condition
numericState {
entity("sensor.temperature")
below = 20
}
// Logical operators
and {
state { ... }
state { ... }
}
or {
state { ... }
state { ... }
}
not {
state { ... }
}
}Actions define what happens when an automation triggers:
actions {
// Call a service
service {
service = ActionNames.Light.TurnOn
target("light.bedroom")
data["brightness"] = 128
data["color_temp"] = 300
}
// Delay
delay {
seconds = 5
}
// Conditional execution
ifElse {
conditions {
state {
entity("binary_sensor.door")
state = "open"
}
}
then {
service { ... }
}
otherwise {
service { ... }
}
}
// Call another script
callAction {
action = "script.my_custom_script"
}
}Scripts are reusable sequences of actions:
val myScript = HAScript {
id("turn_on_movie_mode")
alias = "Movie Mode"
blocks {
// Turn off lights
callAction {
action = ActionNames.Light.TurnOff
entity("light.living_room")
}
// Wait a moment
delay {
seconds = 2
}
// Turn on TV
callAction {
action = ActionNames.Switch.TurnOn
entity("switch.tv")
}
}
}Create Lovelace dashboards programmatically:
dashboard {
path = "main"
title = "Main Dashboard"
icon = "mdi:home"
showInSidebar = true
view {
title = "Living Room"
icon = "mdi:sofa"
cards {
entitiesCard {
title = "Lights"
entities {
entity("light.living_room")
entity("light.kitchen")
}
}
conditionalCard {
conditions {
numericState {
entity("sensor.temperature")
above = 25
}
}
cards {
entityCard {
entity = "climate.ac"
}
}
}
}
}
}Available card types:
entitiesCard: Display multiple entitiesentityCard: Display a single entityconditionalCard: Show cards based on conditionsmushroomTemplateCard: Mushroom template cardsverticalStackCard: Stack cards verticallyhorizontalStackCard: Stack cards horizontallygridCard: Arrange cards in a grid
Define helper entities:
// Input Boolean
inputBoolean {
alias = "guest_mode"
name = "Guest Mode"
icon = "mdi:account-multiple"
}
// Counter
counter {
alias = "days_since_cleaning"
name = "Days Since Last Cleaning"
icon = "mdi:broom"
initial = 0
step = 1
}
// Binary Sensor
binarySensor {
name = "Custom Sensor"
deviceClass = "motion"
// ... configuration
}This project uses Bazel as its build system:
# Build the project
bazel build //...
# Run tests
bazel test //...
# Run the configuration generator
bazel run //your_project:main -- --output-dir=/path/to/homeassistant/configYou can create higher-level abstractions for common patterns:
// Define a reusable pattern
fun HAProject.motionLightAutomation(
motionSensor: String,
light: String,
delayMinutes: Int = 5
) {
automation {
id = "motion_light_${light.replace(".", "_")}"
triggers {
stateTrigger {
entities(motionSensor)
transitionTo = "on"
}
}
actions {
service {
service = ActionNames.Light.TurnOn
target(light)
}
}
}
automation {
id = "motion_light_off_${light.replace(".", "_")}"
triggers {
stateTrigger {
entities(motionSensor)
transitionTo = "off"
forDuration {
minutes = delayMinutes
}
}
}
actions {
service {
service = ActionNames.Light.TurnOff
target(light)
}
}
}
}
// Usage
HAProjectCommand {
motionLightAutomation("binary_sensor.hallway_motion", "light.hallway")
motionLightAutomation("binary_sensor.bathroom_motion", "light.bathroom", delayMinutes = 2)
}Use Kotlin extension functions for cleaner syntax:
fun HAProject.livingRoomAutomations() {
automation {
id = "living_room_morning_lights"
// ...
}
automation {
id = "living_room_evening_lights"
// ...
}
}
// In main
HAProjectCommand {
livingRoomAutomations()
}Define entity references as typed objects:
object Entities {
object Lights {
const val livingRoom = "light.living_room"
const val bedroom = "light.bedroom"
const val kitchen = "light.kitchen"
}
object Sensors {
const val temperature = "sensor.living_room_temperature"
const val humidity = "sensor.living_room_humidity"
}
}
// Usage
actions {
service {
service = ActionNames.Light.TurnOn
target(Entities.Lights.livingRoom)
}
}- id: "turn_on_all_lights"
alias: "Turn on all lights at sunset"
trigger:
- platform: sun
event: sunset
action:
- service: light.turn_on
target:
entity_id: light.living_room
- service: light.turn_on
target:
entity_id: light.bedroom
- service: light.turn_on
target:
entity_id: light.kitchenautomation {
id = "turn_on_all_lights"
alias = "Turn on all lights at sunset"
triggers {
stateTrigger {
entity("sun.sun")
transitionTo = "below_horizon"
}
}
actions {
listOf(
Entities.Lights.livingRoom,
Entities.Lights.bedroom,
Entities.Lights.kitchen
).forEach { light ->
service {
service = ActionNames.Light.TurnOn
target(light)
}
}
}
}Benefits:
- IDE autocomplete and type checking
- Reusable functions and abstractions
- Better refactoring capabilities
- Compile-time error detection
- Full power of Kotlin (loops, conditionals, functions)
[Add your license here]