huebot 1.2.0 → 1.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7febcd2a42112b4c3636581d9419a58c490a1157637e143fd46e6e7d94aefba5
4
- data.tar.gz: d4a9ab3178d381786595f14a14899e55af7789b4650cfa2da1928b83b5fe2135
3
+ metadata.gz: 643b636258939c0eb021d13d9369d845be85a5854ca1f6c38fa66c6556551c68
4
+ data.tar.gz: e5e1e74fded7ed9d456f8a7ce0c65f8f787a08187eb019413bddaff7bddaca74
5
5
  SHA512:
6
- metadata.gz: 15150d4cd9652f7d5e2518bd714de8155f1b574054360f61c0ac383c30f6fe81e3421f0bf64f0da43f090dabd2677b71609ae0a1e4f3741524adcdd631bd1116
7
- data.tar.gz: 548d5adc4af710d2b1c9719e54fad7aa5fe5ba18749845089d0cb66fb59b4fd5e8279acc4214e961757c9956bad0b79ed8dacf4e531e3f54097322ce44a6b4bb
6
+ metadata.gz: '084232072462be01ec7867aadb43df7b476e4c0b6fe0fcc33b1d10cd23f568b8474d83ed450f0487123ce874c33a729d84bfa9e4cc683a59958e3b5fe7bd64e0'
7
+ data.tar.gz: 99c1ba63f4ff21161017fe42180ae76c5ee27721e32e7663eb5d09906953ef88886dd7e003f40e783e0ede9e3e5e123fab5635b5e0413085bf5882ebcb386bb8
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: 2 # 2 second pause before the next step
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: 2 # 2 second pause before the next step
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: 5 # pause an extra 5 sec after the transition
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: 5
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: 5
103
+ pause:
104
+ after: 5
100
105
  - transition:
101
106
  state:
102
107
  bri: 254
103
108
  time: 10
104
- pause: 5
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
- i = node.instruction
22
- case i
21
+ case node.instruction
23
22
  when Program::AST::Transition
24
- transition i.state, i.devices, i.sleep
23
+ transition node.instruction
25
24
  when Program::AST::SerialControl
26
- serial node.children, i.loop, i.sleep
25
+ serial node.children, node.instruction
27
26
  when Program::AST::ParallelControl
28
- parallel node.children, i.loop, i.sleep
27
+ parallel node.children, node.instruction
29
28
  else
30
- raise Error, "Unexpected instruction '#{i.class.name}'"
29
+ raise Error, "Unexpected instruction '#{node.instruction.class.name}'"
31
30
  end
32
31
  end
33
32
 
34
- def transition(state, device_refs, sleep_time = nil)
35
- time = (state["transitiontime"] || 4).to_f / 10
36
- devices = map_devices device_refs
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 time if i.wait
45
45
  }
46
46
  }.map(&:join)
47
- wait sleep_time if sleep_time
47
+ wait i.pause.post if i.pause&.post
48
48
  end
49
49
 
50
- def serial(nodes, lp, sleep_time = nil)
51
- control_loop(lp) { |loop_type|
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 sleep_time if sleep_time
58
+ wait i.pause.post if i.pause&.post
58
59
  end
59
60
 
60
- def parallel(nodes, lp, sleep_time = nil)
61
- control_loop(lp) { |loop_type|
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 sleep_time if sleep_time
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
84
  lp.n.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
@@ -5,6 +5,8 @@ require 'json'
5
5
  module Huebot
6
6
  module CLI
7
7
  module Helpers
8
+ DEFAULT_API_VERSION = 1.1
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") || 1.0).to_f
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") || 1.0).to_f
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
@@ -51,40 +51,56 @@ module Huebot
51
51
  end
52
52
 
53
53
  def build_transition(t, errors, warnings, inherited_devices = nil)
54
+ if t.nil?
55
+ errors << "'transition' may not be blank"
56
+ t = {}
57
+ end
58
+
54
59
  state = build_state(t, errors, warnings)
55
60
  devices = build_devices(t, errors, warnings, inherited_devices)
56
- slp = build_sleep(t, errors, warnings)
61
+ pause = build_pause(t, errors, warnings)
62
+ wait = @api_version >= 1.1 ? build_wait(t, errors, warnings) : true
57
63
 
58
64
  errors << "'transition' requires devices" if devices.empty?
59
65
  errors << "Unknown keys in 'transition': #{t.keys.join ", "}" if t.keys.any?
60
66
 
61
- instruction = Program::AST::Transition.new(state, devices, slp)
67
+ instruction = Program::AST::Transition.new(state, devices, wait, pause)
62
68
  return instruction, []
63
69
  end
64
70
 
65
71
  def build_serial(t, errors, warnings, inherited_devices = nil)
72
+ if t.nil?
73
+ errors << "'serial' may not be blank"
74
+ t = {}
75
+ end
76
+
66
77
  lp = build_loop(t, errors, warnings)
67
- slp = build_sleep(t, errors, warnings)
78
+ pause = build_pause(t, errors, warnings)
68
79
  devices = build_devices(t, errors, warnings, inherited_devices)
69
80
  children = build_steps(t, errors, warnings, devices)
70
81
 
71
82
  errors << "'serial' requires steps" if children.empty?
72
83
  errors << "Unknown keys in 'serial': #{t.keys.join ", "}" if t.keys.any?
73
84
 
74
- instruction = Program::AST::SerialControl.new(lp, slp)
85
+ instruction = Program::AST::SerialControl.new(lp, pause)
75
86
  return instruction, children
76
87
  end
77
88
 
78
89
  def build_parallel(t, errors, warnings, inherited_devices = nil)
90
+ if t.nil?
91
+ errors << "'parallel' may not be blank"
92
+ t = {}
93
+ end
94
+
79
95
  lp = build_loop(t, errors, warnings)
80
- slp = build_sleep(t, errors, warnings)
96
+ pause = build_pause(t, errors, warnings)
81
97
  devices = build_devices(t, errors, warnings, inherited_devices)
82
98
  children = build_steps(t, errors, warnings, devices)
83
99
 
84
100
  errors << "'parallel' requires steps" if children.empty?
85
101
  errors << "Unknown keys in 'parallel': #{t.keys.join ", "}" if t.keys.any?
86
102
 
87
- instruction = Program::AST::ParallelControl.new(lp, slp)
103
+ instruction = Program::AST::ParallelControl.new(lp, pause)
88
104
  return instruction, children
89
105
  end
90
106
 
@@ -167,9 +183,7 @@ module Huebot
167
183
  loop_val = t.delete "loop"
168
184
  case loop_val
169
185
  when Hash
170
- pause = loop_val.delete "pause"
171
- errors << "'loop.pause' must be an integer. Found '#{pause.class.name}'" if pause and !pause.is_a? Integer
172
-
186
+ pause = build_pause(loop_val, errors, warnings)
173
187
  lp =
174
188
  case loop_val.keys
175
189
  when INFINITE_KEYS
@@ -216,11 +230,19 @@ module Huebot
216
230
  Program::AST::DeadlineLoop.new(stop_time)
217
231
  end
218
232
 
219
- def build_sleep(t, errors, warnings)
220
- sleep_val = t.delete "pause"
221
- case sleep_val
233
+ def build_pause(t, errors, warnings)
234
+ case @api_version
235
+ when 1.0 then build_pause_1_0(t, errors, warnings)
236
+ when 1.1 then build_pause_1_1(t, errors, warnings)
237
+ else raise Error, "Unknown api version '#{@api_version}'"
238
+ end
239
+ end
240
+
241
+ def build_pause_1_0(t, errors, warnings)
242
+ pause_val = t.delete "pause"
243
+ case pause_val
222
244
  when Integer, Float
223
- sleep_val
245
+ Program::AST::Pause.new(nil, pause_val)
224
246
  when nil
225
247
  nil
226
248
  else
@@ -229,6 +251,39 @@ module Huebot
229
251
  end
230
252
  end
231
253
 
254
+ def build_pause_1_1(t, errors, warnings)
255
+ pause_val = t.delete "pause"
256
+ case pause_val
257
+ when Integer, Float
258
+ Program::AST::Pause.new(nil, pause_val)
259
+ when Hash
260
+ pre = pause_val.delete "before"
261
+ post = pause_val.delete "after"
262
+ errors << "'pause.before' must be an integer or float" unless pre.nil? or pre.is_a? Integer or pre.is_a? Float
263
+ errors << "'pause.after' must be an integer or float" unless post.nil? or post.is_a? Integer or post.is_a? Float
264
+ errors << "Unknown keys in 'pause': #{pause_val.keys.join ", "}" if pause_val.keys.any?
265
+ Program::AST::Pause.new(pre, post)
266
+ when nil
267
+ nil
268
+ else
269
+ errors << "'pause' must be an integer or float"
270
+ nil
271
+ end
272
+ end
273
+
274
+ def build_wait(t, errors, warnings)
275
+ wait = t.delete "wait"
276
+ case wait
277
+ when true, false
278
+ wait
279
+ when nil
280
+ true
281
+ else
282
+ errors << "'transition.wait' must be true or false"
283
+ true
284
+ end
285
+ end
286
+
232
287
  def build_devices(t, errors, warnings, inherited_devices = nil)
233
288
  devices_ref = t.delete("devices") || {}
234
289
  return inherited_devices if devices_ref.empty? and inherited_devices
@@ -13,7 +13,7 @@ module Huebot
13
13
  def self.build(src)
14
14
  compiler_class =
15
15
  case src.api_version
16
- when 1.0 then ApiV1
16
+ when 1.0, 1.1 then ApiV1
17
17
  else raise Error, "Unknown API version '#{src.api_version}'"
18
18
  end
19
19
  compiler = compiler_class.new(src.api_version)
@@ -16,15 +16,16 @@ module Huebot
16
16
  module AST
17
17
  Node = Struct.new(:instruction, :children, :errors, :warnings)
18
18
 
19
- Transition = Struct.new(:state, :devices, :sleep)
20
- SerialControl = Struct.new(:loop, :sleep)
21
- ParallelControl = Struct.new(:loop, :sleep)
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)
28
29
  DeviceRef = Struct.new(:ref)
29
30
  Light = Struct.new(:name)
30
31
  Group = Struct.new(:name)
@@ -1,4 +1,4 @@
1
1
  module Huebot
2
2
  # Gem version
3
- VERSION = '1.2.0'
3
+ VERSION = '1.3.0'
4
4
  end
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.2.0
4
+ version: 1.3.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-21 00:00:00.000000000 Z
11
+ date: 2023-12-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest