diff --git a/README.md b/README.md index b40730e..2c2d008 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,11 @@ # Gome-Assistant -Golang ↔️ Home Assistant - Write your [Home Assistant](https://www.home-assistant.io/) automations with a strongly-typed Golang library! +## Disclaimer + +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! + ## Quick Start ### Installation @@ -24,26 +26,37 @@ Keeping with the simplicity that Go is famous for, you don't need a specific env > _❗ 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._ -## Disclaimer +## gome-assistant Concepts -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 — maybe likely — that breaking changes will happen before v1.0.0! +### Overview -## gome-assistant Concepts (TODO) +The general flow is -First, you'll need to create your app. +1. Create your app +2. Register automations +3. Start app ```go import ga "github.com/saml-dev/gome-assistant" -// replace with IP and port of your Home Assistant installation if needed +// replace with IP and port of your Home Assistant installation app := ga.App("0.0.0.0:8123") + +// create automations here (see next sections) + +// register automations +app.RegisterSchedules(...) +app.RegisterEntityListeners(...) +app.RegisterEventListeners(...) + +app.Start() ``` A full reference is available on [pkg.go.dev](https://pkg.go.dev/github.com/saml-dev/gome-assistant), but all you need to know to get started are the three types of automations in gome-assistant. ### Schedules -Schedules are as you expect, a way to run a function on a schedule. The most common schedule will be once a day. +Schedules are simply a way to run a function on a schedule. The most common schedule is once a day at a certain time. ```go _7pm := ga.NewSchedule().Call(myFunc).Daily().At("19:00").Build() @@ -56,19 +69,13 @@ Schedules can also be run at sunrise or sunset, with an optional [offset](https: sunrise := ga.NewSchedule().Call(myFunc).Daily().Sunrise(app, "-30m").Build() ``` -Schedules are also used to run a function on a certain interval. Offset is used to offset the first run of a schedule from midnight. +Schedules are also used to run a function on an interval. Offset is used to offset the first run of a schedule from midnight. ```go // run every hour at the 30-minute mark interval := ga.NewSchedule().Call(a).Every("1h").Offset("30m").Build() ``` -All schedules must be registered with your app. This will panic if there are any issues with the schedule. - -```go -app.RegisterSchedules(_7pm, sunrise, interval) -``` - #### Schedule Callback function The function passed to `.Call()` must take @@ -76,6 +83,78 @@ The function passed to `.Call()` must take - `*ga.Service` used to call home assistant services - `*ga.State` used to retrieve state from home assistant +```go +func myFunc(se *ga.Service, st *ga.State) { + ... +} +``` + ### Entity Listeners +Entity Listeners are used to respond to entities changing state. The simplest entity listener looks like: + +```go +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 should 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. | +| ExceptionDay(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. | + +#### Entity Listener Callback function + +The function passed to `.Call()` must take + +- `*ga.Service` used to call home assistant services +- `*ga.State` used to retrieve state from home assistant +- `ga.EntityData` which is the entity that triggered the listener + +```go +func myFunc(se *ga.Service, st *ga.State, e ga.EntityData) { + ... +} +``` + ### Event Listeners + +Event Listeners are used to respond to entities changing state. The simplest event listener looks like: + +```go +evl := ga.NewEntityListener().EntityIds("binary_sensor.front_door").Call(myFunc).Build() +``` + +Event listeners have other functions to change the behavior. + +| Function | Info | +| ------------------------------------ | ----------------------------------------------------------------------------------- | +| Throttle("30s") | Minimum time between function calls. | +| 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. | +| ExceptionDay(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. | + +#### Event Listener Callback function + +The function passed to `.Call()` must take + +- `*ga.Service` used to call home assistant services +- `*ga.State` used to retrieve state from home assistant + +```go +func myFunc(se *ga.Service, st *ga.State) { + ... +} +``` + + diff --git a/entitylistener.go b/entitylistener.go index 5d043f8..61b4e24 100644 --- a/entitylistener.go +++ b/entitylistener.go @@ -27,7 +27,7 @@ type EntityListener struct { } // TODO: add state object as second arg -type EntityListenerCallback func(*Service, EntityData) +type EntityListenerCallback func(*Service, *State, EntityData) type EntityData struct { TriggerEntityId string @@ -192,14 +192,14 @@ func callEntityListeners(app *app, msgBytes []byte) { if l.delay != 0 { l.delayTimer = time.AfterFunc(l.delay, func() { - go l.callback(app.service, entityData) + go l.callback(app.service, app.state, entityData) l.lastRan = carbon.Now() }) return } // run now if no delay set - go l.callback(app.service, entityData) + go l.callback(app.service, app.state, entityData) l.lastRan = carbon.Now() } } diff --git a/example/example.go b/example/example.go index 6b3ffa3..791c4dc 100644 --- a/example/example.go +++ b/example/example.go @@ -47,7 +47,7 @@ func main() { } -func pantryLights(service *ga.Service, sensor ga.EntityData) { +func pantryLights(service *ga.Service, state *ga.State, sensor ga.EntityData) { l := "light.pantry" if sensor.ToState == "on" { service.HomeAssistant.TurnOn(l) @@ -68,6 +68,7 @@ func onEvent(service *ga.Service, data ga.EventData) { } func lightsOut(service *ga.Service, state *ga.State) { + // always turn off outside lights service.Light.TurnOff("light.outside_lights") s, err := state.Get("binary_sensor.living_room_motion") if err != nil { @@ -76,7 +77,7 @@ func lightsOut(service *ga.Service, state *ga.State) { } // if no motion detected in living room for 30mins - if s.State == "off" && time.Now().Sub(s.LastChanged).Minutes() > 30 { + if s.State == "off" && time.Since(s.LastChanged).Minutes() > 30 { service.Light.TurnOff("light.main_lights") } } diff --git a/state.go b/state.go index 6e86253..9e6f02b 100644 --- a/state.go +++ b/state.go @@ -32,3 +32,11 @@ func (s *State) Get(entityId string) (EntityState, error) { json.Unmarshal(resp, &es) return es, nil } + +func (s *State) Equals(entityId string, expectedState string) (bool, error) { + currentState, err := s.Get(entityId) + if err != nil { + return false, err + } + return currentState.State == expectedState, nil +}