huebot 1.1.0 → 1.2.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 +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
|