huebot 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|