huebot 0.4.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 267bb3cc14e143785af2250a762b365f513bb18a3630b68edf10d3df3748de2a
4
- data.tar.gz: 7afa69b3aa54a936deebbcea75b409f2411fa5482554b22cd29aff088ce351b6
3
+ metadata.gz: 5da1b1dcd0f32ce09e44155b39d7bc6ed92aa285944fc3a67801cf02642737c0
4
+ data.tar.gz: 9116d3a891e608276e64517f9252a42cddd615ca9952c55e816ca53196bdde15
5
5
  SHA512:
6
- metadata.gz: 442d3fb05c7f1e6610437ff5fc2f92d25694e2e9342ff9b16217ba80b7982538d48f807304e487fd583385864e9f4cf86491e33d2c66ba663b22449867ba780c
7
- data.tar.gz: '01908c65fb9c472271904973e313bc080eb96d91b557ef2e8bb3902a5ea054f7fb71b3b37bf6f51f356bf742c5ab32a336e4c013059a0a537dc3a538955dfc97'
6
+ metadata.gz: fc1ae1f775c685e635fe36d85ef33548a50163f80378f42301f73e9bdf868343fecb4b9a9fa64490466771dc44b51f95745587bc32423e65cdc306dae0e01fa9
7
+ data.tar.gz: b5721fb115fc0f8c38e9389063a9012a51d7aa2fa0cf2f1b7a2d93812d916b2ffb1f27508358483a9656b4bdb09d7f78fc7bd9e4f65ff599214c9cdd2f0e8a93
data/README.md CHANGED
@@ -1,50 +1,120 @@
1
1
  # Huebot
2
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.
3
+ Program your Hue lights!
4
4
 
5
5
  $ huebot run dimmer.yml --light="Office Desk"
6
6
 
7
- **dimmer.yml**
7
+ A few examples are below. [See the Wiki](https://github.com/jhollinger/huebot/wiki) for full documentation.
8
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.
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
- initial:
13
- switch: on
14
- brightness: 254
15
- device: $all
16
-
17
- loop: true
18
-
19
- transitions:
20
- - device: $all
21
- brightness: 150
22
- time: 100
23
- wait: 20
24
-
25
- - device: $all
26
- brightness: 254
27
- time: 100
28
- wait: 20
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
- 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)
106
+ [See the Wiki](https://github.com/jhollinger/huebot/wiki) for more documentation and examples.
32
107
 
33
108
  ## Install
34
109
 
35
110
  gem install huebot
36
111
 
37
- ## License
38
-
39
- Huebot is licensed under the MIT license (see LICENSE file).
112
+ Having trouble with Hue Bridge auto discovery? Me too. If you know your bridge's IP (and ideally have assigned it a static one), you can set it manually:
40
113
 
41
- A patched version of the "hue" gem is bundled in huebot's codebase (to remove a dependency that's unnecessarily annoying to install). The license for it can be found at `lib/hue/LICENSE`.
114
+ huebot set-ip <your bridge's IP>
42
115
 
43
- ## UNDER ACTIVE DEVELOPMENT
116
+ Configuration is stored in `~/.config/huebot`.
44
117
 
45
- **TODO**
118
+ ## License
46
119
 
47
- * Validate number of inputs against compiled programs
48
- * Brief explanation various features
49
- * Wiki entry with more examples
50
- * Link to official Hue docs
120
+ Huebot is licensed under the MIT license (see LICENSE file).
data/bin/huebot CHANGED
@@ -1,47 +1,80 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  # Used for local testing
4
- # $LOAD_PATH.unshift File.join(File.dirname(__FILE__), '..', 'lib')
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
- 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")
11
+ bridge, error = Huebot::Bridge.connect
12
+ if error
13
+ $stderr.puts error
14
+ exit 1
15
+ end
16
+ retval = Huebot::CLI::Runner.ls bridge
17
+ exit retval
15
18
 
16
19
  when :run
17
20
  opts, sources = cli.get_input!
21
+ if sources.empty?
22
+ cli.help!
23
+ exit 1
24
+ end
18
25
 
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 }
26
+ bridge, error = Huebot::Bridge.connect
27
+ if error
28
+ $stderr.puts error
29
+ exit 1
30
+ end
31
+ retval = Huebot::CLI::Runner.run(bridge, sources, opts)
32
+ exit retval
31
33
 
32
34
  when :check
33
35
  opts, sources = cli.get_input!
36
+ if sources.empty?
37
+ cli.help!
38
+ exit 1
39
+ end
40
+
41
+ bridge, error = Huebot::Bridge.connect
42
+ if error
43
+ $stderr.puts error
44
+ exit 1
45
+ end
46
+ retval = Huebot::CLI::Runner.check(bridge, sources, opts)
47
+ exit retval
48
+
49
+ when :"get-state"
50
+ opts, _sources = cli.get_input!
51
+ if opts.inputs.empty?
52
+ cli.help!
53
+ exit 1
54
+ end
55
+
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
63
+
64
+ when :"set-ip"
65
+ ip = cli.get_args(num: 1).first
66
+ retval = Huebot::CLI::Runner.set_ip ip
67
+ exit retval
34
68
 
35
- client = Hue::Client.new
36
- device_mapper = Huebot::DeviceMapper.new(client, opts.inputs)
37
- compiler = Huebot::Compiler.new(device_mapper)
69
+ when :"clear-ip"
70
+ cli.get_args(num: 0)
71
+ retval = Huebot::CLI::Runner.unregister
72
+ exit retval
38
73
 
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
74
+ when :unregister
75
+ cli.get_args(num: 0)
76
+ retval = Huebot::CLI::Runner.unregister
77
+ exit retval
45
78
 
46
79
  else cli.help!
47
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(client)
8
- @client = client
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
- 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
11
+ exec program.data
25
12
  end
26
13
 
27
14
  private
28
15
 
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
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 parallel_transitions(t)
40
- t.children.map { |sub_t|
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
- transition sub_t
35
+ # TODO error handling
36
+ device.set_state state
37
+ wait time
43
38
  }
44
39
  }.map(&:join)
45
- wait t.wait if t.wait and t.wait > 0
40
+ wait sleep_time if sleep_time
46
41
  end
47
42
 
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
43
+ def serial(nodes, lp, sleep_time = nil)
44
+ control_loop(lp) {
45
+ nodes.map { |node|
46
+ exec node
55
47
  }
56
- }.map(&:join)
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(time)
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,24 @@
1
+ module Huebot
2
+ class Bridge
3
+ def self.connect(config = Huebot::Config.new)
4
+ client = Client.new(config)
5
+ error = client.connect
6
+ return nil, error if error
7
+ return new(client)
8
+ end
9
+
10
+ attr_reader :client
11
+
12
+ def initialize(client)
13
+ @client = client
14
+ end
15
+
16
+ def lights
17
+ client.get!("/lights").map { |(id, attrs)| Light.new(client, id, attrs) }
18
+ end
19
+
20
+ def groups
21
+ client.get!("/groups").map { |(id, attrs)| Group.new(client, id, attrs) }
22
+ end
23
+ end
24
+ 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