huebot 1.2.0 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -6
- data/lib/huebot/bot.rb +43 -25
- data/lib/huebot/cli/helpers.rb +4 -2
- data/lib/huebot/cli/runner.rb +14 -0
- data/lib/huebot/compiler/api_v1.rb +154 -21
- data/lib/huebot/compiler/compiler.rb +1 -1
- data/lib/huebot/program.rb +10 -7
- data/lib/huebot/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2fd718fb625c5f417b31619579459bdfcaffac98276d9981882df7d9b1e192b9
|
4
|
+
data.tar.gz: 213fb99651e6826283d4c0be21c26e9442f146ea2df1734a2acb5c53f0e6eafa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: caa7f46fb1d7ac5954f7b1e8f8b48c81b69c3cea59c7ce0ad246eb6ecafda318bd161eec1aff2a41e7e8f6a84af4d2a18a555f56dab808e97b39ff3396b91d9c
|
7
|
+
data.tar.gz: 2221b64eb67ab257232358e52657446cb3b8c95d7f102220954ee71e9a85bc1447ec1566712b651bd1a0d5c4db8be2eff82097551302186cd092688ad0df5548
|
data/README.md
CHANGED
@@ -34,13 +34,15 @@ serial:
|
|
34
34
|
state:
|
35
35
|
bri: 150
|
36
36
|
time: 10 # 10 second transition
|
37
|
-
pause:
|
37
|
+
pause:
|
38
|
+
after: 2 # 2 second pause before the next step
|
38
39
|
|
39
40
|
- transition:
|
40
41
|
state:
|
41
42
|
bri: 254
|
42
43
|
time: 10 # 10 second transition
|
43
|
-
pause:
|
44
|
+
pause:
|
45
|
+
after: 2 # 2 second pause before the next step
|
44
46
|
|
45
47
|
- transition:
|
46
48
|
state:
|
@@ -78,12 +80,14 @@ serial:
|
|
78
80
|
state:
|
79
81
|
bri: 254
|
80
82
|
time: 10 # transition over 10 seconds
|
81
|
-
pause:
|
83
|
+
pause:
|
84
|
+
after: 5 # pause an extra 5 sec after the transition
|
82
85
|
- transition:
|
83
86
|
state:
|
84
87
|
bri: 25
|
85
88
|
time: 10
|
86
|
-
pause:
|
89
|
+
pause:
|
90
|
+
after: 5
|
87
91
|
|
88
92
|
# Parallel branch 2: Fade inputs #2 and #4 down and up
|
89
93
|
- serial:
|
@@ -96,12 +100,14 @@ serial:
|
|
96
100
|
state:
|
97
101
|
bri: 25
|
98
102
|
time: 10
|
99
|
-
pause:
|
103
|
+
pause:
|
104
|
+
after: 5
|
100
105
|
- transition:
|
101
106
|
state:
|
102
107
|
bri: 254
|
103
108
|
time: 10
|
104
|
-
pause:
|
109
|
+
pause:
|
110
|
+
after: 5
|
105
111
|
```
|
106
112
|
|
107
113
|
[See the Wiki](https://github.com/jhollinger/huebot/wiki) for more documentation and examples.
|
data/lib/huebot/bot.rb
CHANGED
@@ -18,47 +18,49 @@ module Huebot
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def exec(node)
|
21
|
-
|
22
|
-
case i
|
21
|
+
case node.instruction
|
23
22
|
when Program::AST::Transition
|
24
|
-
transition
|
23
|
+
transition node.instruction
|
25
24
|
when Program::AST::SerialControl
|
26
|
-
serial node.children,
|
25
|
+
serial node.children, node.instruction
|
27
26
|
when Program::AST::ParallelControl
|
28
|
-
parallel node.children,
|
27
|
+
parallel node.children, node.instruction
|
29
28
|
else
|
30
|
-
raise Error, "Unexpected instruction '#{
|
29
|
+
raise Error, "Unexpected instruction '#{node.instruction.class.name}'"
|
31
30
|
end
|
32
31
|
end
|
33
32
|
|
34
|
-
def transition(
|
35
|
-
time = (state["transitiontime"] || 4).to_f / 10
|
36
|
-
devices = map_devices
|
33
|
+
def transition(i)
|
34
|
+
time = (i.state["transitiontime"] || 4).to_f / 10
|
35
|
+
devices = map_devices i.devices
|
37
36
|
@logger.log :transition, {devices: devices.map(&:name)}
|
38
37
|
|
38
|
+
wait i.pause.pre if i.pause&.pre
|
39
39
|
devices.map { |device|
|
40
40
|
Thread.new {
|
41
41
|
# TODO error handling
|
42
|
-
_res = device.set_state state
|
43
|
-
@logger.log :set_state, {device: device.name, state: state, result: nil}
|
44
|
-
wait time
|
42
|
+
_res = device.set_state i.state
|
43
|
+
@logger.log :set_state, {device: device.name, state: i.state, result: nil}
|
44
|
+
wait Program::AST::Num.new(time) if i.wait
|
45
45
|
}
|
46
46
|
}.map(&:join)
|
47
|
-
wait
|
47
|
+
wait i.pause.post if i.pause&.post
|
48
48
|
end
|
49
49
|
|
50
|
-
def serial(nodes,
|
51
|
-
|
50
|
+
def serial(nodes, i)
|
51
|
+
wait i.pause.pre if i.pause&.pre
|
52
|
+
control_loop(i.loop) { |loop_type|
|
52
53
|
@logger.log :serial, {loop: loop_type}
|
53
54
|
nodes.each { |node|
|
54
55
|
exec node
|
55
56
|
}
|
56
57
|
}
|
57
|
-
wait
|
58
|
+
wait i.pause.post if i.pause&.post
|
58
59
|
end
|
59
60
|
|
60
|
-
def parallel(nodes,
|
61
|
-
|
61
|
+
def parallel(nodes, i)
|
62
|
+
wait i.pause.pre if i.pause&.pre
|
63
|
+
control_loop(i.loop) { |loop_type|
|
62
64
|
@logger.log :parallel, {loop: loop_type}
|
63
65
|
nodes.map { |node|
|
64
66
|
Thread.new {
|
@@ -67,33 +69,37 @@ module Huebot
|
|
67
69
|
}
|
68
70
|
}.map(&:join)
|
69
71
|
}
|
70
|
-
wait
|
72
|
+
wait i.pause.post if i.pause&.post
|
71
73
|
end
|
72
74
|
|
73
75
|
def control_loop(lp)
|
74
76
|
case lp
|
75
77
|
when Program::AST::InfiniteLoop
|
76
78
|
loop {
|
79
|
+
wait lp.pause.pre if lp.pause&.pre
|
77
80
|
yield :infinite
|
78
|
-
wait lp.pause if lp.pause
|
81
|
+
wait lp.pause.post if lp.pause&.post
|
79
82
|
}
|
80
83
|
when Program::AST::CountedLoop
|
81
|
-
lp.n.times {
|
84
|
+
number(lp.n).round.times {
|
85
|
+
wait lp.pause.pre if lp.pause&.pre
|
82
86
|
yield :counted
|
83
|
-
wait lp.pause if lp.pause
|
87
|
+
wait lp.pause.post if lp.pause&.post
|
84
88
|
}
|
85
89
|
when Program::AST::DeadlineLoop
|
86
90
|
until Time.now >= lp.stop_time
|
91
|
+
wait lp.pause.pre if lp.pause&.pre
|
87
92
|
yield :deadline
|
88
|
-
wait lp.pause if lp.pause
|
93
|
+
wait lp.pause.post if lp.pause&.post
|
89
94
|
end
|
90
95
|
when Program::AST::TimerLoop
|
91
96
|
sec = ((lp.hours * 60) + lp.minutes) * 60
|
92
97
|
time = 0
|
93
98
|
until time >= sec
|
94
99
|
start = Time.now
|
100
|
+
wait lp.pause.pre if lp.pause&.pre
|
95
101
|
yield :timer
|
96
|
-
wait lp.pause if lp.pause
|
102
|
+
wait lp.pause.post if lp.pause&.post
|
97
103
|
time += (Time.now - start).round
|
98
104
|
end
|
99
105
|
else
|
@@ -118,11 +124,23 @@ module Huebot
|
|
118
124
|
}
|
119
125
|
end
|
120
126
|
|
121
|
-
def wait(
|
127
|
+
def wait(n)
|
128
|
+
seconds = number n
|
122
129
|
@logger.log :pause, {time: seconds}
|
123
130
|
@waiter.call seconds
|
124
131
|
end
|
125
132
|
|
133
|
+
def number(n)
|
134
|
+
case n
|
135
|
+
when Program::AST::Num
|
136
|
+
n.n
|
137
|
+
when Program::AST::RandomNum
|
138
|
+
rand(n.min..n.max)
|
139
|
+
else
|
140
|
+
raise Error, "Unknown numeric type. Expected Program::AST::Num, Program::AST::NRandomNum, found: #{n.class.name}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
126
144
|
module Waiter
|
127
145
|
def self.call(seconds)
|
128
146
|
sleep seconds
|
data/lib/huebot/cli/helpers.rb
CHANGED
@@ -5,6 +5,8 @@ require 'json'
|
|
5
5
|
module Huebot
|
6
6
|
module CLI
|
7
7
|
module Helpers
|
8
|
+
DEFAULT_API_VERSION = 1.2
|
9
|
+
|
8
10
|
#
|
9
11
|
# Returns the command given to huebot.
|
10
12
|
#
|
@@ -70,7 +72,7 @@ module Huebot
|
|
70
72
|
opts.stderr.puts "Unknown file extension '#{ext}'. Expected .yaml, .yml, or .json"
|
71
73
|
return []
|
72
74
|
end
|
73
|
-
version = (src.delete("version") ||
|
75
|
+
version = (src.delete("version") || DEFAULT_API_VERSION).to_f
|
74
76
|
Program::Src.new(src, path, version)
|
75
77
|
}
|
76
78
|
|
@@ -80,7 +82,7 @@ module Huebot
|
|
80
82
|
src = raw[0] == "{" ? JSON.load(raw) : YAML.safe_load(raw)
|
81
83
|
|
82
84
|
opts.stdout.puts "Executing..." if opts.read_stdin
|
83
|
-
version = (src.delete("version") ||
|
85
|
+
version = (src.delete("version") || DEFAULT_API_VERSION).to_f
|
84
86
|
sources << Program::Src.new(src, "STDIN", version)
|
85
87
|
end
|
86
88
|
sources
|
data/lib/huebot/cli/runner.rb
CHANGED
@@ -8,6 +8,8 @@ module Huebot
|
|
8
8
|
rescue ::Huebot::Error => e
|
9
9
|
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
10
10
|
return 1
|
11
|
+
rescue Interrupt
|
12
|
+
return 130
|
11
13
|
end
|
12
14
|
|
13
15
|
def self.run(sources, lights, groups, opts)
|
@@ -25,6 +27,8 @@ module Huebot
|
|
25
27
|
rescue ::Huebot::Error => e
|
26
28
|
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
27
29
|
return 1
|
30
|
+
rescue Interrupt
|
31
|
+
return 130
|
28
32
|
end
|
29
33
|
|
30
34
|
def self.check(sources, lights, groups, opts)
|
@@ -47,6 +51,8 @@ module Huebot
|
|
47
51
|
rescue ::Huebot::Error => e
|
48
52
|
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
49
53
|
return 1
|
54
|
+
rescue Interrupt
|
55
|
+
return 130
|
50
56
|
end
|
51
57
|
|
52
58
|
def self.get_state(lights, groups, opts)
|
@@ -56,6 +62,8 @@ module Huebot
|
|
56
62
|
opts.stdout.puts " #{device.get_state}"
|
57
63
|
end
|
58
64
|
0
|
65
|
+
rescue Interrupt
|
66
|
+
return 130
|
59
67
|
end
|
60
68
|
|
61
69
|
def self.set_ip(config, ip, opts)
|
@@ -64,6 +72,8 @@ module Huebot
|
|
64
72
|
rescue ::Huebot::Error => e
|
65
73
|
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
66
74
|
return 1
|
75
|
+
rescue Interrupt
|
76
|
+
return 130
|
67
77
|
end
|
68
78
|
|
69
79
|
def self.clear_ip(config, opts)
|
@@ -72,6 +82,8 @@ module Huebot
|
|
72
82
|
rescue ::Huebot::Error => e
|
73
83
|
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
74
84
|
return 1
|
85
|
+
rescue Interrupt
|
86
|
+
return 130
|
75
87
|
end
|
76
88
|
|
77
89
|
def self.unregister(config, opts)
|
@@ -80,6 +92,8 @@ module Huebot
|
|
80
92
|
rescue ::Huebot::Error => e
|
81
93
|
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
82
94
|
return 1
|
95
|
+
rescue Interrupt
|
96
|
+
return 130
|
83
97
|
end
|
84
98
|
end
|
85
99
|
end
|
@@ -10,6 +10,8 @@ module Huebot
|
|
10
10
|
PARALLEL_KEYS = ["parallel"].freeze
|
11
11
|
INFINITE_KEYS = ["infinite"].freeze
|
12
12
|
COUNT_KEYS = ["count"].freeze
|
13
|
+
RANDOM_KEYS = ["random"].freeze
|
14
|
+
MIN_MAX = ["min", "max"].freeze
|
13
15
|
TIMER_KEYS = ["timer"].freeze
|
14
16
|
DEADLINE_KEYS = ["until"].freeze
|
15
17
|
HHMM = /\A[0-9]{2}:[0-9]{2}\Z/.freeze
|
@@ -51,40 +53,56 @@ module Huebot
|
|
51
53
|
end
|
52
54
|
|
53
55
|
def build_transition(t, errors, warnings, inherited_devices = nil)
|
56
|
+
if t.nil?
|
57
|
+
errors << "'transition' may not be blank"
|
58
|
+
t = {}
|
59
|
+
end
|
60
|
+
|
54
61
|
state = build_state(t, errors, warnings)
|
55
62
|
devices = build_devices(t, errors, warnings, inherited_devices)
|
56
|
-
|
63
|
+
pause = build_pause(t, errors, warnings)
|
64
|
+
wait = @api_version >= 1.1 ? build_wait(t, errors, warnings) : true
|
57
65
|
|
58
66
|
errors << "'transition' requires devices" if devices.empty?
|
59
67
|
errors << "Unknown keys in 'transition': #{t.keys.join ", "}" if t.keys.any?
|
60
68
|
|
61
|
-
instruction = Program::AST::Transition.new(state, devices,
|
69
|
+
instruction = Program::AST::Transition.new(state, devices, wait, pause)
|
62
70
|
return instruction, []
|
63
71
|
end
|
64
72
|
|
65
73
|
def build_serial(t, errors, warnings, inherited_devices = nil)
|
74
|
+
if t.nil?
|
75
|
+
errors << "'serial' may not be blank"
|
76
|
+
t = {}
|
77
|
+
end
|
78
|
+
|
66
79
|
lp = build_loop(t, errors, warnings)
|
67
|
-
|
80
|
+
pause = build_pause(t, errors, warnings)
|
68
81
|
devices = build_devices(t, errors, warnings, inherited_devices)
|
69
82
|
children = build_steps(t, errors, warnings, devices)
|
70
83
|
|
71
84
|
errors << "'serial' requires steps" if children.empty?
|
72
85
|
errors << "Unknown keys in 'serial': #{t.keys.join ", "}" if t.keys.any?
|
73
86
|
|
74
|
-
instruction = Program::AST::SerialControl.new(lp,
|
87
|
+
instruction = Program::AST::SerialControl.new(lp, pause)
|
75
88
|
return instruction, children
|
76
89
|
end
|
77
90
|
|
78
91
|
def build_parallel(t, errors, warnings, inherited_devices = nil)
|
92
|
+
if t.nil?
|
93
|
+
errors << "'parallel' may not be blank"
|
94
|
+
t = {}
|
95
|
+
end
|
96
|
+
|
79
97
|
lp = build_loop(t, errors, warnings)
|
80
|
-
|
98
|
+
pause = build_pause(t, errors, warnings)
|
81
99
|
devices = build_devices(t, errors, warnings, inherited_devices)
|
82
100
|
children = build_steps(t, errors, warnings, devices)
|
83
101
|
|
84
102
|
errors << "'parallel' requires steps" if children.empty?
|
85
103
|
errors << "Unknown keys in 'parallel': #{t.keys.join ", "}" if t.keys.any?
|
86
104
|
|
87
|
-
instruction = Program::AST::ParallelControl.new(lp,
|
105
|
+
instruction = Program::AST::ParallelControl.new(lp, pause)
|
88
106
|
return instruction, children
|
89
107
|
end
|
90
108
|
|
@@ -167,32 +185,58 @@ module Huebot
|
|
167
185
|
loop_val = t.delete "loop"
|
168
186
|
case loop_val
|
169
187
|
when Hash
|
170
|
-
pause = loop_val
|
171
|
-
errors << "'loop.pause' must be an integer. Found '#{pause.class.name}'" if pause and !pause.is_a? Integer
|
172
|
-
|
188
|
+
pause = build_pause(loop_val, errors, warnings)
|
173
189
|
lp =
|
174
190
|
case loop_val.keys
|
175
191
|
when INFINITE_KEYS
|
176
|
-
loop_val
|
192
|
+
loop_val.fetch("infinite") == true ? Program::AST::InfiniteLoop.new : Program::AST::CountedLoop.new(Program::AST::Num.new(1))
|
177
193
|
when COUNT_KEYS
|
178
|
-
num = loop_val
|
194
|
+
num = loop_val.fetch("count")
|
179
195
|
errors << "'loop.count' must be an integer. Found '#{num.class.name}'" unless num.is_a? Integer
|
180
|
-
Program::AST::CountedLoop.new(num)
|
196
|
+
Program::AST::CountedLoop.new(Program::AST::Num.new(num))
|
197
|
+
when RANDOM_KEYS
|
198
|
+
n = build_random loop_val, errors, warnings
|
199
|
+
Program::AST::CountedLoop.new(n)
|
181
200
|
when TIMER_KEYS
|
182
|
-
build_timer_loop loop_val
|
201
|
+
build_timer_loop loop_val.fetch("timer"), errors, warnings
|
183
202
|
when DEADLINE_KEYS
|
184
|
-
build_deadline_loop loop_val
|
203
|
+
build_deadline_loop loop_val.fetch("until"), errors, warnings
|
185
204
|
else
|
186
205
|
errors << "'loop' must contain exactly one of: 'infinite', 'count', 'timer', or 'until', and optionally 'pause'. Found: #{loop_val.keys.join ", "}"
|
187
|
-
Program::AST::CountedLoop.new(1)
|
206
|
+
Program::AST::CountedLoop.new(Program::AST::Num.new(1))
|
188
207
|
end
|
189
208
|
lp.pause = pause
|
190
209
|
lp
|
191
210
|
when nil
|
192
|
-
Program::AST::CountedLoop.new(1)
|
211
|
+
Program::AST::CountedLoop.new(Program::AST::Num.new(1))
|
193
212
|
else
|
194
213
|
errors << "'loop' must be an object. Found '#{loop_val.class.name}'"
|
195
|
-
Program::AST::CountedLoop.new(1)
|
214
|
+
Program::AST::CountedLoop.new(Program::AST::Num.new(1))
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def build_random(t, errors, warnings)
|
219
|
+
random = t.delete("random") || {}
|
220
|
+
min = build_random_n(random, "min", errors, warnings)
|
221
|
+
max = build_random_n(random, "max", errors, warnings)
|
222
|
+
errors << "'random.max' must be greater than 'random.min'" unless max > min
|
223
|
+
errors << "Unknown keys in 'random': #{random.keys.join ", "}" if random.keys.any?
|
224
|
+
Program::AST::RandomNum.new(min, max)
|
225
|
+
end
|
226
|
+
|
227
|
+
def build_random_n(t, name, errors, warnings)
|
228
|
+
n = t.delete name
|
229
|
+
case n
|
230
|
+
when Integer, Float
|
231
|
+
if n >= 0
|
232
|
+
n
|
233
|
+
else
|
234
|
+
errors << "'random.#{name}' must be >= 0, found #{n}"
|
235
|
+
0
|
236
|
+
end
|
237
|
+
else
|
238
|
+
errors << "'random.#{name}' must be an integer or float > 0, found #{n.class.name}"
|
239
|
+
0
|
196
240
|
end
|
197
241
|
end
|
198
242
|
|
@@ -216,11 +260,20 @@ module Huebot
|
|
216
260
|
Program::AST::DeadlineLoop.new(stop_time)
|
217
261
|
end
|
218
262
|
|
219
|
-
def
|
220
|
-
|
221
|
-
|
263
|
+
def build_pause(t, errors, warnings)
|
264
|
+
case @api_version
|
265
|
+
when 1.0 then build_pause_1_0(t, errors, warnings)
|
266
|
+
when 1.1 then build_pause_1_1(t, errors, warnings)
|
267
|
+
when 1.2 then build_pause_1_2(t, errors, warnings)
|
268
|
+
else raise Error, "Unknown api version '#{@api_version}'"
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def build_pause_1_0(t, errors, warnings)
|
273
|
+
pause_val = t.delete "pause"
|
274
|
+
case pause_val
|
222
275
|
when Integer, Float
|
223
|
-
|
276
|
+
Program::AST::Pause.new(nil, Program::AST::Num.new(pause_val))
|
224
277
|
when nil
|
225
278
|
nil
|
226
279
|
else
|
@@ -229,6 +282,86 @@ module Huebot
|
|
229
282
|
end
|
230
283
|
end
|
231
284
|
|
285
|
+
def build_pause_1_1(t, errors, warnings)
|
286
|
+
pause_val = t.delete "pause"
|
287
|
+
case pause_val
|
288
|
+
when Integer, Float
|
289
|
+
Program::AST::Pause.new(nil, Program::AST::Num.new(pause_val))
|
290
|
+
when Hash
|
291
|
+
pre = pause_val.delete "before"
|
292
|
+
post = pause_val.delete "after"
|
293
|
+
errors << "'pause.before' must be an integer or float" unless pre.nil? or pre.is_a? Integer or pre.is_a? Float
|
294
|
+
errors << "'pause.after' must be an integer or float" unless post.nil? or post.is_a? Integer or post.is_a? Float
|
295
|
+
errors << "Unknown keys in 'pause': #{pause_val.keys.join ", "}" if pause_val.keys.any?
|
296
|
+
pre = Program::AST::Num.new(pre) if pre
|
297
|
+
post = Program::AST::Num.new(post) if post
|
298
|
+
Program::AST::Pause.new(pre, post)
|
299
|
+
when nil
|
300
|
+
nil
|
301
|
+
else
|
302
|
+
errors << "'pause' must be an integer or float, or an object with 'before' and/or 'after'"
|
303
|
+
nil
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def build_pause_1_2(t, errors, warnings)
|
308
|
+
pause_val = t.delete "pause"
|
309
|
+
case pause_val
|
310
|
+
when Integer, Float
|
311
|
+
Program::AST::Pause.new(nil, Program::AST::Num.new(pause_val))
|
312
|
+
when Hash
|
313
|
+
pre = build_pause_part(pause_val, "before", errors, warnings)
|
314
|
+
post = build_pause_part(pause_val, "after", errors, warnings)
|
315
|
+
errors << "'pause' requires one or both of 'before' or 'after'" if pre.nil? and post.nil?
|
316
|
+
errors << "Unknown keys in 'pause': #{pause_val.keys.join ", "}" if pause_val.keys.any?
|
317
|
+
Program::AST::Pause.new(pre, post)
|
318
|
+
when nil
|
319
|
+
nil
|
320
|
+
else
|
321
|
+
errors << "'pause' must be an integer or float, or an object with 'before' and/or 'after'"
|
322
|
+
nil
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
def build_pause_part(t, part, errors, warnings)
|
327
|
+
val = t.delete part
|
328
|
+
case val
|
329
|
+
when nil
|
330
|
+
nil
|
331
|
+
when Integer, Float
|
332
|
+
Program::AST::Num.new(val)
|
333
|
+
when Hash
|
334
|
+
if @api_version < 1.2
|
335
|
+
errors << "Unknown 'pause.#{part}' type (#{val.class.name})"
|
336
|
+
return Program::AST::Num.new(1)
|
337
|
+
end
|
338
|
+
|
339
|
+
case val.keys
|
340
|
+
when RANDOM_KEYS
|
341
|
+
build_random val, errors, warnings
|
342
|
+
else
|
343
|
+
errors << "Expected 'pause.#{part}' to contain 'random', found #{val.keys.join ", "}"
|
344
|
+
Program::AST::Num.new(1)
|
345
|
+
end
|
346
|
+
else
|
347
|
+
errors << "Unknown 'pause.#{part}' type (#{val.class.name})"
|
348
|
+
Program::AST::Num.new(1)
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
def build_wait(t, errors, warnings)
|
353
|
+
wait = t.delete "wait"
|
354
|
+
case wait
|
355
|
+
when true, false
|
356
|
+
wait
|
357
|
+
when nil
|
358
|
+
true
|
359
|
+
else
|
360
|
+
errors << "'transition.wait' must be true or false"
|
361
|
+
true
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
232
365
|
def build_devices(t, errors, warnings, inherited_devices = nil)
|
233
366
|
devices_ref = t.delete("devices") || {}
|
234
367
|
return inherited_devices if devices_ref.empty? and inherited_devices
|
data/lib/huebot/program.rb
CHANGED
@@ -16,15 +16,18 @@ module Huebot
|
|
16
16
|
module AST
|
17
17
|
Node = Struct.new(:instruction, :children, :errors, :warnings)
|
18
18
|
|
19
|
-
Transition = Struct.new(:state, :devices, :
|
20
|
-
SerialControl = Struct.new(:loop, :
|
21
|
-
ParallelControl = Struct.new(:loop, :
|
19
|
+
Transition = Struct.new(:state, :devices, :wait, :pause)
|
20
|
+
SerialControl = Struct.new(:loop, :pause)
|
21
|
+
ParallelControl = Struct.new(:loop, :pause)
|
22
22
|
|
23
23
|
InfiniteLoop = Struct.new(:pause)
|
24
24
|
CountedLoop = Struct.new(:n, :pause)
|
25
25
|
TimerLoop = Struct.new(:hours, :minutes, :pause)
|
26
26
|
DeadlineLoop = Struct.new(:stop_time, :pause)
|
27
27
|
|
28
|
+
Pause = Struct.new(:pre, :post)
|
29
|
+
Num = Struct.new(:n)
|
30
|
+
RandomNum = Struct.new(:min, :max)
|
28
31
|
DeviceRef = Struct.new(:ref)
|
29
32
|
Light = Struct.new(:name)
|
30
33
|
Group = Struct.new(:name)
|
@@ -55,14 +58,14 @@ module Huebot
|
|
55
58
|
end
|
56
59
|
|
57
60
|
def errors(node = data)
|
58
|
-
node.children.reduce(node.errors) { |
|
59
|
-
|
61
|
+
node.children.reduce(node.errors) { |acc, child|
|
62
|
+
acc + errors(child)
|
60
63
|
}
|
61
64
|
end
|
62
65
|
|
63
66
|
def warnings(node = data)
|
64
|
-
node.children.reduce(node.warnings) { |
|
65
|
-
|
67
|
+
node.children.reduce(node.warnings) { |acc, child|
|
68
|
+
acc + warnings(child)
|
66
69
|
}
|
67
70
|
end
|
68
71
|
|
data/lib/huebot/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: huebot
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Hollinger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-12-
|
11
|
+
date: 2023-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|