huebot 1.1.0 → 1.2.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 +4 -3
- data/bin/huebot +39 -19
- data/lib/huebot/bot.rb +35 -12
- data/lib/huebot/bridge.rb +3 -6
- data/lib/huebot/{cli.rb → cli/cli.rb} +2 -1
- data/lib/huebot/cli/config.rb +47 -0
- data/lib/huebot/cli/helpers.rb +69 -37
- data/lib/huebot/cli/runner.rb +34 -26
- data/lib/huebot/client.rb +1 -1
- data/lib/huebot/compiler/api_v1.rb +1 -1
- data/lib/huebot/device_mapper.rb +6 -8
- data/lib/huebot/group.rb +0 -1
- data/lib/huebot/light.rb +0 -1
- data/lib/huebot/logging/collecting_logger.rb +20 -0
- data/lib/huebot/logging/io_logger.rb +19 -0
- data/lib/huebot/logging/logging.rb +7 -0
- data/lib/huebot/logging/null_logger.rb +9 -0
- data/lib/huebot/version.rb +1 -1
- data/lib/huebot.rb +3 -3
- metadata +9 -5
- data/lib/huebot/config.rb +0 -41
- /data/lib/huebot/{compiler.rb → compiler/compiler.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7febcd2a42112b4c3636581d9419a58c490a1157637e143fd46e6e7d94aefba5
|
4
|
+
data.tar.gz: d4a9ab3178d381786595f14a14899e55af7789b4650cfa2da1928b83b5fe2135
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 15150d4cd9652f7d5e2518bd714de8155f1b574054360f61c0ac383c30f6fe81e3421f0bf64f0da43f090dabd2677b71609ae0a1e4f3741524adcdd631bd1116
|
7
|
+
data.tar.gz: 548d5adc4af710d2b1c9719e54fad7aa5fe5ba18749845089d0cb66fb59b4fd5e8279acc4214e961757c9956bad0b79ed8dacf4e531e3f54097322ce44a6b4bb
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Huebot
|
2
2
|
|
3
|
-
Program your Hue lights!
|
3
|
+
Program your Hue lights using YAML or JSON!
|
4
4
|
|
5
|
-
$ huebot run dimmer.
|
5
|
+
$ huebot run dimmer.yaml --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
|
|
@@ -64,7 +64,8 @@ serial:
|
|
64
64
|
|
65
65
|
# Run these steps in parallel in an infinite loop
|
66
66
|
- parallel:
|
67
|
-
loop:
|
67
|
+
loop:
|
68
|
+
infinite: true
|
68
69
|
steps:
|
69
70
|
# Parallel branch 1: Fade inputs #1 and #3 up and down
|
70
71
|
- serial:
|
data/bin/huebot
CHANGED
@@ -1,23 +1,27 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
3
|
# Used for local testing
|
4
|
-
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') if ENV["
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib') if ENV["DEV"] == "1"
|
5
5
|
|
6
6
|
require 'huebot'
|
7
7
|
|
8
8
|
Huebot::CLI::Helpers.tap { |cli|
|
9
9
|
case cli.get_cmd
|
10
10
|
when :ls
|
11
|
+
cli.get_args!(num: 0)
|
12
|
+
opts = cli.get_opts!
|
11
13
|
bridge, error = Huebot::Bridge.connect
|
12
14
|
if error
|
13
15
|
$stderr.puts error
|
14
16
|
exit 1
|
15
17
|
end
|
16
|
-
|
18
|
+
|
19
|
+
retval = Huebot::CLI::Runner.ls(bridge.lights, bridge.groups, opts)
|
17
20
|
exit retval
|
18
21
|
|
19
22
|
when :run
|
20
|
-
opts
|
23
|
+
opts = cli.get_opts!
|
24
|
+
sources = cli.get_input! opts
|
21
25
|
if sources.empty?
|
22
26
|
cli.help!
|
23
27
|
exit 1
|
@@ -28,26 +32,35 @@ Huebot::CLI::Helpers.tap { |cli|
|
|
28
32
|
$stderr.puts error
|
29
33
|
exit 1
|
30
34
|
end
|
31
|
-
|
35
|
+
|
36
|
+
retval = Huebot::CLI::Runner.run(sources, bridge.lights, bridge.groups, opts)
|
32
37
|
exit retval
|
33
38
|
|
34
39
|
when :check
|
35
|
-
opts
|
40
|
+
opts = cli.get_opts!
|
41
|
+
sources = cli.get_input! opts
|
36
42
|
if sources.empty?
|
37
43
|
cli.help!
|
38
44
|
exit 1
|
39
45
|
end
|
40
46
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
+
lights, groups =
|
48
|
+
if opts.no_device_check
|
49
|
+
[[], []]
|
50
|
+
else
|
51
|
+
bridge, error = Huebot::Bridge.connect
|
52
|
+
if error
|
53
|
+
$stderr.puts error
|
54
|
+
exit 1
|
55
|
+
end
|
56
|
+
[bridge.lights, bridge.groups]
|
57
|
+
end
|
58
|
+
|
59
|
+
retval = Huebot::CLI::Runner.check(sources, lights, groups, opts)
|
47
60
|
exit retval
|
48
61
|
|
49
62
|
when :"get-state"
|
50
|
-
opts
|
63
|
+
opts = cli.get_opts!
|
51
64
|
if opts.inputs.empty?
|
52
65
|
cli.help!
|
53
66
|
exit 1
|
@@ -58,22 +71,29 @@ Huebot::CLI::Helpers.tap { |cli|
|
|
58
71
|
$stderr.puts error
|
59
72
|
exit 1
|
60
73
|
end
|
61
|
-
|
74
|
+
|
75
|
+
retval = Huebot::CLI::Runner.get_state(bridge.lights, bridge.groups, opts)
|
62
76
|
exit retval
|
63
77
|
|
64
78
|
when :"set-ip"
|
65
|
-
|
66
|
-
|
79
|
+
opts = cli.get_opts!
|
80
|
+
ip = cli.get_args!(num: 1).first
|
81
|
+
config = Huebot::CLI::Config.new
|
82
|
+
retval = Huebot::CLI::Runner.set_ip config, ip, opts
|
67
83
|
exit retval
|
68
84
|
|
69
85
|
when :"clear-ip"
|
70
|
-
cli.
|
71
|
-
|
86
|
+
opts = cli.get_opts!
|
87
|
+
cli.get_args!(num: 0)
|
88
|
+
config = Huebot::CLI::Config.new
|
89
|
+
retval = Huebot::CLI::Runner.clear_ip config, opts
|
72
90
|
exit retval
|
73
91
|
|
74
92
|
when :unregister
|
75
|
-
cli.
|
76
|
-
|
93
|
+
opts = cli.get_opts!
|
94
|
+
cli.get_args!(num: 0)
|
95
|
+
config = Huebot::CLI::Config.new
|
96
|
+
retval = Huebot::CLI::Runner.unregister config, opts
|
77
97
|
exit retval
|
78
98
|
|
79
99
|
else cli.help!
|
data/lib/huebot/bot.rb
CHANGED
@@ -1,14 +1,18 @@
|
|
1
1
|
module Huebot
|
2
|
+
# The Huebot runtime
|
2
3
|
class Bot
|
3
4
|
Error = Class.new(StandardError)
|
4
5
|
|
5
|
-
def initialize(device_mapper)
|
6
|
+
def initialize(device_mapper, waiter: nil, logger: nil)
|
6
7
|
@device_mapper = device_mapper
|
7
|
-
|
8
|
+
@logger = logger || Logging::NullLogger.new
|
9
|
+
@waiter = waiter || Waiter
|
8
10
|
end
|
9
11
|
|
10
12
|
def execute(program)
|
13
|
+
@logger.log :start, {program: program.name}
|
11
14
|
exec program.data
|
15
|
+
@logger.log :stop, {program: program.name}
|
12
16
|
end
|
13
17
|
|
14
18
|
private
|
@@ -30,10 +34,13 @@ module Huebot
|
|
30
34
|
def transition(state, device_refs, sleep_time = nil)
|
31
35
|
time = (state["transitiontime"] || 4).to_f / 10
|
32
36
|
devices = map_devices device_refs
|
37
|
+
@logger.log :transition, {devices: devices.map(&:name)}
|
38
|
+
|
33
39
|
devices.map { |device|
|
34
40
|
Thread.new {
|
35
41
|
# TODO error handling
|
36
|
-
device.set_state state
|
42
|
+
_res = device.set_state state
|
43
|
+
@logger.log :set_state, {device: device.name, state: state, result: nil}
|
37
44
|
wait time
|
38
45
|
}
|
39
46
|
}.map(&:join)
|
@@ -41,8 +48,9 @@ module Huebot
|
|
41
48
|
end
|
42
49
|
|
43
50
|
def serial(nodes, lp, sleep_time = nil)
|
44
|
-
control_loop(lp) {
|
45
|
-
|
51
|
+
control_loop(lp) { |loop_type|
|
52
|
+
@logger.log :serial, {loop: loop_type}
|
53
|
+
nodes.each { |node|
|
46
54
|
exec node
|
47
55
|
}
|
48
56
|
}
|
@@ -50,7 +58,8 @@ module Huebot
|
|
50
58
|
end
|
51
59
|
|
52
60
|
def parallel(nodes, lp, sleep_time = nil)
|
53
|
-
control_loop(lp) {
|
61
|
+
control_loop(lp) { |loop_type|
|
62
|
+
@logger.log :parallel, {loop: loop_type}
|
54
63
|
nodes.map { |node|
|
55
64
|
Thread.new {
|
56
65
|
# TODO error handling
|
@@ -64,19 +73,27 @@ module Huebot
|
|
64
73
|
def control_loop(lp)
|
65
74
|
case lp
|
66
75
|
when Program::AST::InfiniteLoop
|
67
|
-
loop {
|
76
|
+
loop {
|
77
|
+
yield :infinite
|
78
|
+
wait lp.pause if lp.pause
|
79
|
+
}
|
68
80
|
when Program::AST::CountedLoop
|
69
|
-
lp.n.times {
|
81
|
+
lp.n.times {
|
82
|
+
yield :counted
|
83
|
+
wait lp.pause if lp.pause
|
84
|
+
}
|
70
85
|
when Program::AST::DeadlineLoop
|
71
86
|
until Time.now >= lp.stop_time
|
72
|
-
yield
|
87
|
+
yield :deadline
|
88
|
+
wait lp.pause if lp.pause
|
73
89
|
end
|
74
90
|
when Program::AST::TimerLoop
|
75
91
|
sec = ((lp.hours * 60) + lp.minutes) * 60
|
76
92
|
time = 0
|
77
93
|
until time >= sec
|
78
94
|
start = Time.now
|
79
|
-
yield
|
95
|
+
yield :timer
|
96
|
+
wait lp.pause if lp.pause
|
80
97
|
time += (Time.now - start).round
|
81
98
|
end
|
82
99
|
else
|
@@ -102,8 +119,14 @@ module Huebot
|
|
102
119
|
end
|
103
120
|
|
104
121
|
def wait(seconds)
|
105
|
-
|
106
|
-
|
122
|
+
@logger.log :pause, {time: seconds}
|
123
|
+
@waiter.call seconds
|
124
|
+
end
|
125
|
+
|
126
|
+
module Waiter
|
127
|
+
def self.call(seconds)
|
128
|
+
sleep seconds
|
129
|
+
end
|
107
130
|
end
|
108
131
|
end
|
109
132
|
end
|
data/lib/huebot/bridge.rb
CHANGED
@@ -1,24 +1,21 @@
|
|
1
1
|
module Huebot
|
2
2
|
class Bridge
|
3
|
-
def self.connect(
|
4
|
-
client = Client.new(config)
|
3
|
+
def self.connect(client = Client.new)
|
5
4
|
error = client.connect
|
6
5
|
return nil, error if error
|
7
6
|
return new(client)
|
8
7
|
end
|
9
8
|
|
10
|
-
attr_reader :client
|
11
|
-
|
12
9
|
def initialize(client)
|
13
10
|
@client = client
|
14
11
|
end
|
15
12
|
|
16
13
|
def lights
|
17
|
-
client.get!("/lights").map { |(id, attrs)| Light.new(client, id, attrs) }
|
14
|
+
@client.get!("/lights").map { |(id, attrs)| Light.new(@client, id, attrs) }
|
18
15
|
end
|
19
16
|
|
20
17
|
def groups
|
21
|
-
client.get!("/groups").map { |(id, attrs)| Group.new(client, id, attrs) }
|
18
|
+
@client.get!("/groups").map { |(id, attrs)| Group.new(@client, id, attrs) }
|
22
19
|
end
|
23
20
|
end
|
24
21
|
end
|
@@ -8,8 +8,9 @@ module Huebot
|
|
8
8
|
#
|
9
9
|
# @attr inputs [Array<String>]
|
10
10
|
#
|
11
|
-
Options = Struct.new(:inputs, :read_stdin)
|
11
|
+
Options = Struct.new(:inputs, :read_stdin, :debug, :no_device_check, :stdin, :stdout, :stderr, :bot_waiter)
|
12
12
|
|
13
|
+
autoload :Config, 'huebot/cli/config'
|
13
14
|
autoload :Helpers, 'huebot/cli/helpers'
|
14
15
|
autoload :Runner, 'huebot/cli/runner'
|
15
16
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'yaml'
|
3
|
+
|
4
|
+
module Huebot
|
5
|
+
module CLI
|
6
|
+
class Config
|
7
|
+
def initialize(path = "~/.config/huebot")
|
8
|
+
@path = File.expand_path(path)
|
9
|
+
@dir = File.dirname(@path)
|
10
|
+
@dir_exists = File.exist? @dir
|
11
|
+
reload
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](attr)
|
15
|
+
@config[attr.to_s]
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(attr, val)
|
19
|
+
if val.nil?
|
20
|
+
@config.delete(attr.to_s)
|
21
|
+
else
|
22
|
+
@config[attr.to_s] = val
|
23
|
+
end
|
24
|
+
write
|
25
|
+
end
|
26
|
+
|
27
|
+
def clear
|
28
|
+
@config.clear
|
29
|
+
write
|
30
|
+
end
|
31
|
+
|
32
|
+
def reload
|
33
|
+
@config = File.exist?(@path) ? YAML.load_file(@path) : {}
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def write
|
39
|
+
unless @dir_exists
|
40
|
+
FileUtils.mkdir_p @dir
|
41
|
+
@dir_exists = true
|
42
|
+
end
|
43
|
+
File.write(@path, YAML.dump(@config))
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/huebot/cli/helpers.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'yaml'
|
3
|
+
require 'json'
|
3
4
|
|
4
5
|
module Huebot
|
5
6
|
module CLI
|
@@ -9,65 +10,80 @@ module Huebot
|
|
9
10
|
#
|
10
11
|
# @return [Symbol]
|
11
12
|
#
|
12
|
-
def self.get_cmd
|
13
|
-
|
13
|
+
def self.get_cmd(argv = ARGV)
|
14
|
+
argv[0].to_s.to_sym
|
14
15
|
end
|
15
16
|
|
16
|
-
def self.get_args(min: nil, max: nil, num: nil)
|
17
|
-
args =
|
17
|
+
def self.get_args!(argv = ARGV, min: nil, max: nil, num: nil)
|
18
|
+
args, error = get_args(argv, min: min, max: max, num: num)
|
19
|
+
if error
|
20
|
+
$stderr.puts error
|
21
|
+
exit 1
|
22
|
+
end
|
23
|
+
args
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.get_args(argv = ARGV, min: nil, max: nil, num: nil)
|
27
|
+
args = argv[1..]
|
18
28
|
if num
|
19
29
|
if num != args.size
|
20
|
-
|
21
|
-
exit 1
|
30
|
+
return nil, "Expected #{num} args, found #{args.size}"
|
22
31
|
end
|
23
32
|
elsif min and max
|
24
33
|
if args.size < min or args.size > max
|
25
|
-
|
34
|
+
return nil, "Expected #{min}-#{max} args, found #{args.size}"
|
26
35
|
end
|
27
36
|
elsif min
|
28
37
|
if args.size < min
|
29
|
-
|
30
|
-
exit 1
|
38
|
+
return nil, "Expected at least #{min} args, found #{args.size}"
|
31
39
|
end
|
32
40
|
elsif max
|
33
41
|
if args.size > max
|
34
|
-
|
35
|
-
exit 1
|
42
|
+
return nil, "Expected no more than #{max} args, found #{args.size}"
|
36
43
|
end
|
37
44
|
end
|
38
|
-
args
|
45
|
+
return args, nil
|
39
46
|
end
|
40
47
|
|
41
48
|
#
|
42
49
|
# Parses and returns input from the CLI. Serious errors might result in the program exiting.
|
43
50
|
#
|
44
|
-
# @
|
51
|
+
# @param opts [Huebot::CLI::Options] All given CLI options
|
45
52
|
# @return [Array<Huebot::Program::Src>] Array of given program sources
|
46
53
|
#
|
47
|
-
def self.get_input!
|
48
|
-
|
49
|
-
parser.parse!
|
50
|
-
|
51
|
-
files = ARGV[1..-1]
|
54
|
+
def self.get_input!(opts, argv = ARGV)
|
55
|
+
files = argv[1..-1]
|
52
56
|
if (bad_paths = files.select { |p| !File.exist? p }).any?
|
53
|
-
|
54
|
-
|
57
|
+
opts.stderr.puts "Cannot find #{bad_paths.join ', '}"
|
58
|
+
return []
|
55
59
|
end
|
56
60
|
|
57
61
|
sources = files.map { |path|
|
58
|
-
|
62
|
+
ext = File.extname path
|
63
|
+
src =
|
64
|
+
case ext
|
65
|
+
when ".yaml", ".yml"
|
66
|
+
YAML.safe_load(File.read path) || {}
|
67
|
+
when ".json"
|
68
|
+
JSON.load(File.read path) || {}
|
69
|
+
else
|
70
|
+
opts.stderr.puts "Unknown file extension '#{ext}'. Expected .yaml, .yml, or .json"
|
71
|
+
return []
|
72
|
+
end
|
59
73
|
version = (src.delete("version") || 1.0).to_f
|
60
74
|
Program::Src.new(src, path, version)
|
61
75
|
}
|
62
76
|
|
63
|
-
if
|
64
|
-
puts "Please enter your YAML Huebot program below, followed by Ctrl+d:" if
|
65
|
-
|
66
|
-
|
77
|
+
if !opts.stdin.isatty or opts.read_stdin
|
78
|
+
opts.stdout.puts "Please enter your YAML or JSON Huebot program below, followed by Ctrl+d:" if opts.read_stdin
|
79
|
+
raw = opts.stdin.read.lstrip
|
80
|
+
src = raw[0] == "{" ? JSON.load(raw) : YAML.safe_load(raw)
|
81
|
+
|
82
|
+
opts.stdout.puts "Executing..." if opts.read_stdin
|
67
83
|
version = (src.delete("version") || 1.0).to_f
|
68
84
|
sources << Program::Src.new(src, "STDIN", version)
|
69
85
|
end
|
70
|
-
|
86
|
+
sources
|
71
87
|
end
|
72
88
|
|
73
89
|
#
|
@@ -95,27 +111,35 @@ module Huebot
|
|
95
111
|
|
96
112
|
all_lights = programs.reduce([]) { |acc, p| acc + p.light_names }
|
97
113
|
if (missing_lights = device_mapper.missing_lights all_lights).any?
|
98
|
-
print_messages! io, "Unknown lights", missing_lights
|
114
|
+
print_messages! io, "Unknown lights", missing_lights unless quiet
|
99
115
|
end
|
100
116
|
|
101
117
|
all_groups = programs.reduce([]) { |acc, p| acc + p.group_names }
|
102
118
|
if (missing_groups = device_mapper.missing_groups all_groups).any?
|
103
|
-
print_messages! io, "Unknown groups", missing_groups
|
119
|
+
print_messages! io, "Unknown groups", missing_groups unless quiet
|
104
120
|
end
|
105
121
|
|
106
122
|
all_vars = programs.reduce([]) { |acc, p| acc + p.device_refs }
|
107
123
|
if (missing_vars = device_mapper.missing_vars all_vars).any?
|
108
|
-
print_messages! io, "Unknown device inputs", missing_vars.map { |d| "$#{d}" }
|
124
|
+
print_messages! io, "Unknown device inputs", missing_vars.map { |d| "$#{d}" } unless quiet
|
109
125
|
end
|
110
126
|
|
111
127
|
invalid_devices = missing_lights.size + missing_groups.size + missing_vars.size
|
112
128
|
return invalid_progs.any?, imperfect_progs.any?, invalid_devices > 0
|
113
129
|
end
|
114
130
|
|
131
|
+
def self.get_opts!
|
132
|
+
opts = default_options
|
133
|
+
parser = option_parser opts
|
134
|
+
parser.parse!
|
135
|
+
opts
|
136
|
+
end
|
137
|
+
|
115
138
|
# Print help and exit
|
116
139
|
def self.help!
|
117
|
-
|
118
|
-
|
140
|
+
opts = default_options
|
141
|
+
parser = option_parser opts
|
142
|
+
opts.stdout.puts parser.help
|
119
143
|
exit 1
|
120
144
|
end
|
121
145
|
|
@@ -135,15 +159,14 @@ module Huebot
|
|
135
159
|
}
|
136
160
|
end
|
137
161
|
|
138
|
-
def self.option_parser
|
139
|
-
|
140
|
-
parser = OptionParser.new { |opts|
|
162
|
+
def self.option_parser(options)
|
163
|
+
OptionParser.new { |opts|
|
141
164
|
opts.banner = %(
|
142
165
|
List all lights and groups:
|
143
166
|
huebot ls
|
144
167
|
|
145
168
|
Run program(s):
|
146
|
-
huebot run prog1.yaml [prog2.
|
169
|
+
huebot run prog1.yaml [prog2.yml [prog3.json ...]] [options]
|
147
170
|
|
148
171
|
Run program from STDIN:
|
149
172
|
cat prog1.yaml | huebot run [options]
|
@@ -168,9 +191,18 @@ module Huebot
|
|
168
191
|
opts.on("-lLIGHT", "--light=LIGHT", "Light ID or name") { |l| options.inputs << Light::Input.new(l) }
|
169
192
|
opts.on("-gGROUP", "--group=GROUP", "Group ID or name") { |g| options.inputs << Group::Input.new(g) }
|
170
193
|
opts.on("-i", "Read program from STDIN") { options.read_stdin = true }
|
171
|
-
opts.on("
|
194
|
+
opts.on("--debug", "Print debug info during run") { options.debug = true }
|
195
|
+
opts.on("--no-device-check", "Don't validate devices against the Bridge ('check' cmd only)") { options.no_device_check = true }
|
196
|
+
opts.on("-h", "--help", "Prints this help") { options.stdout.puts opts; exit }
|
172
197
|
}
|
173
|
-
|
198
|
+
end
|
199
|
+
|
200
|
+
def self.default_options
|
201
|
+
options = Options.new([], false)
|
202
|
+
options.stdin = $stdin
|
203
|
+
options.stdout = $stdout
|
204
|
+
options.stderr = $stderr
|
205
|
+
options
|
174
206
|
end
|
175
207
|
end
|
176
208
|
end
|
data/lib/huebot/cli/runner.rb
CHANGED
@@ -1,76 +1,84 @@
|
|
1
1
|
module Huebot
|
2
2
|
module CLI
|
3
3
|
module Runner
|
4
|
-
def self.ls(
|
5
|
-
puts "Lights\n" +
|
6
|
-
"\nGroups\n" +
|
4
|
+
def self.ls(lights, groups, opts)
|
5
|
+
opts.stdout.puts "Lights\n" + lights.map { |l| " #{l.id}: #{l.name}" }.join("\n") + \
|
6
|
+
"\nGroups\n" + groups.map { |g| " #{g.id}: #{g.name}" }.join("\n")
|
7
7
|
return 0
|
8
8
|
rescue ::Huebot::Error => e
|
9
|
-
|
9
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
10
10
|
return 1
|
11
11
|
end
|
12
12
|
|
13
|
-
def self.run(
|
14
|
-
device_mapper = Huebot::DeviceMapper.new(bridge, opts.inputs)
|
13
|
+
def self.run(sources, lights, groups, opts)
|
15
14
|
programs = sources.map { |src|
|
16
15
|
Huebot::Compiler.build src
|
17
16
|
}
|
18
|
-
|
17
|
+
device_mapper = Huebot::DeviceMapper.new(lights: lights, groups: groups, inputs: opts.inputs)
|
18
|
+
found_errors, _found_warnings, missing_devices = Helpers.check! programs, device_mapper, opts.stderr
|
19
19
|
return 1 if found_errors || missing_devices
|
20
20
|
|
21
|
-
|
21
|
+
logger = opts.debug ? Logging::IOLogger.new(opts.stdout) : nil
|
22
|
+
bot = Huebot::Bot.new(device_mapper, logger: logger, waiter: opts.bot_waiter)
|
22
23
|
programs.each { |prog| bot.execute prog }
|
23
24
|
return 0
|
24
25
|
rescue ::Huebot::Error => e
|
25
|
-
|
26
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
26
27
|
return 1
|
27
28
|
end
|
28
29
|
|
29
|
-
def self.check(
|
30
|
-
device_mapper = Huebot::DeviceMapper.new(bridge, opts.inputs)
|
30
|
+
def self.check(sources, lights, groups, opts)
|
31
31
|
programs = sources.map { |src|
|
32
32
|
Huebot::Compiler.build src
|
33
33
|
}
|
34
|
-
|
34
|
+
|
35
|
+
# Assume all devices and inputs are correct
|
36
|
+
if opts.no_device_check
|
37
|
+
light_input_names = opts.inputs.select { |i| i.is_a? Light::Input }.map(&:val)
|
38
|
+
lights = programs.reduce(light_input_names) { |acc, p| acc + p.light_names }.uniq.each_with_index.map { |name, i| Light.new(nil, i+1, {"name" => name}) }
|
39
|
+
|
40
|
+
group_input_names = opts.inputs.select { |i| i.is_a? Group::Input }.map(&:val)
|
41
|
+
groups = programs.reduce(group_input_names) { |acc, p| acc + p.group_names }.uniq.each_with_index.map { |name, i| Group.new(nil, i+1, {"name" => name}) }
|
42
|
+
end
|
43
|
+
|
44
|
+
device_mapper = Huebot::DeviceMapper.new(lights: lights, groups: groups, inputs: opts.inputs)
|
45
|
+
found_errors, found_warnings, missing_devices = Helpers.check! programs, device_mapper, opts.stderr
|
35
46
|
return (found_errors || found_warnings || missing_devices) ? 1 : 0
|
36
47
|
rescue ::Huebot::Error => e
|
37
|
-
|
48
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
38
49
|
return 1
|
39
50
|
end
|
40
51
|
|
41
|
-
def self.get_state(
|
42
|
-
device_mapper = Huebot::DeviceMapper.new(
|
52
|
+
def self.get_state(lights, groups, opts)
|
53
|
+
device_mapper = Huebot::DeviceMapper.new(lights: lights, groups: groups, inputs: opts.inputs)
|
43
54
|
device_mapper.each do |device|
|
44
|
-
puts device.name
|
45
|
-
puts " #{device.get_state}"
|
55
|
+
opts.stdout.puts device.name
|
56
|
+
opts.stdout.puts " #{device.get_state}"
|
46
57
|
end
|
47
58
|
0
|
48
59
|
end
|
49
60
|
|
50
|
-
def self.set_ip
|
51
|
-
config = Huebot::Config.new
|
61
|
+
def self.set_ip(config, ip, opts)
|
52
62
|
config["ip"] = ip
|
53
63
|
0
|
54
64
|
rescue ::Huebot::Error => e
|
55
|
-
|
65
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
56
66
|
return 1
|
57
67
|
end
|
58
68
|
|
59
|
-
def self.clear_ip
|
60
|
-
config = Huebot::Config.new
|
69
|
+
def self.clear_ip(config, opts)
|
61
70
|
config["ip"] = nil
|
62
71
|
0
|
63
72
|
rescue ::Huebot::Error => e
|
64
|
-
|
73
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
65
74
|
return 1
|
66
75
|
end
|
67
76
|
|
68
|
-
def self.unregister
|
69
|
-
config = Huebot::Config.new
|
77
|
+
def self.unregister(config, opts)
|
70
78
|
config.clear
|
71
79
|
0
|
72
80
|
rescue ::Huebot::Error => e
|
73
|
-
|
81
|
+
opts.stderr.puts "#{e.class.name}: #{e.message}"
|
74
82
|
return 1
|
75
83
|
end
|
76
84
|
end
|
data/lib/huebot/client.rb
CHANGED
data/lib/huebot/device_mapper.rb
CHANGED
@@ -2,20 +2,18 @@ module Huebot
|
|
2
2
|
class DeviceMapper
|
3
3
|
Unmapped = Class.new(Error)
|
4
4
|
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
|
8
|
-
@
|
9
|
-
@
|
10
|
-
@groups_by_id = all_groups.reduce({}) { |a, g| a[g.id] = g; a }
|
11
|
-
@groups_by_name = all_groups.reduce({}) { |a, g| a[g.name] = g; a }
|
5
|
+
def initialize(lights: [], groups:[], inputs: [])
|
6
|
+
@lights_by_id = lights.each_with_object({}) { |l, a| a[l.id] = l }
|
7
|
+
@lights_by_name = lights.each_with_object({}) { |l, a| a[l.name] = l }
|
8
|
+
@groups_by_id = groups.each_with_object({}) { |g, a| a[g.id] = g }
|
9
|
+
@groups_by_name = groups.each_with_object({}) { |g, a| a[g.name] = g }
|
12
10
|
@devices_by_var = inputs.each_with_index.each_with_object({}) { |(x, idx), obj|
|
13
11
|
obj[idx + 1] =
|
14
12
|
case x
|
15
13
|
when Light::Input then @lights_by_id[x.val.to_i] || @lights_by_name[x.val]
|
16
14
|
when Group::Input then @groups_by_id[x.val.to_i] || @groups_by_name[x.val]
|
17
15
|
else raise Error, "Invalid input: #{x}"
|
18
|
-
end || raise(Unmapped, "Could not find #{x.class.name[8..-
|
16
|
+
end || raise(Unmapped, "Could not find #{x.class.name[8..-8].downcase} with id or name '#{x.val}'")
|
19
17
|
}
|
20
18
|
@all = @devices_by_var.values
|
21
19
|
end
|
data/lib/huebot/group.rb
CHANGED
data/lib/huebot/light.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Huebot
|
2
|
+
module Logging
|
3
|
+
class CollectingLogger
|
4
|
+
attr_reader :events
|
5
|
+
|
6
|
+
def initialize
|
7
|
+
@events = []
|
8
|
+
@mut = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(event_type, data = {})
|
12
|
+
now = Time.now
|
13
|
+
@mut.synchronize {
|
14
|
+
@events << [now, event_type, data]
|
15
|
+
}
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Huebot
|
4
|
+
module Logging
|
5
|
+
class IOLogger
|
6
|
+
def initialize(io)
|
7
|
+
@io = io
|
8
|
+
@mut = Mutex.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def log(event_type, data = {})
|
12
|
+
ts = Time.now.iso8601
|
13
|
+
@mut.synchronize {
|
14
|
+
@io.puts "#{ts} #{event_type} #{data.to_json}"
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/huebot/version.rb
CHANGED
data/lib/huebot.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
module Huebot
|
2
2
|
Error = Class.new(StandardError)
|
3
3
|
|
4
|
-
autoload :Config, 'huebot/config'
|
5
4
|
autoload :Client, 'huebot/client'
|
6
|
-
autoload :CLI, 'huebot/cli'
|
5
|
+
autoload :CLI, 'huebot/cli/cli'
|
7
6
|
autoload :Bridge, 'huebot/bridge'
|
8
7
|
autoload :DeviceState, 'huebot/device_state'
|
9
8
|
autoload :Light, 'huebot/light'
|
10
9
|
autoload :Group, 'huebot/group'
|
11
10
|
autoload :DeviceMapper, 'huebot/device_mapper'
|
12
11
|
autoload :Program, 'huebot/program'
|
13
|
-
autoload :Compiler, 'huebot/compiler'
|
12
|
+
autoload :Compiler, 'huebot/compiler/compiler'
|
14
13
|
autoload :Bot, 'huebot/bot'
|
14
|
+
autoload :Logging, 'huebot/logging/logging'
|
15
15
|
autoload :VERSION, 'huebot/version'
|
16
16
|
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.
|
4
|
+
version: 1.2.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-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -50,17 +50,21 @@ files:
|
|
50
50
|
- lib/huebot.rb
|
51
51
|
- lib/huebot/bot.rb
|
52
52
|
- lib/huebot/bridge.rb
|
53
|
-
- lib/huebot/cli.rb
|
53
|
+
- lib/huebot/cli/cli.rb
|
54
|
+
- lib/huebot/cli/config.rb
|
54
55
|
- lib/huebot/cli/helpers.rb
|
55
56
|
- lib/huebot/cli/runner.rb
|
56
57
|
- lib/huebot/client.rb
|
57
|
-
- lib/huebot/compiler.rb
|
58
58
|
- lib/huebot/compiler/api_v1.rb
|
59
|
-
- lib/huebot/
|
59
|
+
- lib/huebot/compiler/compiler.rb
|
60
60
|
- lib/huebot/device_mapper.rb
|
61
61
|
- lib/huebot/device_state.rb
|
62
62
|
- lib/huebot/group.rb
|
63
63
|
- lib/huebot/light.rb
|
64
|
+
- lib/huebot/logging/collecting_logger.rb
|
65
|
+
- lib/huebot/logging/io_logger.rb
|
66
|
+
- lib/huebot/logging/logging.rb
|
67
|
+
- lib/huebot/logging/null_logger.rb
|
64
68
|
- lib/huebot/program.rb
|
65
69
|
- lib/huebot/version.rb
|
66
70
|
homepage: https://github.com/jhollinger/huebot
|
data/lib/huebot/config.rb
DELETED
@@ -1,41 +0,0 @@
|
|
1
|
-
require 'fileutils'
|
2
|
-
require 'yaml'
|
3
|
-
|
4
|
-
module Huebot
|
5
|
-
class Config
|
6
|
-
def initialize(path = "~/.config/huebot")
|
7
|
-
@path = File.expand_path(path)
|
8
|
-
@dir = File.dirname(@path)
|
9
|
-
@dir_exists = File.exist? @dir
|
10
|
-
@config = File.exist?(@path) ? YAML.load_file(@path) : {}
|
11
|
-
end
|
12
|
-
|
13
|
-
def [](attr)
|
14
|
-
@config[attr.to_s]
|
15
|
-
end
|
16
|
-
|
17
|
-
def []=(attr, val)
|
18
|
-
if val.nil?
|
19
|
-
@config.delete(attr.to_s)
|
20
|
-
else
|
21
|
-
@config[attr.to_s] = val
|
22
|
-
end
|
23
|
-
write
|
24
|
-
end
|
25
|
-
|
26
|
-
def clear
|
27
|
-
@config.clear
|
28
|
-
write
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def write
|
34
|
-
unless @dir_exists
|
35
|
-
FileUtils.mkdir_p @dir
|
36
|
-
@dir_exists = true
|
37
|
-
end
|
38
|
-
File.write(@path, YAML.dump(@config))
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
File without changes
|