Write strongly typed Home Assistant automations in Go!
Gome-Assistant is a new library, and I'm opening it up early to get some user feedback on the API and help shape the direction. I plan for it to grow to cover all Home Assistant use cases, services, and event types. So it's possible that breaking changes will happen before v1.0.0!
go get saml.dev/gome-assistant
You can generate type-safe constants for all your Home Assistant entities using go generate. This makes it easier to reference entities in your code.
- Create a
gen.yamlfile in your project root:
# Required: insert the URL of your Home Assistant here
url: "http://192.168.1.123:8123"
# Insert your auth token here, or set the HA_AUTH_TOKEN env var
ha_auth_token: ""
# Optional: defaults to zone.home
home_zone_entity_id: "zone.home"
# Optional: List of domains to include when generating constants
# If provided, only these domains will be processed
#include_domains: ["zone", "binary_sensor", "light"]
# Optional: List of domains to exclude when generating constants
# Only used if include_domains is empty
#exclude_domains: ["device_tracker", "person"]- Add a
//go:generatecomment in your project:
//go:generate go run saml.dev/gome-assistant/cmd/generateOptionally use the -config flag to customize the file path of the config file.
- Run the generator:
go generate
This will create an entities package with type-safe constants for all your Home Assistant entities, organized by domain. For example:
import "your_project/entities"
// Instead of writing "light.living_room" as a string:
entities.Light.LivingRoom // Type-safe constant
// All your entities are organized by domain
entities.Switch.Kitchen
entities.Climate.Bedroom
entities.MediaPlayer.TVRoomThe constants are based on the entity ID itself, not the name of the entity in Home Assistant.
Check out cmd/example/example.go for an example of the 3 types of automations — schedules, entity listeners, and event listeners.
ℹ️ Instead of copying and pasting, try typing it yourself to see how autocomplete guides you through the setup using a builder pattern.
Keeping with the simplicity that Go is famous for, you don't need a specific environment or docker container to run Gome-Assistant. You just write and run your code like any other Go binary. So once you build your code, you can run it however you like — using screen or tmux, a cron job, a linux service, or wrap it up in a docker container if you like!
❗ No promises, but I may provide a Docker image with file watching to automatically restart gome-assistant, to make it easier to use gome-assistant on a fully managed Home Assistant installation.
The general flow is
- Create your app
- Register automations
- Start app
import ga "saml.dev/gome-assistant"
// replace with IP and port of your Home Assistant installation
app, err := ga.NewApp(ga.NewAppRequest{
URL: "http://192.168.1.123:8123",
HAAuthToken: os.Getenv("HA_AUTH_TOKEN"),
HomeZoneEntityID: "zone.home",
})
// create automations here (see next sections)
// register automations
app.RegisterSchedules(...)
app.RegisterEntityListeners(...)
app.RegisterEventListeners(...)
app.RegisterIntervals(...)
app.Start()A full reference is available on pkg.go.dev, but all you need to know to get started are the core concepts below.
Daily Schedules run at a specific time each day.
_7pm := ga.NewDailySchedule().Call(myFunc).At("19:00").Build()Schedules can also be run at sunrise or sunset, with an optional offset.
// 30 mins before sunrise
sunrise := ga.NewDailySchedule().Call(myFunc).Sunrise(app, "-30m").Build()
// at sunset
sunset := ga.NewDailySchedule().Call(myFunc).Sunset().Build()Daily schedules have other functions to change the behavior.
| Function | Info |
|---|---|
| ExceptionDates(t time.Time, ...time.Time) | Skip the schedule on the given date(s). Functions like a blocklist. Cannot be combined with OnlyOnDates. |
| OnlyOnDates(t time.Time, ...time.Time) | Run only on the given date(s). Functions like an allowlist. Cannot be combined with ExceptionDates. |
The function passed to .Call() must take
*ga.Serviceused to call home assistant services*ga.Stateused to retrieve state from home assistant
func myFunc(se *ga.Service, st *ga.State) {
// ...
}Entity Listeners are used to respond to entities changing state. The simplest entity listener looks like:
etl := ga.NewEntityListener().EntityIDs("binary_sensor.front_door").Call(myFunc).Build()Entity listeners have other functions to change the behavior.
| Function | Info |
|---|---|
| ToState("on") | Function only called if new state matches argument. |
| FromState("on") | Function only called if old state matches argument. |
| Throttle("30s") | Minimum time between function calls. |
| Duration("30s") | Requires ToState(). Sets how long the entity must be in the state before running your function. |
| OnlyAfter("03:00") | Only run your function after a specified time of day. |
| OnlyBefore("03:00") | Only run your function before a specified time of day. |
| OnlyBetween("03:00", "14:00") | Only run your function between two specified times of day. |
| ExceptionDates(time.Time, ...time.Time) | A one time exception on the given date. Time is ignored, applies to whole day. Functions like a "blocklist". |
| ExceptionRange(time.Time, time.Time) | A one time exception between the two date/times. Both date and time are considered. Functions like a "blocklist". |
| RunOnStartup() | Run your callback during App.Start(). |
The function passed to .Call() must take
*ga.Serviceused to call home assistant services*ga.Stateused to retrieve state from home assistantga.EntityDatawhich is the entity that triggered the listener
func myFunc(se *ga.Service, st *ga.State, e ga.EntityData) {
// ...
}Event listeners allow you to respond to Home Assistant events in real-time. You can create an event listener using the builder pattern:
eventListener := ga.
NewEventListener().
EventTypes("zwave_js_value_notification"). // Specify one or more event types
Call(myCallbackFunc). // Specify the callback function
Build()
// Register the listener with your app
app.RegisterEventListeners(eventListener)Event listeners have other functions to change the behavior.
| Function | Info |
|---|---|
| OnlyBetween("03:00", "14:00") | Only run your function between two specified times of day |
| OnlyAfter("03:00") | Only run your function after a specified time of day |
| OnlyBefore("03:00") | Only run your function before a specified time of day |
| Throttle("30s") | Minimum time between function calls |
| ExceptionDates(time.Time, ...time.Time) | A one time exception on the given date. Time is ignored, applies to whole day. Functions like a "blocklist" |
| ExceptionRange(time.Time, time.Time) | A one time exception between the two date/times. Both date and time are considered. Functions like a "blocklist" |
The callback function receives three parameters:
func myCallback(service *ga.Service, state ga.State, data ga.EventData) {
// You can unmarshal the raw JSON into a type-safe struct
ev := ga.EventZWaveJSValueNotification{}
json.Unmarshal(data.RawEventJSON, &ev)
// Handle the event...
}💡 Check
eventTypes.gofor pre-defined event types, or create your own struct type for custom events and contribute them back to gome-assistant with a PR.
Automation callbacks receive a *ga.Service, which is used to call Home Assistant services.
Service methods that act on Home Assistant targets take a ga.Target. The most common way to create one is with ga.Entities(...):
func lightsOut(service *ga.Service) {
_, err := service.HomeAssistant.TurnOff(context.Background(), ga.Entities(
"light.kitchen",
"fan.office",
"switch.desk",
))
if err != nil {
// handle error
}
}Use generated entity constants when available:
_, err := service.Light.TurnOn(ctx, ga.Entities(
entities.Light.LivingRoom,
entities.Light.Kitchen,
))For more control, construct a ga.Target directly or use another helper:
_, err := service.Light.TurnOff(ctx, ga.Target{
EntityIDs: []string{"light.kitchen"},
AreaIDs: []string{"kitchen"},
DeviceIDs: []string{"abc123"},
})
_, err = service.Light.TurnOff(ctx, ga.Areas("kitchen"))
_, err = service.Light.TurnOff(ctx, ga.Devices("abc123"))Intervals are used to run a function on an interval.
// run every hour at the 30-minute mark
interval := ga.NewInterval().Call(myFunc).Every("1h").StartingAt("00:30").Build()
// run every 5 minutes between 10am and 5pm
interval = ga.NewInterval().Call(myFunc).Every("5m").StartingAt("10:00").EndingAt("17:00").Build()Intervals have other functions to change the behavior.
| Function | Info |
|---|---|
| StartingAt(TimeString) | What time the interval begins to run each day. |
| EndingAt(TimeString) | What time the interval stops running each day. |
| ExceptionDates(time.Time, ...time.Time) | A one time exception on the given date. Time is ignored, applies to whole day. |
| ExceptionRange(time.Time, time.Time) | A one time exception between the two date/times. Both date and time are considered. |
The function passed to .Call() must take
*ga.Serviceused to call home assistant services*ga.Stateused to retrieve state from home assistant
func myFunc(se *ga.Service, st *ga.State) {
// ...
}