ecobee 0.2.3 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -5
- data/examples/set_mode.rb +17 -19
- data/lib/ecobee.rb +12 -8
- data/lib/ecobee/http.rb +143 -0
- data/lib/ecobee/register.rb +19 -20
- data/lib/ecobee/thermostat.rb +7 -7
- data/lib/ecobee/token.rb +79 -62
- data/lib/ecobee/version.rb +1 -1
- metadata +3 -3
- data/lib/ecobee/client.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 48dab0a8186e1abe5f05115a744bf06a6167b718
|
4
|
+
data.tar.gz: d76e8de7fbf97ae44095436197f1d7085a324d9d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7e5ca066357160e4f43a67d90f6186437cf98f7a1105f1dba652eac447c588cec03c43f8c2e44f3caf791a0eabca9fc4b64f213f4a588e32c0ad9411cade0c05
|
7
|
+
data.tar.gz: 0bbc4eb430f37858787f915b10ab4af799fac335651344357e41becf7a883e3dd090d9359438e04ab0b9de50671df6cd4ab084b051b8ed244be8b777142c157a
|
data/README.md
CHANGED
@@ -5,17 +5,14 @@ Ecobee API Ruby Gem. Implements:
|
|
5
5
|
- Persistent HTTP connection management
|
6
6
|
- Methods for GET & POST requests w/ JSON parsing & error handling
|
7
7
|
- Persistent storage for API key & refresh tokens
|
8
|
+
- Storage uses iCloud Drive if available for shared computer use
|
8
9
|
- Block/Proc hooks for token storage load/save to add app config data
|
9
|
-
-
|
10
|
+
- Thermostat abstraction class for simple thermostat interaction
|
10
11
|
- Example usage scripts (see /examples/\*)
|
11
12
|
|
12
13
|
TODO:
|
13
|
-
- Add dedicated symbol class to Thermostat
|
14
14
|
- Add RDoc documentation
|
15
|
-
- Add timeout to Ecobee::Token#wait
|
16
15
|
- Add redirect based registration
|
17
|
-
- Implement throttling algorithm based on API feedback
|
18
|
-
- Create examples of proper error handling
|
19
16
|
|
20
17
|
## Installation
|
21
18
|
|
data/examples/set_mode.rb
CHANGED
@@ -1,25 +1,25 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
#
|
3
|
+
# Example of using the Ecobee::HTTP API directly (bypassing the
|
4
|
+
# Ecobee::Thermostat abstraction class).
|
5
|
+
#
|
3
6
|
# Loops through menu showing current thermostat mode, with option
|
4
|
-
# to change the mode.
|
7
|
+
# to change the mode.
|
5
8
|
|
6
9
|
require 'pp'
|
7
|
-
|
8
10
|
require_relative '../lib/ecobee'
|
9
|
-
|
10
11
|
@hvac_modes = Ecobee::HVAC_MODES + ['quit']
|
11
12
|
|
12
13
|
class TestFunctions
|
13
|
-
def initialize(
|
14
|
-
@
|
14
|
+
def initialize(token)
|
15
|
+
@http = token.http
|
15
16
|
end
|
16
17
|
|
17
18
|
def print_summary
|
18
|
-
response = @
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
))
|
19
|
+
response = @http.get(arg: :thermostat,
|
20
|
+
options: Ecobee::Selection(
|
21
|
+
includeEquipmentStatus: true,
|
22
|
+
includeSettings: true))
|
23
23
|
|
24
24
|
puts "Found %d thermostats." % response['thermostatList'].length
|
25
25
|
|
@@ -36,12 +36,12 @@ class TestFunctions
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def update_mode(mode)
|
39
|
-
@
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
@http.post(arg: :thermostat,
|
40
|
+
body: {
|
41
|
+
'thermostat' => {
|
42
|
+
'settings' => { 'hvacMode' => mode }
|
43
|
+
}
|
44
|
+
}.merge(Ecobee::Selection()))
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
@@ -52,9 +52,7 @@ if token.pin
|
|
52
52
|
token.wait
|
53
53
|
end
|
54
54
|
|
55
|
-
test_functions = TestFunctions.new(
|
56
|
-
Ecobee::Client.new(token: token)
|
57
|
-
)
|
55
|
+
test_functions = TestFunctions.new(token)
|
58
56
|
|
59
57
|
loop do
|
60
58
|
test_functions.print_summary
|
data/lib/ecobee.rb
CHANGED
@@ -2,18 +2,26 @@ require 'pp'
|
|
2
2
|
require 'json'
|
3
3
|
require 'net/http'
|
4
4
|
|
5
|
-
require_relative 'ecobee/
|
5
|
+
require_relative 'ecobee/http'
|
6
6
|
require_relative 'ecobee/register'
|
7
7
|
require_relative 'ecobee/thermostat'
|
8
8
|
require_relative 'ecobee/token'
|
9
9
|
require_relative 'ecobee/version'
|
10
10
|
|
11
11
|
module Ecobee
|
12
|
+
|
13
|
+
class HTTPError < StandardError ; end
|
14
|
+
|
15
|
+
class AuthError < HTTPError ; end
|
16
|
+
|
12
17
|
API_HOST = 'api.ecobee.com'
|
13
18
|
API_PORT = 443
|
19
|
+
API_URI_BASE= "https://#{API_HOST}:#{API_PORT}"
|
14
20
|
|
15
21
|
CONTENT_TYPE = ['application/json', { 'charset' => 'UTF-8' }]
|
16
22
|
|
23
|
+
DEFAULT_POLL_INTERVAL = 30
|
24
|
+
|
17
25
|
DEFAULT_FILES = [
|
18
26
|
'~/Library/Mobile Documents/com~apple~CloudDocs/.ecobee_token',
|
19
27
|
'~/.ecobee_token'
|
@@ -30,16 +38,12 @@ module Ecobee
|
|
30
38
|
|
31
39
|
HVAC_MODES = %w{auto auxHeatOnly cool heat off}
|
32
40
|
|
41
|
+
MAX_LOG_LENGTH = 1200
|
42
|
+
|
33
43
|
REFRESH_PAD = 30
|
34
|
-
REFRESH_TOKEN_CHECK = 10
|
35
44
|
|
36
45
|
SCOPES = [:smartWrite, :smartRead]
|
37
|
-
|
38
|
-
URL_BASE= "https://#{API_HOST}:#{API_PORT}"
|
39
|
-
URL_API = "#{URL_BASE}/1/"
|
40
|
-
URL_GET_PIN = URL_BASE +
|
41
|
-
'/authorize?response_type=ecobeePin&client_id=%s&scope=%s'
|
42
|
-
URL_TOKEN = "#{URL_BASE}/token"
|
46
|
+
DEFAULT_SCOPE = SCOPES[1]
|
43
47
|
|
44
48
|
def self.FanMode(mode)
|
45
49
|
{ 'auto' => 'Auto',
|
data/lib/ecobee/http.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
module Ecobee
|
2
|
+
|
3
|
+
class HTTPError < StandardError ; end
|
4
|
+
class AuthError < HTTPError ; end
|
5
|
+
|
6
|
+
class HTTP
|
7
|
+
|
8
|
+
def initialize(log_file: nil, token: nil)
|
9
|
+
raise ArgumentError.new('Missing token') unless token
|
10
|
+
@token = token
|
11
|
+
open_log log_file
|
12
|
+
http
|
13
|
+
end
|
14
|
+
|
15
|
+
def get(
|
16
|
+
arg: nil,
|
17
|
+
no_auth: false,
|
18
|
+
resource_prefix: '1/',
|
19
|
+
retries: 3,
|
20
|
+
options: nil,
|
21
|
+
validate_status: true
|
22
|
+
)
|
23
|
+
uri = URI.escape(sprintf("#{Ecobee::API_URI_BASE}/%s%s%s",
|
24
|
+
resource_prefix,
|
25
|
+
arg.to_s.sub(/^\//, ''),
|
26
|
+
options ? "?json=#{options.to_json}" : ''))
|
27
|
+
log "http.get uri=#{uri}"
|
28
|
+
request = Net::HTTP::Get.new(URI(uri))
|
29
|
+
request['Content-Type'] = *CONTENT_TYPE
|
30
|
+
request['Authorization'] = @token.authorization unless no_auth
|
31
|
+
response = nil
|
32
|
+
retries.times do
|
33
|
+
http_response = http.request request
|
34
|
+
response = JSON.parse(http_response.body)
|
35
|
+
log "http.get response=#{response.pretty_inspect}"
|
36
|
+
response = validate_status(response) if validate_status
|
37
|
+
break unless response == :retry
|
38
|
+
sleep 3
|
39
|
+
end
|
40
|
+
case response
|
41
|
+
when :retry
|
42
|
+
raise Ecobee::HTTPError.new('HTTP.get: retries exhausted')
|
43
|
+
else
|
44
|
+
response
|
45
|
+
end
|
46
|
+
rescue SocketError => msg
|
47
|
+
raise Ecobee::HTTPError.new("HTTP.get SocketError => #{msg}")
|
48
|
+
rescue JSON::ParserError => msg
|
49
|
+
raise Ecobee::HTTPError.new("HTTP.get JSON::ParserError => #{msg}")
|
50
|
+
end
|
51
|
+
|
52
|
+
def log(arg)
|
53
|
+
return unless @log_fh
|
54
|
+
if arg.length > MAX_LOG_LENGTH
|
55
|
+
arg = arg.slice(0, MAX_LOG_LENGTH).chomp + "\n ...truncated..."
|
56
|
+
end
|
57
|
+
@log_fh.puts "#{Time.now} #{arg.chomp}"
|
58
|
+
@log_fh.flush
|
59
|
+
end
|
60
|
+
|
61
|
+
def post(
|
62
|
+
arg: nil,
|
63
|
+
body: nil,
|
64
|
+
no_auth: false,
|
65
|
+
resource_prefix: '1/',
|
66
|
+
retries: 3,
|
67
|
+
options: {},
|
68
|
+
validate_status: true
|
69
|
+
)
|
70
|
+
uri = URI.escape(sprintf("#{Ecobee::API_URI_BASE}/%s%s%s",
|
71
|
+
resource_prefix,
|
72
|
+
arg.to_s.sub(/^\//, ''),
|
73
|
+
options.length > 0 ? "?json=#{options.to_json}" : ''))
|
74
|
+
log "http.post uri=#{uri}"
|
75
|
+
request = Net::HTTP::Post.new(URI(uri))
|
76
|
+
request['Content-Type'] = *CONTENT_TYPE
|
77
|
+
request['Authorization'] = @token.authorization unless no_auth
|
78
|
+
if body
|
79
|
+
log "http.post body=#{body.pretty_inspect}"
|
80
|
+
request.body = JSON.generate(body)
|
81
|
+
elsif options.length > 0
|
82
|
+
request.set_form_data({ 'format' => 'json' }.merge(options))
|
83
|
+
end
|
84
|
+
response = nil
|
85
|
+
retries.times do
|
86
|
+
http_response = http.request request
|
87
|
+
response = JSON.parse(http_response.body)
|
88
|
+
log "http.post response=#{response.pretty_inspect}"
|
89
|
+
response = validate_status(response) if validate_status
|
90
|
+
break unless response == :retry
|
91
|
+
sleep 3
|
92
|
+
end
|
93
|
+
case response
|
94
|
+
when :retry
|
95
|
+
raise Ecobee::HTTPError.new('HTTP.get: retries exhausted')
|
96
|
+
else
|
97
|
+
response
|
98
|
+
end
|
99
|
+
rescue SocketError => msg
|
100
|
+
raise Ecobee::HTTPError.new("HTTP.get SocketError => #{msg}")
|
101
|
+
rescue JSON::ParserError => msg
|
102
|
+
raise Ecobee::HTTPError.new("HTTP.get JSON::ParserError => #{msg}")
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def http
|
108
|
+
@http ||= Net::HTTP.new(API_HOST, API_PORT)
|
109
|
+
unless @http.active?
|
110
|
+
@http.use_ssl = true
|
111
|
+
Net::HTTP.start(API_HOST, API_PORT)
|
112
|
+
end
|
113
|
+
@http
|
114
|
+
end
|
115
|
+
|
116
|
+
def open_log(log_file)
|
117
|
+
return unless log_file
|
118
|
+
log_file = File.expand_path log_file
|
119
|
+
@log_fh = File.new(log_file, 'a')
|
120
|
+
rescue Exception => msg
|
121
|
+
raise Ecobee::HTTPError.new("open_log: #{msg}")
|
122
|
+
end
|
123
|
+
|
124
|
+
def validate_status(response)
|
125
|
+
if !response.key? 'status'
|
126
|
+
raise Ecobee::HTTPError.new('Validate Error: Missing Status')
|
127
|
+
elsif !response['status'].key? 'code'
|
128
|
+
raise Ecobee::HTTPError.new('Validate Error: Missing Status Code')
|
129
|
+
elsif response['status']['code'] == 14
|
130
|
+
:retry
|
131
|
+
elsif response['status']['code'] != 0
|
132
|
+
raise Ecobee::HTTPError.new(
|
133
|
+
"Validate Error: (Code #{response['status']['code']}) " +
|
134
|
+
"#{response['status']['message']}"
|
135
|
+
)
|
136
|
+
else
|
137
|
+
response
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
data/lib/ecobee/register.rb
CHANGED
@@ -1,12 +1,15 @@
|
|
1
1
|
module Ecobee
|
2
2
|
|
3
3
|
class Register
|
4
|
-
attr_reader :
|
5
|
-
|
6
|
-
def initialize(
|
7
|
-
|
8
|
-
|
9
|
-
|
4
|
+
attr_reader :expire, :result
|
5
|
+
|
6
|
+
def initialize(
|
7
|
+
app_key: nil,
|
8
|
+
http: nil,
|
9
|
+
scope: DEFAULT_SCOPE
|
10
|
+
)
|
11
|
+
@result = get_pin(app_key: app_key, http: http, scope: scope)
|
12
|
+
@expire = Time.now.to_i + result['expires_in'] * 60
|
10
13
|
end
|
11
14
|
|
12
15
|
def code
|
@@ -25,26 +28,22 @@ module Ecobee
|
|
25
28
|
@result['scope']
|
26
29
|
end
|
27
30
|
|
28
|
-
private
|
31
|
+
private
|
29
32
|
|
30
|
-
def get_pin(app_key: nil, scope: nil)
|
31
|
-
|
32
|
-
|
33
|
+
def get_pin(app_key: nil, http: nil, scope: nil)
|
34
|
+
scope = scope.to_s if scope.is_a? Symbol
|
35
|
+
arg = "?response_type=ecobeePin&client_id=#{app_key}&scope=#{scope}"
|
36
|
+
result = http.get(arg: arg,
|
37
|
+
no_auth: true,
|
38
|
+
resource_prefix: 'authorize',
|
39
|
+
validate_status: false)
|
33
40
|
if result.key? 'error'
|
34
|
-
raise Ecobee::
|
35
|
-
|
36
|
-
result['error'],
|
37
|
-
result['error_description'])
|
41
|
+
raise Ecobee::AuthError.new(
|
42
|
+
"Register Error: (#{result['error']}) #{result['error_description']}"
|
38
43
|
)
|
39
44
|
else
|
40
45
|
result
|
41
46
|
end
|
42
|
-
rescue SocketError => msg
|
43
|
-
raise Ecobee::TokenError.new("GET failed: #{msg}")
|
44
|
-
rescue JSON::ParserError => msg
|
45
|
-
raise Ecobee::TokenError.new("Parse Error: #{msg}")
|
46
|
-
# rescue Exception => msg
|
47
|
-
# raise Ecobee::TokenError.new("Unknown Error: #{msg}")
|
48
47
|
end
|
49
48
|
|
50
49
|
end
|
data/lib/ecobee/thermostat.rb
CHANGED
@@ -24,12 +24,10 @@ module Ecobee
|
|
24
24
|
includeSensors: true
|
25
25
|
}
|
26
26
|
|
27
|
-
|
28
|
-
attr_reader :auto_refresh
|
27
|
+
attr_reader :auto_refresh, :http
|
29
28
|
|
30
29
|
def initialize(
|
31
30
|
auto_refresh: 0,
|
32
|
-
client: nil,
|
33
31
|
fake_index: nil,
|
34
32
|
index: 0,
|
35
33
|
fake_max_index: 0,
|
@@ -37,9 +35,11 @@ module Ecobee
|
|
37
35
|
selection_args: {},
|
38
36
|
token: nil
|
39
37
|
)
|
40
|
-
@auto_refresh = auto_refresh
|
41
38
|
# TODO: add auto-refresh thread handling
|
42
|
-
@
|
39
|
+
@auto_refresh = auto_refresh
|
40
|
+
|
41
|
+
raise ArgumentError.new('No token: specified') unless token
|
42
|
+
@http = token.http
|
43
43
|
@fake_index = fake_index
|
44
44
|
@fake_max_index = fake_max_index
|
45
45
|
@index = index
|
@@ -156,7 +156,7 @@ module Ecobee
|
|
156
156
|
end
|
157
157
|
|
158
158
|
def refresh
|
159
|
-
response = @
|
159
|
+
response = @http.get(arg: :thermostat, options: @selection)
|
160
160
|
if @index + 1 > response['thermostatList'].length
|
161
161
|
raise ThermostatError.new('No such thermostat')
|
162
162
|
end
|
@@ -240,7 +240,7 @@ module Ecobee
|
|
240
240
|
body = my_selection
|
241
241
|
body.merge!({ 'functions' => functions }) if functions
|
242
242
|
body.merge!({ 'thermostat' => thermostat }) if thermostat
|
243
|
-
@
|
243
|
+
@http.post(:thermostat, body: body)
|
244
244
|
end
|
245
245
|
|
246
246
|
end
|
data/lib/ecobee/token.rb
CHANGED
@@ -5,15 +5,21 @@ module Ecobee
|
|
5
5
|
:access_token_expire,
|
6
6
|
:app_key,
|
7
7
|
:callbacks,
|
8
|
+
:http,
|
8
9
|
:pin,
|
9
10
|
:refresh_token,
|
10
11
|
:result,
|
11
12
|
:status,
|
12
13
|
:scope,
|
14
|
+
:token_file,
|
13
15
|
:token_type
|
14
16
|
|
15
17
|
#AUTH_ERRORS = %w(slow_down authorization_pending authorization_expired)
|
16
|
-
|
18
|
+
|
19
|
+
@STATUSES = {
|
20
|
+
authorization_pending: 'Registration begun but has not been approved.',
|
21
|
+
ready: 'Registration approved and valid token received.'
|
22
|
+
}
|
17
23
|
|
18
24
|
def initialize(
|
19
25
|
access_token: nil,
|
@@ -33,6 +39,8 @@ module Ecobee
|
|
33
39
|
@token_file = expand_files token_file
|
34
40
|
|
35
41
|
@authorization_thread, @pin, @status, @token_type = nil
|
42
|
+
@poll_interval = DEFAULT_POLL_INTERVAL
|
43
|
+
@http = Ecobee::HTTP.new(log_file: "/tmp/token.log", token: self)
|
36
44
|
|
37
45
|
@refresh_pad = REFRESH_PAD + rand(REFRESH_PAD)
|
38
46
|
|
@@ -41,15 +49,33 @@ module Ecobee
|
|
41
49
|
end
|
42
50
|
|
43
51
|
def access_token
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
52
|
+
if @access_token
|
53
|
+
if access_token_expired?
|
54
|
+
if @refresh_token
|
55
|
+
refresh_access_token
|
56
|
+
else
|
57
|
+
token_register
|
58
|
+
end
|
59
|
+
else
|
60
|
+
desired_status = (@refresh_token ? :ready : :authorization_pending)
|
61
|
+
if @refresh_token
|
62
|
+
if @status != desired_status
|
63
|
+
puts "Status: MISMATCH: #{@status} vs #{desired_status}" if @status
|
64
|
+
@status = desired_status
|
65
|
+
end
|
66
|
+
@access_token
|
67
|
+
else
|
68
|
+
check_for_authorization
|
69
|
+
end
|
70
|
+
end
|
71
|
+
else
|
72
|
+
@status = :authorization_pending
|
73
|
+
token_register
|
74
|
+
end
|
50
75
|
end
|
51
76
|
|
52
77
|
def access_token_expired?
|
78
|
+
return true unless @access_token_expire
|
53
79
|
Time.now.to_i > @access_token_expire - @refresh_pad
|
54
80
|
end
|
55
81
|
|
@@ -85,27 +111,20 @@ module Ecobee
|
|
85
111
|
end
|
86
112
|
|
87
113
|
def refresh_access_token
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
114
|
+
arg = sprintf("?grant_type=refresh_token&refresh_token=%s&client_id=%s",
|
115
|
+
@refresh_token,
|
116
|
+
@app_key)
|
117
|
+
result = @http.post(arg: arg,
|
118
|
+
no_auth: true,
|
119
|
+
resource_prefix: 'token',
|
120
|
+
validate_status: false)
|
95
121
|
if result.key? 'error'
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
else
|
103
|
-
puts "DUMPING(result): #{result.pretty_inspect}"
|
104
|
-
raise Ecobee::TokenError.new(
|
105
|
-
"Result Error: (%s) %s" % [result['error'],
|
106
|
-
result['error_description']]
|
107
|
-
)
|
108
|
-
end
|
122
|
+
@access_token, @access_token_expire, @pin, @scope, @refresh_token = nil
|
123
|
+
config_save
|
124
|
+
raise Ecobee::AuthError.new(
|
125
|
+
"Result Error: (%s) %s" % [result['error'],
|
126
|
+
result['error_description']]
|
127
|
+
)
|
109
128
|
else
|
110
129
|
@access_token = result['access_token']
|
111
130
|
@access_token_expire = Time.now.to_i + result['expires_in']
|
@@ -117,12 +136,6 @@ module Ecobee
|
|
117
136
|
config_save
|
118
137
|
@access_token
|
119
138
|
end
|
120
|
-
rescue SocketError => msg
|
121
|
-
raise Ecobee::TokenError.new("POST failed: #{msg}")
|
122
|
-
rescue JSON::ParserError => msg
|
123
|
-
raise Ecobee::TokenError.new("Result parsing: #{msg}")
|
124
|
-
# rescue Exception => msg
|
125
|
-
# raise Ecobee::TokenError.new("Unknown Error: #{msg}")
|
126
139
|
end
|
127
140
|
|
128
141
|
def register_callback(type, *callback, &block)
|
@@ -134,8 +147,14 @@ module Ecobee
|
|
134
147
|
end
|
135
148
|
end
|
136
149
|
|
137
|
-
def wait
|
138
|
-
|
150
|
+
def wait(timeout: nil)
|
151
|
+
if timeout
|
152
|
+
Timeout::timeout(timeout) { wait(timeout: nil) }
|
153
|
+
else
|
154
|
+
sleep 0.01 while @status == :authorization_pending
|
155
|
+
end
|
156
|
+
rescue Timeout::Error
|
157
|
+
ensure
|
139
158
|
@status
|
140
159
|
end
|
141
160
|
|
@@ -148,9 +167,10 @@ module Ecobee
|
|
148
167
|
unless @authorization_thread && @authorization_thread.alive?
|
149
168
|
@authorization_thread = Thread.new {
|
150
169
|
loop do
|
151
|
-
|
152
|
-
sleep
|
170
|
+
puts "Sleeping for #{@poll_interval}"
|
171
|
+
sleep @poll_interval
|
153
172
|
break if @status == :ready
|
173
|
+
puts "check_for_authorization_single"
|
154
174
|
check_for_authorization_single
|
155
175
|
end
|
156
176
|
}
|
@@ -159,21 +179,23 @@ module Ecobee
|
|
159
179
|
end
|
160
180
|
|
161
181
|
def check_for_authorization_single
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
182
|
+
arg = sprintf("?grant_type=ecobeePin&code=%s&client_id=%s",
|
183
|
+
@access_token,
|
184
|
+
@app_key)
|
185
|
+
result = @http.post(arg: arg,
|
186
|
+
no_auth: true,
|
187
|
+
resource_prefix: 'token',
|
188
|
+
validate_status: false)
|
169
189
|
if result.key? 'error'
|
170
190
|
@status = :authorization_pending
|
171
191
|
if result['error'] == 'invalid_client'
|
172
|
-
|
173
|
-
elsif
|
174
|
-
|
175
|
-
|
176
|
-
|
192
|
+
token_register
|
193
|
+
elsif ['slow_down', 'authorization_pending'].include? result['error']
|
194
|
+
nil
|
195
|
+
else
|
196
|
+
@access_token, @access_token_expire, @pin, @scope, @refresh_token = nil
|
197
|
+
config_save
|
198
|
+
raise Ecobee::AuthError.new(
|
177
199
|
"Result Error: (%s) %s" % [result['error'],
|
178
200
|
result['error_description']]
|
179
201
|
)
|
@@ -189,12 +211,6 @@ module Ecobee
|
|
189
211
|
config_save
|
190
212
|
@access_token
|
191
213
|
end
|
192
|
-
rescue SocketError => msg
|
193
|
-
raise Ecobee::TokenError.new("POST failed: #{msg}")
|
194
|
-
rescue JSON::ParserError => msg
|
195
|
-
raise Ecobee::TokenError.new("Result parsing: #{msg}")
|
196
|
-
# rescue Exception => msg
|
197
|
-
# raise Ecobee::TokenError.new("Unknown Error: #{msg}")
|
198
214
|
end
|
199
215
|
|
200
216
|
def config_load_to_memory(config)
|
@@ -246,7 +262,7 @@ module Ecobee
|
|
246
262
|
File.open(file, 'r').read(16 * 1024)
|
247
263
|
)
|
248
264
|
rescue JSON::ParserError => msg
|
249
|
-
raise Ecobee::
|
265
|
+
raise Ecobee::AuthError.new("Result parsing: #{msg}")
|
250
266
|
rescue Errno::ENOENT
|
251
267
|
{}
|
252
268
|
end
|
@@ -295,21 +311,22 @@ module Ecobee
|
|
295
311
|
end
|
296
312
|
end
|
297
313
|
|
298
|
-
def
|
314
|
+
def token_register
|
299
315
|
@status = :authorization_pending
|
300
|
-
result = Register.new(app_key: @app_key,
|
316
|
+
result = Ecobee::Register.new(app_key: @app_key,
|
317
|
+
http: @http,
|
318
|
+
scope: @scope)
|
319
|
+
@poll_interval = result.interval
|
301
320
|
@pin = result.pin
|
302
321
|
@access_token = result.code
|
303
|
-
@access_token_expire = result.
|
322
|
+
@access_token_expire = result.expire
|
304
323
|
@refresh_token = nil
|
305
324
|
@scope = result.scope
|
306
325
|
check_for_authorization
|
307
326
|
config_save
|
308
|
-
@access_token
|
327
|
+
@access_token
|
309
328
|
end
|
310
329
|
|
311
330
|
end
|
312
331
|
|
313
|
-
class TokenError < StandardError ; end
|
314
|
-
|
315
332
|
end
|
data/lib/ecobee/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ecobee
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rob Zwissler
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-08-
|
11
|
+
date: 2016-08-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -75,7 +75,7 @@ files:
|
|
75
75
|
- examples/test_thermostat_object.rb
|
76
76
|
- examples/test_token.rb
|
77
77
|
- lib/ecobee.rb
|
78
|
-
- lib/ecobee/
|
78
|
+
- lib/ecobee/http.rb
|
79
79
|
- lib/ecobee/register.rb
|
80
80
|
- lib/ecobee/thermostat.rb
|
81
81
|
- lib/ecobee/token.rb
|
data/lib/ecobee/client.rb
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
module Ecobee
|
2
|
-
|
3
|
-
class Client
|
4
|
-
def initialize(token: nil)
|
5
|
-
raise ArgumentError.new('Missing token') unless token
|
6
|
-
@token = token
|
7
|
-
end
|
8
|
-
|
9
|
-
def get(arg, options = nil)
|
10
|
-
new_uri = URL_API + arg.to_s.sub(/^\//, '')
|
11
|
-
new_uri += '?json=' + options.to_json if options
|
12
|
-
request = Net::HTTP::Get.new(URI(URI.escape(new_uri)))
|
13
|
-
request['Content-Type'] = *CONTENT_TYPE
|
14
|
-
request['Authorization'] = @token.authorization
|
15
|
-
http_response = http.request request
|
16
|
-
response = validate_status JSON.parse(http_response.body)
|
17
|
-
# if response == :retry
|
18
|
-
# get(arg, options)
|
19
|
-
# else
|
20
|
-
# response
|
21
|
-
# end
|
22
|
-
rescue JSON::ParserError => msg
|
23
|
-
raise ClientError.new("JSON::ParserError => #{msg}")
|
24
|
-
end
|
25
|
-
|
26
|
-
def post(arg, options: {}, body: nil)
|
27
|
-
new_uri = URL_API + arg.to_s.sub(/^\//, '')
|
28
|
-
request = Net::HTTP::Post.new(URI new_uri)
|
29
|
-
request.set_form_data({ 'format' => 'json' }.merge(options))
|
30
|
-
request.body = JSON.generate(body) if body
|
31
|
-
request['Content-Type'] = *CONTENT_TYPE
|
32
|
-
request['Authorization'] = @token.authorization
|
33
|
-
http_response = http.request request
|
34
|
-
response = validate_status JSON.parse http_response.body
|
35
|
-
# if response == :retry
|
36
|
-
# post(arg, options: options, body: body)
|
37
|
-
# else
|
38
|
-
# response
|
39
|
-
# end
|
40
|
-
rescue JSON::ParserError => msg
|
41
|
-
raise ClientError.new("JSON::ParserError => #{msg}")
|
42
|
-
end
|
43
|
-
|
44
|
-
def validate_status(response)
|
45
|
-
if !response.key? 'status'
|
46
|
-
raise ClientError.new('Missing Status')
|
47
|
-
elsif !response['status'].key? 'code'
|
48
|
-
raise ClientError.new('Missing Status Code')
|
49
|
-
elsif response['status']['code'] == 14
|
50
|
-
:retry
|
51
|
-
elsif response['status']['code'] != 0
|
52
|
-
raise ClientError.new(
|
53
|
-
"GET Error: #{response['status']['code']} " +
|
54
|
-
"Message: #{response['status']['message']}"
|
55
|
-
)
|
56
|
-
else
|
57
|
-
response
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
private
|
62
|
-
|
63
|
-
def http
|
64
|
-
@http ||= Net::HTTP.new(API_HOST, API_PORT)
|
65
|
-
unless @http.active?
|
66
|
-
@http.use_ssl = true
|
67
|
-
Net::HTTP.start(API_HOST, API_PORT)
|
68
|
-
end
|
69
|
-
@http
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
class ClientError < StandardError ; end
|
74
|
-
|
75
|
-
end
|