forked from saml-dev/gome-assistant
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinterval.go
More file actions
201 lines (170 loc) · 4.97 KB
/
interval.go
File metadata and controls
201 lines (170 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package gomeassistant
import (
"fmt"
"time"
"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
exceptionDates []time.Time
exceptionRanges []timeRange
enabledEntities []internal.EnabledDisabledInfo
disabledEntities []internal.EnabledDisabledInfo
}
func (i Interval) Hash() string {
return fmt.Sprint(i.startTime, i.endTime, i.frequency, i.callback, i.exceptionDates, i.exceptionRanges)
}
// Call
type intervalBuilder struct {
interval Interval
}
// Every
type intervalBuilderCall struct {
interval Interval
}
// Offset, ExceptionDates, 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 (ib intervalBuilder) Call(callback IntervalCallback) intervalBuilderCall {
ib.interval.callback = callback
return intervalBuilderCall(ib)
}
// Takes a DurationString ("2h", "5m", etc) to set the frequency of the interval.
func (ib intervalBuilderCall) Every(s DurationString) intervalBuilderEnd {
d := internal.ParseDuration(string(s))
ib.interval.frequency = d
return intervalBuilderEnd(ib)
}
// Takes a TimeString ("HH:MM") when this interval will start running for the day.
func (ib intervalBuilderEnd) StartingAt(s TimeString) intervalBuilderEnd {
ib.interval.startTime = s
return ib
}
// Takes a TimeString ("HH:MM") when this interval will stop running for the day.
func (ib intervalBuilderEnd) EndingAt(s TimeString) intervalBuilderEnd {
ib.interval.endTime = s
return ib
}
func (ib intervalBuilderEnd) ExceptionDates(t time.Time, tl ...time.Time) intervalBuilderEnd {
ib.interval.exceptionDates = append(tl, t)
return ib
}
func (ib intervalBuilderEnd) ExceptionRange(start, end time.Time) intervalBuilderEnd {
ib.interval.exceptionRanges = append(ib.interval.exceptionRanges, timeRange{start, end})
return ib
}
/*
Enable this interval only when the current state of {entityId} matches {state}.
If there is a network error while retrieving state, the interval runs if {runOnNetworkError} is true.
*/
func (ib intervalBuilderEnd) EnabledWhen(entityId, state string, runOnNetworkError bool) intervalBuilderEnd {
if entityId == "" {
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
}
i := internal.EnabledDisabledInfo{
Entity: entityId,
State: state,
RunOnError: runOnNetworkError,
}
ib.interval.enabledEntities = append(ib.interval.enabledEntities, i)
return ib
}
/*
Disable this interval when the current state of {entityId} matches {state}.
If there is a network error while retrieving state, the interval runs if {runOnNetworkError} is true.
*/
func (ib intervalBuilderEnd) DisabledWhen(entityId, state string, runOnNetworkError bool) intervalBuilderEnd {
if entityId == "" {
panic(fmt.Sprintf("entityId is empty in EnabledWhen entityId='%s' state='%s'", entityId, state))
}
i := internal.EnabledDisabledInfo{
Entity: entityId,
State: state,
RunOnError: runOnNetworkError,
}
ib.interval.disabledEntities = append(ib.interval.disabledEntities, i)
return ib
}
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 := checkExceptionDates(i.exceptionDates); c.fail {
return
}
if c := checkExceptionRanges(i.exceptionRanges); c.fail {
return
}
if c := checkEnabledEntity(a.state, i.enabledEntities); c.fail {
return
}
if c := checkDisabledEntity(a.state, i.disabledEntities); 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()))
}