ecobee 0.1.1 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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