alexa_hue 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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:
|