huebot 0.5.0 → 1.0.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 +96 -27
- data/bin/huebot +35 -32
- data/lib/huebot/bot.rb +82 -39
- data/lib/huebot/cli/helpers.rb +170 -0
- data/lib/huebot/cli/runner.rb +78 -0
- data/lib/huebot/cli.rb +2 -139
- data/lib/huebot/client.rb +5 -4
- data/lib/huebot/compiler/api_v1.rb +285 -0
- data/lib/huebot/compiler.rb +11 -149
- data/lib/huebot/device_mapper.rb +38 -24
- data/lib/huebot/device_state.rb +5 -1
- data/lib/huebot/group.rb +10 -3
- data/lib/huebot/light.rb +10 -3
- data/lib/huebot/program.rb +71 -21
- data/lib/huebot/version.rb +1 -1
- data/lib/huebot.rb +3 -22
- metadata +34 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5da1b1dcd0f32ce09e44155b39d7bc6ed92aa285944fc3a67801cf02642737c0
|
4
|
+
data.tar.gz: 9116d3a891e608276e64517f9252a42cddd615ca9952c55e816ca53196bdde15
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fc1ae1f775c685e635fe36d85ef33548a50163f80378f42301f73e9bdf868343fecb4b9a9fa64490466771dc44b51f95745587bc32423e65cdc306dae0e01fa9
|
7
|
+
data.tar.gz: b5721fb115fc0f8c38e9389063a9012a51d7aa2fa0cf2f1b7a2d93812d916b2ffb1f27508358483a9656b4bdb09d7f78fc7bd9e4f65ff599214c9cdd2f0e8a93
|
data/README.md
CHANGED
@@ -1,34 +1,109 @@
|
|
1
1
|
# Huebot
|
2
2
|
|
3
|
-
Program your Hue lights
|
3
|
+
Program your Hue lights!
|
4
4
|
|
5
5
|
$ huebot run dimmer.yml --light="Office Desk"
|
6
6
|
|
7
|
-
|
7
|
+
A few examples are below. [See the Wiki](https://github.com/jhollinger/huebot/wiki) for full documentation.
|
8
8
|
|
9
|
-
|
9
|
+
**dimmer.yaml**
|
10
|
+
|
11
|
+
This (very simple) program starts with the light(s) on at full brightness, then enters an hour and a half long loop of slowly dimming and raising the light(s). It finishes by turning them off again. Since no color is specified, the light(s) will retain whatever color they last had.
|
12
|
+
|
13
|
+
```yaml
|
14
|
+
serial:
|
15
|
+
devices:
|
16
|
+
lights:
|
17
|
+
- LR Lamp 1
|
18
|
+
- LR Lamp 2
|
19
|
+
groups:
|
20
|
+
- Dining Room
|
21
|
+
steps:
|
22
|
+
- transition:
|
23
|
+
state:
|
24
|
+
on: true
|
25
|
+
bri: 254
|
26
|
+
|
27
|
+
- serial:
|
28
|
+
loop:
|
29
|
+
timer:
|
30
|
+
hours: 1
|
31
|
+
minutes: 30
|
32
|
+
steps:
|
33
|
+
- transition:
|
34
|
+
state:
|
35
|
+
bri: 150
|
36
|
+
time: 10 # 10 second transition
|
37
|
+
pause: 2 # 2 second pause before the next step
|
38
|
+
|
39
|
+
- transition:
|
40
|
+
state:
|
41
|
+
bri: 254
|
42
|
+
time: 10 # 10 second transition
|
43
|
+
pause: 2 # 2 second pause before the next step
|
44
|
+
|
45
|
+
- transition:
|
46
|
+
state:
|
47
|
+
on: false
|
48
|
+
```
|
49
|
+
|
50
|
+
**party.yaml**
|
51
|
+
|
52
|
+
This more complicated program starts by switching devices on, then enters an infinite loop of two parallel steps. One branch fades up and down while the other branch is fading more lights down _then_ up.
|
10
53
|
|
11
54
|
```yaml
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
55
|
+
serial:
|
56
|
+
steps:
|
57
|
+
# Turn all inputs on to a mid brightness
|
58
|
+
- transition:
|
59
|
+
devices:
|
60
|
+
inputs: $all
|
61
|
+
state:
|
62
|
+
on: true
|
63
|
+
bri: 100
|
64
|
+
|
65
|
+
# Run these steps in parallel in an infinite loop
|
66
|
+
- parallel:
|
67
|
+
loop: true
|
68
|
+
steps:
|
69
|
+
# Parallel branch 1: Fade inputs #1 and #3 up and down
|
70
|
+
- serial:
|
71
|
+
devices:
|
72
|
+
inputs:
|
73
|
+
- $1
|
74
|
+
- $3
|
75
|
+
steps:
|
76
|
+
- transition:
|
77
|
+
state:
|
78
|
+
bri: 254
|
79
|
+
time: 10 # transition over 10 seconds
|
80
|
+
pause: 5 # pause an extra 5 sec after the transition
|
81
|
+
- transition:
|
82
|
+
state:
|
83
|
+
bri: 25
|
84
|
+
time: 10
|
85
|
+
pause: 5
|
86
|
+
|
87
|
+
# Parallel branch 2: Fade inputs #2 and #4 down and up
|
88
|
+
- serial:
|
89
|
+
devices:
|
90
|
+
inputs:
|
91
|
+
- $2
|
92
|
+
- $4
|
93
|
+
steps:
|
94
|
+
- transition:
|
95
|
+
state:
|
96
|
+
bri: 25
|
97
|
+
time: 10
|
98
|
+
pause: 5
|
99
|
+
- transition:
|
100
|
+
state:
|
101
|
+
bri: 254
|
102
|
+
time: 10
|
103
|
+
pause: 5
|
29
104
|
```
|
30
105
|
|
31
|
-
|
106
|
+
[See the Wiki](https://github.com/jhollinger/huebot/wiki) for more documentation and examples.
|
32
107
|
|
33
108
|
## Install
|
34
109
|
|
@@ -43,9 +118,3 @@ Configuration is stored in `~/.config/huebot`.
|
|
43
118
|
## License
|
44
119
|
|
45
120
|
Huebot is licensed under the MIT license (see LICENSE file).
|
46
|
-
|
47
|
-
**TODO**
|
48
|
-
|
49
|
-
* Validate number of inputs against compiled programs
|
50
|
-
* More explanation various features in Wiki
|
51
|
-
* More examples in Wiki
|
data/bin/huebot
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
# Used for local testing
|
4
|
-
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
5
5
|
|
6
6
|
require 'huebot'
|
7
|
-
require 'huebot/cli'
|
8
7
|
|
9
|
-
Huebot::CLI.tap { |cli|
|
8
|
+
Huebot::CLI::Helpers.tap { |cli|
|
10
9
|
case cli.get_cmd
|
11
10
|
when :ls
|
12
11
|
bridge, error = Huebot::Bridge.connect
|
@@ -14,64 +13,68 @@ Huebot::CLI.tap { |cli|
|
|
14
13
|
$stderr.puts error
|
15
14
|
exit 1
|
16
15
|
end
|
17
|
-
|
18
|
-
|
19
|
-
"\nGroups\n" + bridge.groups.map { |g| " #{g.id}: #{g.name}" }.join("\n")
|
16
|
+
retval = Huebot::CLI::Runner.ls bridge
|
17
|
+
exit retval
|
20
18
|
|
21
19
|
when :run
|
22
20
|
opts, sources = cli.get_input!
|
21
|
+
if sources.empty?
|
22
|
+
cli.help!
|
23
|
+
exit 1
|
24
|
+
end
|
23
25
|
|
24
26
|
bridge, error = Huebot::Bridge.connect
|
25
27
|
if error
|
26
28
|
$stderr.puts error
|
27
29
|
exit 1
|
28
30
|
end
|
29
|
-
|
30
|
-
|
31
|
-
compiler = Huebot::Compiler.new(device_mapper)
|
32
|
-
|
33
|
-
programs = sources.map { |src|
|
34
|
-
compiler.build src.ir, File.basename(src.filepath, ".*")
|
35
|
-
}
|
36
|
-
found_errors, _found_warnings = cli.check! programs, $stderr
|
37
|
-
exit 1 if found_errors
|
38
|
-
|
39
|
-
bot = Huebot::Bot.new(bridge)
|
40
|
-
programs.each { |prog| bot.execute prog }
|
31
|
+
retval = Huebot::CLI::Runner.run(bridge, sources, opts)
|
32
|
+
exit retval
|
41
33
|
|
42
34
|
when :check
|
43
35
|
opts, sources = cli.get_input!
|
36
|
+
if sources.empty?
|
37
|
+
cli.help!
|
38
|
+
exit 1
|
39
|
+
end
|
44
40
|
|
45
41
|
bridge, error = Huebot::Bridge.connect
|
46
42
|
if error
|
47
43
|
$stderr.puts error
|
48
44
|
exit 1
|
49
45
|
end
|
46
|
+
retval = Huebot::CLI::Runner.check(bridge, sources, opts)
|
47
|
+
exit retval
|
50
48
|
|
51
|
-
|
52
|
-
|
49
|
+
when :"get-state"
|
50
|
+
opts, _sources = cli.get_input!
|
51
|
+
if opts.inputs.empty?
|
52
|
+
cli.help!
|
53
|
+
exit 1
|
54
|
+
end
|
53
55
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
56
|
+
bridge, error = Huebot::Bridge.connect
|
57
|
+
if error
|
58
|
+
$stderr.puts error
|
59
|
+
exit 1
|
60
|
+
end
|
61
|
+
retval = Huebot::CLI::Runner.get_state(bridge, opts.inputs)
|
62
|
+
exit retval
|
60
63
|
|
61
64
|
when :"set-ip"
|
62
65
|
ip = cli.get_args(num: 1).first
|
63
|
-
|
64
|
-
|
66
|
+
retval = Huebot::CLI::Runner.set_ip ip
|
67
|
+
exit retval
|
65
68
|
|
66
69
|
when :"clear-ip"
|
67
70
|
cli.get_args(num: 0)
|
68
|
-
|
69
|
-
|
71
|
+
retval = Huebot::CLI::Runner.unregister
|
72
|
+
exit retval
|
70
73
|
|
71
74
|
when :unregister
|
72
75
|
cli.get_args(num: 0)
|
73
|
-
|
74
|
-
|
76
|
+
retval = Huebot::CLI::Runner.unregister
|
77
|
+
exit retval
|
75
78
|
|
76
79
|
else cli.help!
|
77
80
|
end
|
data/lib/huebot/bot.rb
CHANGED
@@ -1,64 +1,107 @@
|
|
1
1
|
module Huebot
|
2
2
|
class Bot
|
3
|
-
attr_reader :client
|
4
|
-
|
5
3
|
Error = Class.new(StandardError)
|
6
4
|
|
7
|
-
def initialize(
|
8
|
-
@
|
5
|
+
def initialize(device_mapper)
|
6
|
+
@device_mapper = device_mapper
|
7
|
+
#@client = device_mapper.bridge.client
|
9
8
|
end
|
10
9
|
|
11
10
|
def execute(program)
|
12
|
-
|
13
|
-
|
14
|
-
if program.transitions.any?
|
15
|
-
if program.loop?
|
16
|
-
loop { iterate program.transitions }
|
17
|
-
elsif program.loops > 0
|
18
|
-
program.loops.times { iterate program.transitions }
|
19
|
-
else
|
20
|
-
iterate program.transitions
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
transition program.final_state if program.final_state
|
11
|
+
exec program.data
|
25
12
|
end
|
26
13
|
|
27
14
|
private
|
28
15
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
16
|
+
def exec(node)
|
17
|
+
i = node.instruction
|
18
|
+
case i
|
19
|
+
when Program::AST::Transition
|
20
|
+
transition i.state, i.devices, i.sleep
|
21
|
+
when Program::AST::SerialControl
|
22
|
+
serial node.children, i.loop, i.sleep
|
23
|
+
when Program::AST::ParallelControl
|
24
|
+
parallel node.children, i.loop, i.sleep
|
25
|
+
else
|
26
|
+
raise Error, "Unexpected instruction '#{i.class.name}'"
|
36
27
|
end
|
37
28
|
end
|
38
29
|
|
39
|
-
def
|
40
|
-
|
30
|
+
def transition(state, device_refs, sleep_time = nil)
|
31
|
+
time = (state["transitiontime"] || 4).to_f / 10
|
32
|
+
devices = map_devices device_refs
|
33
|
+
devices.map { |device|
|
41
34
|
Thread.new {
|
42
|
-
|
35
|
+
# TODO error handling
|
36
|
+
device.set_state state
|
37
|
+
wait time
|
43
38
|
}
|
44
39
|
}.map(&:join)
|
45
|
-
wait
|
40
|
+
wait sleep_time if sleep_time
|
46
41
|
end
|
47
42
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
device.set_state t.state
|
53
|
-
wait time
|
54
|
-
wait t.wait if t.wait
|
43
|
+
def serial(nodes, lp, sleep_time = nil)
|
44
|
+
control_loop(lp) {
|
45
|
+
nodes.map { |node|
|
46
|
+
exec node
|
55
47
|
}
|
56
|
-
}
|
48
|
+
}
|
49
|
+
wait sleep_time if sleep_time
|
50
|
+
end
|
51
|
+
|
52
|
+
def parallel(nodes, lp, sleep_time = nil)
|
53
|
+
control_loop(lp) {
|
54
|
+
nodes.map { |node|
|
55
|
+
Thread.new {
|
56
|
+
# TODO error handling
|
57
|
+
exec node
|
58
|
+
}
|
59
|
+
}.map(&:join)
|
60
|
+
}
|
61
|
+
wait sleep_time if sleep_time
|
62
|
+
end
|
63
|
+
|
64
|
+
def control_loop(lp)
|
65
|
+
case lp
|
66
|
+
when Program::AST::InfiniteLoop
|
67
|
+
loop { yield }
|
68
|
+
when Program::AST::CountedLoop
|
69
|
+
lp.n.times { yield }
|
70
|
+
when Program::AST::DeadlineLoop
|
71
|
+
until Time.now >= lp.stop_time
|
72
|
+
yield
|
73
|
+
end
|
74
|
+
when Program::AST::TimerLoop
|
75
|
+
sec = ((lp.hours * 60) + lp.minutes) * 60
|
76
|
+
time = 0
|
77
|
+
until time >= sec
|
78
|
+
start = Time.now
|
79
|
+
yield
|
80
|
+
time += (Time.now - start).round
|
81
|
+
end
|
82
|
+
else
|
83
|
+
raise Error, "Unexpected loop type '#{lp.class.name}'"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def map_devices(refs)
|
88
|
+
refs.reduce([]) { |acc, ref|
|
89
|
+
devices =
|
90
|
+
case ref
|
91
|
+
when Program::AST::Light
|
92
|
+
[@device_mapper.light!(ref.name)]
|
93
|
+
when Program::AST::Group
|
94
|
+
[@device_mapper.group!(ref.name)]
|
95
|
+
when Program::AST::DeviceRef
|
96
|
+
Array(@device_mapper.var! ref.ref)
|
97
|
+
else
|
98
|
+
raise Error, "Unknown device reference '#{ref.class.name}'"
|
99
|
+
end
|
100
|
+
acc + devices
|
101
|
+
}
|
57
102
|
end
|
58
103
|
|
59
|
-
def wait(
|
60
|
-
ms = time * 100
|
61
|
-
seconds = ms / 1000.to_f
|
104
|
+
def wait(seconds)
|
62
105
|
# TODO sleep in small bursts in a loop so can detect if an Interrupt was caught
|
63
106
|
sleep seconds
|
64
107
|
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Huebot
|
5
|
+
module CLI
|
6
|
+
module Helpers
|
7
|
+
#
|
8
|
+
# Returns the command given to huebot.
|
9
|
+
#
|
10
|
+
# @return [Symbol]
|
11
|
+
#
|
12
|
+
def self.get_cmd
|
13
|
+
ARGV[0].to_s.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.get_args(min: nil, max: nil, num: nil)
|
17
|
+
args = ARGV[1..]
|
18
|
+
if num
|
19
|
+
if num != args.size
|
20
|
+
$stderr.puts "Expected #{num} args, found #{args.size}"
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
elsif min and max
|
24
|
+
if args.size < min or args.size > max
|
25
|
+
$stderr.puts "Expected #{min}-#{max} args, found #{args.size}"
|
26
|
+
end
|
27
|
+
elsif min
|
28
|
+
if args.size < min
|
29
|
+
$stderr.puts "Expected at least #{num} args, found #{args.size}"
|
30
|
+
exit 1
|
31
|
+
end
|
32
|
+
elsif max
|
33
|
+
if args.size > max
|
34
|
+
$stderr.puts "Expected no more than #{num} args, found #{args.size}"
|
35
|
+
exit 1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
args
|
39
|
+
end
|
40
|
+
|
41
|
+
#
|
42
|
+
# Parses and returns input from the CLI. Serious errors might result in the program exiting.
|
43
|
+
#
|
44
|
+
# @return [Huebot::CLI::Options] All given CLI options
|
45
|
+
# @return [Array<Huebot::Program::Src>] Array of given program sources
|
46
|
+
#
|
47
|
+
def self.get_input!
|
48
|
+
options, parser = option_parser
|
49
|
+
parser.parse!
|
50
|
+
|
51
|
+
files = ARGV[1..-1]
|
52
|
+
if (bad_paths = files.select { |p| !File.exist? p }).any?
|
53
|
+
$stderr.puts "Cannot find #{bad_paths.join ', '}"
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
|
57
|
+
sources = files.map { |path|
|
58
|
+
src = YAML.load_file(path)
|
59
|
+
version = (src.delete("version") || 1.0).to_f
|
60
|
+
Program::Src.new(src, path, version)
|
61
|
+
}
|
62
|
+
|
63
|
+
if options.read_stdin
|
64
|
+
src = YAML.load($stdin.read)
|
65
|
+
version = (src.delete("version") || 1.0).to_f
|
66
|
+
sources << Program::Src.new(src, "STDIN", version)
|
67
|
+
end
|
68
|
+
return options, sources
|
69
|
+
end
|
70
|
+
|
71
|
+
#
|
72
|
+
# Prints any program errors or warnings, and returns a boolean for each.
|
73
|
+
#
|
74
|
+
# @param programs [Array<Huebot::Program>]
|
75
|
+
# @param device_mapper [Huebot::DeviceMapper]
|
76
|
+
# @param io [IO] Usually $stdout or $stderr
|
77
|
+
# @param quiet [Boolean] if true, don't print anything
|
78
|
+
#
|
79
|
+
def self.check!(programs, device_mapper, io, quiet: false)
|
80
|
+
if (invalid_progs = programs.select { |prog| prog.errors.any? }).any?
|
81
|
+
errors = invalid_progs.reduce([]) { |acc, prog|
|
82
|
+
acc + prog.errors.map { |e| " #{prog.name}: #{e}" }
|
83
|
+
}
|
84
|
+
print_messages! io, "Errors", errors unless quiet
|
85
|
+
end
|
86
|
+
|
87
|
+
if (imperfect_progs = programs.select { |prog| prog.warnings.any? }).any?
|
88
|
+
warnings = imperfect_progs.reduce([]) { |acc, prog|
|
89
|
+
acc + prog.warnings.map { |e| " #{prog.name}: #{e}" }
|
90
|
+
}
|
91
|
+
print_messages! io, "Warnings", warnings unless quiet
|
92
|
+
end
|
93
|
+
|
94
|
+
all_lights = programs.reduce([]) { |acc, p| acc + p.light_names }
|
95
|
+
if (missing_lights = device_mapper.missing_lights all_lights).any?
|
96
|
+
print_messages! io, "Unknown lights", missing_lights
|
97
|
+
end
|
98
|
+
|
99
|
+
all_groups = programs.reduce([]) { |acc, p| acc + p.group_names }
|
100
|
+
if (missing_groups = device_mapper.missing_groups all_groups).any?
|
101
|
+
print_messages! io, "Unknown groups", missing_groups
|
102
|
+
end
|
103
|
+
|
104
|
+
all_vars = programs.reduce([]) { |acc, p| acc + p.device_refs }
|
105
|
+
if (missing_vars = device_mapper.missing_vars all_vars).any?
|
106
|
+
print_messages! io, "Unknown device inputs", missing_vars.map { |d| "$#{d}" }
|
107
|
+
end
|
108
|
+
|
109
|
+
invalid_devices = missing_lights.size + missing_groups.size + missing_vars.size
|
110
|
+
return invalid_progs.any?, imperfect_progs.any?, invalid_devices > 0
|
111
|
+
end
|
112
|
+
|
113
|
+
# Print help and exit
|
114
|
+
def self.help!
|
115
|
+
_, parser = option_parser
|
116
|
+
puts parser.help
|
117
|
+
exit 1
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
#
|
123
|
+
# Print each message (of the given type) for each program.
|
124
|
+
#
|
125
|
+
# @param io [IO] Usually $stdout or $stderr
|
126
|
+
# @param label [String] Top-level for this group of messages
|
127
|
+
# @param errors [Array<String>]
|
128
|
+
#
|
129
|
+
def self.print_messages!(io, label, errors)
|
130
|
+
io.puts "#{label}:"
|
131
|
+
errors.each_with_index { |msg, i|
|
132
|
+
io.puts " #{i+1}) #{msg}"
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def self.option_parser
|
137
|
+
options = Options.new([], false)
|
138
|
+
parser = OptionParser.new { |opts|
|
139
|
+
opts.banner = %(
|
140
|
+
List all lights and groups:
|
141
|
+
huebot ls
|
142
|
+
|
143
|
+
Run program(s):
|
144
|
+
huebot run file1.yml [file2.yml [file3.yml ...]] [options]
|
145
|
+
|
146
|
+
Validate programs and inputs:
|
147
|
+
huebot check file1.yml [file2.yml [file3.yml ...]] [options]
|
148
|
+
|
149
|
+
Print the current state of the given lights and/or groups:
|
150
|
+
huebot get-state [options]
|
151
|
+
|
152
|
+
Manually set/clear the IP for your Hue Bridge (useful when on a VPN):
|
153
|
+
huebot set-ip 192.168.1.20
|
154
|
+
huebot clear-ip
|
155
|
+
|
156
|
+
Clear all connection config:
|
157
|
+
huebot unregister
|
158
|
+
|
159
|
+
Options:
|
160
|
+
).strip
|
161
|
+
opts.on("-lLIGHT", "--light=LIGHT", "Light ID or name") { |l| options.inputs << Light::Input.new(l) }
|
162
|
+
opts.on("-gGROUP", "--group=GROUP", "Group ID or name") { |g| options.inputs << Group::Input.new(g) }
|
163
|
+
opts.on("-i", "Read program from STDIN") { options.read_stdin = true }
|
164
|
+
opts.on("-h", "--help", "Prints this help") { puts opts; exit }
|
165
|
+
}
|
166
|
+
return options, parser
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Huebot
|
2
|
+
module CLI
|
3
|
+
module Runner
|
4
|
+
def self.ls(bridge)
|
5
|
+
puts "Lights\n" + bridge.lights.map { |l| " #{l.id}: #{l.name}" }.join("\n") + \
|
6
|
+
"\nGroups\n" + bridge.groups.map { |g| " #{g.id}: #{g.name}" }.join("\n")
|
7
|
+
return 0
|
8
|
+
rescue ::Huebot::Error => e
|
9
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
10
|
+
return 1
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.run(bridge, sources, opts)
|
14
|
+
device_mapper = Huebot::DeviceMapper.new(bridge, opts.inputs)
|
15
|
+
programs = sources.map { |src|
|
16
|
+
Huebot::Compiler.build src
|
17
|
+
}
|
18
|
+
found_errors, _found_warnings, missing_devices = Helpers.check! programs, device_mapper, $stderr
|
19
|
+
return 1 if found_errors || missing_devices
|
20
|
+
|
21
|
+
bot = Huebot::Bot.new(device_mapper)
|
22
|
+
programs.each { |prog| bot.execute prog }
|
23
|
+
return 0
|
24
|
+
rescue ::Huebot::Error => e
|
25
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
26
|
+
return 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.check(bridge, sources, opts)
|
30
|
+
device_mapper = Huebot::DeviceMapper.new(bridge, opts.inputs)
|
31
|
+
programs = sources.map { |src|
|
32
|
+
Huebot::Compiler.build src
|
33
|
+
}
|
34
|
+
found_errors, found_warnings, missing_devices = Helpers.check! programs, device_mapper, $stdout
|
35
|
+
return (found_errors || found_warnings || missing_devices) ? 1 : 0
|
36
|
+
rescue ::Huebot::Error => e
|
37
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
38
|
+
return 1
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.get_state(bridge, inputs)
|
42
|
+
device_mapper = Huebot::DeviceMapper.new(bridge, inputs)
|
43
|
+
device_mapper.each do |device|
|
44
|
+
puts device.name
|
45
|
+
puts " #{device.get_state}"
|
46
|
+
end
|
47
|
+
0
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.set_ip
|
51
|
+
config = Huebot::Config.new
|
52
|
+
config["ip"] = ip
|
53
|
+
0
|
54
|
+
rescue ::Huebot::Error => e
|
55
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
56
|
+
return 1
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.clear_ip
|
60
|
+
config = Huebot::Config.new
|
61
|
+
config["ip"] = nil
|
62
|
+
0
|
63
|
+
rescue ::Huebot::Error => e
|
64
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
65
|
+
return 1
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.unregister
|
69
|
+
config = Huebot::Config.new
|
70
|
+
config.clear
|
71
|
+
0
|
72
|
+
rescue ::Huebot::Error => e
|
73
|
+
$stderr.puts "#{e.class.name}: #{e.message}"
|
74
|
+
return 1
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|