ecobee 0.1.1 → 0.1.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 02e1f09ccaf034288f69027fbdee27dcda1ec37e
4
- data.tar.gz: dc85e92a4b63632526b5f842af407f79c815f3d9
3
+ metadata.gz: 50df9ac248431f92cafec6438d42aa0bb04d964f
4
+ data.tar.gz: 1928432f79c576e9d6f88a04a00a5a8a8ca1d511
5
5
  SHA512:
6
- metadata.gz: 8be36180162e8eaccbd9b55988d63fef4c2f838f2d20dde0ecbe46abcfa28966e103fa5011538e53f0812a22c3a6543548fba2df01664f7ec7da8ae0955cc2e0
7
- data.tar.gz: 86e228b3a77ab77b45f4dee223e8e488d37bb3e68cd187217afc844ab5a9fa7795960faceb0cfec9b9648002208097d974b1e70e59c583528630aa71f9ae6009
6
+ metadata.gz: dd700da725bca1f2b2cd79e371cbc14e9b8a85b2852c2433852d473fc561ec39b67a6dc90d454ef8235fc55a5fdb1945044535a14257a4ce5a6cdd627a2f13bb
7
+ data.tar.gz: d06a3c81087115ef8d741c749bcc99ea52efd0cc668c2543f10c4b524bfec85ac65c4dfb715a62b327136e4c536f834133207e120aee6246f24ba4cefac05a63
data/README.md CHANGED
@@ -3,34 +3,42 @@
3
3
  Ecobee API Ruby Gem. Implements:
4
4
  - OAuth PIN-based token registration & renewal
5
5
  - Persistent HTTP connection
6
- - Methods for get & push requests
6
+ - Methods for GET & POST requests
7
7
  - Persistent storage for API key & refresh tokens
8
8
  - Example usage scripts (see /examples/\*)
9
9
 
10
- TODO:
11
- - Implement throttling / blocking (?)
12
- - Convert storage to generic hook
10
+ Status:
11
+ - Working, but is very basic. Contact me with feature requests.
13
12
 
13
+ TODO:
14
+ - Document API
15
+ - Convert token storage to optional block/proc
16
+ - Add timeout to Ecobee::Token#wait
17
+ - Add redirect based registration
18
+ - Implement throttling / blocking
19
+ - Helper methods/classes for building/reading requests
14
20
 
15
21
  ## Installation
16
22
 
17
- Add this line to your application's Gemfile:
23
+ The latest ecobee Ruby Gem is [available from Rubygems.org](https://rubygems.org/gems/ecobee).
18
24
 
19
- ```ruby
20
- gem 'ecobee'
25
+ To install from the command line, run:
26
+ ```
27
+ gem install ecobee
21
28
  ```
22
29
 
23
- And then execute:
24
-
25
- $ bundle
30
+ ## Usage
26
31
 
27
- Or install it yourself as:
32
+ 1. Obtain an Application Key from Ecobee by [registering your project](https://www.ecobee.com/developers).
28
33
 
29
- $ gem install ecobee
34
+ 2. Using Ecobee::Token, obtain an OAuth Access Token.
35
+ - Instantiate Ecobee::Token with the api_key and desired scope.
36
+ - Give user Ecobee::Token#pin and instructions to register your Application via the [Ecobee My Apps Portal](https://www.ecobee.com/consumerportal/index.html#/my-apps).
37
+ - You can call Ecobee::Token#wait to block until the user confirms the PIN code.
30
38
 
31
- ## Usage
39
+ 3. Instantiate Ecobee::Client with the token object.
32
40
 
33
- TODO: Write usage instructions here
41
+ 4. Call Ecobee::Client#get or Ecobee::Client#post to interact with [Ecobee's API](https://www.ecobee.com/home/developer/api/introduction/index.shtml).
34
42
 
35
43
  ## Development
36
44
 
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Allows for display and control of your Ecobee in the Mac OS X
4
+ # menubar, using BitBar (http://getbitbar.com). -- @robzr
5
+ #
6
+ # <bitbar.title>EcobeeStat</bitbar.title>
7
+ # <bitbar.version>v1.0</bitbar.version>
8
+ # <bitbar.author>Rob Zwissler</bitbar.author>
9
+ # <bitbar.author.github>robzr</bitbar.author.github>
10
+ # <bitbar.desc>Ecobee Thermostat Control</bitbar.desc>
11
+ # <bitbar.image>http://github.com/robzr/ecobee</bitbar.image>
12
+ # <bitbar.dependencies>ruby</bitbar.dependencies>
13
+ # <bitbar.abouturl>http://github.com/robzr/ecobee</bitbar.abouturl>
14
+
15
+ require 'pp'
16
+ require 'ecobee'
17
+ #require_relative '/Users/robzr/GitHub/ecobee/lib/ecobee.rb'
18
+ #require_relative '/Users/robzr/GitHub/ecobee/lib/ecobee/client.rb'
19
+ #require_relative '/Users/robzr/GitHub/ecobee/lib/ecobee/token.rb'
20
+ #require_relative '/Users/robzr/GitHub/ecobee/lib/ecobee/register.rb'
21
+
22
+ API_KEY = 'u2Krw0OumeliB0OnwiaogySvgExhy2K4'
23
+ HVAC_MODES = ['auto', 'auxHeatOnly', 'cool', 'heat', 'off', 'quit']
24
+ DEG = '°'
25
+
26
+ module Ecobee
27
+ class ResponseError < StandardError ; end
28
+
29
+ class BitBar
30
+ def initialize(client)
31
+ @client = client
32
+ end
33
+
34
+ def get_thermostat(args = {})
35
+ index = args.delete(:index) || 0
36
+ http_response = @client.get('thermostat',
37
+ Ecobee::Selection(args))
38
+ response = JSON.parse(http_response.body)
39
+ get_thermostat_list_index(index: index,
40
+ response: validate_status(response))
41
+ rescue JSON::ParserError => msg
42
+ raise ResponseError.new("JSON::ParserError => #{msg}")
43
+ end
44
+
45
+ def get_thermostat_list_index(index: 0, response: nil)
46
+ if !response.key? 'thermostatList'
47
+ raise ResponseError.new('Missing thermostatList')
48
+ elsif index >= response['thermostatList'].length
49
+ raise ResponseError.new(
50
+ "Missing thermostatList Index #{index} (Max Found: " +
51
+ "#{response['thermostatList'].length - 1})"
52
+ )
53
+ else
54
+ response['thermostatList'][index]
55
+ end
56
+ end
57
+
58
+ def validate_status(response)
59
+ if !response.key? 'status'
60
+ raise ResponseError.new('Missing Status')
61
+ elsif !response['status'].key? 'code'
62
+ raise ResponseError.new('Missing Status Code')
63
+ elsif response['status']['code'] != 0
64
+ raise ResponseError.new(
65
+ "GET Error: #{response['status']['code']} " +
66
+ "Message: #{response['status']['message']}"
67
+ )
68
+ else
69
+ response
70
+ end
71
+ end
72
+
73
+ # puts "Heat: #{info['runtime']['desiredHeat'] / 10}#{DEG} | color=red"
74
+
75
+ def set_hold(cool_hold: nil, heat_hold: nil)
76
+ functions = [{
77
+ 'type' => 'setHold',
78
+ 'params' => {
79
+ 'holdType' => 'nextTransition',
80
+ }
81
+ }]
82
+ functions[0]['params']['coolHoldTemp'] = cool_hold
83
+ functions[0]['params']['heatHoldTemp'] = heat_hold
84
+ http_response = @client.post(
85
+ 'thermostat',
86
+ body: {
87
+ 'selection' => {
88
+ 'selectionType' => 'registered',
89
+ 'selectionMatch' => '',
90
+ },
91
+ 'functions' => functions
92
+ }
93
+ )
94
+ response = JSON.parse(http_response.body)
95
+ end
96
+
97
+ def update_mode(mode)
98
+ http_response = @client.post(
99
+ 'thermostat',
100
+ body: {
101
+ 'selection' => {
102
+ 'selectionType' => 'registered',
103
+ 'selectionMatch' => '',
104
+ },
105
+ 'thermostat' => {
106
+ 'settings' => {
107
+ 'hvacMode' => mode
108
+ }
109
+ }
110
+ }
111
+ )
112
+ response = JSON.parse(http_response.body)
113
+ end
114
+ end
115
+ end
116
+
117
+ def header(info)
118
+ puts "#{info['runtime']['actualTemperature'] / 10.0}#{DEG}"
119
+ puts '---'
120
+ end
121
+
122
+ def cool_menu(info)
123
+ present_mode = info['settings']['hvacMode']
124
+ return unless ['auto', 'cool'].include? present_mode
125
+ puts "Cool: #{info['runtime']['desiredCool'] / 10}#{DEG} | color=blue"
126
+ cool_low = info['settings']['coolRangeLow'] / 10
127
+ cool_high = info['settings']['coolRangeHigh'] / 10
128
+ (cool_low..cool_high).reverse_each do |temp|
129
+ flag, color = ''
130
+ flag = ' :arrow_left:' if temp == info['runtime']['actualTemperature'] / 10
131
+ color = ' color=blue' if temp == info['runtime']['desiredCool'] / 10
132
+ puts("--#{temp}#{DEG}#{flag}|#{color} bash=\"#{$0}\" " +
133
+ "param1=\"set_cool=#{temp}\" refresh=true terminal=false")
134
+ end
135
+ end
136
+
137
+ def heat_menu(info)
138
+ present_mode = info['settings']['hvacMode']
139
+ return unless ['auto', 'auxHeatOnly', 'heat'].include? present_mode
140
+ puts "Heat: #{info['runtime']['desiredHeat'] / 10}#{DEG} | color=red"
141
+ heat_low = info['settings']['heatRangeLow'] / 10
142
+ heat_high = info['settings']['heatRangeHigh'] / 10
143
+ (heat_low..heat_high).reverse_each do |temp|
144
+ flag, color = ''
145
+ flag = ' :arrow_left:' if temp == info['runtime']['actualTemperature'] / 10
146
+ color = ' color=red' if temp == info['runtime']['desiredHeat'] / 10
147
+ puts("--#{temp}#{DEG}#{flag}|#{color} bash=\"#{$0}\" " +
148
+ "param1=\"set_heat=#{temp}\" refresh=true terminal=false")
149
+ end
150
+ end
151
+
152
+ def mode_menu(info)
153
+ puts "Mode: #{info['settings']['hvacMode']}"
154
+ Ecobee::HVAC_MODES.reject { |mode| mode == info['settings']['hvacMode'] }
155
+ .each do |mode|
156
+ puts("--#{mode} | bash=\"#{$0}\" param1=\"set_mode=#{mode}\" " +
157
+ "refresh=true terminal=false")
158
+ end
159
+ end
160
+
161
+ def separator
162
+ puts '---'
163
+ end
164
+
165
+ def stat_info(info)
166
+ puts info['name']
167
+ info['remoteSensors'].each do |sensor|
168
+ temp = sensor['capability'].select do |cap|
169
+ cap['type'] == 'temperature'
170
+ end
171
+ temp = temp[0]['value'].to_i / 10.0
172
+ puts "--#{sensor['name']}: #{temp}#{DEG}"
173
+ end
174
+ puts "#{info['brand']} #{Ecobee::Model(info['modelNumber'])}"
175
+ puts "Status: #{info['equipmentStatus']}"
176
+ end
177
+
178
+ def website
179
+ puts 'Ecobee Web Portal|href="https://www.ecobee.com/consumerportal/index.html"'
180
+ end
181
+
182
+ token = Ecobee::Token.new(
183
+ app_key: API_KEY, app_name: API_KEY,
184
+ scope: :smartWrite,
185
+ token_file: '~/.ecobee_token'
186
+ )
187
+ if token.pin
188
+ puts "Ecobee | color=red"
189
+ puts "---"
190
+ puts "Registration Needed | color=red"
191
+ puts "---"
192
+ puts 'Login to Ecobee | href=\'https://www.ecobee.com/consumerportal/index.html\''
193
+ puts 'Select \'My Apps\' from the drop-down menu'
194
+ puts 'Press the \'Add Application\' button'
195
+ puts "Enter authorization code: #{token.pin}"
196
+ exit
197
+ end
198
+
199
+ ecobar = Ecobee::BitBar.new Ecobee::Client.new(token: token)
200
+
201
+ case arg = ARGV.shift
202
+ when /^dump/
203
+ pp ecobar.get_thermostat(
204
+ :includeRuntime => true,
205
+ :includeExtendedRuntime => true,
206
+ :includeElectricity => true,
207
+ :includeSettings => true,
208
+ :includeLocation => true,
209
+ :includeProgram => true,
210
+ :includeEvents => true,
211
+ :includeDevice => true,
212
+ :includeTechnician => true,
213
+ :includeUtility => true,
214
+ :includeAlerts => true,
215
+ :includeWeather => true,
216
+ :includeOemConfig => true,
217
+ :includeEquipmentStatus => true,
218
+ :includeNotificationSettings => true,
219
+ :includeVersion => true,
220
+ :includeSensors => true
221
+ )
222
+ when /^set_mode=/
223
+ mode = arg.sub(/^.*=/, '')
224
+ ecobar.update_mode mode
225
+ when /^set_cool=/
226
+ info = ecobar.get_thermostat(includeRuntime: true,
227
+ includeSettings: true)
228
+ cool_hold = arg.sub(/^.*=/, '').to_i * 10
229
+ heat_hold = info['runtime']['desiredHeat']
230
+
231
+ ecobar.set_hold(cool_hold: cool_hold, heat_hold: heat_hold)
232
+ when /^set_heat=/
233
+ info = ecobar.get_thermostat(includeRuntime: true,
234
+ includeSettings: true)
235
+ cool_hold = info['runtime']['desiredCool']
236
+ heat_hold = arg.sub(/^.*=/, '').to_i * 10
237
+
238
+ ecobar.set_hold(cool_hold: cool_hold, heat_hold: heat_hold)
239
+ else
240
+ info = ecobar.get_thermostat(includeRuntime: true,
241
+ includeSettings: true,
242
+ includeEquipmentStatus: true,
243
+ includeSensors: true)
244
+ header info
245
+ cool_menu info
246
+ heat_menu info
247
+ separator
248
+ stat_info info
249
+ mode_menu info
250
+ separator
251
+ website
252
+ end
@@ -1,9 +1,12 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # Loops through menu showing current thermostat mode, with option
4
+ # to change the mode. -- @robzr
2
5
 
3
6
  require 'pp'
4
7
  require 'ecobee'
5
8
 
6
- HVAC_MODES = ['auto', 'auxHeatOnly', 'cool', 'heat', 'off', 'quit']
9
+ @hvac_modes = Ecobee::HVAC_MODES + ['quit']
7
10
 
8
11
  class TestFunctions
9
12
  def initialize(client)
@@ -55,37 +58,36 @@ class TestFunctions
55
58
  )
56
59
  response = JSON.parse(http_response.body)
57
60
  end
58
-
59
61
  end
60
62
 
61
-
62
63
  token = Ecobee::Token.new(
63
- api_key: ENV['ECOBEE_API_KEY'],
64
+ app_key: ENV['ECOBEE_APP_KEY'],
65
+ app_name: 'set_mode',
64
66
  scope: :smartWrite,
65
67
  token_file: '~/.ecobee_token'
66
68
  )
67
69
 
68
- puts token.pin_message if token.pin
69
- token.wait
70
-
71
- test_functions = TestFunctions.new(Ecobee::Client.new(token: token))
70
+ puts token.pin_message if token.pin token.wait
71
+ test_functions = TestFunctions.new(
72
+ Ecobee::Client.new(token: token)
73
+ )
72
74
 
73
75
  loop do
74
76
  test_functions.print_summary
75
77
 
76
78
  answer = -1
77
- while !answer.between?(0, HVAC_MODES.length)
79
+ until answer.between?(0, @hvac_modes.length - 1)
78
80
  puts
79
- (1..HVAC_MODES.length).each do |num|
80
- printf "%d) %s\n", num, HVAC_MODES[num - 1]
81
+ (1..@hvac_modes.length).each do |num|
82
+ printf "%d) %s\n", num, @hvac_modes[num - 1]
81
83
  end
82
84
  print "Enter mode: "
83
85
  answer = gets.to_i - 1
84
- abort if answer == (HVAC_MODES.length - 1)
86
+ abort if answer == (@hvac_modes.length - 1)
85
87
  end
86
88
  puts
87
89
 
88
- result = test_functions.update_mode(HVAC_MODES[answer])
90
+ result = test_functions.update_mode(@hvac_modes[answer])
89
91
 
90
92
  unless result.key?('status') && (result['status']['code'] == 0)
91
93
  puts "Unknown result: #{result.to_s}\n"
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # Refreshes token; displays details on saved token. -- @robzr
2
4
 
3
5
  require 'pp'
4
6
  require 'ecobee'
7
+ require_relative '/Users/robzr/GitHub/ecobee/lib/ecobee/token.rb'
8
+ require_relative '/Users/robzr/GitHub/ecobee/lib/ecobee/register.rb'
5
9
 
6
10
  token = Ecobee::Token.new(
7
- api_key: ENV['ECOBEE_API_KEY'],
11
+ app_key: ENV['ECOBEE_API_KEY'],
12
+ app_name: 'ecobee-gem',
8
13
  token_file: '~/.ecobee_token'
9
14
  )
10
15
 
@@ -12,7 +12,7 @@ module Ecobee
12
12
  API_PORT = 443
13
13
  CONTENT_TYPE = ['application/json', { 'charset' => 'UTF-8' }]
14
14
 
15
- DEFAULT_APP_NAME = 'ecobee-gem'
15
+ HVAC_MODES = ['auto', 'auxHeatOnly', 'cool', 'heat', 'off']
16
16
 
17
17
  REFRESH_INTERVAL_PAD = 60
18
18
  REFRESH_TOKEN_CHECK = 10
@@ -22,9 +22,11 @@ module Ecobee
22
22
  URL_BASE= "https://#{API_HOST}:#{API_PORT}"
23
23
 
24
24
  URL_API = "#{URL_BASE}/1/"
25
- URL_GET_PIN = "#{URL_BASE}/authorize?response_type=ecobeePin&client_id=%s&scope=%s"
25
+ URL_GET_PIN = URL_BASE +
26
+ '/authorize?response_type=ecobeePin&client_id=%s&scope=%s'
26
27
  URL_TOKEN = "#{URL_BASE}/token"
27
28
 
29
+
28
30
  def self.Model(model)
29
31
  { 'idtSmart' => 'ecobee Smart',
30
32
  'idtEms' => 'ecobee Smart EMS',
@@ -55,8 +57,32 @@ module Ecobee
55
57
  15 => 'Duplicate data violation.',
56
58
  16 => 'Invalid token. Token has been deauthorized by user. You must ' +
57
59
  're-request authorization.'
58
- }[core] || 'Unknown Error.'
60
+ }[code] || 'Unknown Error.'
59
61
  end
60
62
 
61
- end
63
+ def self.Selection(arg = {})
64
+ { 'selection' => {
65
+ 'selectionType' => 'registered',
66
+ 'selectionMatch' => '',
67
+ 'includeRuntime' => 'false',
68
+ 'includeExtendedRuntime' => 'false',
69
+ 'includeElectricity' => 'false',
70
+ 'includeSettings' => 'false',
71
+ 'includeLocation' => 'false',
72
+ 'includeProgram' => 'false',
73
+ 'includeEvents' => 'false',
74
+ 'includeDevice' => 'false',
75
+ 'includeTechnician' => 'false',
76
+ 'includeUtility' => 'false',
77
+ 'includeAlerts' => 'false',
78
+ 'includeWeather' => 'false',
79
+ 'includeOemConfig' => 'false',
80
+ 'includeEquipmentStatus' => 'false',
81
+ 'includeNotificationSettings' => 'false',
82
+ 'includeVersion' => 'false',
83
+ 'includeSensors' => 'false',
84
+ }.merge(Hash[*arg.map { |k,v| [k.to_s, v.to_s] }.flatten])
85
+ }
86
+ end
62
87
 
88
+ end
@@ -10,7 +10,8 @@ module Ecobee
10
10
  def get(arg, options = nil)
11
11
  new_uri = URL_API + arg.sub(/^\//, '')
12
12
  new_uri += '?json=' + options.to_json if options
13
- request = Net::HTTP::Get.new(URI new_uri)
13
+
14
+ request = Net::HTTP::Get.new(URI(URI.escape(new_uri)))
14
15
  request['Content-Type'] = *CONTENT_TYPE
15
16
  request['Authorization'] = @token.authorization
16
17
  http.request(request)
@@ -19,7 +20,7 @@ module Ecobee
19
20
  def post(arg, options: {}, body: nil)
20
21
  new_uri = URL_API + arg.sub(/^\//, '')
21
22
  request = Net::HTTP::Post.new(URI new_uri)
22
- request.set_form_data({ 'format': 'json' }.merge(options))
23
+ request.set_form_data({ 'format' => 'json' }.merge(options))
23
24
  request.body = JSON.generate(body) if body
24
25
  request['Content-Type'] = *CONTENT_TYPE
25
26
  request['Authorization'] = @token.authorization
@@ -1,12 +1,13 @@
1
1
  module Ecobee
2
+ require 'date'
2
3
 
3
4
  class Register
4
- attr_reader :result
5
+ attr_reader :expires_at, :result
5
6
 
6
- def initialize(api_key: nil, scope: SCOPES[0])
7
- raise ArgumentError.new('Missing api_key') unless api_key
8
-
9
- @result = get_pin(api_key: api_key, scope: scope)
7
+ def initialize(app_key: nil, scope: SCOPES[0])
8
+ raise ArgumentError.new('Missing app_key') unless app_key
9
+ @result = get_pin(app_key: app_key, scope: scope)
10
+ @expires_at = DateTime.now.strftime('%s').to_i + result['expires_in'] * 60
10
11
  end
11
12
 
12
13
  def code
@@ -21,27 +22,28 @@ module Ecobee
21
22
  @result['ecobeePin']
22
23
  end
23
24
 
25
+ def scope
26
+ @result['scope']
27
+ end
28
+
24
29
  private
25
30
 
26
- def get_pin(api_key: nil, scope: nil)
27
- uri_pin = URI(URL_GET_PIN % [api_key, scope.to_s])
31
+ def get_pin(app_key: nil, scope: nil)
32
+ uri_pin = URI(URL_GET_PIN % [app_key, scope.to_s])
28
33
  result = JSON.parse Net::HTTP.get(uri_pin)
29
34
  if result.key? 'error'
30
- raise Ecobee::RegisterError.new(
31
- "Result Error: (%s) %s" % [result['error'], result['error_description']]
35
+ raise Ecobee::TokenError.new(
36
+ "Register Error: (%s) %s" % [result['error'], result['error_description']]
32
37
  )
33
38
  end
34
39
  result
35
40
  rescue SocketError => msg
36
- raise Ecobee::RegisterError.new("GET failed: #{msg}")
41
+ raise Ecobee::TokenError.new("GET failed: #{msg}")
37
42
  rescue JSON::ParserError => msg
38
- raise Ecobee::RegisterError.new("Result parsing: #{msg}")
43
+ raise Ecobee::TokenError.new("Parse Error: #{msg}")
39
44
  rescue Exception => msg
40
- raise Ecobee::RegisterError.new("Unknown Error: #{msg}")
45
+ raise Ecobee::TokenError.new("Unknown Error: #{msg}")
41
46
  end
42
47
  end
43
48
 
44
- class RegisterError < StandardError
45
- end
46
-
47
49
  end
@@ -1,4 +1,5 @@
1
1
  module Ecobee
2
+ require 'date'
2
3
 
3
4
  class Token
4
5
  attr_reader :access_token,
@@ -12,26 +13,26 @@ module Ecobee
12
13
  :type
13
14
 
14
15
  def initialize(
15
- api_key: nil,
16
- app_name: DEFAULT_APP_NAME,
16
+ app_key: nil,
17
+ app_name: nil,
17
18
  code: nil,
18
19
  refresh_token: nil,
19
20
  scope: SCOPES[0],
20
21
  token_file: nil
21
22
  )
22
- @api_key = api_key
23
+ @app_key = app_key
23
24
  @app_name = app_name
24
25
  @code = code
25
- @access_token, @expires_at, @pin, @type = nil
26
+ @access_token, @code_expires_at, @expires_at, @pin, @type = nil
26
27
  @refresh_token = refresh_token
27
28
  @scope = scope
28
29
  @status = :authorization_pending
29
30
  @token_file = File.expand_path(token_file)
30
- read_token_file unless @refresh_token
31
+ parse_token_file unless @refresh_token
31
32
  if @refresh_token
32
33
  refresh
33
34
  else
34
- register unless code
35
+ register unless pin_is_valid
35
36
  check_for_token
36
37
  launch_monitor_thread unless @status == :ready
37
38
  end
@@ -46,6 +47,14 @@ module Ecobee
46
47
  "#{@type} #{@access_token}"
47
48
  end
48
49
 
50
+ def pin_is_valid
51
+ if @pin && @code && @code_expires_at
52
+ @code_expires_at.to_i >= DateTime.now.strftime('%s').to_i
53
+ else
54
+ false
55
+ end
56
+ end
57
+
49
58
  def pin_message
50
59
  "Log into Ecobee web portal, select My Apps widget, Add Application, " +
51
60
  "enter the PIN #{@pin || ''}"
@@ -56,7 +65,7 @@ module Ecobee
56
65
  URI(URL_TOKEN),
57
66
  'grant_type' => 'refresh_token',
58
67
  'refresh_token' => @refresh_token,
59
- 'client_id' => @api_key
68
+ 'client_id' => @app_key
60
69
  )
61
70
  result = JSON.parse(response.body)
62
71
  if result.key? 'error'
@@ -69,6 +78,7 @@ module Ecobee
69
78
  @access_token = result['access_token']
70
79
  @expires_at = Time.now + result['expires_in']
71
80
  @refresh_token = result['refresh_token']
81
+ @pin, @code, @code_expires_at = nil
72
82
  @scope = result['scope']
73
83
  @type = result['token_type']
74
84
  @status = :ready
@@ -94,12 +104,13 @@ module Ecobee
94
104
  URI(URL_TOKEN),
95
105
  'grant_type' => 'ecobeePin',
96
106
  'code' => @code,
97
- 'client_id' => @api_key
107
+ 'client_id' => @app_key
98
108
  )
99
109
  result = JSON.parse(response.body)
100
110
  if result.key? 'error'
101
111
  unless ['slow_down', 'authorization_pending'].include? result['error']
102
- pp result
112
+ # TODO: throttle or just ignore...?
113
+ pp result
103
114
  raise Ecobee::TokenError.new(
104
115
  "Result Error: (%s) %s" % [result['error'],
105
116
  result['error_description']]
@@ -112,6 +123,7 @@ pp result
112
123
  @expires_at = Time.now + result['expires_in']
113
124
  @refresh_token = result['refresh_token']
114
125
  @scope = result['scope']
126
+ @pin, @code, @code_expires_at = nil
115
127
  write_token_file
116
128
  end
117
129
  rescue SocketError => msg
@@ -132,32 +144,63 @@ pp result
132
144
  }
133
145
  end
134
146
 
135
- def read_token_file
136
- tf = File.open(@token_file, 'r').read(16 * 1024)
137
- config = JSON.parse(tf)
138
- if config.key? @app_name
139
- @app_key ||= config[@app_name]['app_key']
140
- @refresh_token = config[@app_name]['refresh_token']
147
+ def parse_token_file
148
+ #puts "Before Parse: app_key:#{@app_key} refresh_token:#{@refresh_token} pin:#{pin}"
149
+ return unless (config = read_token_file).is_a? Hash
150
+ section = (@app_name && config.key?(@app_name)) ? @app_name : @app_key
151
+ if config.key?(section)
152
+ @app_key ||= if config[section].key?('app_key')
153
+ config[section]['app_key']
154
+ else
155
+ @app_name
156
+ end
157
+ if config[section].key?('refresh_token')
158
+ @refresh_token ||= config[section]['refresh_token']
159
+ elsif config[section].key?('pin')
160
+ @pin ||= config[section]['pin']
161
+ @code ||= config[section]['code']
162
+ @code_expires_at ||= config[section]['code_expires_at'].to_i
163
+ end
141
164
  end
165
+ #puts "After Parse: app_key:#{@app_key} refresh_token:#{@refresh_token} pin:#{pin}"
166
+ end
167
+
168
+ def read_token_file
169
+ JSON.parse(
170
+ File.open(@token_file, 'r').read(16 * 1024)
171
+ )
142
172
  rescue Errno::ENOENT
173
+ {}
143
174
  end
144
175
 
145
176
  def register
146
- result = Register.new(api_key: @api_key, scope: @scope)
177
+ result = Register.new(app_key: @app_key, scope: @scope)
147
178
  @pin = result.pin
148
179
  @code = result.code
180
+ @code_expires_at = result.expires_at
181
+ @scope = result.scope
182
+ write_token_file
149
183
  result
150
184
  end
151
185
 
152
186
  def write_token_file
153
187
  return unless @token_file
188
+ if config = read_token_file
189
+ config.delete(@app_name)
190
+ config.delete(@app_key)
191
+ end
192
+ section = @app_name || @app_key
193
+ config[section] = {}
194
+ config[section]['app_key'] = @app_key if @app_key && section != @app_key
195
+ if @refresh_token
196
+ config[section]['refresh_token'] = @refresh_token
197
+ elsif @pin
198
+ config[section]['pin'] = @pin
199
+ config[section]['code'] = @code
200
+ config[section]['code_expires_at'] = @code_expires_at
201
+ end
154
202
  File.open(@token_file, 'w') do |tf|
155
- tf.puts JSON.pretty_generate({
156
- @app_name => {
157
- 'app_key' => @app_key,
158
- 'refresh_token' => @refresh_token
159
- }
160
- })
203
+ tf.puts JSON.pretty_generate(config)
161
204
  end
162
205
  end
163
206
 
@@ -1,3 +1,3 @@
1
1
  module Ecobee
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.6"
3
3
  end
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.1.1
4
+ version: 0.1.6
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-02 00:00:00.000000000 Z
11
+ date: 2016-08-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -70,6 +70,7 @@ files:
70
70
  - bin/console
71
71
  - bin/setup
72
72
  - ecobee.gemspec
73
+ - examples/bitbar_plugin.rb
73
74
  - examples/set_mode.rb
74
75
  - examples/test_token.rb
75
76
  - lib/ecobee.rb