mirror of
https://github.com/Xevion/go-ha.git
synced 2026-01-31 02:24:26 -06:00
Merge pull request #12 from saml-dev/feat-enabled-disabled-list
feat: EnabledWhen / DisabledWhen upgrades
This commit is contained in:
@@ -29,7 +29,7 @@ type App struct {
|
|||||||
httpClient *http.HttpClient
|
httpClient *http.HttpClient
|
||||||
|
|
||||||
service *Service
|
service *Service
|
||||||
state *State
|
state *StateImpl
|
||||||
|
|
||||||
schedules pq.PriorityQueue
|
schedules pq.PriorityQueue
|
||||||
intervals pq.PriorityQueue
|
intervals pq.PriorityQueue
|
||||||
@@ -192,7 +192,7 @@ func (a *App) RegisterEventListeners(evls ...EventListener) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSunriseSunset(s *State, sunrise bool, dateToUse carbon.Carbon, offset ...DurationString) carbon.Carbon {
|
func getSunriseSunset(s *StateImpl, sunrise bool, dateToUse carbon.Carbon, offset ...DurationString) carbon.Carbon {
|
||||||
date := dateToUse.Carbon2Time()
|
date := dateToUse.Carbon2Time()
|
||||||
rise, set := sunriseLib.SunriseSunset(s.latitude, s.longitude, date.Year(), date.Month(), date.Day())
|
rise, set := sunriseLib.SunriseSunset(s.latitude, s.longitude, date.Year(), date.Month(), date.Day())
|
||||||
rise, set = rise.Local(), set.Local()
|
rise, set = rise.Local(), set.Local()
|
||||||
@@ -291,6 +291,6 @@ func (a *App) GetService() *Service {
|
|||||||
return a.service
|
return a.service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) GetState() *State {
|
func (a *App) GetState() State {
|
||||||
return a.state
|
return a.state
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-15
@@ -86,35 +86,60 @@ func checkExceptionRanges(eList []timeRange) conditionCheck {
|
|||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkEnabledEntity(s *State, eid, expectedState string, runOnNetworkError bool) conditionCheck {
|
func checkEnabledEntity(s State, infos []internal.EnabledDisabledInfo) conditionCheck {
|
||||||
cc := conditionCheck{fail: false}
|
cc := conditionCheck{fail: false}
|
||||||
if eid == "" || expectedState == "" {
|
if len(infos) == 0 {
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
matches, err := s.Equals(eid, expectedState)
|
for _, edi := range infos {
|
||||||
if err != nil {
|
matches, err := s.Equals(edi.Entity, edi.State)
|
||||||
cc.fail = !runOnNetworkError
|
|
||||||
return cc
|
|
||||||
}
|
|
||||||
|
|
||||||
cc.fail = !matches
|
if err != nil {
|
||||||
|
if edi.RunOnError {
|
||||||
|
// keep checking
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// don't run this automation
|
||||||
|
cc.fail = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !matches {
|
||||||
|
cc.fail = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkDisabledEntity(s *State, eid, expectedState string, runOnNetworkError bool) conditionCheck {
|
func checkDisabledEntity(s State, infos []internal.EnabledDisabledInfo) conditionCheck {
|
||||||
cc := conditionCheck{fail: false}
|
cc := conditionCheck{fail: false}
|
||||||
if eid == "" || expectedState == "" {
|
if len(infos) == 0 {
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
matches, err := s.Equals(eid, expectedState)
|
for _, edi := range infos {
|
||||||
if err != nil {
|
matches, err := s.Equals(edi.Entity, edi.State)
|
||||||
cc.fail = !runOnNetworkError
|
|
||||||
return cc
|
if err != nil {
|
||||||
|
if edi.RunOnError {
|
||||||
|
// keep checking
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
// don't run this automation
|
||||||
|
cc.fail = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches {
|
||||||
|
cc.fail = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cc.fail = matches
|
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
package gomeassistant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"saml.dev/gome-assistant/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockState struct {
|
||||||
|
EqualsReturn bool
|
||||||
|
EqualsError bool
|
||||||
|
GetReturn EntityState
|
||||||
|
GetError bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s MockState) AfterSunrise(_ ...DurationString) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (s MockState) BeforeSunrise(_ ...DurationString) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (s MockState) AfterSunset(_ ...DurationString) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (s MockState) BeforeSunset(_ ...DurationString) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func (s MockState) Get(eid string) (EntityState, error) {
|
||||||
|
if s.GetError {
|
||||||
|
return EntityState{}, errors.New("some error")
|
||||||
|
}
|
||||||
|
return s.GetReturn, nil
|
||||||
|
}
|
||||||
|
func (s MockState) Equals(eid, state string) (bool, error) {
|
||||||
|
if s.EqualsError {
|
||||||
|
return false, errors.New("some error")
|
||||||
|
}
|
||||||
|
return s.EqualsReturn, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var runOnError = internal.EnabledDisabledInfo{
|
||||||
|
Entity: "eid",
|
||||||
|
State: "state",
|
||||||
|
RunOnError: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var dontRunOnError = internal.EnabledDisabledInfo{
|
||||||
|
Entity: "eid",
|
||||||
|
State: "state",
|
||||||
|
RunOnError: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
func list(infos ...internal.EnabledDisabledInfo) []internal.EnabledDisabledInfo {
|
||||||
|
ret := []internal.EnabledDisabledInfo{}
|
||||||
|
ret = append(ret, infos...)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnabledEntity_StateEqual_Passes(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsReturn: true,
|
||||||
|
}
|
||||||
|
c := checkEnabledEntity(state, list(runOnError))
|
||||||
|
assert.False(t, c.fail, "should pass")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnabledEntity_StateNotEqual_Fails(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsReturn: false,
|
||||||
|
}
|
||||||
|
c := checkEnabledEntity(state, list(runOnError))
|
||||||
|
assert.True(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnabledEntity_NetworkError_DontRun_Fails(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsError: true,
|
||||||
|
}
|
||||||
|
c := checkEnabledEntity(state, list(dontRunOnError))
|
||||||
|
assert.True(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnabledEntity_NetworkError_StillRun_Passes(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsError: true,
|
||||||
|
}
|
||||||
|
c := checkEnabledEntity(state, list(runOnError))
|
||||||
|
assert.False(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabledEntity_StateEqual_Fails(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsReturn: true,
|
||||||
|
}
|
||||||
|
c := checkDisabledEntity(state, list(runOnError))
|
||||||
|
assert.True(t, c.fail, "should pass")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabledEntity_StateNotEqual_Passes(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsReturn: false,
|
||||||
|
}
|
||||||
|
c := checkDisabledEntity(state, list(runOnError))
|
||||||
|
assert.False(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabledEntity_NetworkError_DontRun_Fails(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsError: true,
|
||||||
|
}
|
||||||
|
c := checkDisabledEntity(state, list(dontRunOnError))
|
||||||
|
assert.True(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDisabledEntity_NetworkError_StillRun_Passes(t *testing.T) {
|
||||||
|
state := MockState{
|
||||||
|
EqualsError: true,
|
||||||
|
}
|
||||||
|
c := checkDisabledEntity(state, list(runOnError))
|
||||||
|
assert.False(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatesMatch(t *testing.T) {
|
||||||
|
c := checkStatesMatch("hey", "hey")
|
||||||
|
assert.False(t, c.fail, "should pass")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStatesDontMatch(t *testing.T) {
|
||||||
|
c := checkStatesMatch("hey", "bye")
|
||||||
|
assert.True(t, c.fail, "should fail")
|
||||||
|
}
|
||||||
+15
-19
@@ -29,15 +29,11 @@ type EntityListener struct {
|
|||||||
runOnStartup bool
|
runOnStartup bool
|
||||||
runOnStartupCompleted bool
|
runOnStartupCompleted bool
|
||||||
|
|
||||||
enabledEntity string
|
enabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityState string
|
disabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityRunOnError bool
|
|
||||||
disabledEntity string
|
|
||||||
disabledEntityState string
|
|
||||||
disabledEntityRunOnError bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntityListenerCallback func(*Service, *State, EntityData)
|
type EntityListenerCallback func(*Service, State, EntityData)
|
||||||
|
|
||||||
type EntityData struct {
|
type EntityData struct {
|
||||||
TriggerEntityId string
|
TriggerEntityId string
|
||||||
@@ -164,12 +160,12 @@ func (b elBuilder3) EnabledWhen(entityId, state string, runOnNetworkError bool)
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
||||||
}
|
}
|
||||||
if b.entityListener.disabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting EnabledWhen on an entity listener with params entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
b.entityListener.enabledEntity = entityId
|
b.entityListener.enabledEntities = append(b.entityListener.enabledEntities, i)
|
||||||
b.entityListener.enabledEntityState = state
|
|
||||||
b.entityListener.enabledEntityRunOnError = runOnNetworkError
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,12 +177,12 @@ func (b elBuilder3) DisabledWhen(entityId, state string, runOnNetworkError bool)
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
||||||
}
|
}
|
||||||
if b.entityListener.enabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting DisabledWhen on an entity listener with params entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
b.entityListener.disabledEntity = entityId
|
b.entityListener.disabledEntities = append(b.entityListener.disabledEntities, i)
|
||||||
b.entityListener.disabledEntityState = state
|
|
||||||
b.entityListener.disabledEntityRunOnError = runOnNetworkError
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,10 +233,10 @@ func callEntityListeners(app *App, msgBytes []byte) {
|
|||||||
if c := checkExceptionRanges(l.exceptionRanges); c.fail {
|
if c := checkExceptionRanges(l.exceptionRanges); c.fail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c := checkEnabledEntity(app.state, l.enabledEntity, l.enabledEntityState, l.enabledEntityRunOnError); c.fail {
|
if c := checkEnabledEntity(app.state, l.enabledEntities); c.fail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c := checkDisabledEntity(app.state, l.disabledEntity, l.disabledEntityState, l.disabledEntityRunOnError); c.fail {
|
if c := checkDisabledEntity(app.state, l.disabledEntities); c.fail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+15
-19
@@ -21,15 +21,11 @@ type EventListener struct {
|
|||||||
exceptionDates []time.Time
|
exceptionDates []time.Time
|
||||||
exceptionRanges []timeRange
|
exceptionRanges []timeRange
|
||||||
|
|
||||||
enabledEntity string
|
enabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityState string
|
disabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityRunOnError bool
|
|
||||||
disabledEntity string
|
|
||||||
disabledEntityState string
|
|
||||||
disabledEntityRunOnError bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type EventListenerCallback func(*Service, *State, EventData)
|
type EventListenerCallback func(*Service, State, EventData)
|
||||||
|
|
||||||
type EventData struct {
|
type EventData struct {
|
||||||
Type string
|
Type string
|
||||||
@@ -106,12 +102,12 @@ func (b eventListenerBuilder3) EnabledWhen(entityId, state string, runOnNetworkE
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in eventListener EnabledWhen entityId='%s' state='%s' runOnNetworkError='%t'", entityId, state, runOnNetworkError))
|
panic(fmt.Sprintf("entityId is empty in eventListener EnabledWhen entityId='%s' state='%s' runOnNetworkError='%t'", entityId, state, runOnNetworkError))
|
||||||
}
|
}
|
||||||
if b.eventListener.disabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting EnabledWhen entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
b.eventListener.enabledEntity = entityId
|
b.eventListener.enabledEntities = append(b.eventListener.enabledEntities, i)
|
||||||
b.eventListener.enabledEntityState = state
|
|
||||||
b.eventListener.enabledEntityRunOnError = runOnNetworkError
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +119,12 @@ func (b eventListenerBuilder3) DisabledWhen(entityId, state string, runOnNetwork
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in eventListener EnabledWhen entityId='%s' state='%s' runOnNetworkError='%t'", entityId, state, runOnNetworkError))
|
panic(fmt.Sprintf("entityId is empty in eventListener EnabledWhen entityId='%s' state='%s' runOnNetworkError='%t'", entityId, state, runOnNetworkError))
|
||||||
}
|
}
|
||||||
if b.eventListener.enabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting DisabledWhen entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
b.eventListener.disabledEntity = entityId
|
b.eventListener.disabledEntities = append(b.eventListener.disabledEntities, i)
|
||||||
b.eventListener.disabledEntityState = state
|
|
||||||
b.eventListener.disabledEntityRunOnError = runOnNetworkError
|
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,10 +162,10 @@ func callEventListeners(app *App, msg ws.ChanMsg) {
|
|||||||
if c := checkExceptionRanges(l.exceptionRanges); c.fail {
|
if c := checkExceptionRanges(l.exceptionRanges); c.fail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c := checkEnabledEntity(app.state, l.enabledEntity, l.enabledEntityState, l.enabledEntityRunOnError); c.fail {
|
if c := checkEnabledEntity(app.state, l.enabledEntities); c.fail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if c := checkDisabledEntity(app.state, l.disabledEntity, l.disabledEntityState, l.disabledEntityRunOnError); c.fail {
|
if c := checkDisabledEntity(app.state, l.disabledEntities); c.fail {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+4
-4
@@ -54,7 +54,7 @@ func main() {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pantryLights(service *ga.Service, state *ga.State, sensor ga.EntityData) {
|
func pantryLights(service *ga.Service, state ga.State, sensor ga.EntityData) {
|
||||||
l := "light.pantry"
|
l := "light.pantry"
|
||||||
if sensor.ToState == "on" {
|
if sensor.ToState == "on" {
|
||||||
service.HomeAssistant.TurnOn(l)
|
service.HomeAssistant.TurnOn(l)
|
||||||
@@ -63,7 +63,7 @@ func pantryLights(service *ga.Service, state *ga.State, sensor ga.EntityData) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func onEvent(service *ga.Service, state *ga.State, data ga.EventData) {
|
func onEvent(service *ga.Service, state ga.State, data ga.EventData) {
|
||||||
// Since the structure of the event changes depending
|
// Since the structure of the event changes depending
|
||||||
// on the event type, you can Unmarshal the raw json
|
// on the event type, you can Unmarshal the raw json
|
||||||
// into a Go type. If a type for your event doesn't
|
// into a Go type. If a type for your event doesn't
|
||||||
@@ -74,7 +74,7 @@ func onEvent(service *ga.Service, state *ga.State, data ga.EventData) {
|
|||||||
log.Default().Println(ev)
|
log.Default().Println(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lightsOut(service *ga.Service, state *ga.State) {
|
func lightsOut(service *ga.Service, state ga.State) {
|
||||||
// always turn off outside lights
|
// always turn off outside lights
|
||||||
service.Light.TurnOff("light.outside_lights")
|
service.Light.TurnOff("light.outside_lights")
|
||||||
s, err := state.Get("binary_sensor.living_room_motion")
|
s, err := state.Get("binary_sensor.living_room_motion")
|
||||||
@@ -89,7 +89,7 @@ func lightsOut(service *ga.Service, state *ga.State) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func sunriseSched(service *ga.Service, state *ga.State) {
|
func sunriseSched(service *ga.Service, state ga.State) {
|
||||||
service.Light.TurnOn("light.living_room_lamps")
|
service.Light.TurnOn("light.living_room_lamps")
|
||||||
service.Light.TurnOff("light.christmas_lights")
|
service.Light.TurnOff("light.christmas_lights")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,14 @@ require (
|
|||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/gobuffalo/envy v1.10.2 // indirect
|
github.com/gobuffalo/envy v1.10.2 // indirect
|
||||||
github.com/gobuffalo/packd v1.0.2 // indirect
|
github.com/gobuffalo/packd v1.0.2 // indirect
|
||||||
github.com/gobuffalo/packr v1.30.1 // indirect
|
github.com/gobuffalo/packr v1.30.1 // indirect
|
||||||
github.com/joho/godotenv v1.4.0 // indirect
|
github.com/joho/godotenv v1.4.0 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
github.com/rogpeppe/go-internal v1.9.0 // indirect
|
||||||
|
github.com/stretchr/objx v0.5.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.8.4 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -57,12 +57,16 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
|
|||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||||
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
|
||||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
|
|||||||
@@ -10,6 +10,12 @@ import (
|
|||||||
"github.com/golang-module/carbon"
|
"github.com/golang-module/carbon"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type EnabledDisabledInfo struct {
|
||||||
|
Entity string
|
||||||
|
State string
|
||||||
|
RunOnError bool
|
||||||
|
}
|
||||||
|
|
||||||
var id int64 = 0
|
var id int64 = 0
|
||||||
|
|
||||||
func GetId() int64 {
|
func GetId() int64 {
|
||||||
|
|||||||
+15
-19
@@ -7,7 +7,7 @@ import (
|
|||||||
"saml.dev/gome-assistant/internal"
|
"saml.dev/gome-assistant/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IntervalCallback func(*Service, *State)
|
type IntervalCallback func(*Service, State)
|
||||||
|
|
||||||
type Interval struct {
|
type Interval struct {
|
||||||
frequency time.Duration
|
frequency time.Duration
|
||||||
@@ -19,12 +19,8 @@ type Interval struct {
|
|||||||
exceptionDates []time.Time
|
exceptionDates []time.Time
|
||||||
exceptionRanges []timeRange
|
exceptionRanges []timeRange
|
||||||
|
|
||||||
enabledEntity string
|
enabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityState string
|
disabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityRunOnError bool
|
|
||||||
disabledEntity string
|
|
||||||
disabledEntityState string
|
|
||||||
disabledEntityRunOnError bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Interval) Hash() string {
|
func (i Interval) Hash() string {
|
||||||
@@ -118,12 +114,12 @@ func (ib intervalBuilderEnd) EnabledWhen(entityId, state string, runOnNetworkErr
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
||||||
}
|
}
|
||||||
if ib.interval.disabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting EnabledWhen on an entity listener with params entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
ib.interval.enabledEntity = entityId
|
ib.interval.enabledEntities = append(ib.interval.enabledEntities, i)
|
||||||
ib.interval.enabledEntityState = state
|
|
||||||
ib.interval.enabledEntityRunOnError = runOnNetworkError
|
|
||||||
return ib
|
return ib
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,12 +131,12 @@ func (ib intervalBuilderEnd) DisabledWhen(entityId, state string, runOnNetworkEr
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
||||||
}
|
}
|
||||||
if ib.interval.enabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting DisabledWhen on an entity listener with params entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
ib.interval.disabledEntity = entityId
|
ib.interval.disabledEntities = append(ib.interval.disabledEntities, i)
|
||||||
ib.interval.disabledEntityState = state
|
|
||||||
ib.interval.disabledEntityRunOnError = runOnNetworkError
|
|
||||||
return ib
|
return ib
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,10 +180,10 @@ func (i Interval) maybeRunCallback(a *App) {
|
|||||||
if c := checkExceptionRanges(i.exceptionRanges); c.fail {
|
if c := checkExceptionRanges(i.exceptionRanges); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c := checkEnabledEntity(a.state, i.enabledEntity, i.enabledEntityState, i.enabledEntityRunOnError); c.fail {
|
if c := checkEnabledEntity(a.state, i.enabledEntities); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c := checkDisabledEntity(a.state, i.disabledEntity, i.disabledEntityState, i.disabledEntityRunOnError); c.fail {
|
if c := checkDisabledEntity(a.state, i.disabledEntities); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go i.callback(a.service, a.state)
|
go i.callback(a.service, a.state)
|
||||||
|
|||||||
+15
-19
@@ -9,7 +9,7 @@ import (
|
|||||||
"saml.dev/gome-assistant/internal"
|
"saml.dev/gome-assistant/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ScheduleCallback func(*Service, *State)
|
type ScheduleCallback func(*Service, State)
|
||||||
|
|
||||||
type DailySchedule struct {
|
type DailySchedule struct {
|
||||||
// 0-23
|
// 0-23
|
||||||
@@ -27,12 +27,8 @@ type DailySchedule struct {
|
|||||||
exceptionDates []time.Time
|
exceptionDates []time.Time
|
||||||
allowlistDates []time.Time
|
allowlistDates []time.Time
|
||||||
|
|
||||||
enabledEntity string
|
enabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityState string
|
disabledEntities []internal.EnabledDisabledInfo
|
||||||
enabledEntityRunOnError bool
|
|
||||||
disabledEntity string
|
|
||||||
disabledEntityState string
|
|
||||||
disabledEntityRunOnError bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s DailySchedule) Hash() string {
|
func (s DailySchedule) Hash() string {
|
||||||
@@ -125,12 +121,12 @@ func (sb scheduleBuilderEnd) EnabledWhen(entityId, state string, runOnNetworkErr
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
||||||
}
|
}
|
||||||
if sb.schedule.disabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting EnabledWhen on a schedule with params entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
sb.schedule.enabledEntity = entityId
|
sb.schedule.enabledEntities = append(sb.schedule.enabledEntities, i)
|
||||||
sb.schedule.enabledEntityState = state
|
|
||||||
sb.schedule.enabledEntityRunOnError = runOnNetworkError
|
|
||||||
return sb
|
return sb
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,12 +138,12 @@ func (sb scheduleBuilderEnd) DisabledWhen(entityId, state string, runOnNetworkEr
|
|||||||
if entityId == "" {
|
if entityId == "" {
|
||||||
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
|
||||||
}
|
}
|
||||||
if sb.schedule.enabledEntity != "" {
|
i := internal.EnabledDisabledInfo{
|
||||||
panic(fmt.Sprintf("You can't use EnabledWhen and DisabledWhen together. Error occurred while setting DisabledWhen on a schedule with params entityId=%s state=%s runOnNetworkError=%t", entityId, state, runOnNetworkError))
|
Entity: entityId,
|
||||||
|
State: state,
|
||||||
|
RunOnError: runOnNetworkError,
|
||||||
}
|
}
|
||||||
sb.schedule.disabledEntity = entityId
|
sb.schedule.disabledEntities = append(sb.schedule.disabledEntities, i)
|
||||||
sb.schedule.disabledEntityState = state
|
|
||||||
sb.schedule.disabledEntityRunOnError = runOnNetworkError
|
|
||||||
return sb
|
return sb
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,10 +182,10 @@ func (s DailySchedule) maybeRunCallback(a *App) {
|
|||||||
if c := checkAllowlistDates(s.allowlistDates); c.fail {
|
if c := checkAllowlistDates(s.allowlistDates); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c := checkEnabledEntity(a.state, s.enabledEntity, s.enabledEntityState, s.enabledEntityRunOnError); c.fail {
|
if c := checkEnabledEntity(a.state, s.enabledEntities); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if c := checkDisabledEntity(a.state, s.disabledEntity, s.disabledEntityState, s.disabledEntityRunOnError); c.fail {
|
if c := checkDisabledEntity(a.state, s.disabledEntities); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
go s.callback(a.service, a.state)
|
go s.callback(a.service, a.state)
|
||||||
|
|||||||
@@ -10,8 +10,17 @@ import (
|
|||||||
"saml.dev/gome-assistant/internal/http"
|
"saml.dev/gome-assistant/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type State interface {
|
||||||
|
AfterSunrise(...DurationString) bool
|
||||||
|
BeforeSunrise(...DurationString) bool
|
||||||
|
AfterSunset(...DurationString) bool
|
||||||
|
BeforeSunset(...DurationString) bool
|
||||||
|
Get(entityId string) (EntityState, error)
|
||||||
|
Equals(entityId, state string) (bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
// State is used to retrieve state from Home Assistant.
|
// State is used to retrieve state from Home Assistant.
|
||||||
type State struct {
|
type StateImpl struct {
|
||||||
httpClient *http.HttpClient
|
httpClient *http.HttpClient
|
||||||
latitude float64
|
latitude float64
|
||||||
longitude float64
|
longitude float64
|
||||||
@@ -24,8 +33,8 @@ type EntityState struct {
|
|||||||
LastChanged time.Time `json:"last_changed"`
|
LastChanged time.Time `json:"last_changed"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func newState(c *http.HttpClient, homeZoneEntityId string) (*State, error) {
|
func newState(c *http.HttpClient, homeZoneEntityId string) (*StateImpl, error) {
|
||||||
state := &State{httpClient: c}
|
state := &StateImpl{httpClient: c}
|
||||||
err := state.getLatLong(c, homeZoneEntityId)
|
err := state.getLatLong(c, homeZoneEntityId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -33,7 +42,7 @@ func newState(c *http.HttpClient, homeZoneEntityId string) (*State, error) {
|
|||||||
return state, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) getLatLong(c *http.HttpClient, homeZoneEntityId string) error {
|
func (s *StateImpl) getLatLong(c *http.HttpClient, homeZoneEntityId string) error {
|
||||||
resp, err := s.Get(homeZoneEntityId)
|
resp, err := s.Get(homeZoneEntityId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("couldn't get latitude/longitude from home assistant entity '%s'. Did you type it correctly? It should be a zone like 'zone.home'", homeZoneEntityId)
|
return fmt.Errorf("couldn't get latitude/longitude from home assistant entity '%s'. Did you type it correctly? It should be a zone like 'zone.home'", homeZoneEntityId)
|
||||||
@@ -54,7 +63,7 @@ func (s *State) getLatLong(c *http.HttpClient, homeZoneEntityId string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Get(entityId string) (EntityState, error) {
|
func (s *StateImpl) Get(entityId string) (EntityState, error) {
|
||||||
resp, err := s.httpClient.GetState(entityId)
|
resp, err := s.httpClient.GetState(entityId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return EntityState{}, err
|
return EntityState{}, err
|
||||||
@@ -64,7 +73,7 @@ func (s *State) Get(entityId string) (EntityState, error) {
|
|||||||
return es, nil
|
return es, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) Equals(entityId string, expectedState string) (bool, error) {
|
func (s *StateImpl) Equals(entityId string, expectedState string) (bool, error) {
|
||||||
currentState, err := s.Get(entityId)
|
currentState, err := s.Get(entityId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
@@ -72,20 +81,20 @@ func (s *State) Equals(entityId string, expectedState string) (bool, error) {
|
|||||||
return currentState.State == expectedState, nil
|
return currentState.State == expectedState, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) BeforeSunrise(offset ...DurationString) bool {
|
func (s *StateImpl) BeforeSunrise(offset ...DurationString) bool {
|
||||||
sunrise := getSunriseSunset(s /* sunrise = */, true, carbon.Now(), offset...)
|
sunrise := getSunriseSunset(s /* sunrise = */, true, carbon.Now(), offset...)
|
||||||
return carbon.Now().Lt(sunrise)
|
return carbon.Now().Lt(sunrise)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) AfterSunrise(offset ...DurationString) bool {
|
func (s *StateImpl) AfterSunrise(offset ...DurationString) bool {
|
||||||
return !s.BeforeSunrise(offset...)
|
return !s.BeforeSunrise(offset...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) BeforeSunset(offset ...DurationString) bool {
|
func (s *StateImpl) BeforeSunset(offset ...DurationString) bool {
|
||||||
sunset := getSunriseSunset(s /* sunrise = */, false, carbon.Now(), offset...)
|
sunset := getSunriseSunset(s /* sunrise = */, false, carbon.Now(), offset...)
|
||||||
return carbon.Now().Lt(sunset)
|
return carbon.Now().Lt(sunset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *State) AfterSunset(offset ...DurationString) bool {
|
func (s *StateImpl) AfterSunset(offset ...DurationString) bool {
|
||||||
return !s.BeforeSunset(offset...)
|
return !s.BeforeSunset(offset...)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user