nest_connect 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6f5c2b73bcd136fabe37e7edcb8ca7b4d618ae1f2950194f9ec5d1eb104dfa38
4
+ data.tar.gz: 86736b9bc49a503a7c0a0565fa829caf80b5e82c47ed1ab0f7d789ac8d11f6d0
5
+ SHA512:
6
+ metadata.gz: 5cbaa9af5fddbca1099573f12c1c600842193e39df0f50166bf79732d462f2a2cf16a49a6d4af17d6f35036f1220a03972ce4d353ab571a148a8dc6eb458f607
7
+ data.tar.gz: 86a18ae0c0ec9e8b2890cbf7958cb73ee4bf3c20980750f8c095408d90ea2dd7ac3d8dc98083185a1c497a82674ff14d62f1b36fc90a6728c4a4aac6671b5434
data/.gitignore ADDED
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
12
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.1
7
+ before_install: gem install bundler -v 1.17.1
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ git_source(:github) {|repo_name| 'https://github.com/#{repo_name}' }
4
+
5
+ # Specify your gem's dependencies in nest_connect.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nest_connect (0.1.0)
5
+ faraday (~> 0.15)
6
+ faraday_middleware (~> 0.12)
7
+ thor (~> 0.20)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ addressable (2.5.2)
13
+ public_suffix (>= 2.0.2, < 4.0)
14
+ byebug (10.0.2)
15
+ crack (0.4.3)
16
+ safe_yaml (~> 1.0.0)
17
+ diff-lcs (1.3)
18
+ faraday (0.15.4)
19
+ multipart-post (>= 1.2, < 3)
20
+ faraday_middleware (0.12.2)
21
+ faraday (>= 0.7.4, < 1.0)
22
+ hashdiff (0.3.7)
23
+ multipart-post (2.0.0)
24
+ public_suffix (3.0.3)
25
+ rake (10.5.0)
26
+ rspec (3.8.0)
27
+ rspec-core (~> 3.8.0)
28
+ rspec-expectations (~> 3.8.0)
29
+ rspec-mocks (~> 3.8.0)
30
+ rspec-core (3.8.0)
31
+ rspec-support (~> 3.8.0)
32
+ rspec-expectations (3.8.2)
33
+ diff-lcs (>= 1.2.0, < 2.0)
34
+ rspec-support (~> 3.8.0)
35
+ rspec-mocks (3.8.0)
36
+ diff-lcs (>= 1.2.0, < 2.0)
37
+ rspec-support (~> 3.8.0)
38
+ rspec-support (3.8.0)
39
+ safe_yaml (1.0.4)
40
+ thor (0.20.3)
41
+ webmock (3.4.2)
42
+ addressable (>= 2.3.6)
43
+ crack (>= 0.3.2)
44
+ hashdiff
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ bundler (~> 1.17)
51
+ byebug
52
+ nest_connect!
53
+ rake (~> 10.0)
54
+ rspec (~> 3.0)
55
+ webmock
56
+
57
+ BUNDLED WITH
58
+ 1.17.1
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # NestConnect
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/nest_connect`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'nest_connect'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install nest_connect
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/nest_connect.
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "nest_connect"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/nest_connect ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'nest_connect'
5
+ require 'thor'
6
+ require 'byebug'
7
+
8
+ module NestConnect
9
+ class App < Thor
10
+ desc 'authorize', 'Get an access token'
11
+ method_option :auth_code, desc: 'Pincode to connect with Nest', required: true
12
+ method_option :client_id, desc: 'Client ID', required: true
13
+ method_option :client_secret, desc: 'Client Secret', required: true
14
+
15
+ def authorize
16
+ NestConnect::API::Authorize.new(
17
+ auth_code: options[:auth_code],
18
+ client_id: options[:client_id],
19
+ client_secret: options[:client_secret]
20
+ ).run
21
+ end
22
+
23
+ desc 'stream', 'Debug Nest API streaming'
24
+ def stream
25
+ NestConnect::API::Stream.new.run
26
+ end
27
+ end
28
+ end
29
+
30
+ NestConnect::App.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,177 @@
1
+ begin
2
+ require 'net/https'
3
+ rescue LoadError
4
+ warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
5
+ require 'net/http'
6
+ end
7
+ require 'zlib'
8
+
9
+ class Faraday::RequestOptions
10
+ attr_accessor :on_data
11
+
12
+ def stream_response?
13
+ on_data.is_a?(Proc)
14
+ end
15
+ end
16
+
17
+ module NestConnect
18
+ class Adapter
19
+ class StreamingNetHttp < Faraday::Adapter
20
+ NET_HTTP_EXCEPTIONS = [
21
+ IOError,
22
+ Errno::ECONNABORTED,
23
+ Errno::ECONNREFUSED,
24
+ Errno::ECONNRESET,
25
+ Errno::EHOSTUNREACH,
26
+ Errno::EINVAL,
27
+ Errno::ENETUNREACH,
28
+ Errno::EPIPE,
29
+ Net::HTTPBadResponse,
30
+ Net::HTTPHeaderSyntaxError,
31
+ Net::ProtocolError,
32
+ SocketError,
33
+ Zlib::GzipFile::Error,
34
+ ]
35
+
36
+ NET_HTTP_EXCEPTIONS << OpenSSL::SSL::SSLError if defined?(OpenSSL)
37
+ NET_HTTP_EXCEPTIONS << Net::OpenTimeout if defined?(Net::OpenTimeout)
38
+
39
+ def call(env)
40
+ super
41
+ with_net_http_connection(env) do |http|
42
+ configure_ssl(http, env[:ssl]) if env[:url].scheme == 'https' and env[:ssl]
43
+ configure_request(http, env[:request])
44
+
45
+ begin
46
+ http_response = perform_request(http, env)
47
+ rescue *NET_HTTP_EXCEPTIONS => err
48
+ if defined?(OpenSSL) && OpenSSL::SSL::SSLError === err
49
+ raise Faraday::SSLError, err
50
+ else
51
+ raise Error::ConnectionFailed, err
52
+ end
53
+ end
54
+
55
+ save_response(env, http_response.code.to_i, http_response.body || '', nil, http_response.message) do |response_headers|
56
+ http_response.each_header do |key, value|
57
+ response_headers[key] = value
58
+ end
59
+ end
60
+ end
61
+
62
+ @app.call env
63
+ rescue Timeout::Error, Errno::ETIMEDOUT => err
64
+ raise Faraday::Error::TimeoutError, err
65
+ end
66
+
67
+ def create_request(env)
68
+ request = Net::HTTPGenericRequest.new \
69
+ env[:method].to_s.upcase, # request method
70
+ !!env[:body], # is there request body
71
+ :head != env[:method], # is there response body
72
+ env[:url].request_uri, # request uri path
73
+ env[:request_headers] # request headers
74
+
75
+ if env[:body].respond_to?(:read)
76
+ request.body_stream = env[:body]
77
+ else
78
+ request.body = env[:body]
79
+ end
80
+ request
81
+ end
82
+
83
+ def perform_request(http, env)
84
+ if env[:request].stream_response?
85
+ size = 0
86
+ yielded = false
87
+ http_response = perform_request_with_wrapped_block(http, env) do |chunk|
88
+ if chunk.bytesize > 0 || size > 0
89
+ yielded = true
90
+ size += chunk.bytesize
91
+ env[:request].on_data.call(chunk, size)
92
+ end
93
+ end
94
+ env[:request].on_data.call("", 0) unless yielded
95
+ # Net::HTTP returns something, but it's not meaningful according to the docs.
96
+ http_response.body = nil
97
+ http_response
98
+ else
99
+ http_response = perform_request_with_wrapped_block(http, env)
100
+ end
101
+ end
102
+
103
+ def perform_request_with_wrapped_block(http, env, &block)
104
+ if :get == env[:method] and !env[:body]
105
+ # prefer `get` to `request` because the former handles gzip (ruby 1.9)
106
+ request_via_get_method(http, env, &block)
107
+ else
108
+ request_via_request_method(http, env, &block)
109
+ end
110
+ end
111
+
112
+ def request_via_get_method(http, env, &block)
113
+ http.get env[:url].request_uri, env[:request_headers], &block
114
+ end
115
+
116
+ def request_via_request_method(http, env, &block)
117
+ if block_given?
118
+ http.request create_request(env) do |response|
119
+ response.read_body(&block)
120
+ end
121
+ else
122
+ http.request create_request(env)
123
+ end
124
+ end
125
+
126
+ def with_net_http_connection(env)
127
+ yield net_http_connection(env)
128
+ end
129
+
130
+ def net_http_connection(env)
131
+ if proxy = env[:request][:proxy]
132
+ Net::HTTP::Proxy(proxy[:uri].hostname, proxy[:uri].port, proxy[:user], proxy[:password])
133
+ else
134
+ Net::HTTP
135
+ end.new(env[:url].hostname, env[:url].port || (env[:url].scheme == 'https' ? 443 : 80))
136
+ end
137
+
138
+ def configure_ssl(http, ssl)
139
+ http.use_ssl = true
140
+ http.verify_mode = ssl_verify_mode(ssl)
141
+ http.cert_store = ssl_cert_store(ssl)
142
+
143
+ http.cert = ssl[:client_cert] if ssl[:client_cert]
144
+ http.key = ssl[:client_key] if ssl[:client_key]
145
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
146
+ http.ca_path = ssl[:ca_path] if ssl[:ca_path]
147
+ http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
148
+ http.ssl_version = ssl[:version] if ssl[:version]
149
+ end
150
+
151
+ def configure_request(http, req)
152
+ http.read_timeout = http.open_timeout = req[:timeout] if req[:timeout]
153
+ http.open_timeout = req[:open_timeout] if req[:open_timeout]
154
+
155
+ @config_block.call(http) if @config_block
156
+ end
157
+
158
+ def ssl_cert_store(ssl)
159
+ return ssl[:cert_store] if ssl[:cert_store]
160
+ # Use the default cert store by default, i.e. system ca certs
161
+ cert_store = OpenSSL::X509::Store.new
162
+ cert_store.set_default_paths
163
+ cert_store
164
+ end
165
+
166
+ def ssl_verify_mode(ssl)
167
+ ssl[:verify_mode] || begin
168
+ if ssl.fetch(:verify, true)
169
+ OpenSSL::SSL::VERIFY_PEER
170
+ else
171
+ OpenSSL::SSL::VERIFY_NONE
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,38 @@
1
+ require 'faraday'
2
+ require 'faraday_middleware'
3
+ require_relative 'adapter/streaming_net_http'
4
+
5
+ module NestConnect
6
+ class API
7
+
8
+ private
9
+
10
+ def api_endpoint
11
+ 'https://developer-api.nest.com'
12
+ end
13
+
14
+ def connection
15
+ Faraday.new(url: api_endpoint) do |faraday|
16
+ faraday.response :json, :content_type => 'application/json'
17
+ faraday.request :json
18
+ faraday.use FaradayMiddleware::FollowRedirects
19
+ faraday.use NestConnect::Adapter::StreamingNetHttp
20
+ end
21
+ end
22
+
23
+ def configuration
24
+ GlobalConfig.new
25
+ end
26
+
27
+ def access_token
28
+ configuration.access_token
29
+ end
30
+ end
31
+ end
32
+
33
+ require_relative 'authorize'
34
+ require_relative 'stream'
35
+ require_relative 'devices/camera'
36
+ require_relative 'devices/protect'
37
+ require_relative 'devices/thermostat'
38
+ require_relative 'devices/structure'
@@ -0,0 +1,55 @@
1
+ module NestConnect
2
+ class API
3
+ class Authorize < API
4
+ def initialize(auth_code:, client_id:, client_secret:, stdout: STDOUT)
5
+ @auth_code = auth_code
6
+ @client_id = client_id
7
+ @client_secret = client_secret
8
+ @stdout = stdout
9
+ end
10
+
11
+ def run
12
+ response = connection.post do |request|
13
+ request.url(url)
14
+ request.headers.merge!(headers)
15
+ request.body = body
16
+ end
17
+
18
+ if response.status == 200
19
+ configuration.access_token = response.body['access_token']
20
+ else
21
+ stdout.write response.body
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :auth_code, :client_id, :client_secret, :stdout
28
+
29
+ def url
30
+ 'oauth2/access_token'
31
+ end
32
+
33
+ def headers
34
+ { 'Content-Type' => 'application/x-www-form-urlencoded' }
35
+ end
36
+
37
+ def body
38
+ URI.encode_www_form(data)
39
+ end
40
+
41
+ def data
42
+ {
43
+ code: auth_code,
44
+ client_id: client_id,
45
+ client_secret: client_secret,
46
+ grant_type: 'authorization_code'
47
+ }
48
+ end
49
+
50
+ def api_endpoint
51
+ 'https://api.home.nest.com'
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,41 @@
1
+ module NestConnect
2
+ class API
3
+ class Devices
4
+ class Camera < API
5
+ def initialize(device_id)
6
+ @device_id = device_id
7
+ end
8
+
9
+ def put(body)
10
+ connection.put do |request|
11
+ request.url(url)
12
+ request.headers.merge!(headers)
13
+ request.body = body
14
+ end
15
+ end
16
+
17
+ def get
18
+ connection.get do |request|
19
+ request.url(url)
20
+ request.headers.merge!(headers)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :device_id
27
+
28
+ def url
29
+ "devices/cameras/#{device_id}"
30
+ end
31
+
32
+ def headers
33
+ {
34
+ 'Content-Type' => 'application/json',
35
+ 'Authorization' => "Bearer #{access_token}"
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,33 @@
1
+ module NestConnect
2
+ class API
3
+ class Devices
4
+ class Protect < API
5
+ def initialize(device_id)
6
+ @device_id = device_id
7
+ end
8
+
9
+ def get
10
+ connection.get do |request|
11
+ request.url(url)
12
+ request.headers.merge!(headers)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :device_id
19
+
20
+ def url
21
+ "devices/smoke_co_alarms/#{device_id}"
22
+ end
23
+
24
+ def headers
25
+ {
26
+ 'Content-Type' => 'application/json',
27
+ 'Authorization' => "Bearer #{access_token}"
28
+ }
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,41 @@
1
+ module NestConnect
2
+ class API
3
+ class Devices
4
+ class Structure < API
5
+ def initialize(structure_id)
6
+ @structure_id = structure_id
7
+ end
8
+
9
+ def put(body)
10
+ connection.put do |request|
11
+ request.url(url)
12
+ request.headers.merge!(headers)
13
+ request.body = body
14
+ end
15
+ end
16
+
17
+ def get
18
+ connection.get do |request|
19
+ request.url(url)
20
+ request.headers.merge!(headers)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :structure_id
27
+
28
+ def url
29
+ "devices/structures/#{structure_id}"
30
+ end
31
+
32
+ def headers
33
+ {
34
+ 'Content-Type' => 'application/json',
35
+ 'Authorization' => "Bearer #{access_token}"
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,41 @@
1
+ module NestConnect
2
+ class API
3
+ class Devices
4
+ class Thermostat < API
5
+ def initialize(device_id)
6
+ @device_id = device_id
7
+ end
8
+
9
+ def put(body)
10
+ connection.put do |request|
11
+ request.url(url)
12
+ request.headers.merge!(headers)
13
+ request.body = body
14
+ end
15
+ end
16
+
17
+ def get
18
+ connection.get do |request|
19
+ request.url(url)
20
+ request.headers.merge!(headers)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :device_id
27
+
28
+ def url
29
+ "devices/thermostats/#{device_id}"
30
+ end
31
+
32
+ def headers
33
+ {
34
+ 'Content-Type' => 'application/json',
35
+ 'Authorization' => "Bearer #{access_token}"
36
+ }
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,25 @@
1
+ module NestConnect
2
+ class API
3
+ class Stream < API
4
+ def initialize(output: STDOUT)
5
+ @output = output
6
+ end
7
+
8
+ def run
9
+ connection.get do |request|
10
+ request.headers['Accept'] = 'text/event-stream'
11
+ request.headers['Authorization'] = "Bearer #{access_token}"
12
+ request.headers['Cache-Control'] = 'no-cache'
13
+
14
+ request.options.on_data = Proc.new do |chunk, overall_received_bytes|
15
+ output.write chunk
16
+ end
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :output
23
+ end
24
+ end
25
+ end