diff --git a/app.go b/app.go index 3ee7262..90be757 100644 --- a/app.go +++ b/app.go @@ -25,10 +25,12 @@ type app struct { state *State schedules pq.PriorityQueue - entityListeners map[string][]entityListener + entityListeners map[string][]*entityListener entityListenersId int64 } +type TimeString string + /* NewApp establishes the websocket connection and returns an object you can use to register schedules and listeners. @@ -50,7 +52,7 @@ func NewApp(connString string) app { service: service, state: state, schedules: pq.New(), - entityListeners: map[string][]entityListener{}, + entityListeners: map[string][]*entityListener{}, } } @@ -85,9 +87,9 @@ func (a *app) RegisterSchedule(s schedule) { func (a *app) RegisterEntityListener(el entityListener) { for _, entity := range el.entityIds { if elList, ok := a.entityListeners[entity]; ok { - a.entityListeners[entity] = append(elList, el) + a.entityListeners[entity] = append(elList, &el) } else { - a.entityListeners[entity] = []entityListener{el} + a.entityListeners[entity] = []*entityListener{&el} } } } @@ -95,32 +97,34 @@ func (a *app) RegisterEntityListener(el entityListener) { // Sunrise take an optional string that is passed to time.ParseDuration. // Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration // for full list. -func (a *app) Sunrise(offset ...string) string { +func (a *app) Sunrise(offset ...TimeString) string { return getSunriseSunset(a, true, offset) } // Sunset take an optional string that is passed to time.ParseDuration. // Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration // for full list. -func (a *app) Sunset(offset ...string) string { +func (a *app) Sunset(offset ...TimeString) string { return getSunriseSunset(a, false, offset) } -func getSunriseSunset(a *app, sunrise bool, offset []string) string { +func getSunriseSunset(a *app, sunrise bool, offset []TimeString) string { printString := "Sunset" attrKey := "next_setting" if sunrise { printString = "Sunrise" attrKey = "next_rising" } + var t time.Duration var err error if len(offset) == 1 { - t, err = time.ParseDuration(offset[0]) + t, err = time.ParseDuration(string(offset[0])) if err != nil { log.Fatalf("Could not parse offset passed to %s: \"%s\"", printString, offset[0]) } } + // get next sunrise/sunset time from HA state, err := a.state.Get("sun.sun") if err != nil { @@ -172,20 +176,4 @@ func (a *app) Start() { go callEntityListeners(a, msg.Raw) } } - - // NOTE:should the prio queue and websocket listener both write to a channel or something? - // then select from that and spawn new goroutine to call callback? - - // TODO: loop through schedules and create heap priority queue - - // TODO: figure out looping listening to messages for - // listeners } - -const ( - FrequencyMissing time.Duration = 0 - - Daily time.Duration = time.Hour * 24 - Hourly time.Duration = time.Hour - Minutely time.Duration = time.Minute -) diff --git a/entitylistener.go b/entitylistener.go index a3b9a2b..758cbcb 100644 --- a/entitylistener.go +++ b/entitylistener.go @@ -3,6 +3,7 @@ package gomeassistant import ( "encoding/json" "errors" + "log" "time" "github.com/golang-module/carbon" @@ -16,12 +17,13 @@ type entityListener struct { toState string betweenStart string betweenEnd string + throttle time.Duration + lastRan carbon.Carbon err error } type entityListenerCallback func(*Service, EntityData) -// TODO: use this to flatten json sent from HA for trigger event type EntityData struct { TriggerEntityId string FromState string @@ -52,43 +54,12 @@ type msgState struct { Attributes map[string]any `json:"attributes"` } -type triggerMsg struct { - Id int64 `json:"id"` - Type string `json:"type"` - Event struct { - Variables struct { - Trigger struct { - EntityId string `json:"entity_id"` - FromState triggerMsgState `json:"from_state"` - ToState triggerMsgState `json:"to_state"` - } - } `json:"variables"` - } `json:"event"` -} - -type triggerMsgState struct { - State string `json:"state"` - Attributes map[string]any `json:"attributes"` - LastChanged string `json:"last_changed"` -} - -type subscribeMsg struct { - Id int64 `json:"id"` - Type string `json:"type"` - Trigger subscribeMsgTrigger `json:"trigger"` -} - -type subscribeMsgTrigger struct { - Platform string `json:"platform"` - EntityId string `json:"entity_id"` - From string `json:"from"` - To string `json:"to"` -} - /* Methods */ func EntityListenerBuilder() elBuilder1 { - return elBuilder1{entityListener{}} + return elBuilder1{entityListener{ + lastRan: carbon.Now().StartOfCentury(), + }} } type elBuilder1 struct { @@ -145,6 +116,15 @@ func (b elBuilder3) ToState(s string) elBuilder3 { return b } +func (b elBuilder3) Throttle(s TimeString) elBuilder3 { + d, err := time.ParseDuration(string(s)) + if err != nil { + log.Fatalf("Couldn't parse string duration passed to Throttle(): \"%s\" see https://pkg.go.dev/time#ParseDuration for valid time units", s) + } + b.entityListener.throttle = d + return b +} + func (b elBuilder3) Build() entityListener { return b.entityListener } @@ -197,6 +177,13 @@ func callEntityListeners(app *app, msgBytes []byte) { return } + // don't run callback if Throttle is set and that duration hasn't passed since lastRan + if l.throttle.Seconds() > 0 && // throttle is set + !l.lastRan.Eq(carbon.Now().StartOfCentury()) && // lastRan is set aka this callback has been called since starting gomeassistant + l.lastRan.DiffAbsInSeconds(carbon.Now()) < int64(l.throttle.Seconds()) { // it's been less than seconds since it last ran + return + } + entityData := EntityData{ TriggerEntityId: eid, FromState: data.OldState.State, @@ -205,6 +192,7 @@ func callEntityListeners(app *app, msgBytes []byte) { ToAttributes: data.NewState.Attributes, LastChanged: data.OldState.LastChanged, } - l.callback(app.service, entityData) + go l.callback(app.service, entityData) + l.lastRan = carbon.Now() } } diff --git a/cmd/main/testing.go b/example/main/testing.go similarity index 98% rename from cmd/main/testing.go rename to example/main/testing.go index bed30c0..13e22ed 100644 --- a/cmd/main/testing.go +++ b/example/main/testing.go @@ -19,6 +19,7 @@ func main() { EntityIds("group.office_ceiling_lights"). Call(listenerCB). OnlyAfter("23:03"). + // Throttle("5s"). Build() app.RegisterEntityListener(simpleListener) diff --git a/schedule.go b/schedule.go index 38d0e86..b726a70 100644 --- a/schedule.go +++ b/schedule.go @@ -107,8 +107,8 @@ func (sb scheduleBuilderDaily) At(s string) scheduleBuilderEnd { return scheduleBuilderEnd(sb) } -func (sb scheduleBuilderCall) Every(s string) scheduleBuilderCustom { - d, err := time.ParseDuration(s) +func (sb scheduleBuilderCall) Every(s TimeString) scheduleBuilderCustom { + d, err := time.ParseDuration(string(s)) if err != nil { log.Fatalf("couldn't parse string duration passed to Every(): \"%s\" see https://pkg.go.dev/time#ParseDuration for valid time units", s) } @@ -116,8 +116,8 @@ func (sb scheduleBuilderCall) Every(s string) scheduleBuilderCustom { return scheduleBuilderCustom(sb) } -func (sb scheduleBuilderCustom) Offset(s string) scheduleBuilderEnd { - t, err := time.ParseDuration(s) +func (sb scheduleBuilderCustom) Offset(s TimeString) scheduleBuilderEnd { + t, err := time.ParseDuration(string(s)) if err != nil { log.Fatalf("Couldn't parse string duration passed to Offset(): \"%s\" see https://pkg.go.dev/time#ParseDuration for valid time units", s) }