alexa_hue 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.DS_Store +0 -0
- data/.gitignore +139 -0
- data/Gemfile +20 -0
- data/Guardfile +0 -0
- data/Rakefile +1 -0
- data/alexa_hue.gemspec +37 -0
- data/lib/alexa_hue/custom_slots.rb +7 -0
- data/lib/alexa_hue/fix_schedule_syntax.rb +19 -0
- data/lib/alexa_hue/hue_switch.rb +522 -0
- data/lib/alexa_hue/intent_schema.rb +7 -0
- data/lib/alexa_hue/sample_utterances.rb +7 -0
- data/lib/alexa_hue/version.rb +5 -0
- data/lib/alexa_hue.rb +144 -0
- data/skills_config/.DS_Store +0 -0
- data/skills_config/custom_slots.txt +46 -0
- data/skills_config/intent_schema.txt +65 -0
- data/skills_config/sample_utterances.txt +68 -0
- metadata +245 -0
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,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
|
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:
|