diff --git a/app.go b/app.go index dadce47..eb14bce 100644 --- a/app.go +++ b/app.go @@ -2,7 +2,6 @@ package gomeassistant import ( "context" - "fmt" "log" "os" "time" @@ -30,7 +29,11 @@ type app struct { eventListeners map[string][]*EventListener } -type TimeString string +/* +DurationString represents a duration, such as "2s" or "24h". +See https://pkg.go.dev/time#ParseDuration for all valid time units. +*/ +type DurationString string /* NewApp establishes the websocket connection and returns an object @@ -112,7 +115,7 @@ func (a *app) RegisterEventListener(evl EventListener) { } } -func getSunriseSunset(a *app, sunrise bool, offset []TimeString) carbon.Carbon { +func getSunriseSunset(a *app, sunrise bool, offset []DurationString) carbon.Carbon { printString := "Sunset" attrKey := "next_setting" if sunrise { @@ -145,10 +148,6 @@ func getSunriseSunset(a *app, sunrise bool, offset []TimeString) carbon.Carbon { return nextSetOrRise } -func carbon2TimeString(c carbon.Carbon) string { - return fmt.Sprintf("%02d:%02d", c.Hour(), c.Minute()) -} - func (a *app) Start() { // schedules go runSchedules(a) diff --git a/entitylistener.go b/entitylistener.go index ec41c00..93eea1d 100644 --- a/entitylistener.go +++ b/entitylistener.go @@ -17,6 +17,8 @@ type EntityListener struct { betweenEnd string throttle time.Duration lastRan carbon.Carbon + delay time.Duration + delayTimer *time.Timer } type EntityListenerCallback func(*Service, EntityData) @@ -111,7 +113,20 @@ func (b elBuilder3) ToState(s string) elBuilder3 { return b } -func (b elBuilder3) Throttle(s TimeString) elBuilder3 { +func (b elBuilder3) Duration(s DurationString) elBuilder3 { + // TODO: test this, should rename duration? not sure if Delay implies that state change cancels the callback + // if change name to Duration then enforce being used with ToState, should FromState be allowed? + // if FromState set to 3, then state changes to 2 and changes again to 1 halfway through delay, should the + // delay reset? + d, err := time.ParseDuration(string(s)) + if err != nil { + log.Fatalf("Couldn't parse string duration passed to For(): \"%s\" see https://pkg.go.dev/time#ParseDuration for valid time units", s) + } + b.entityListener.delay = d + return b +} + +func (b elBuilder3) Throttle(s DurationString) 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) @@ -145,6 +160,9 @@ func callEntityListeners(app *app, msgBytes []byte) { continue } if c := checkStatesMatch(l.toState, data.NewState.State); c.fail { + if l.delayTimer != nil { + l.delayTimer.Stop() + } continue } if c := checkThrottle(l.throttle, l.lastRan); c.fail { @@ -159,6 +177,16 @@ func callEntityListeners(app *app, msgBytes []byte) { ToAttributes: data.NewState.Attributes, LastChanged: data.OldState.LastChanged, } + + if l.delay != 0 { + l.delayTimer = time.AfterFunc(l.delay, func() { + go l.callback(app.service, entityData) + l.lastRan = carbon.Now() + }) + return + } + + // run now if no delay set go l.callback(app.service, entityData) l.lastRan = carbon.Now() } diff --git a/eventListener.go b/eventListener.go index de2baee..915c515 100644 --- a/eventListener.go +++ b/eventListener.go @@ -71,7 +71,7 @@ func (b eventListenerBuilder3) OnlyBefore(end string) eventListenerBuilder3 { return b } -func (b eventListenerBuilder3) Throttle(s TimeString) eventListenerBuilder3 { +func (b eventListenerBuilder3) Throttle(s DurationString) eventListenerBuilder3 { 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) diff --git a/schedule.go b/schedule.go index c375cdf..45943d8 100644 --- a/schedule.go +++ b/schedule.go @@ -20,7 +20,7 @@ type Schedule struct { isSunrise bool isSunset bool - sunOffset TimeString + sunOffset DurationString } func (s Schedule) Hash() string { @@ -98,7 +98,7 @@ func (sb scheduleBuilderDaily) At(s string) scheduleBuilderEnd { // Sunrise takes an app pointer and an optional duration string that is passed to time.ParseDuration. // Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration // for full list. -func (sb scheduleBuilderDaily) Sunrise(a *app, offset ...TimeString) scheduleBuilderEnd { +func (sb scheduleBuilderDaily) Sunrise(a *app, offset ...DurationString) scheduleBuilderEnd { sb.schedule.realStartTime = getSunriseSunset(a, true, offset).Carbon2Time() sb.schedule.isSunrise = true return scheduleBuilderEnd(sb) @@ -107,13 +107,13 @@ func (sb scheduleBuilderDaily) Sunrise(a *app, offset ...TimeString) scheduleBui // Sunset takes an app pointer and an optional duration string that is passed to time.ParseDuration. // Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration // for full list. -func (sb scheduleBuilderDaily) Sunset(a *app, offset ...TimeString) scheduleBuilderEnd { +func (sb scheduleBuilderDaily) Sunset(a *app, offset ...DurationString) scheduleBuilderEnd { sb.schedule.realStartTime = getSunriseSunset(a, false, offset).Carbon2Time() sb.schedule.isSunset = true return scheduleBuilderEnd(sb) } -func (sb scheduleBuilderCall) Every(s TimeString) scheduleBuilderCustom { +func (sb scheduleBuilderCall) Every(s DurationString) 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) @@ -122,7 +122,7 @@ func (sb scheduleBuilderCall) Every(s TimeString) scheduleBuilderCustom { return scheduleBuilderCustom(sb) } -func (sb scheduleBuilderCustom) Offset(s TimeString) scheduleBuilderEnd { +func (sb scheduleBuilderCustom) Offset(s DurationString) 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) @@ -174,7 +174,7 @@ func popSchedule(a *app) Schedule { func requeueSchedule(a *app, s Schedule) { if s.isSunrise || s.isSunset { - nextSunTime := getSunriseSunset(a, s.isSunrise, []TimeString{s.sunOffset}) + nextSunTime := getSunriseSunset(a, s.isSunrise, []DurationString{s.sunOffset}) // this is true when there is a negative offset, so schedule runs before sunset/sunrise and // HA still shows today's sunset as next sunset. Just add 24h as a default handler