mirror of
https://github.com/Xevion/go-ha.git
synced 2025-12-06 05:15:15 -06:00
split daily schedule and interval
This commit is contained in:
60
app.go
60
app.go
@@ -25,6 +25,7 @@ type App struct {
|
|||||||
state *State
|
state *State
|
||||||
|
|
||||||
schedules pq.PriorityQueue
|
schedules pq.PriorityQueue
|
||||||
|
intervals pq.PriorityQueue
|
||||||
entityListeners map[string][]*EntityListener
|
entityListeners map[string][]*EntityListener
|
||||||
entityListenersId int64
|
entityListenersId int64
|
||||||
eventListeners map[string][]*EventListener
|
eventListeners map[string][]*EventListener
|
||||||
@@ -36,6 +37,11 @@ See https://pkg.go.dev/time#ParseDuration for all valid time units.
|
|||||||
*/
|
*/
|
||||||
type DurationString string
|
type DurationString string
|
||||||
|
|
||||||
|
/*
|
||||||
|
TimeString is a 24-hr format time "HH:MM" such as "07:30".
|
||||||
|
*/
|
||||||
|
type TimeString string
|
||||||
|
|
||||||
type timeRange struct {
|
type timeRange struct {
|
||||||
start time.Time
|
start time.Time
|
||||||
end time.Time
|
end time.Time
|
||||||
@@ -62,6 +68,7 @@ func NewApp(connString string) *App {
|
|||||||
service: service,
|
service: service,
|
||||||
state: state,
|
state: state,
|
||||||
schedules: pq.New(),
|
schedules: pq.New(),
|
||||||
|
intervals: pq.New(),
|
||||||
entityListeners: map[string][]*EntityListener{},
|
entityListeners: map[string][]*EntityListener{},
|
||||||
eventListeners: map[string][]*EventListener{},
|
eventListeners: map[string][]*EventListener{},
|
||||||
}
|
}
|
||||||
@@ -73,41 +80,41 @@ func (a *App) Cleanup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) RegisterSchedules(schedules ...Schedule) {
|
func (a *App) RegisterSchedules(schedules ...DailySchedule) {
|
||||||
for _, s := range schedules {
|
for _, s := range schedules {
|
||||||
// realStartTime already set for sunset/sunrise
|
// realStartTime already set for sunset/sunrise
|
||||||
if s.isSunrise || s.isSunset {
|
if s.isSunrise || s.isSunset {
|
||||||
a.schedules.Insert(s, float64(s.realStartTime.Unix()))
|
s.nextRunTime = getSunriseSunsetFromApp(a, s.isSunrise, s.sunOffset).Carbon2Time()
|
||||||
|
a.schedules.Insert(s, float64(s.nextRunTime.Unix()))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.frequency == 0 {
|
now := carbon.Now()
|
||||||
panic("A schedule must use either Daily() or Every() when built.")
|
startTime := carbon.Now().SetTimeMilli(s.hour, s.minute, 0, 0)
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
// TODO: on the day that daylight savings starts/ends,
|
|
||||||
// this results in schedules being an hour late or hour early because
|
|
||||||
// StartOfDay() occurs before the switch. This happens if you start
|
|
||||||
// gome assistant on this day, and VERIFY I think will persist until you
|
|
||||||
// start it again. Sunrise/sunset should be unaffected since those come
|
|
||||||
// from HA.
|
|
||||||
//
|
|
||||||
// IDEA: splitting schedule into Interval and DailySchedule could address this on schedule
|
|
||||||
// side, by using SetTime instead of Add(time.Duration).
|
|
||||||
startTime := carbon.Now().StartOfDay().Carbon2Time()
|
|
||||||
// apply offset if set
|
|
||||||
if s.offset.Minutes() > 0 {
|
|
||||||
startTime = startTime.Add(s.offset)
|
|
||||||
}
|
|
||||||
|
|
||||||
// advance first scheduled time by frequency until it is in the future
|
// advance first scheduled time by frequency until it is in the future
|
||||||
for startTime.Before(now) {
|
if startTime.Lt(now) {
|
||||||
startTime = startTime.Add(s.frequency)
|
startTime = startTime.AddDay()
|
||||||
}
|
}
|
||||||
|
|
||||||
s.realStartTime = startTime
|
s.nextRunTime = startTime.Carbon2Time()
|
||||||
a.schedules.Insert(s, float64(startTime.Unix()))
|
a.schedules.Insert(s, float64(startTime.Carbon2Time().Unix()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) RegisterIntervals(intervals ...Interval) {
|
||||||
|
for _, i := range intervals {
|
||||||
|
if i.frequency == 0 {
|
||||||
|
panic("A schedule must use either set frequency via Every().")
|
||||||
|
}
|
||||||
|
|
||||||
|
i.nextRunTime = internal.ParseTime(string(i.startTime)).Carbon2Time()
|
||||||
|
now := time.Now()
|
||||||
|
for i.nextRunTime.Before(now) {
|
||||||
|
i.nextRunTime = i.nextRunTime.Add(i.frequency)
|
||||||
|
}
|
||||||
|
fmt.Println(i)
|
||||||
|
a.intervals.Insert(i, float64(i.nextRunTime.Unix()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +188,9 @@ func (a *App) Start() {
|
|||||||
log.Default().Println("Starting", a.schedules.Len(), "schedules")
|
log.Default().Println("Starting", a.schedules.Len(), "schedules")
|
||||||
log.Default().Println("Starting", len(a.entityListeners), "entity listeners")
|
log.Default().Println("Starting", len(a.entityListeners), "entity listeners")
|
||||||
log.Default().Println("Starting", len(a.eventListeners), "event listeners")
|
log.Default().Println("Starting", len(a.eventListeners), "event listeners")
|
||||||
// schedules
|
|
||||||
go runSchedules(a)
|
go runSchedules(a)
|
||||||
|
go runIntervals(a)
|
||||||
|
|
||||||
// subscribe to state_changed events
|
// subscribe to state_changed events
|
||||||
id := internal.GetId()
|
id := internal.GetId()
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ type EventListener struct {
|
|||||||
exceptionRanges []timeRange
|
exceptionRanges []timeRange
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add state object as second arg
|
|
||||||
type EventListenerCallback func(*Service, *State, EventData)
|
type EventListenerCallback func(*Service, *State, EventData)
|
||||||
|
|
||||||
type EventData struct {
|
type EventData struct {
|
||||||
|
|||||||
@@ -19,17 +19,15 @@ func main() {
|
|||||||
Build()
|
Build()
|
||||||
|
|
||||||
_11pmSched := ga.
|
_11pmSched := ga.
|
||||||
NewSchedule().
|
NewDailySchedule().
|
||||||
Call(lightsOut).
|
Call(lightsOut).
|
||||||
Daily().
|
|
||||||
At("23:00").
|
At("23:00").
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
_30minsBeforeSunrise := ga.
|
_30minsBeforeSunrise := ga.
|
||||||
NewSchedule().
|
NewDailySchedule().
|
||||||
Call(sunriseSched).
|
Call(sunriseSched).
|
||||||
Daily().
|
Sunrise("-30m").
|
||||||
Sunrise(app, "-30m").
|
|
||||||
Build()
|
Build()
|
||||||
|
|
||||||
zwaveEventListener := ga.
|
zwaveEventListener := ga.
|
||||||
@@ -39,8 +37,7 @@ func main() {
|
|||||||
Build()
|
Build()
|
||||||
|
|
||||||
app.RegisterEntityListeners(pantryDoor)
|
app.RegisterEntityListeners(pantryDoor)
|
||||||
app.RegisterSchedules(_11pmSched)
|
app.RegisterSchedules(_11pmSched, _30minsBeforeSunrise)
|
||||||
app.RegisterSchedules(_30minsBeforeSunrise)
|
|
||||||
app.RegisterEventListeners(zwaveEventListener)
|
app.RegisterEventListeners(zwaveEventListener)
|
||||||
|
|
||||||
app.Start()
|
app.Start()
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"reflect"
|
||||||
|
"runtime"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-module/carbon"
|
"github.com/golang-module/carbon"
|
||||||
@@ -15,12 +17,13 @@ func GetId() int64 {
|
|||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parses a HH:MM string.
|
||||||
func ParseTime(s string) carbon.Carbon {
|
func ParseTime(s string) carbon.Carbon {
|
||||||
t, err := time.Parse("15:04", s)
|
t, err := time.Parse("15:04", s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to parse time string \"%s\"; format must be HH:MM.", s)
|
log.Fatalf("Failed to parse time string \"%s\"; format must be HH:MM.", s)
|
||||||
}
|
}
|
||||||
return carbon.Now().StartOfDay().SetHour(t.Hour()).SetMinute(t.Minute())
|
return carbon.Now().SetTimeMilli(t.Hour(), t.Minute(), 0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseDuration(s string) time.Duration {
|
func ParseDuration(s string) time.Duration {
|
||||||
@@ -30,3 +33,7 @@ func ParseDuration(s string) time.Duration {
|
|||||||
}
|
}
|
||||||
return d
|
return d
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetFunctionName(i interface{}) string {
|
||||||
|
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
||||||
|
}
|
||||||
|
|||||||
158
interval.go
Normal file
158
interval.go
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
package gomeassistant
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/saml-dev/gome-assistant/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IntervalCallback func(*Service, *State)
|
||||||
|
|
||||||
|
type Interval struct {
|
||||||
|
frequency time.Duration
|
||||||
|
callback IntervalCallback
|
||||||
|
startTime TimeString
|
||||||
|
endTime TimeString
|
||||||
|
nextRunTime time.Time
|
||||||
|
|
||||||
|
exceptionDays []time.Time
|
||||||
|
exceptionRanges []timeRange
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Interval) Hash() string {
|
||||||
|
return fmt.Sprint(i.startTime, i.endTime, i.frequency, i.callback, i.exceptionDays, i.exceptionRanges)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call
|
||||||
|
type intervalBuilder struct {
|
||||||
|
interval Interval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every
|
||||||
|
type intervalBuilderCall struct {
|
||||||
|
interval Interval
|
||||||
|
}
|
||||||
|
|
||||||
|
// Offset, ExceptionDay, ExceptionRange
|
||||||
|
type intervalBuilderEnd struct {
|
||||||
|
interval Interval
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInterval() intervalBuilder {
|
||||||
|
return intervalBuilder{
|
||||||
|
Interval{
|
||||||
|
frequency: 0,
|
||||||
|
startTime: "00:00",
|
||||||
|
endTime: "00:00",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Interval) String() string {
|
||||||
|
return fmt.Sprintf("Interval{ call %q every %s%s%s }",
|
||||||
|
internal.GetFunctionName(i.callback),
|
||||||
|
i.frequency,
|
||||||
|
formatStartOrEndString(i.startTime /* isStart = */, true),
|
||||||
|
formatStartOrEndString(i.endTime /* isStart = */, false),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatStartOrEndString(s TimeString, isStart bool) string {
|
||||||
|
if s == "00:00" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if isStart {
|
||||||
|
return fmt.Sprintf(" starting at %s", s)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf(" ending at %s", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb intervalBuilder) Call(callback IntervalCallback) intervalBuilderCall {
|
||||||
|
sb.interval.callback = callback
|
||||||
|
return intervalBuilderCall(sb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a DurationString ("2h", "5m", etc) to set the frequency of the interval.
|
||||||
|
func (sb intervalBuilderCall) Every(s DurationString) intervalBuilderEnd {
|
||||||
|
d := internal.ParseDuration(string(s))
|
||||||
|
sb.interval.frequency = d
|
||||||
|
return intervalBuilderEnd(sb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a TimeString ("HH:MM") when this interval will start running for the day.
|
||||||
|
func (sb intervalBuilderEnd) StartingAt(s TimeString) intervalBuilderEnd {
|
||||||
|
sb.interval.startTime = s
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
// Takes a TimeString ("HH:MM") when this interval will stop running for the day.
|
||||||
|
func (sb intervalBuilderEnd) EndingAt(s TimeString) intervalBuilderEnd {
|
||||||
|
sb.interval.endTime = s
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb intervalBuilderEnd) ExceptionDay(t time.Time) intervalBuilderEnd {
|
||||||
|
sb.interval.exceptionDays = append(sb.interval.exceptionDays, t)
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb intervalBuilderEnd) ExceptionRange(start, end time.Time) intervalBuilderEnd {
|
||||||
|
sb.interval.exceptionRanges = append(sb.interval.exceptionRanges, timeRange{start, end})
|
||||||
|
return sb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sb intervalBuilderEnd) Build() Interval {
|
||||||
|
return sb.interval
|
||||||
|
}
|
||||||
|
|
||||||
|
// app.Start() functions
|
||||||
|
func runIntervals(a *App) {
|
||||||
|
if a.intervals.Len() == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
i := popInterval(a)
|
||||||
|
|
||||||
|
// run callback for all intervals before now in case they overlap
|
||||||
|
for i.nextRunTime.Before(time.Now()) {
|
||||||
|
i.maybeRunCallback(a)
|
||||||
|
requeueInterval(a, i)
|
||||||
|
|
||||||
|
i = popInterval(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
time.Sleep(time.Until(i.nextRunTime))
|
||||||
|
i.maybeRunCallback(a)
|
||||||
|
requeueInterval(a, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Interval) maybeRunCallback(a *App) {
|
||||||
|
if c := checkStartEndTime(i.startTime /* isStart = */, true); c.fail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := checkStartEndTime(i.endTime /* isStart = */, false); c.fail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := checkExceptionDays(i.exceptionDays); c.fail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c := checkExceptionRanges(i.exceptionRanges); c.fail {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go i.callback(a.service, a.state)
|
||||||
|
}
|
||||||
|
|
||||||
|
func popInterval(a *App) Interval {
|
||||||
|
i, _ := a.intervals.Pop()
|
||||||
|
return i.(Interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
func requeueInterval(a *App, i Interval) {
|
||||||
|
i.nextRunTime = i.nextRunTime.Add(i.frequency)
|
||||||
|
|
||||||
|
a.intervals.Insert(i, float64(i.nextRunTime.Unix()))
|
||||||
|
}
|
||||||
22
listeners.go
22
listeners.go
@@ -4,6 +4,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-module/carbon"
|
"github.com/golang-module/carbon"
|
||||||
|
"github.com/saml-dev/gome-assistant/internal"
|
||||||
i "github.com/saml-dev/gome-assistant/internal"
|
i "github.com/saml-dev/gome-assistant/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -85,3 +86,24 @@ func checkExceptionRanges(eList []timeRange) conditionCheck {
|
|||||||
}
|
}
|
||||||
return cc
|
return cc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func checkStartEndTime(s TimeString, isStart bool) conditionCheck {
|
||||||
|
cc := conditionCheck{fail: false}
|
||||||
|
// pass immediately if default
|
||||||
|
if s == "00:00" {
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
parsedTime := internal.ParseTime(string(s)).Carbon2Time()
|
||||||
|
if isStart {
|
||||||
|
if parsedTime.After(now) {
|
||||||
|
cc.fail = true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if parsedTime.Before(now) {
|
||||||
|
cc.fail = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|||||||
148
schedule.go
148
schedule.go
@@ -2,8 +2,6 @@ package gomeassistant
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"runtime"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-module/carbon"
|
"github.com/golang-module/carbon"
|
||||||
@@ -12,11 +10,14 @@ import (
|
|||||||
|
|
||||||
type ScheduleCallback func(*Service, *State)
|
type ScheduleCallback func(*Service, *State)
|
||||||
|
|
||||||
type Schedule struct {
|
type DailySchedule struct {
|
||||||
frequency time.Duration
|
// 0-23
|
||||||
|
hour int
|
||||||
|
// 0-59
|
||||||
|
minute int
|
||||||
|
|
||||||
callback ScheduleCallback
|
callback ScheduleCallback
|
||||||
offset time.Duration
|
nextRunTime time.Time
|
||||||
realStartTime time.Time
|
|
||||||
|
|
||||||
isSunrise bool
|
isSunrise bool
|
||||||
isSunset bool
|
isSunset bool
|
||||||
@@ -26,59 +27,41 @@ type Schedule struct {
|
|||||||
exceptionRanges []timeRange
|
exceptionRanges []timeRange
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Schedule) Hash() string {
|
func (s DailySchedule) Hash() string {
|
||||||
return fmt.Sprint(s.offset, s.frequency, s.callback)
|
return fmt.Sprint(s.hour, s.minute, s.callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
type scheduleBuilder struct {
|
type scheduleBuilder struct {
|
||||||
schedule Schedule
|
schedule DailySchedule
|
||||||
}
|
}
|
||||||
|
|
||||||
type scheduleBuilderCall struct {
|
type scheduleBuilderCall struct {
|
||||||
schedule Schedule
|
schedule DailySchedule
|
||||||
}
|
|
||||||
|
|
||||||
type scheduleBuilderDaily struct {
|
|
||||||
schedule Schedule
|
|
||||||
}
|
|
||||||
|
|
||||||
type scheduleBuilderCustom struct {
|
|
||||||
schedule Schedule
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type scheduleBuilderEnd struct {
|
type scheduleBuilderEnd struct {
|
||||||
schedule Schedule
|
schedule DailySchedule
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSchedule() scheduleBuilder {
|
func NewDailySchedule() scheduleBuilder {
|
||||||
return scheduleBuilder{
|
return scheduleBuilder{
|
||||||
Schedule{
|
DailySchedule{
|
||||||
frequency: 0,
|
hour: 0,
|
||||||
offset: 0,
|
minute: 0,
|
||||||
|
sunOffset: "0s",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Schedule) String() string {
|
func (s DailySchedule) String() string {
|
||||||
return fmt.Sprintf("Schedule{ call %q %s %s }",
|
return fmt.Sprintf("Schedule{ call %q daily at %s }",
|
||||||
getFunctionName(s.callback),
|
internal.GetFunctionName(s.callback),
|
||||||
frequencyToString(s.frequency),
|
stringHourMinute(s.hour, s.minute),
|
||||||
offsetToString(s),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func offsetToString(s Schedule) string {
|
func stringHourMinute(hour, minute int) string {
|
||||||
if s.frequency.Hours() == 24 {
|
return fmt.Sprintf("%02d:%02d", hour, minute)
|
||||||
return fmt.Sprintf("%02d:%02d", int(s.offset.Hours()), int(s.offset.Minutes())%60)
|
|
||||||
}
|
|
||||||
return s.offset.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func frequencyToString(d time.Duration) string {
|
|
||||||
if d.Hours() == 24 {
|
|
||||||
return "daily at"
|
|
||||||
}
|
|
||||||
return "every " + d.String() + " with offset"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb scheduleBuilder) Call(callback ScheduleCallback) scheduleBuilderCall {
|
func (sb scheduleBuilder) Call(callback ScheduleCallback) scheduleBuilderCall {
|
||||||
@@ -86,62 +69,30 @@ func (sb scheduleBuilder) Call(callback ScheduleCallback) scheduleBuilderCall {
|
|||||||
return scheduleBuilderCall(sb)
|
return scheduleBuilderCall(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb scheduleBuilderCall) Daily() scheduleBuilderDaily {
|
// At takes a string in 24hr format time like "15:30".
|
||||||
sb.schedule.frequency = time.Hour * 24
|
func (sb scheduleBuilderCall) At(s string) scheduleBuilderEnd {
|
||||||
return scheduleBuilderDaily(sb)
|
|
||||||
}
|
|
||||||
|
|
||||||
// At takes a string 24hr format time like "15:30".
|
|
||||||
func (sb scheduleBuilderDaily) At(s string) scheduleBuilderEnd {
|
|
||||||
t := internal.ParseTime(s)
|
t := internal.ParseTime(s)
|
||||||
sb.schedule.offset = time.Duration(t.Hour())*time.Hour + time.Duration(t.Minute())*time.Minute
|
sb.schedule.hour = t.Hour()
|
||||||
|
sb.schedule.minute = t.Minute()
|
||||||
return scheduleBuilderEnd(sb)
|
return scheduleBuilderEnd(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sunrise takes an app pointer and an optional duration string that is passed to time.ParseDuration.
|
// Sunrise takes an optional duration string that is passed to time.ParseDuration.
|
||||||
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
|
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
|
||||||
// for full list.
|
// for full list.
|
||||||
func (sb scheduleBuilderDaily) Sunrise(a *App, offset ...DurationString) scheduleBuilderEnd {
|
func (sb scheduleBuilderCall) Sunrise(offset ...DurationString) scheduleBuilderEnd {
|
||||||
sb.schedule.realStartTime = getSunriseSunsetFromApp(a, true, offset...).Carbon2Time()
|
|
||||||
sb.schedule.isSunrise = true
|
sb.schedule.isSunrise = true
|
||||||
return scheduleBuilderEnd(sb)
|
return scheduleBuilderEnd(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sunset takes an app pointer and an optional duration string that is passed to time.ParseDuration.
|
// Sunset takes an optional duration string that is passed to time.ParseDuration.
|
||||||
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
|
// Examples include "-1.5h", "30m", etc. See https://pkg.go.dev/time#ParseDuration
|
||||||
// for full list.
|
// for full list.
|
||||||
func (sb scheduleBuilderDaily) Sunset(a *App, offset ...DurationString) scheduleBuilderEnd {
|
func (sb scheduleBuilderCall) Sunset(offset ...DurationString) scheduleBuilderEnd {
|
||||||
sb.schedule.realStartTime = getSunriseSunsetFromApp(a, false, offset...).Carbon2Time()
|
|
||||||
sb.schedule.isSunset = true
|
sb.schedule.isSunset = true
|
||||||
return scheduleBuilderEnd(sb)
|
return scheduleBuilderEnd(sb)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb scheduleBuilderCall) Every(s DurationString) scheduleBuilderCustom {
|
|
||||||
d := internal.ParseDuration(string(s))
|
|
||||||
sb.schedule.frequency = d
|
|
||||||
return scheduleBuilderCustom(sb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb scheduleBuilderCustom) Offset(s DurationString) scheduleBuilderEnd {
|
|
||||||
d := internal.ParseDuration(string(s))
|
|
||||||
sb.schedule.offset = d
|
|
||||||
return scheduleBuilderEnd(sb)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb scheduleBuilderCustom) ExceptionDay(t time.Time) scheduleBuilderCustom {
|
|
||||||
sb.schedule.exceptionDays = append(sb.schedule.exceptionDays, t)
|
|
||||||
return sb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb scheduleBuilderCustom) ExceptionRange(start, end time.Time) scheduleBuilderCustom {
|
|
||||||
sb.schedule.exceptionRanges = append(sb.schedule.exceptionRanges, timeRange{start, end})
|
|
||||||
return sb
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb scheduleBuilderCustom) Build() Schedule {
|
|
||||||
return sb.schedule
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sb scheduleBuilderEnd) ExceptionDay(t time.Time) scheduleBuilderEnd {
|
func (sb scheduleBuilderEnd) ExceptionDay(t time.Time) scheduleBuilderEnd {
|
||||||
sb.schedule.exceptionDays = append(sb.schedule.exceptionDays, t)
|
sb.schedule.exceptionDays = append(sb.schedule.exceptionDays, t)
|
||||||
return sb
|
return sb
|
||||||
@@ -152,14 +103,10 @@ func (sb scheduleBuilderEnd) ExceptionRange(start, end time.Time) scheduleBuilde
|
|||||||
return sb
|
return sb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sb scheduleBuilderEnd) Build() Schedule {
|
func (sb scheduleBuilderEnd) Build() DailySchedule {
|
||||||
return sb.schedule
|
return sb.schedule
|
||||||
}
|
}
|
||||||
|
|
||||||
func getFunctionName(i interface{}) string {
|
|
||||||
return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()
|
|
||||||
}
|
|
||||||
|
|
||||||
// app.Start() functions
|
// app.Start() functions
|
||||||
func runSchedules(a *App) {
|
func runSchedules(a *App) {
|
||||||
if a.schedules.Len() == 0 {
|
if a.schedules.Len() == 0 {
|
||||||
@@ -170,21 +117,21 @@ func runSchedules(a *App) {
|
|||||||
sched := popSchedule(a)
|
sched := popSchedule(a)
|
||||||
|
|
||||||
// run callback for all schedules before now in case they overlap
|
// run callback for all schedules before now in case they overlap
|
||||||
for sched.realStartTime.Before(time.Now()) {
|
for sched.nextRunTime.Before(time.Now()) {
|
||||||
maybeRunCallback(a, sched)
|
sched.maybeRunCallback(a)
|
||||||
requeueSchedule(a, sched)
|
requeueSchedule(a, sched)
|
||||||
|
|
||||||
sched = popSchedule(a)
|
sched = popSchedule(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Next schedule:", sched.realStartTime)
|
fmt.Println("Next schedule:", sched.nextRunTime)
|
||||||
time.Sleep(time.Until(sched.realStartTime))
|
time.Sleep(time.Until(sched.nextRunTime))
|
||||||
maybeRunCallback(a, sched)
|
sched.maybeRunCallback(a)
|
||||||
requeueSchedule(a, sched)
|
requeueSchedule(a, sched)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func maybeRunCallback(a *App, s Schedule) {
|
func (s DailySchedule) maybeRunCallback(a *App) {
|
||||||
if c := checkExceptionDays(s.exceptionDays); c.fail {
|
if c := checkExceptionDays(s.exceptionDays); c.fail {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -194,31 +141,32 @@ func maybeRunCallback(a *App, s Schedule) {
|
|||||||
go s.callback(a.service, a.state)
|
go s.callback(a.service, a.state)
|
||||||
}
|
}
|
||||||
|
|
||||||
func popSchedule(a *App) Schedule {
|
func popSchedule(a *App) DailySchedule {
|
||||||
_sched, _ := a.schedules.Pop()
|
_sched, _ := a.schedules.Pop()
|
||||||
return _sched.(Schedule)
|
return _sched.(DailySchedule)
|
||||||
}
|
}
|
||||||
|
|
||||||
func requeueSchedule(a *App, s Schedule) {
|
func requeueSchedule(a *App, s DailySchedule) {
|
||||||
if s.isSunrise || s.isSunset {
|
if s.isSunrise || s.isSunset {
|
||||||
var nextSunTime carbon.Carbon
|
var nextSunTime carbon.Carbon
|
||||||
if s.sunOffset != "" {
|
// "0s" is default value
|
||||||
|
if s.sunOffset != "0s" {
|
||||||
nextSunTime = getSunriseSunsetFromApp(a, s.isSunrise, s.sunOffset)
|
nextSunTime = getSunriseSunsetFromApp(a, s.isSunrise, s.sunOffset)
|
||||||
} else {
|
} else {
|
||||||
nextSunTime = getSunriseSunsetFromApp(a, s.isSunrise)
|
nextSunTime = getSunriseSunsetFromApp(a, s.isSunrise)
|
||||||
}
|
}
|
||||||
|
|
||||||
// this is true when there is a negative offset, so schedule runs before sunset/sunrise and
|
// 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
|
// HA still shows today's sunset as next sunset. Just add 1 day as a default handler
|
||||||
// since we can't get tomorrow's sunset from HA at this point.
|
// since we can't get tomorrow's sunset from HA at this point.
|
||||||
if nextSunTime.IsToday() {
|
if nextSunTime.IsToday() {
|
||||||
nextSunTime = nextSunTime.AddHours(24)
|
nextSunTime = nextSunTime.AddDay()
|
||||||
}
|
}
|
||||||
|
|
||||||
s.realStartTime = nextSunTime.Carbon2Time()
|
s.nextRunTime = nextSunTime.Carbon2Time()
|
||||||
} else {
|
} else {
|
||||||
s.realStartTime = s.realStartTime.Add(s.frequency)
|
s.nextRunTime = carbon.Time2Carbon(s.nextRunTime).AddDay().Carbon2Time()
|
||||||
}
|
}
|
||||||
|
|
||||||
a.schedules.Insert(s, float64(s.realStartTime.Unix()))
|
a.schedules.Insert(s, float64(s.nextRunTime.Unix()))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user