huebot 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +46 -0
- data/bin/huebot +48 -0
- data/lib/huebot.rb +31 -0
- data/lib/huebot/bot.rb +66 -0
- data/lib/huebot/cli.rb +118 -0
- data/lib/huebot/compiler.rb +159 -0
- data/lib/huebot/device_mapper.rb +51 -0
- data/lib/huebot/program.rb +32 -0
- data/lib/huebot/version.rb +4 -0
- metadata +67 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 565062504fb9277b5047788566d50d5ea36223567f9ad5d8466a7ffa7886b661
|
4
|
+
data.tar.gz: 31d364292a69ffa6193ead6699d4b0642249c686a4917e040acf6e42408e9d02
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: cbaaa7f8beb87c9443a697957766338d961ae08602e8472298d80234945b152e101099fa8ad5edae3226b86fe7c6a7528273034a29680a02f9d40c5ab99f4b34
|
7
|
+
data.tar.gz: e31b3ed57ccd4f47c64841c2707d067776d89f5f7c7b45c26393de0928f321f0a18d3c5fc7a89c7e432abea401f8262891d0b6b770dcfcc2b21ec9e0d99aa14f
|
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Huebot
|
2
|
+
|
3
|
+
Orchestration and automation for Philips Hue devices. Huebot can be used as a Ruby library or a command line utility. Huebot programs are declared as YAML files.
|
4
|
+
|
5
|
+
$ huebot run dimmer.yml --light="Office Desk"
|
6
|
+
|
7
|
+
**dimmer.yml**
|
8
|
+
|
9
|
+
This (very simple) program starts with the light(s) on at full brightness, then enters an infinite loop of slowly dimming and raising the light(s). Since no color is specified, the light(s) will retain whatever color they last had.
|
10
|
+
|
11
|
+
## Install
|
12
|
+
|
13
|
+
gem install huebot
|
14
|
+
|
15
|
+
The curl library headers are required. On Ubuntu they can be installed with `apt-get install libcurl4-openssl-dev`.
|
16
|
+
|
17
|
+
```yaml
|
18
|
+
initial:
|
19
|
+
switch: on
|
20
|
+
brightness: 254
|
21
|
+
device: $all
|
22
|
+
|
23
|
+
loop: true
|
24
|
+
|
25
|
+
transitions:
|
26
|
+
- device: $all
|
27
|
+
brightness: 150
|
28
|
+
time: 100
|
29
|
+
wait: 20
|
30
|
+
|
31
|
+
- device: $all
|
32
|
+
brightness: 254
|
33
|
+
time: 100
|
34
|
+
wait: 20
|
35
|
+
```
|
36
|
+
|
37
|
+
The variable `$all` refers to all lights and/or groups passed in on the command line. They can be also referred to individually as `$1`, `$2`, `$3`, etc. The names of lights and groups can also be hard-coded into your program. [See examples in the Wiki.](https://github.com/jhollinger/huebot/wiki)
|
38
|
+
|
39
|
+
## UNDER ACTIVE DEVELOPMENT
|
40
|
+
|
41
|
+
**TODO**
|
42
|
+
|
43
|
+
* Validate number of inputs against compiled programs
|
44
|
+
* Brief explanation various features
|
45
|
+
* Wiki entry with more examples
|
46
|
+
* Link to official Hue docs
|
data/bin/huebot
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# TODO remove
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
|
5
|
+
|
6
|
+
require 'huebot'
|
7
|
+
require 'huebot/cli'
|
8
|
+
|
9
|
+
Huebot::CLI.tap { |cli|
|
10
|
+
case cli.get_cmd
|
11
|
+
when :ls
|
12
|
+
client = Hue::Client.new
|
13
|
+
puts "Lights\n" + client.lights.map { |l| " #{l.id}: #{l.name}" }.join("\n") + \
|
14
|
+
"\nGroups\n" + client.groups.map { |g| " #{g.id}: #{g.name}" }.join("\n")
|
15
|
+
|
16
|
+
when :run
|
17
|
+
opts, sources = cli.get_input!
|
18
|
+
|
19
|
+
client = Hue::Client.new
|
20
|
+
device_mapper = Huebot::DeviceMapper.new(client, opts.inputs)
|
21
|
+
compiler = Huebot::Compiler.new(device_mapper)
|
22
|
+
|
23
|
+
programs = sources.map { |src|
|
24
|
+
compiler.build src.ir, File.basename(src.filepath, ".*")
|
25
|
+
}
|
26
|
+
found_errors, _found_warnings = cli.check! programs, $stderr
|
27
|
+
exit 1 if found_errors
|
28
|
+
|
29
|
+
bot = Huebot::Bot.new(client)
|
30
|
+
programs.each { |prog| bot.execute prog }
|
31
|
+
|
32
|
+
when :check
|
33
|
+
opts, sources = cli.get_input!
|
34
|
+
|
35
|
+
client = Hue::Client.new
|
36
|
+
device_mapper = Huebot::DeviceMapper.new(client, opts.inputs)
|
37
|
+
compiler = Huebot::Compiler.new(device_mapper)
|
38
|
+
|
39
|
+
programs = sources.map { |src|
|
40
|
+
compiler.build src.ir, File.basename(src.filepath, ".*")
|
41
|
+
}
|
42
|
+
found_errors, found_warnings = cli.check! programs, $stdout
|
43
|
+
# TODO validate NUMBER of inputs against each program
|
44
|
+
exit (found_errors || found_warnings) ? 1 : 0
|
45
|
+
|
46
|
+
else cli.help!
|
47
|
+
end
|
48
|
+
}
|
data/lib/huebot.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'hue'
|
2
|
+
|
3
|
+
module Huebot
|
4
|
+
autoload :DeviceMapper, 'huebot/device_mapper'
|
5
|
+
autoload :Program, 'huebot/program'
|
6
|
+
autoload :Compiler, 'huebot/compiler'
|
7
|
+
autoload :Bot, 'huebot/bot'
|
8
|
+
autoload :VERSION, 'huebot/version'
|
9
|
+
|
10
|
+
#
|
11
|
+
# Struct for storing a program's Intermediate Representation and source filepath.
|
12
|
+
#
|
13
|
+
# @attr ir [Hash]
|
14
|
+
# @attr filepath [String]
|
15
|
+
#
|
16
|
+
ProgramSrc = Struct.new(:ir, :filepath)
|
17
|
+
|
18
|
+
#
|
19
|
+
# Struct for specifying a Light input (id or name)
|
20
|
+
#
|
21
|
+
# @attr val [Integer|String] id or name
|
22
|
+
#
|
23
|
+
LightInput = Struct.new(:val)
|
24
|
+
|
25
|
+
#
|
26
|
+
# Struct for specifying a Gropu input (id or name)
|
27
|
+
#
|
28
|
+
# @attr val [Integer|String] id or name
|
29
|
+
#
|
30
|
+
GroupInput = Struct.new(:val)
|
31
|
+
end
|
data/lib/huebot/bot.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Huebot
|
2
|
+
class Bot
|
3
|
+
attr_reader :client
|
4
|
+
|
5
|
+
Error = Class.new(StandardError)
|
6
|
+
|
7
|
+
def initialize(client)
|
8
|
+
@client = client
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute(program)
|
12
|
+
transition program.initial_state if program.initial_state
|
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
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def iterate(transitions)
|
30
|
+
transitions.each do |t|
|
31
|
+
if t.respond_to?(:children)
|
32
|
+
parallel_transitions t
|
33
|
+
else
|
34
|
+
transition t
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def parallel_transitions(t)
|
40
|
+
t.children.map { |sub_t|
|
41
|
+
Thread.new {
|
42
|
+
transition sub_t
|
43
|
+
}
|
44
|
+
}.map(&:join)
|
45
|
+
wait t.wait if t.wait and t.wait > 0
|
46
|
+
end
|
47
|
+
|
48
|
+
def transition(t)
|
49
|
+
time = t.state[:transitiontime] || 4
|
50
|
+
t.devices.map { |device|
|
51
|
+
Thread.new {
|
52
|
+
device.set_state t.state
|
53
|
+
wait time
|
54
|
+
wait t.wait if t.wait
|
55
|
+
}
|
56
|
+
}.map(&:join)
|
57
|
+
end
|
58
|
+
|
59
|
+
def wait(time)
|
60
|
+
ms = time * 100
|
61
|
+
seconds = ms / 1000.to_f
|
62
|
+
# TODO sleep in small bursts in a loop so can detect if an Interrupt was caught
|
63
|
+
sleep seconds
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/huebot/cli.rb
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
require 'ostruct'
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module Huebot
|
6
|
+
#
|
7
|
+
# Helpers for running huebot in cli-mode.
|
8
|
+
#
|
9
|
+
module CLI
|
10
|
+
#
|
11
|
+
# Struct for storing cli options and program files.
|
12
|
+
#
|
13
|
+
# @attr inputs [Array<String>]
|
14
|
+
#
|
15
|
+
Options = Struct.new(:inputs)
|
16
|
+
|
17
|
+
#
|
18
|
+
# Returns the command given to huebot.
|
19
|
+
#
|
20
|
+
# @return [Symbol]
|
21
|
+
#
|
22
|
+
def self.get_cmd
|
23
|
+
ARGV[0].to_s.to_sym
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Parses and returns input from the CLI. Serious errors might result in the program exiting.
|
28
|
+
#
|
29
|
+
# @return [Huebot::CLI::Options] All given CLI options
|
30
|
+
# @return [Array<Huebot::ProgramSrc>] Array of given program sources
|
31
|
+
#
|
32
|
+
def self.get_input!
|
33
|
+
options, parser = option_parser
|
34
|
+
parser.parse!
|
35
|
+
|
36
|
+
files = ARGV[1..-1]
|
37
|
+
if files.empty?
|
38
|
+
puts parser.help
|
39
|
+
exit 1
|
40
|
+
elsif (bad_paths = files.select { |p| !File.exists? p }).any?
|
41
|
+
$stderr.puts "Cannot find #{bad_paths.join ', '}"
|
42
|
+
exit 1
|
43
|
+
else
|
44
|
+
return options, files.map { |path|
|
45
|
+
ProgramSrc.new(YAML.load_file(path), path)
|
46
|
+
}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Prints any program errors or warnings, and returns a boolean for each.
|
52
|
+
#
|
53
|
+
# @param programs [Array<Huebot::Program>]
|
54
|
+
# @param io [IO] Usually $stdout or $stderr
|
55
|
+
# @param quiet [Boolean] if true, don't print anything
|
56
|
+
#
|
57
|
+
def self.check!(programs, io, quiet: false)
|
58
|
+
if (invalid_progs = programs.select { |prog| prog.errors.any? }).any?
|
59
|
+
print_messages! io, "Errors", invalid_progs, :errors unless quiet
|
60
|
+
end
|
61
|
+
|
62
|
+
if (imperfect_progs = programs.select { |prog| prog.warnings.any? }).any?
|
63
|
+
puts "" if invalid_progs.any?
|
64
|
+
print_messages! io, "Warnings", imperfect_progs, :warnings unless quiet
|
65
|
+
end
|
66
|
+
|
67
|
+
return invalid_progs.any?, imperfect_progs.any?
|
68
|
+
end
|
69
|
+
|
70
|
+
# Print help and exit
|
71
|
+
def self.help!
|
72
|
+
_, parser = option_parser
|
73
|
+
puts parser.help
|
74
|
+
exit 1
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
#
|
80
|
+
# Print each message (of the given type) for each program.
|
81
|
+
#
|
82
|
+
# @param io [IO] Usually $stdout or $stderr
|
83
|
+
# @param label [String] Top-level for this group of messages
|
84
|
+
# @param progs [Array<Huebot::CLI::Program>]
|
85
|
+
# @param msg_type [Symbol] name of method that holds the messages (i.e. :errors or :warnings)
|
86
|
+
#
|
87
|
+
def self.print_messages!(io, label, progs, msg_type)
|
88
|
+
io.puts "#{label}:"
|
89
|
+
progs.each { |prog|
|
90
|
+
io.puts " #{prog.name}:"
|
91
|
+
prog.send(msg_type).each_with_index { |msg, i| io.puts " #{i+1}. #{msg}" }
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.option_parser
|
96
|
+
options = Options.new([])
|
97
|
+
parser = OptionParser.new { |opts|
|
98
|
+
opts.banner = %(
|
99
|
+
List all lights and groups:
|
100
|
+
huebot ls
|
101
|
+
|
102
|
+
Run program(s):
|
103
|
+
huebot run file1.yml [file2.yml [file3.yml ...]] [options]
|
104
|
+
|
105
|
+
Validate programs and inputs:
|
106
|
+
huebot check file1.yml [file2.yml [file3.yml ...]] [options]
|
107
|
+
|
108
|
+
Options:
|
109
|
+
).strip
|
110
|
+
opts.on("-lLIGHT", "--light=LIGHT", "Light ID or name") { |l| options.inputs << LightInput.new(l) }
|
111
|
+
opts.on("-gGROUP", "--group=GROUP", "Group ID or name") { |g| options.inputs << GroupInput.new(g) }
|
112
|
+
opts.on("--all", "All lights and groups TODO") { $stderr.puts "Not Implemented"; exit 1 }
|
113
|
+
opts.on("-h", "--help", "Prints this help") { puts opts; exit }
|
114
|
+
}
|
115
|
+
return options, parser
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
module Huebot
|
2
|
+
class Compiler
|
3
|
+
def initialize(device_mapper)
|
4
|
+
@device_mapper = device_mapper
|
5
|
+
end
|
6
|
+
|
7
|
+
#
|
8
|
+
# Build a huebot program from an intermediate representation (a Hash).
|
9
|
+
#
|
10
|
+
# @param ir [Hash]
|
11
|
+
# @param default_name [String] A name to use if one isn't specified
|
12
|
+
# @return [Huebot::Program]
|
13
|
+
#
|
14
|
+
def build(ir, default_name = nil)
|
15
|
+
ir = ir.clone
|
16
|
+
prog = Huebot::Program.new
|
17
|
+
prog.name = ir.delete("name") || default_name
|
18
|
+
|
19
|
+
# loop/loops
|
20
|
+
val_loop = ir.delete("loop") || ir.delete(:loop)
|
21
|
+
prog.errors << "'loop' must be 'true' or 'false'." if !val_loop.nil? and ![true, false].include?(val_loop)
|
22
|
+
prog.loop = val_loop == true
|
23
|
+
|
24
|
+
val_loops = ir.delete("loops") || ir.delete(:loops)
|
25
|
+
prog.errors << "'loops' must be a positive integer." if !val_loops.nil? and val_loops.to_i < 0
|
26
|
+
prog.loops = val_loops.to_i
|
27
|
+
|
28
|
+
prog.errors << "'loop' and 'loops' are mutually exclusive." if prog.loop? and prog.loops > 0
|
29
|
+
|
30
|
+
# initial state
|
31
|
+
if (val_init = ir.delete("initial") || ir.delete(:initial))
|
32
|
+
errors, warnings, state = build_transition val_init
|
33
|
+
prog.initial_state = state
|
34
|
+
prog.errors += errors
|
35
|
+
prog.warnings += warnings
|
36
|
+
end
|
37
|
+
|
38
|
+
# transitions
|
39
|
+
if (val_trns = ir.delete("transitions") || ir.delete(:transitions))
|
40
|
+
val_trns.each do |val_trn|
|
41
|
+
errors, warnings, state = if val_trn["parallel"] || val_trn[:parallel]
|
42
|
+
build_parallel_transition val_trn
|
43
|
+
else
|
44
|
+
build_transition val_trn
|
45
|
+
end
|
46
|
+
prog.transitions << state
|
47
|
+
prog.errors += errors
|
48
|
+
prog.warnings += warnings
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# final state
|
53
|
+
if (val_fnl = ir.delete("final") || ir.delete(:final))
|
54
|
+
errors, warnings, state = build_transition val_fnl
|
55
|
+
prog.final_state = state
|
56
|
+
prog.errors += errors
|
57
|
+
prog.warnings += warnings
|
58
|
+
end
|
59
|
+
|
60
|
+
# be strict about extra crap
|
61
|
+
if (unknown = ir.keys.map(&:to_s)).any?
|
62
|
+
prog.errors << "Unrecognized values: #{unknown.join ', '}."
|
63
|
+
end
|
64
|
+
|
65
|
+
# Add any warnings
|
66
|
+
prog.warnings << "'final' is defined but will never be reached because 'loop' is 'true'." if prog.final_state and prog.loop?
|
67
|
+
|
68
|
+
prog
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
def build_parallel_transition(t)
|
74
|
+
errors, warnings = [], []
|
75
|
+
transition = Huebot::Program::ParallelTransition.new(0, [])
|
76
|
+
|
77
|
+
transition.wait = t.delete("wait") || t.delete(:wait)
|
78
|
+
errors << "'wait' must be a positive integer." if transition.wait and transition.wait.to_i <= 0
|
79
|
+
|
80
|
+
parallel = t.delete("parallel") || t.delete(:parallel)
|
81
|
+
if !parallel.is_a? Array
|
82
|
+
errors << "'parallel' must be an array of transitions"
|
83
|
+
else
|
84
|
+
parallel.each do |sub_t|
|
85
|
+
sub_errors, sub_warnings, sub_transition = build_transition(sub_t)
|
86
|
+
errors += sub_errors
|
87
|
+
warnings += sub_warnings
|
88
|
+
transition.children << sub_transition
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
return errors, warnings, transition
|
93
|
+
end
|
94
|
+
|
95
|
+
def build_transition(t)
|
96
|
+
errors, warnings = [], []
|
97
|
+
transition = Huebot::Program::Transition.new
|
98
|
+
transition.devices = []
|
99
|
+
|
100
|
+
map_devices(t, :light, :lights, :light!) { |map_errors, devices|
|
101
|
+
errors += map_errors
|
102
|
+
transition.devices += devices
|
103
|
+
}
|
104
|
+
|
105
|
+
map_devices(t, :group, :groups, :group!) { |map_errors, devices|
|
106
|
+
errors += map_errors
|
107
|
+
transition.devices += devices
|
108
|
+
}
|
109
|
+
|
110
|
+
map_devices(t, :device, :devices, :var!) { |map_errors, devices|
|
111
|
+
errors += map_errors
|
112
|
+
transition.devices += devices
|
113
|
+
}
|
114
|
+
errors << "Missing light/lights, group/groups, or device/devices" if transition.devices.empty?
|
115
|
+
|
116
|
+
transition.wait = t.delete("wait") || t.delete(:wait)
|
117
|
+
errors << "'wait' must be a positive integer." if transition.wait and transition.wait.to_i <= 0
|
118
|
+
|
119
|
+
state = {}
|
120
|
+
switch = t.delete("switch")
|
121
|
+
switch = t.delete(:switch) if switch.nil?
|
122
|
+
if !switch.nil?
|
123
|
+
state[:on] = case switch
|
124
|
+
when true, :on then true
|
125
|
+
when false, :off then false
|
126
|
+
else
|
127
|
+
errors << "Unrecognized 'switch' value '#{switch}'."
|
128
|
+
nil
|
129
|
+
end
|
130
|
+
end
|
131
|
+
state[:transitiontime] = t.delete("time") || t.delete(:time) || t.delete("transitiontime") || t.delete(:transitiontime) || 4
|
132
|
+
|
133
|
+
transition.state = t.merge(state).reduce({}) { |a, (key, val)|
|
134
|
+
a[key.to_sym] = val
|
135
|
+
a
|
136
|
+
}
|
137
|
+
return errors, warnings, transition
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
def map_devices(t, singular_key, plural_key, ref_type)
|
143
|
+
errors, devices = [], []
|
144
|
+
|
145
|
+
key = t[singular_key.to_s] || t[singular_key]
|
146
|
+
keys = t[plural_key.to_s] || t[plural_key]
|
147
|
+
|
148
|
+
(Array(key) + Array(keys)).each { |x|
|
149
|
+
begin
|
150
|
+
devices += Array(@device_mapper.send(ref_type, x))
|
151
|
+
rescue Huebot::DeviceMapper::Unmapped => e
|
152
|
+
errors << e.message
|
153
|
+
end
|
154
|
+
}
|
155
|
+
|
156
|
+
yield errors, devices
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Huebot
|
2
|
+
class DeviceMapper
|
3
|
+
Unmapped = Class.new(StandardError)
|
4
|
+
|
5
|
+
def initialize(client, inputs = [])
|
6
|
+
all_lights, all_groups = client.lights, client.groups
|
7
|
+
|
8
|
+
@lights_by_id = all_lights.reduce({}) { |a, l| a[l.id] = l; a }
|
9
|
+
@lights_by_name = all_lights.reduce({}) { |a, l| a[l.name] = l; a }
|
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 }
|
12
|
+
@devices_by_var = inputs.each_with_index.reduce({}) { |a, (x, idx)|
|
13
|
+
dev = case x
|
14
|
+
when LightInput then @lights_by_id[x.val] || @lights_by_name[x.val]
|
15
|
+
when GroupInput then @groups_by_id[x.val] || @groups_by_name[x.val]
|
16
|
+
else raise "Invalid input: #{x}"
|
17
|
+
end || raise(Unmapped, "Could not find #{x.class.name[8..-6].downcase} with id or name '#{x.val}'")
|
18
|
+
a["$#{idx + 1}"] = dev
|
19
|
+
a
|
20
|
+
}
|
21
|
+
@all = @devices_by_var.values
|
22
|
+
end
|
23
|
+
|
24
|
+
def light!(id)
|
25
|
+
case id
|
26
|
+
when Integer
|
27
|
+
@lights_by_id[id]
|
28
|
+
when String
|
29
|
+
@lights_by_name[id]
|
30
|
+
end || (raise Unmapped, "Unmapped light '#{id}'")
|
31
|
+
end
|
32
|
+
|
33
|
+
def group!(id)
|
34
|
+
case id
|
35
|
+
when Integer
|
36
|
+
@groups_by_id[id]
|
37
|
+
when String
|
38
|
+
@groups_by_name[id]
|
39
|
+
end || (raise Unmapped, "Unmapped group '#{id}'")
|
40
|
+
end
|
41
|
+
|
42
|
+
def var!(id)
|
43
|
+
case id
|
44
|
+
when "$all"
|
45
|
+
@all
|
46
|
+
else
|
47
|
+
@devices_by_var[id]
|
48
|
+
end || (raise Unmapped, "Unmapped device '#{id}'")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Huebot
|
2
|
+
class Program
|
3
|
+
Transition = Struct.new(:wait, :state, :devices)
|
4
|
+
ParallelTransition = Struct.new(:wait, :children)
|
5
|
+
|
6
|
+
attr_accessor :name
|
7
|
+
attr_accessor :initial_state
|
8
|
+
attr_accessor :transitions
|
9
|
+
attr_accessor :final_state
|
10
|
+
attr_accessor :loop
|
11
|
+
attr_accessor :loops
|
12
|
+
attr_accessor :errors
|
13
|
+
attr_accessor :warnings
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@name = nil
|
17
|
+
@initial_state = nil
|
18
|
+
@transitions = []
|
19
|
+
@final_state = nil
|
20
|
+
@loop = false
|
21
|
+
@loops = 0
|
22
|
+
@errors = []
|
23
|
+
@warnings = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?
|
27
|
+
errors.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method :loop?, :loop
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: huebot
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordan Hollinger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-12-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: hue
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 0.2.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 0.2.0
|
27
|
+
description: Declare and run YAML programs for Philips Hue devices
|
28
|
+
email: jordan.hollinger@gmail.com
|
29
|
+
executables:
|
30
|
+
- huebot
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- README.md
|
35
|
+
- bin/huebot
|
36
|
+
- lib/huebot.rb
|
37
|
+
- lib/huebot/bot.rb
|
38
|
+
- lib/huebot/cli.rb
|
39
|
+
- lib/huebot/compiler.rb
|
40
|
+
- lib/huebot/device_mapper.rb
|
41
|
+
- lib/huebot/program.rb
|
42
|
+
- lib/huebot/version.rb
|
43
|
+
homepage: https://github.com/jhollinger/huebot
|
44
|
+
licenses:
|
45
|
+
- MIT
|
46
|
+
metadata: {}
|
47
|
+
post_install_message:
|
48
|
+
rdoc_options: []
|
49
|
+
require_paths:
|
50
|
+
- lib
|
51
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ">="
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 2.1.0
|
56
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
requirements: []
|
62
|
+
rubyforge_project:
|
63
|
+
rubygems_version: 2.7.6
|
64
|
+
signing_key:
|
65
|
+
specification_version: 4
|
66
|
+
summary: Orchestration for Hue devices
|
67
|
+
test_files: []
|