alexa_hue 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b9fe3c650ed88c2c5cf706a1877915c87b97ecc1
4
+ data.tar.gz: 48b1157a1a2a04ab60b4f2c45fbda7621e873098
5
+ SHA512:
6
+ metadata.gz: e77f71eb3efe6ecd76f1d2caaff0c90a3a76f43489ed9c6b19b0b8a6ec7d6ba38b2a7c84b4e17bf4b1c2c6cb6b29929368b99ea56c50bb791d93e1b37c9cb63f
7
+ data.tar.gz: b90184d7747efcfce1f23f72d37d7749783609eb303ce93345e7528770437e6eb8edc0c439c8840580cf96399115e5fb8cf00244ecdad74129cbf684792cf57b
data/.DS_Store ADDED
Binary file
data/.gitignore ADDED
@@ -0,0 +1,139 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/tmp
15
+ test/version_tmp
16
+ tmp
17
+ *.bundle
18
+ *.so
19
+ *.o
20
+ *.a
21
+ mkmf.log
22
+
23
+ # Created by https://www.gitignore.io
24
+
25
+ ### RubyMine ###
26
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm
27
+
28
+ *.iml
29
+
30
+ ## Directory-based project format:
31
+ .idea/
32
+ # if you remove the above rule, at least ignore the following:
33
+
34
+ # User-specific stuff:
35
+ # .idea/workspace.xml
36
+ # .idea/tasks.xml
37
+ # .idea/dictionaries
38
+
39
+ # Sensitive or high-churn files:
40
+ # .idea/dataSources.ids
41
+ # .idea/dataSources.xml
42
+ # .idea/sqlDataSources.xml
43
+ # .idea/dynamic.xml
44
+ # .idea/uiDesigner.xml
45
+
46
+ # Gradle:
47
+ # .idea/gradle.xml
48
+ # .idea/libraries
49
+
50
+ # Mongo Explorer plugin:
51
+ # .idea/mongoSettings.xml
52
+
53
+ ## File-based project format:
54
+ *.ipr
55
+ *.iws
56
+
57
+ ## Plugin-specific files:
58
+
59
+ # IntelliJ
60
+ /out/
61
+
62
+ # mpeltonen/sbt-idea plugin
63
+ .idea_modules/
64
+
65
+ # JIRA plugin
66
+ atlassian-ide-plugin.xml
67
+
68
+ # Crashlytics plugin (for Android Studio and IntelliJ)
69
+ com_crashlytics_export_strings.xml
70
+ crashlytics.properties
71
+ crashlytics-build.properties
72
+
73
+
74
+ ### SublimeText ###
75
+ # cache files for sublime text
76
+ *.tmlanguage.cache
77
+ *.tmPreferences.cache
78
+ *.stTheme.cache
79
+
80
+ # workspace files are user-specific
81
+ *.sublime-workspace
82
+
83
+ # project files should be checked into the repository, unless a significant
84
+ # proportion of contributors will probably not be using SublimeText
85
+ # *.sublime-project
86
+
87
+ # sftp configuration file
88
+ sftp-config.json
89
+
90
+
91
+ ### Vim ###
92
+ [._]*.s[a-w][a-z]
93
+ [._]s[a-w][a-z]
94
+ *.un~
95
+ Session.vim
96
+ .netrwhist
97
+ *~
98
+
99
+
100
+ ### Ruby ###
101
+ *.gem
102
+ *.rbc
103
+ /.config
104
+ /coverage/
105
+ /InstalledFiles
106
+ /pkg/
107
+ /spec/reports/
108
+ /test/tmp/
109
+ /test/version_tmp/
110
+ /tmp/
111
+
112
+ ## Specific to RubyMotion:
113
+ .dat*
114
+ .repl_history
115
+ build/
116
+
117
+ ## Documentation cache and generated files:
118
+ /.yardoc/
119
+ /_yardoc/
120
+ /doc/
121
+ /rdoc/
122
+
123
+ ## Environment normalisation:
124
+ /.bundle/
125
+ /vendor/bundle
126
+ /lib/bundler/man/
127
+
128
+ # for a library or gem, you might want to ignore these files since the code is
129
+ # intended to run in multiple environments; otherwise, check them in:
130
+ # Gemfile.lock
131
+ # .ruby-version
132
+ # .ruby-gemset
133
+
134
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
135
+ .rvmrc
136
+
137
+ .idea
138
+
139
+ Gemfile.lock
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'chronic', '~> 0.10.0'
6
+ gem 'chronic_duration', '~> 0'
7
+ gem 'httparty', '~> 0.13.0'
8
+ gem 'numbers_in_words', '~> 0.2.0'
9
+ gem 'sinatra'
10
+ gem 'alexa_objects'
11
+ gem 'activesupport'
12
+
13
+ group :development do
14
+ gem "guard", "2.12.5", require: false
15
+ gem "guard-rspec", '4.5.0', require: false
16
+ gem "guard-bundler", require: false
17
+ gem "guard-rake", require: false
18
+ gem "terminal-notifier-guard", require: false
19
+ gem 'guard-yard', require: false
20
+ end
data/Guardfile ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/alexa_hue.gemspec ADDED
@@ -0,0 +1,37 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'alexa_hue/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "alexa_hue"
8
+ spec.version = Sinatra::Hue::VERSION
9
+ spec.authors = ["Kyle Lucas"]
10
+ spec.email = ["kglucas93@gmail.com"]
11
+ spec.summary = %q{A sinatra middleware for alexa hue actions.}
12
+ spec.description = %q{}
13
+ spec.homepage = "http://github.com/kylegrantlucas/alexa_hue"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = '>= 1.9.3'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib", 'skills_config']
21
+
22
+ spec.add_runtime_dependency 'json'
23
+ spec.add_runtime_dependency 'alexa_objects'
24
+ spec.add_runtime_dependency 'httparty'
25
+ spec.add_runtime_dependency 'numbers_in_words'
26
+ spec.add_runtime_dependency 'activesupport'
27
+ spec.add_runtime_dependency 'chronic'
28
+ spec.add_runtime_dependency 'chronic_duration'
29
+ spec.add_runtime_dependency 'sinatra'
30
+
31
+ spec.add_development_dependency "bundler", "~> 1.6"
32
+ spec.add_development_dependency "rake"
33
+ spec.add_development_dependency "rspec"
34
+ spec.add_development_dependency "webmock"
35
+
36
+ spec.add_development_dependency "codeclimate-test-reporter"
37
+ end
@@ -0,0 +1,7 @@
1
+ module Sinatra
2
+ module Hue
3
+ def self.custom_slots
4
+ File.read(File.expand_path('../../../skills_config/custom_slots.txt', __FILE__))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ def fix_schedule_syntax(string)
2
+ sub_time = string.match(/time \d{2}:\d{2}/)
3
+ sub_duration = string.match(/schedule PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?/)
4
+
5
+ if sub_time
6
+ sub_time = sub_time.to_s
7
+ string.slice!(sub_time).strip
8
+ string << " #{sub_time}"
9
+ string.sub!("time", "schedule at")
10
+ end
11
+
12
+ if sub_duration
13
+ sub_duration = sub_duration.to_s
14
+ string.slice!(sub_duration).strip
15
+ sub_duration = ChronicDuration.parse(sub_duration.split(' ').last)
16
+ string << " schedule in #{sub_duration} seconds"
17
+ end
18
+ string.strip if string
19
+ end
@@ -0,0 +1,522 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'socket'
4
+ require 'ipaddr'
5
+ require 'timeout'
6
+ require 'chronic'
7
+ require 'chronic_duration'
8
+ require 'httparty'
9
+ require 'numbers_in_words'
10
+ require 'numbers_in_words/duck_punch'
11
+ require 'timeout'
12
+
13
+ module Hue
14
+ module_function
15
+
16
+ def devices(options = {})
17
+ SSDP.new(options).devices
18
+ end
19
+
20
+ def first(options = {})
21
+ options = options.merge(:first => true)
22
+ SSDP.new(options).devices
23
+ end
24
+
25
+ class Switch
26
+ attr_accessor :command, :lights_array, :_group, :body, :schedule_params, :schedule_ids
27
+ def initialize(command = "", _group = 0, &block)
28
+
29
+ @user = "1234567890"
30
+ begin
31
+ @ip = HTTParty.get("https://www.meethue.com/api/nupnp").first["internalipaddress"] rescue nil
32
+ if @ip.nil?
33
+ bridge = get_bridge_by_SSDP
34
+ @ip = bridge.ip
35
+ end
36
+
37
+ rescue Timeout::Error
38
+ puts "Time Out"
39
+ rescue NoMethodError
40
+ puts "Cannot Find Bridge via Hue broker service, trying SSDP..."
41
+ rescue Errno::ECONNREFUSED
42
+ puts "Connection refused"
43
+ rescue SocketError
44
+ puts "Cannot connect to local network"
45
+ end
46
+
47
+ authorize_user
48
+ populate_switch
49
+
50
+ self.lights_array = []
51
+ self.schedule_ids = []
52
+ self.schedule_params = nil
53
+ self.command = ""
54
+ self._group = "0"
55
+ self.body = {}
56
+ instance_eval(&block) if block_given?
57
+ end
58
+
59
+ def list_lights
60
+ light_list = {}
61
+ HTTParty.get("http://#{@ip}/api/#{@user}/lights").each { |k,v| light_list["#{v['name']}".downcase] = k }
62
+ light_list
63
+ end
64
+
65
+ def list_groups
66
+ group_list = {}
67
+ HTTParty.get("http://#{@ip}/api/#{@user}/groups").each { |k,v| group_list["#{v['name']}".downcase] = k }
68
+ group_list["all"] = "0"
69
+ group_list
70
+ end
71
+
72
+ def list_scenes
73
+ scene_list = {}
74
+ HTTParty.get("http://#{@ip}/api/#{@user}/scenes").each do |scene|
75
+ if scene[1]["owner"] != "none"
76
+ inner = {"id" => scene[0]}.merge(scene[1])
77
+ scene_list.merge!({"#{scene[1]["name"].split(' ').first.downcase}" => inner})
78
+ end
79
+ end
80
+ scene_list
81
+ end
82
+
83
+ def hue (numeric_value)
84
+ clear_attributes
85
+ self.body[:hue] = numeric_value
86
+ end
87
+
88
+ def mired (numeric_value)
89
+ clear_attributes
90
+ self.body[:ct] = numeric_value
91
+ end
92
+
93
+ def color(color_name)
94
+ clear_attributes
95
+ if @colors.keys.include?(color_name.to_sym)
96
+ self.body[:hue] = @colors[color_name.to_sym]
97
+ else
98
+ self.body[:ct] = @mired_colors[color_name.to_sym]
99
+ end
100
+ end
101
+
102
+ def saturation(depth)
103
+ self.body.delete(:scene)
104
+ self.body[:sat] = depth
105
+ end
106
+
107
+ def brightness(depth)
108
+ self.body.delete(:scene)
109
+ self.body[:bri] = depth
110
+ end
111
+
112
+ def clear_attributes
113
+ self.body.delete(:scene)
114
+ self.body.delete(:ct)
115
+ self.body.delete(:hue)
116
+ end
117
+
118
+ def fade(in_seconds)
119
+ self.body[:transitiontime] = in_seconds * 10
120
+ end
121
+
122
+ def light (*args)
123
+ self.lights_array = []
124
+ self._group = ""
125
+ self.body.delete(:scene)
126
+ args.each { |l| self.lights_array.push @lights[l.to_s] if @lights.keys.include?(l.to_s) }
127
+ end
128
+
129
+ def lights(group_name)
130
+ self.lights_array = []
131
+ self.body.delete(:scene)
132
+ group = @groups[group_name.to_s]
133
+ self._group = group if !group.nil?
134
+ end
135
+
136
+ def scene(scene_name)
137
+ clear_attributes
138
+ scene_details = self.list_scenes[scene_name]
139
+ self.lights_array = scene_details["lights"]
140
+ self._group = "0"
141
+ self.body[:scene] = scene_details["id"]
142
+ end
143
+
144
+ def confirm
145
+ params = {:alert => 'select'}
146
+ HTTParty.put("http://#{@ip}/api/#{@user}/groups/0/action" , :body => params.to_json)
147
+ end
148
+
149
+ def save_scene(scene_name)
150
+ scene_name.gsub!(' ','-')
151
+ self.fade 2 if self.body[:transitiontime] == nil
152
+ if self._group.empty?
153
+ light_group = HTTParty.get("http://#{@ip}/api/#{@user}/groups/0")["lights"]
154
+ else
155
+ light_group = HTTParty.get("http://#{@ip}/api/#{@user}/groups/#{self._group}")["lights"]
156
+ end
157
+ params = {name: scene_name, lights: light_group, transitiontime: self.body[:transitiontime]}
158
+ response = HTTParty.put("http://#{@ip}/api/#{@user}/scenes/#{scene_name}", :body => params.to_json)
159
+ confirm if response.first.keys[0] == "success"
160
+ end
161
+
162
+ def lights_on_off
163
+ self.lights_array.each { |l| HTTParty.put("http://#{@ip}/api/#{@user}/lights/#{l}/state", :body => (self.body).to_json) }
164
+ end
165
+
166
+ def group_on_off
167
+ HTTParty.put("http://#{@ip}/api/#{@user}/groups/#{self._group}/action", :body => (self.body.reject { |s| s == :scene }).to_json)
168
+ end
169
+
170
+ def scene_on_off
171
+ if self.body[:on] == true
172
+ HTTParty.put("http://#{@ip}/api/#{@user}/groups/#{self._group}/action", :body => (self.body.select { |s| s == :scene }).to_json)
173
+ elsif self.body[:on] == false
174
+ # turn off individual lights in the scene
175
+ (HTTParty.get("http://#{@ip}/api/#{@user}/scenes"))[self.body[:scene]]["lights"].each do |l|
176
+ puts self.body
177
+ HTTParty.put("http://#{@ip}/api/#{@user}/lights/#{l}/state", :body => (self.body).to_json)
178
+ end
179
+ end
180
+ end
181
+
182
+ def on
183
+ self.body[:on]=true
184
+ lights_on_off if self.lights_array.any?
185
+ group_on_off if (!self._group.empty? && self.body[:scene].nil?)
186
+ scene_on_off if !self.body[:scene].nil?
187
+ end
188
+
189
+ def off
190
+ self.body[:on]=false
191
+ lights_on_off if self.lights_array.any?
192
+ group_on_off if (!self._group.empty? && self.body[:scene].nil?)
193
+ scene_on_off if !self.body[:scene].nil?
194
+ end
195
+
196
+ # Parses times in words (e.g., "eight forty five") to standard HH:MM format
197
+
198
+ def schedule (string, on_or_off = :default)
199
+ self.body[:on] = true if on_or_off == :on
200
+ self.body[:on] = false if on_or_off == :off
201
+ set_time = set_time(string)
202
+ if set_time < Time.now
203
+ p "You've scheduled this in the past"
204
+ else
205
+ set_time = set_time.to_s.split(' ')[0..1].join(' ').sub(' ',"T")
206
+ self.schedule_params = {:name=>"Hue_Switch Alarm",
207
+ :description=>"",
208
+ :localtime=>"#{set_time}",
209
+ :status=>"enabled",
210
+ :autodelete=>true
211
+ }
212
+ if self.lights_array.any?
213
+ lights_array.each do |l|
214
+ self.schedule_params[:command] = {:address=>"/api/#{@user}/lights/#{l}/state", :method=>"PUT", :body=>self.body}
215
+ end
216
+ else
217
+ self.schedule_params[:command] = {:address=>"/api/#{@user}/groups/#{self._group}/action", :method=>"PUT", :body=>self.body}
218
+ end
219
+ self.schedule_ids.push(HTTParty.post("http://#{@ip}/api/#{@user}/schedules", :body => (self.schedule_params).to_json))
220
+ confirm if self.schedule_ids.flatten.last.include?("success")
221
+ end
222
+ end
223
+
224
+ def delete_schedules!
225
+ self.schedule_ids.flatten!
226
+ self.schedule_ids.each { |k|
227
+ id = k["success"]["id"] if k.include?("success")
228
+ HTTParty.delete("http://#{@ip}/api/#{@user}/schedules/#{id}")
229
+ }
230
+ self.schedule_ids = []
231
+ end
232
+
233
+ def colorloop(start_or_stop)
234
+ if start_or_stop == :start
235
+ self.body[:effect] = "colorloop"
236
+ elsif start_or_stop == :stop
237
+ self.body[:effect] = "none"
238
+ end
239
+ end
240
+
241
+ def alert(value)
242
+ if value == :short
243
+ self.body[:alert] = "select"
244
+ elsif value == :long
245
+ self.body[:alert] = "lselect"
246
+ elsif value == :stop
247
+ self.body[:alert] = "none"
248
+ end
249
+ end
250
+
251
+ def reset
252
+ self.command = ""
253
+ self._group = "0"
254
+ self.body = {}
255
+ self.schedule_params = nil
256
+ end
257
+
258
+ #The following two methods are required to use Switch with Zach Feldman's Alexa-home*
259
+ def wake_words
260
+ ["light", "lights", "scene", "seen"]
261
+ end
262
+
263
+ def process_command (command)
264
+ command.sub!("color loop", "colorloop")
265
+ command.sub!("too", "two")
266
+ command.sub!("for", "four")
267
+ command.sub!(/a half$/, 'thirty seconds')
268
+ self.voice command
269
+ end
270
+
271
+ #The rest of the methods allow access to most of the Switch class functionality by supplying a single string
272
+
273
+ def voice(string)
274
+ puts string
275
+ self.reset
276
+ self.command << string
277
+
278
+ parse_voice(string)
279
+
280
+ if self.command.include?("schedule")
281
+ state = string.match(/off|on/)[0].to_sym rescue nil
282
+ self.send("schedule", *[string, state])
283
+ else
284
+ string.include?(' off') ? self.send("off") : self.send("on")
285
+ end
286
+ end
287
+
288
+ private
289
+
290
+ def parse_leading(methods)
291
+ methods.each do |l|
292
+ capture = (self.command.match (/\b#{l}\s\w+/)).to_s.split(' ')
293
+ method = capture[0]
294
+ value = capture[1]
295
+ value = value.in_numbers if value.scan(Regexp.union( (1..10).map {|k| k.in_words} ) ).any?
296
+ value = ((value.to_f/10.to_f)*255).to_i if (value.class == Fixnum) && (l != "fade")
297
+ self.send( method, value )
298
+ end
299
+ end
300
+
301
+ def parse_trailing(method)
302
+ all_keys = Regexp.union((@groups.keys + @lights.keys).flatten)
303
+ value = self.command.match(all_keys).to_s
304
+ self.send( method.first, value )
305
+ end
306
+
307
+ def parse_dynamic(methods)
308
+ methods.each do |d|
309
+ capture = (self.command.match (/\w+ #{d}\b/)).to_s.split(' ')
310
+ method = capture[1]
311
+ value = capture[0].to_sym
312
+ self.send( method, value )
313
+ end
314
+ end
315
+
316
+ def parse_scene(scene_name)
317
+ scene_name.gsub!(' ','-') if scene_name.size > 1
318
+ self.send("scene", scene_name)
319
+ end
320
+
321
+ def parse_save_scene
322
+ save_scene = self.command.partition(/save (scene|seen) as /).last
323
+ self.send( "save_scene", save_scene)
324
+ end
325
+
326
+ def parse_voice(string)
327
+ string.gsub!('schedule ','')
328
+ trailing = string.split(' ') & %w[lights light]
329
+ leading = string.split(' ') & %w[hue brightness saturation fade color]
330
+ dynamic = string.split(' ') & %w[colorloop alert]
331
+ scene_name = string.partition(" scene").first
332
+
333
+ parse_scene(scene_name) if string.include?(" scene") && !string.include?("save")
334
+ parse_leading(leading) if leading.any?
335
+ parse_trailing(trailing) if trailing.any?
336
+ parse_dynamic(dynamic) if dynamic.any?
337
+ parse_save_scene if self.command.scan(/save (scene|seen) as/).length > 0
338
+ end
339
+
340
+ def numbers_to_times(numbers)
341
+ numbers.map!(&:in_numbers)
342
+ numbers.map!(&:to_s)
343
+ numbers.push("0") if numbers[1] == nil
344
+ numbers = numbers.shift + ':' + (numbers[0].to_i + numbers[1].to_i).to_s
345
+ numbers.gsub!(':', ':0') if numbers.split(":")[1].length < 2
346
+ numbers
347
+ end
348
+
349
+ def parse_time(string)
350
+ string.sub!(" noon", " twelve in the afternoon")
351
+ string.sub!("midnight", "twelve in the morning")
352
+ time_modifier = string.downcase.scan(/(evening)|(night|tonight)|(afternoon)|(pm)|(a.m.)|(am)|(p.m.)|(morning)|(today)/).flatten.compact.first
353
+ guess = Time.now.strftime('%H').to_i >= 12 ? "p.m." : "a.m."
354
+ time_modifier = time_modifier.nil? ? guess : time_modifier
355
+ day_modifier = string.scan(/(tomorrow)|(next )?(monday|tuesday|wednesday|thursday|friday|saturday|sunday)/).flatten.compact.join(' ')
356
+ numbers_in_words = string.scan(Regexp.union((1..59).map(&:in_words)))
357
+ set_time = numbers_to_times(numbers_in_words)
358
+ set_time = Chronic.parse(day_modifier + ' ' + set_time + ' ' + time_modifier)
359
+ end
360
+
361
+ def set_time(string)
362
+ if string.scan(/ seconds?| minutes?| hours?| days?| weeks?/).any?
363
+ set_time = string.partition("in").last.strip!
364
+ set_time = Time.now + ChronicDuration.parse(string)
365
+ elsif string.scan(/\d/).any?
366
+ set_time = string.partition("at").last.strip!
367
+ set_time = Chronic.parse(set_time)
368
+ else
369
+ set_time = string.partition("at").last.strip!
370
+ set_time = parse_time(set_time)
371
+ end
372
+ end
373
+
374
+ def authorize_user
375
+ begin
376
+ if HTTParty.get("http://#{@ip}/api/#{@user}/config").include?("whitelist") == false
377
+ body = {:devicetype => "Hue_Switch", :username=>"1234567890"}
378
+ create_user = HTTParty.post("http://#{@ip}/api", :body => body.to_json)
379
+ puts "You need to press the link button on the bridge and run again" if create_user.first.include?("error")
380
+ end
381
+ rescue Errno::ECONNREFUSED
382
+ puts "Cannot Reach Bridge"
383
+ end
384
+ end
385
+
386
+ def populate_switch
387
+ @colors = {red: 65280, pink: 56100, purple: 52180, violet: 47188, blue: 46920, turquoise: 31146, green: 25500, yellow: 12750, orange: 8618}
388
+ @mired_colors = {candle: 500, relax: 467, reading: 346, neutral: 300, concentrate: 231, energize: 136}
389
+ @scenes = [] ; HTTParty.get("http://#{@ip}/api/#{@user}/scenes").keys.each { |k| @scenes.push(k) }
390
+ @groups = {} ; HTTParty.get("http://#{@ip}/api/#{@user}/groups").each { |k,v| @groups["#{v['name']}".downcase] = k } ; @groups["all"] = "0"
391
+ @lights = {} ; HTTParty.get("http://#{@ip}/api/#{@user}/lights").each { |k,v| @lights["#{v['name']}".downcase] = k }
392
+ end
393
+
394
+ def get_bridge_by_SSDP
395
+ discovered_devices = Hue.devices
396
+ bridge = discovered_devices.each do |device|
397
+ next unless device.get_response.include?("hue Personal")
398
+ end.first
399
+ end
400
+ end
401
+
402
+
403
+ # The Device and SSDP classes are basically lifted from Sam Soffes' great GitHub repo:
404
+ # https://github.com/soffes/discover.
405
+
406
+ class Device
407
+ attr_reader :ip
408
+ attr_reader :port
409
+ attr_reader :description_url
410
+ attr_reader :server
411
+ attr_reader :service_type
412
+ attr_reader :usn
413
+ attr_reader :url_base
414
+ attr_reader :name
415
+ attr_reader :manufacturer
416
+ attr_reader :manufacturer_url
417
+ attr_reader :model_name
418
+ attr_reader :model_number
419
+ attr_reader :model_description
420
+ attr_reader :model_url
421
+ attr_reader :serial_number
422
+ attr_reader :software_version
423
+ attr_reader :hardware_version
424
+
425
+ def initialize(info)
426
+ headers = {}
427
+ info[0].split("\r\n").each do |line|
428
+ matches = line.match(/^([\w\-]+):(?:\s)*(.*)$/)
429
+ next unless matches
430
+ headers[matches[1].upcase] = matches[2]
431
+ end
432
+
433
+ @description_url = headers['LOCATION']
434
+ @server = headers['SERVER']
435
+ @service_type = headers['ST']
436
+ @usn = headers['USN']
437
+
438
+ info = info[1]
439
+ @port = info[1]
440
+ @ip = info[2]
441
+ end
442
+
443
+ def get_response
444
+ Net::HTTP.get_response(URI.parse(description_url)).body
445
+ end
446
+ end
447
+
448
+ class SSDP
449
+ # SSDP multicast IPv4 address
450
+ MULTICAST_ADDR = '239.255.255.250'.freeze
451
+
452
+ # SSDP UDP port
453
+ MULTICAST_PORT = 1900.freeze
454
+
455
+ # Listen for all devices
456
+ DEFAULT_SERVICE_TYPE = 'ssdp:all'.freeze
457
+
458
+ # Timeout in 2 seconds
459
+ DEFAULT_TIMEOUT = 2.freeze
460
+
461
+ attr_reader :service_type
462
+ attr_reader :timeout
463
+ attr_reader :first
464
+
465
+ # @param service_type [String] the identifier of the device you're trying to find
466
+ # @param timeout [Fixnum] timeout in seconds
467
+ def initialize(options = {})
468
+ @service_type = options[:service_type]
469
+ @timeout = (options[:timeout] || DEFAULT_TIMEOUT)
470
+ @first = options[:first]
471
+ initialize_socket
472
+ end
473
+
474
+ # Look for devices on the network
475
+ def devices
476
+ @socket.send(search_message, 0, MULTICAST_ADDR, MULTICAST_PORT)
477
+ listen_for_responses(first)
478
+ end
479
+
480
+ private
481
+
482
+ def listen_for_responses(first = false)
483
+ @socket.send(search_message, 0, MULTICAST_ADDR, MULTICAST_PORT)
484
+
485
+ devices = []
486
+ Timeout::timeout(timeout) do
487
+ loop do
488
+ device = Device.new(@socket.recvfrom(2048))
489
+ next if service_type && service_type != device.service_type
490
+
491
+ if first
492
+ return device
493
+ else
494
+ devices << device
495
+ end
496
+ end
497
+ end
498
+ devices
499
+
500
+ rescue Timeout::Error => ex
501
+ devices
502
+ end
503
+
504
+ def initialize_socket
505
+ # Create a socket
506
+ @socket = UDPSocket.open
507
+
508
+ # We're going to use IP with the multicast TTL. Mystery third parameter is a mystery.
509
+ @socket.setsockopt(Socket::IPPROTO_IP, Socket::IP_MULTICAST_TTL, 2)
510
+ end
511
+
512
+ def search_message
513
+ [
514
+ 'M-SEARCH * HTTP/1.1',
515
+ "HOST: #{MULTICAST_ADDR}:reservedSSDPport",
516
+ 'MAN: ssdp:discover',
517
+ "MX: #{timeout}",
518
+ "ST: #{service_type || DEFAULT_SERVICE_TYPE}"
519
+ ].join("\n")
520
+ end
521
+ end
522
+ end
@@ -0,0 +1,7 @@
1
+ module Sinatra
2
+ module Hue
3
+ def self.intent_schema
4
+ File.read(File.expand_path('../../../skills_config/intent_schema.txt', __FILE__))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Sinatra
2
+ module Hue
3
+ def self.sample_utterances
4
+ File.read(File.expand_path('../../../skills_config/sample_utterances.txt', __FILE__))
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Sinatra
2
+ module Hue
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
data/lib/alexa_hue.rb ADDED
@@ -0,0 +1,144 @@
1
+ require 'json'
2
+ require 'alexa_objects'
3
+ require 'httparty'
4
+ require 'numbers_in_words'
5
+ require 'numbers_in_words/duck_punch'
6
+ require 'chronic'
7
+ require 'alexa_hue/version'
8
+ require 'sinatra/base'
9
+ require 'numbers_in_words/duck_punch'
10
+ require 'alexa_hue/hue_switch'
11
+ require 'chronic_duration'
12
+ require 'alexa_hue/fix_schedule_syntax'
13
+
14
+
15
+ LEVELS = {} ; [*1..10].each { |t| LEVELS[t.to_s ] = t.in_words }
16
+
17
+ module Sinatra
18
+ module Hue
19
+ def self.registered(app)
20
+ app.post '/alexa_hue' do
21
+ content_type :json
22
+
23
+ # halt 400, "Invalid Application ID" unless @application_id == "your application id here"
24
+
25
+ # Behavior for Launch Request
26
+ if @echo_request.launch_request?
27
+ response = AlexaObjects::Response.new
28
+ response.spoken_response = "I'm ready to control the lights"
29
+ response.end_session = false
30
+ response.without_card.to_json
31
+
32
+ # Behavior for ControlLights intent. Keys and values need to be adjusted a bit to work with HueSwitch #voice syntax.
33
+ elsif @echo_request.intent_name == "ControlLights"
34
+ if @echo_request.slots.brightness
35
+ LEVELS.keys.reverse_each { |level| @echo_request.slots.brightness.sub!(level, LEVELS[level]) } if @echo_request.slots.schedule.nil?
36
+ end
37
+
38
+ if @echo_request.slots.saturation
39
+ LEVELS.keys.reverse_each { |level| @echo_request.slots.saturation.sub!(level, LEVELS[level]) } if @echo_request.slots.schedule.nil?
40
+ end
41
+
42
+ puts @echo_request.slots
43
+ @echo_request.slots.to_h.each do |k,v|
44
+ @string ||= ""
45
+ next unless v
46
+ if k == :scene || k == :alert
47
+ @string << "#{v.to_s} #{k.to_s} "
48
+ elsif k == :lights || k == :modifier || k == :state
49
+ @string << "#{v.to_s} "
50
+ elsif k == :savescene
51
+ @string << "save scene as #{v.to_s} "
52
+ elsif k == :flash
53
+ @string << "start long alert "
54
+ else
55
+ @string << "#{k.to_s} #{v.to_s} "
56
+ end
57
+ end
58
+
59
+ fix_schedule_syntax(@string)
60
+ @string.sub!("color loop", "colorloop")
61
+ @string.strip!
62
+
63
+ begin
64
+ switch = Hue::Switch.new
65
+ rescue RuntimeError
66
+ response = AlexaObjects::Response.new
67
+ response.end_session = true
68
+ response.spoken_response = "Hello. Before using Hue lighting, you'll need to give me access to your Hue bridge." +
69
+ " Please press the link button on your bridge and launch the skill again within ten seconds."
70
+ halt response.without_card.to_json
71
+ end
72
+
73
+ if @echo_request.slots.lights.nil? && @echo_request.slots.scene.nil? && @echo_request.slots.savescene.nil?
74
+ r = AlexaObjects::Response.new
75
+ r.end_session = false
76
+ r.spoken_response = "Please specify which light or lights you'd like to adjust. I'm ready to control the lights."
77
+ halt r.without_card.to_json
78
+ end
79
+
80
+ if @echo_request.slots.lights
81
+ if @echo_request.slots.lights.scan(/light|lights/).empty?
82
+ r = AlexaObjects::Response.new
83
+ r.end_session = false
84
+ r.spoken_response = "Please specify which light or lights you'd like to adjust. I'm ready to control the lights."
85
+ halt r.without_card.to_json
86
+ end
87
+ end
88
+
89
+ puts @echo_request.slots.lights
90
+ if @echo_request.slots.lights
91
+ if @echo_request.slots.lights.include?('lights')
92
+ puts switch.list_groups.keys.join(', ').downcase
93
+ if !(switch.list_groups.keys.join(', ').downcase.include?("#{@echo_request.slots.lights.sub(' lights','')}"))
94
+ r = AlexaObjects::Response.new
95
+ r.end_session = true
96
+ r.spoken_response = "I couldn't find a group with the name #{@echo_request.slots.lights}"
97
+ halt r.without_card.to_json
98
+ end
99
+
100
+ elsif @echo_request.slots.lights.include?('light')
101
+ if !(switch.list_lights.keys.join(', ').downcase.include?("#{@echo_request.slots.lights.sub(' light','')}"))
102
+ r = AlexaObjects::Response.new
103
+ r.end_session = true
104
+ r.spoken_response = "I couldn't find a light with the name #{@echo_request.slots.lights}"
105
+ halt r.without_card.to_json
106
+ end
107
+ end
108
+ end
109
+
110
+
111
+ #if @string.include?('light ')
112
+ # if (@string.split(' ') & switch.list_lights.keys.join(', ').downcase.split(', ')).empty?
113
+ # r = AlexaObjects::Response.new
114
+ # r.end_session = true
115
+ # r.spoken_response = "I couldn't find a light with the name #{@echo_request.slots.lights}"
116
+ # halt r.without_card.to_json
117
+ # end
118
+ #end
119
+ switch.voice @string
120
+
121
+ response = AlexaObjects::Response.new
122
+ response.end_session = true
123
+ response.spoken_response = "okay"
124
+ response.card_content = "#{@string}...#{@data}"
125
+ response.without_card.to_json # change to .with_card.to_json for debugging info
126
+
127
+ # Behavior for End Session requests.
128
+ elsif @echo_request.intent_name == "EndSession"
129
+ response = AlexaObjects::Response.new
130
+ response.end_session = true
131
+ response.spoken_response = "exiting lighting"
132
+ response.without_card.to_json
133
+
134
+ elsif @echo_request.session_ended_request?
135
+ response = AlexaObjects::Response.new
136
+ response.end_session = true
137
+ response.without_card.to_json
138
+ end
139
+ end
140
+ end
141
+ end
142
+
143
+ register Hue
144
+ end
Binary file
@@ -0,0 +1,46 @@
1
+ LIGHTS
2
+ bedroom lights
3
+ fireplace lights
4
+ floor light
5
+ overhead light
6
+
7
+ ATTRIBUTE
8
+ Saturation
9
+ Brightness
10
+
11
+ COLOR
12
+ Red
13
+ Blue
14
+ Green
15
+ Yellow
16
+ Pink
17
+ Purple
18
+ Violet
19
+ Turquiose
20
+ Reading
21
+ Relax
22
+ Candle
23
+ Neutral
24
+ Concentrate
25
+
26
+ LEVEL
27
+ one
28
+ two
29
+ three
30
+ four
31
+ five
32
+ six
33
+ seven
34
+ eight
35
+ nine
36
+ ten
37
+
38
+ SCENE
39
+ morning
40
+ evening
41
+ dinner
42
+ afternoon
43
+
44
+ STATE
45
+ on
46
+ off
@@ -0,0 +1,65 @@
1
+ {
2
+ "intents": [
3
+ {
4
+ "intent": "ControlLights",
5
+ "slots": [
6
+ {
7
+ "name": "Lights",
8
+ "type": "LIGHTS"
9
+ },
10
+ {
11
+ "name": "Brightness",
12
+ "type": "LEVEL"
13
+ },
14
+ {
15
+ "name": "Saturation",
16
+ "type": "LEVEL"
17
+ },
18
+ {
19
+ "name": "Start",
20
+ "type": "AMAZON.LITERAL"
21
+ },
22
+ {
23
+ "name": "Stop",
24
+ "type": "AMAZON.LITERAL"
25
+ },
26
+ {
27
+ "name": "State",
28
+ "type": "STATE"
29
+ },
30
+ {
31
+ "name": "SaveScene",
32
+ "type": "AMAZON.LITERAL"
33
+ },
34
+ {
35
+ "name": "String",
36
+ "type": "AMAZON.LITERAL"
37
+ },
38
+ {
39
+ "name": "Color",
40
+ "type": "COLOR"
41
+ },
42
+ {
43
+ "name": "Schedule",
44
+ "type": "AMAZON.DURATION"
45
+ },
46
+ {
47
+ "name": "Time",
48
+ "type": "AMAZON.TIME"
49
+ },
50
+ {
51
+ "name": "Flash",
52
+ "type": "AMAZON.LITERAL"
53
+ },
54
+ {
55
+ "name": "Scene",
56
+ "type": "SCENE"
57
+ },
58
+ {
59
+ "name": "EndSession",
60
+ "type": "AMAZON.LITERAL"
61
+ }
62
+ ]
63
+ }
64
+ ]
65
+ }
@@ -0,0 +1,68 @@
1
+ ControlLights turn the {Lights} {State}
2
+ ControlLights turn {State} the {Lights}
3
+ ControlLights set {Scene} scene
4
+ ControlLights set scene {Scene}
5
+ ControlLights set the scene {Scene}
6
+
7
+ ControlLights turn the {Lights} {Color}
8
+ ControlLights turn the {Lights} to {Color}
9
+ ControlLights turn the {Lights} to the {Color}
10
+ ControlLights change the {Lights} to {Color}
11
+ ControlLights change the {Lights} to the {Color}
12
+ ControlLights set the {Lights} to {Color}
13
+ ControlLights set the {Lights} to the {Color}
14
+
15
+ ControlLights turn {State} the {Lights} in {Schedule}
16
+ ControlLights turn the {Lights} {State} in {Schedule}
17
+ ControlLights turn the {Lights} {Color} in {Schedule}
18
+ ControlLights turn the {Lights} to {Color} in {Schedule}
19
+ ControlLights turn the {Lights} to the {Color} in {Schedule}
20
+ ControlLights change the {Lights} to {Color} in {Schedule}
21
+ ControlLights change the {Lights} to the {Color} in {Schedule}
22
+ ControlLights set the {Lights} to {Color} in {Schedule}
23
+ ControlLights set the {Lights} to the {Color} in {Schedule}
24
+
25
+ ControlLights turn {State} the {Lights} at {Time}
26
+ ControlLights turn the {Lights} {State} at {Time}
27
+ ControlLights turn the {Lights} {Color} at {Time}
28
+ ControlLights turn the {Lights} to {Color} at {Time}
29
+ ControlLights turn the {Lights} to the {Color} at {Time}
30
+ ControlLights change the {Lights} to {Color} at {Time}
31
+ ControlLights change the {Lights} to the {Color} at {Time}
32
+ ControlLights set the {Lights} to {Color} at {Time}
33
+ ControlLights set the {Lights} to the {Color} at {Time}
34
+
35
+ ControlLights {Lights} brightness {Brightness}
36
+ ControlLights set the {Lights} to brightness {Brightness}
37
+ ControlLights raise the {Lights} to brightness {Brightness}
38
+ ControlLights dim the {Lights} to brightness {Brightness}
39
+ ControlLights {Lights} saturation {Saturation}
40
+ ControlLights set the {Lights} to saturation {Saturation}
41
+
42
+
43
+ ControlLights set the {Lights} to the color {Color} and saturation {Saturation}
44
+ ControlLights set the {Lights} to the color {Color} and brightness {Brightness}
45
+ ControlLights set the {Lights} to the color {Color} and saturation {Saturation} in {Schedule}
46
+ ControlLights set the {Lights} to the color {Color} and brightness {Brightness} in {Schedule}
47
+
48
+ ControlLights start {short alert|Start} on the {Lights}
49
+ ControlLights start {long alert|Start} on the {Lights}
50
+ ControlLights start {color loop|Start} on the {Lights}
51
+ ControlLights stop {alert|Stop}
52
+ ControlLights stop {color loop|Stop}
53
+
54
+ ControlLights {flash|Flash} {Lights}
55
+ ControlLights {flash|Flash} {Lights} in {Schedule}
56
+ ControlLights {flash|Flash} the {Lights}
57
+ ControlLights {flash|Flash} the {Lights} in {Schedule}
58
+
59
+ ControlLights save scene as {romantic|SaveScene}
60
+ ControlLights save scene as {summer dawn|SaveScene}
61
+ ControlLights save scene as {working at night|SaveScene}
62
+ ControlLights save scene as {purple|SaveScene}
63
+ ControlLights save scene as {midnight cave|SaveScene}
64
+
65
+ ControlLights {exit|EndSession}
66
+ ControlLights {quit|EndSession}
67
+ ControlLights {enough|EndSession}
68
+ ControlLights {stop|EndSession}
metadata ADDED
@@ -0,0 +1,245 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: alexa_hue
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Lucas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-02-14 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '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'
27
+ - !ruby/object:Gem::Dependency
28
+ name: alexa_objects
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: httparty
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: numbers_in_words
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: activesupport
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: chronic
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: chronic_duration
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sinatra
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: bundler
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.6'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.6'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rake
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: rspec
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: webmock
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - ">="
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - ">="
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ - !ruby/object:Gem::Dependency
182
+ name: codeclimate-test-reporter
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - ">="
193
+ - !ruby/object:Gem::Version
194
+ version: '0'
195
+ description: ''
196
+ email:
197
+ - kglucas93@gmail.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - ".DS_Store"
203
+ - ".gitignore"
204
+ - Gemfile
205
+ - Guardfile
206
+ - Rakefile
207
+ - alexa_hue.gemspec
208
+ - lib/alexa_hue.rb
209
+ - lib/alexa_hue/custom_slots.rb
210
+ - lib/alexa_hue/fix_schedule_syntax.rb
211
+ - lib/alexa_hue/hue_switch.rb
212
+ - lib/alexa_hue/intent_schema.rb
213
+ - lib/alexa_hue/sample_utterances.rb
214
+ - lib/alexa_hue/version.rb
215
+ - skills_config/.DS_Store
216
+ - skills_config/custom_slots.txt
217
+ - skills_config/intent_schema.txt
218
+ - skills_config/sample_utterances.txt
219
+ homepage: http://github.com/kylegrantlucas/alexa_hue
220
+ licenses:
221
+ - MIT
222
+ metadata: {}
223
+ post_install_message:
224
+ rdoc_options: []
225
+ require_paths:
226
+ - lib
227
+ - skills_config
228
+ required_ruby_version: !ruby/object:Gem::Requirement
229
+ requirements:
230
+ - - ">="
231
+ - !ruby/object:Gem::Version
232
+ version: 1.9.3
233
+ required_rubygems_version: !ruby/object:Gem::Requirement
234
+ requirements:
235
+ - - ">="
236
+ - !ruby/object:Gem::Version
237
+ version: '0'
238
+ requirements: []
239
+ rubyforge_project:
240
+ rubygems_version: 2.4.8
241
+ signing_key:
242
+ specification_version: 4
243
+ summary: A sinatra middleware for alexa hue actions.
244
+ test_files: []
245
+ has_rdoc: