ewelink 4.1.0 → 6.0.0

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
  SHA256:
3
- metadata.gz: 8cba69a24adaf18a0be08cbf1618b612d6d0e90fd788d8b9f2360f7d19f67406
4
- data.tar.gz: adc650eac1533d343320490132302827a79f0fc4a0571d0dcba91ef926e5f682
3
+ metadata.gz: '03889642d2851312650d6419544fd7423b2b8ca0a64a272386de46646640b191'
4
+ data.tar.gz: b36f496794dce02953b423d601f4e71905afca7c0aebe4fe5bfc65d08f65daff
5
5
  SHA512:
6
- metadata.gz: 39ab9a66085401bb70f34f74b9220e70ad691c5db65b180816d5a284462162064364d6e6ed3f2b5d990f9c3fa3450472fb0b13ba0b3ecbca1a22042de3926d28
7
- data.tar.gz: 19b1627d07a411ef4fe04578f924a7d339413d65d7132b8cd7614877b1e72300b5cbd51f1f9a6469807fe3179e37a65c048f1e699601c4eb3b4f05b82b0e663f
6
+ metadata.gz: c3a3349b5c5f5384eb4f02abcf3749b0674ef839c2354f38d319db4ff15e272f7ec551058b52601a1971307f225eac8d0c5c7cfee00d6537a527bb09f044aceb
7
+ data.tar.gz: 63035965d8732f4fda004b16331ee06a4ad6f55da7a4338b041585823397b14bb6dfecacf83123354b024f2a9fb8e1b724d32011862d9343babec527fff12d2e
data/README.mdown CHANGED
@@ -80,8 +80,6 @@ api.press_rf_bridge_button!(button[:uuid])
80
80
  - `async_actions` (`true` | `false`): To perform actions (pressing an RF
81
81
  bridge button or turning a switch on/off) in asynchronous mode. (default:
82
82
  `false`).
83
- - `update_devices_status_on_connect` (`true` | `false`): To update devices
84
- status (on, off) when connecting to Ewelink API (default: `false`).
85
83
 
86
84
  ### Configuring logger
87
85
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 4.1.0
1
+ 6.0.0
data/ewelink.gemspec CHANGED
@@ -16,7 +16,6 @@ Gem::Specification.new do |s|
16
16
  s.required_ruby_version = '>= 3.1.0'
17
17
 
18
18
  s.add_dependency 'activesupport', '>= 7.0.0', '< 8.0.0'
19
- s.add_dependency 'faye-websocket', '>= 0.11.0', '< 0.12.0'
20
19
  s.add_dependency 'httparty', '>= 0.20.0', '< 0.21.0'
21
20
  s.add_dependency 'thread', '>= 0.2.0', '< 0.3.0'
22
21
 
data/lib/ewelink/api.rb CHANGED
@@ -2,34 +2,26 @@ module Ewelink
2
2
 
3
3
  class Api
4
4
 
5
- APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'.freeze
6
- APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'.freeze
5
+ APP_ID = 'Uw83EKZFxdif7XFXEsrpduz5YyjP7nTl'.freeze
6
+ APP_SECRET = 'mXLOjea0woSMvK9gw7Fjsy7YlFO4iSu6'.freeze
7
+ DEFAULT_COUNTRY_CODE = '+44'.freeze
7
8
  DEFAULT_REGION = 'us'.freeze
8
9
  REQUEST_TIMEOUT = 10.seconds
9
10
  RF_BRIDGE_DEVICE_UIID = 28
10
11
  SWITCH_DEVICES_UIIDS = [1, 5, 6, 24].freeze
11
- URL = 'https://#{region}-api.coolkit.cc:8080'.freeze
12
+ URL = 'https://#{region}-apia.coolkit.cc'.freeze
12
13
  VERSION = 8
13
- WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
14
- WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
15
- SWITCH_STATUS_CHANGE_CHECK_TIMEOUT = 2.seconds
16
- WEB_SOCKET_WAIT_INTERVAL = 0.2.seconds
17
14
 
18
15
  attr_reader :email, :password, :phone_number
19
16
 
20
- def initialize(password:, async_actions: false, email: nil, phone_number: nil, update_devices_status_on_connect: false)
17
+ def initialize(password:, async_actions: false, email: nil, phone_number: nil)
21
18
  @async_actions = async_actions.present?
22
19
  @email = email.presence.try(:strip)
23
20
  @mutexs = {}
24
21
  @password = password.presence || raise(Error.new(':password must be specified'))
25
22
  @phone_number = phone_number.presence.try(:strip)
26
- @update_devices_status_on_connect = update_devices_status_on_connect.present?
27
- @web_socket_authenticated = false
28
- @web_socket_switches_statuses = {}
29
23
 
30
24
  raise(Error.new(':email or :phone_number must be specified')) if email.blank? && phone_number.blank?
31
-
32
- start_web_socket_authentication_check_thread
33
25
  end
34
26
 
35
27
  def async_actions?
@@ -40,23 +32,21 @@ module Ewelink
40
32
  process_action do
41
33
  synchronize(:press_rf_bridge_button) do
42
34
  button = find_rf_bridge_button!(uuid)
43
- web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
44
- params = {
45
- 'action' => 'update',
46
- 'apikey' => button[:api_key],
47
- 'deviceid' => button[:device_id],
48
- 'params' => {
49
- 'cmd' => 'transmit',
50
- 'rfChl' => button[:channel],
51
- },
52
- 'sequence' => web_socket_sequence,
53
- 'ts' => 0,
54
- 'userAgent' => 'app',
55
- }
56
- Ewelink.logger.debug(self.class.name) { "Pressing RF bridge button #{button[:uuid].inspect}" }
57
- send_to_web_socket(JSON.generate(params))
58
- true
59
- end
35
+ headers = authentication_headers.merge(
36
+ 'X-CK-Appid' => APP_ID,
37
+ 'X-CK-Nonce' => nonce,
38
+ )
39
+ params = {
40
+ 'type' => 1,
41
+ 'id' => button[:device_id],
42
+ 'params' => {
43
+ 'cmd' => 'transmit',
44
+ 'rfChl' => button[:channel],
45
+ },
46
+ }
47
+ body = JSON.generate(params)
48
+ response = rest_request(:post, '/v2/device/thing/status', headers:, body:)
49
+ response['error'] == 0
60
50
  end
61
51
  end
62
52
  end
@@ -64,38 +54,13 @@ module Ewelink
64
54
  def reload
65
55
  Ewelink.logger.debug(self.class.name) { 'Reloading API (authentication token, api key, devices, region, connections,...)' }
66
56
 
67
- @web_socket_authenticated = false
68
- @web_socket_switches_statuses.clear
69
-
70
- [@web_socket_ping_thread, @web_socket_thread].each do |thread|
71
- next unless thread
72
- if Thread.current == thread
73
- thread[:stop] = true
74
- else
75
- thread.kill
76
- end
77
- end
78
-
79
- if @web_socket.present?
80
- begin
81
- @web_socket.close if @web_socket.open?
82
- rescue
83
- # Ignoring close errors
84
- end
85
- end
86
-
87
57
  %i(
88
58
  @authentication_infos
89
59
  @devices
90
- @last_web_socket_pong_at
60
+ @homes_ids
91
61
  @region
92
62
  @rf_bridge_buttons
93
63
  @switches
94
- @web_socket_ping_interval
95
- @web_socket_ping_thread
96
- @web_socket_thread
97
- @web_socket_url
98
- @web_socket
99
64
  ).each do |variable|
100
65
  remove_instance_variable(variable) if instance_variable_defined?(variable)
101
66
  end
@@ -105,21 +70,21 @@ module Ewelink
105
70
  def rf_bridge_buttons
106
71
  synchronize(:rf_bridge_buttons) do
107
72
  @rf_bridge_buttons ||= [].tap do |buttons|
108
- rf_bridge_devices = devices.select { |device| device['uiid'] == RF_BRIDGE_DEVICE_UIID }.tap do |devices|
73
+ rf_bridge_devices = devices.select { |device| device['itemData']['extra']['uiid'] == RF_BRIDGE_DEVICE_UIID }.tap do |devices|
109
74
  Ewelink.logger.debug(self.class.name) { "Found #{devices.size} RF 433MHz bridge device(s)" }
110
75
  end
111
76
  rf_bridge_devices.each do |device|
112
- api_key = device['apikey'].presence || next
113
- device_id = device['deviceid'].presence || next
114
- device_name = device['name'].presence || next
115
- buttons = device['params']['rfList'].each do |rf|
77
+ api_key = device['itemData']['apikey'].presence || next
78
+ device_id = device['itemData']['deviceid'].presence || next
79
+ device_name = device['itemData']['name'].presence || next
80
+ buttons = device['itemData']['params']['rfList'].each do |rf|
116
81
  button = {
117
82
  api_key:,
118
83
  channel: rf['rfChl'],
119
84
  device_id:,
120
85
  device_name:,
121
86
  }
122
- remote_info = device['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
87
+ remote_info = device['itemData']['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
123
88
  remote_name = remote_info['name'].try(:squish).presence || next
124
89
  button_info = remote_info['buttonName'].find { |info| info.key?(button[:channel].to_s) }.presence || next
125
90
  button_name = button_info.values.first.try(:squish).presence || next
@@ -138,37 +103,32 @@ module Ewelink
138
103
 
139
104
  def switch_on?(uuid)
140
105
  switch = find_switch!(uuid)
141
- if @web_socket_switches_statuses[switch[:uuid]].nil?
142
- web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
143
- Ewelink.logger.debug(self.class.name) { "Checking switch #{switch[:uuid].inspect} status" }
144
- params = {
145
- 'action' => 'query',
146
- 'apikey' => switch[:api_key],
147
- 'deviceid' => switch[:device_id],
148
- 'sequence' => web_socket_sequence,
149
- 'ts' => 0,
150
- 'userAgent' => 'app',
151
- }
152
- send_to_web_socket(JSON.generate(params))
153
- end
154
- end
155
- web_socket_wait_for(-> { !@web_socket_switches_statuses[switch[:uuid]].nil? }, initialize_web_socket: true) do
156
- @web_socket_switches_statuses[switch[:uuid]] == 'on'
157
- end
106
+ headers = authentication_headers.merge(
107
+ 'X-CK-Appid' => APP_ID,
108
+ 'X-CK-Nonce' => nonce,
109
+ )
110
+ params = {
111
+ 'type' => 1,
112
+ 'id' => switch[:device_id],
113
+ 'params' => 'switch',
114
+ }
115
+ Ewelink.logger.debug(self.class.name) { "Checking switch #{switch[:uuid].inspect} status" }
116
+ response = rest_request(:get, '/v2/device/thing/status', headers:, query: params)
117
+ response['data']['params']['switch'] == 'on'
158
118
  end
159
119
 
160
120
  def switches
161
121
  synchronize(:switches) do
162
122
  @switches ||= [].tap do |switches|
163
- switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['uiid']) }
123
+ switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['itemData']['extra']['uiid']) }
164
124
  switch_devices.each do |device|
165
- api_key = device['apikey'].presence || next
166
- device_id = device['deviceid'].presence || next
167
- name = device['name'].presence || next
125
+ api_key = device['itemData']['apikey'].presence || next
126
+ device_id = device['itemData']['deviceid'].presence || next
127
+ name = device['itemData']['name'].presence || next
168
128
  switch = {
169
129
  api_key:,
170
130
  device_id:,
171
- model: device['productModel'],
131
+ model: device['itemData']['productModel'],
172
132
  name:,
173
133
  }
174
134
  switch[:uuid] = Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, switch[:device_id])
@@ -186,54 +146,30 @@ module Ewelink
186
146
  on = false
187
147
  end
188
148
  switch = find_switch!(uuid)
189
- @web_socket_switches_statuses[switch[:uuid]] = nil
190
- web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
191
- params = {
192
- 'action' => 'update',
193
- 'apikey' => switch[:api_key],
194
- 'deviceid' => switch[:device_id],
195
- 'params' => {
196
- 'switch' => on ? 'on' : 'off',
197
- },
198
- 'sequence' => web_socket_sequence,
199
- 'ts' => 0,
200
- 'userAgent' => 'app',
201
- }
202
- Ewelink.logger.debug(self.class.name) { "Turning switch #{switch[:uuid].inspect} #{on ? 'on' : 'off'}" }
203
- send_to_web_socket(JSON.generate(params))
204
- end
205
- sleep(SWITCH_STATUS_CHANGE_CHECK_TIMEOUT)
206
- switch_on?(switch[:uuid]) # Waiting for switch status update
207
- true
149
+ headers = authentication_headers.merge(
150
+ 'X-CK-Appid' => APP_ID,
151
+ 'X-CK-Nonce' => nonce,
152
+ )
153
+ params = {
154
+ 'type' => 1,
155
+ 'id' => switch[:device_id],
156
+ 'params' => {
157
+ 'switch' => on ? 'on' : 'off',
158
+ },
159
+ }
160
+ body = JSON.generate(params)
161
+ Ewelink.logger.debug(self.class.name) { "Turning switch #{switch[:uuid].inspect} #{on ? 'on' : 'off'}" }
162
+ response = rest_request(:post, '/v2/device/thing/status', headers:, body:)
163
+ response['error'] == 0
208
164
  end
209
165
  end
210
166
 
211
- def update_devices_status_on_connect?
212
- @update_devices_status_on_connect
213
- end
214
-
215
167
  private
216
168
 
217
169
  def api_key
218
170
  authentication_infos[:api_key]
219
171
  end
220
172
 
221
- def authenticate_web_socket_api_key
222
- params = {
223
- 'action' => 'userOnline',
224
- 'apikey' => api_key,
225
- 'appid' => APP_ID,
226
- 'at' => authentication_token,
227
- 'nonce' => nonce,
228
- 'sequence' => web_socket_sequence,
229
- 'ts' => Time.now.to_i,
230
- 'userAgent' => 'app',
231
- 'version' => VERSION,
232
- }
233
- Ewelink.logger.debug(self.class.name) { "Authenticating WebSocket API key: #{api_key.truncate(16).inspect}" }
234
- send_to_web_socket(JSON.generate(params))
235
- end
236
-
237
173
  def authentication_headers
238
174
  { 'Authorization' => "Bearer #{authentication_token}" }
239
175
  end
@@ -242,12 +178,8 @@ module Ewelink
242
178
  synchronize(:authentication_infos) do
243
179
  @authentication_infos ||= begin
244
180
  params = {
245
- 'appid' => APP_ID,
246
- 'imei' => SecureRandom.uuid.upcase,
247
- 'nonce' => nonce,
181
+ 'countryCode' => DEFAULT_COUNTRY_CODE,
248
182
  'password' => password,
249
- 'ts' => Time.now.to_i,
250
- 'version' => VERSION,
251
183
  }
252
184
  if email.present?
253
185
  params['email'] = email
@@ -255,12 +187,20 @@ module Ewelink
255
187
  params['phoneNumber'] = phone_number
256
188
  end
257
189
  body = JSON.generate(params)
258
- response = rest_request(:post, '/api/user/login', { body:, headers: { 'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}" } })
259
- raise(Error.new('Authentication token not found')) if response['at'].blank?
260
- raise(Error.new('API key not found')) if response['user'].blank? || response['user']['apikey'].blank?
190
+ response = rest_request(:post, '/v2/user/login', {
191
+ body:,
192
+ headers: {
193
+ 'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}",
194
+ 'X-CK-Appid': APP_ID,
195
+ 'X-CK-Nonce': nonce,
196
+ },
197
+ })
198
+ raise(Error.new('Invalid authentication response')) unless response['data'].is_a?(Hash) && response['data']['user'].is_a?(Hash)
199
+ raise(Error.new('Authentication token not found')) unless response['data']['at'].present?
200
+ raise(Error.new('API key not found')) unless response['data']['user']['apikey'].present?
261
201
  {
262
- authentication_token: response['at'].tap { Ewelink.logger.debug(self.class.name) { 'Authentication token found' } },
263
- api_key: response['user']['apikey'].tap { Ewelink.logger.debug(self.class.name) { 'API key found' } },
202
+ api_key: response['data']['user']['apikey'].tap { Ewelink.logger.debug(self.class.name) { 'API key found' } },
203
+ authentication_token: response['data']['at'].tap { Ewelink.logger.debug(self.class.name) { 'Authentication token found' } },
264
204
  }
265
205
  end
266
206
  end
@@ -273,15 +213,22 @@ module Ewelink
273
213
  def devices
274
214
  synchronize(:devices) do
275
215
  @devices ||= begin
276
- params = {
277
- 'appid' => APP_ID,
278
- 'getTags' => 1,
279
- 'nonce' => nonce,
280
- 'ts' => Time.now.to_i,
281
- 'version' => VERSION,
282
- }
283
- response = rest_request(:get, '/api/user/device', headers: authentication_headers, query: params)
284
- response['devicelist'].tap { |devices| Ewelink.logger.debug(self.class.name) { "Found #{devices.size} device(s)" } }
216
+ devices = []
217
+ homes_ids.each do |home_id|
218
+ params = {
219
+ 'familyid' => home_id,
220
+ 'num' => 0,
221
+ }
222
+ headers = authentication_headers.merge(
223
+ 'X-CK-Appid' => APP_ID,
224
+ 'X-CK-Nonce' => nonce,
225
+ )
226
+ response = rest_request(:get, '/v2/device/thing', headers:, query: params)
227
+ raise('Invalid devices response') unless response['data'].is_a?(Hash) && response['data']['thingList'].is_a?(Array)
228
+ devices += response['data']['thingList']
229
+ end
230
+ Ewelink.logger.debug(self.class.name) { "Found #{devices.size} device(s)" }
231
+ devices
285
232
  end
286
233
  end
287
234
  end
@@ -294,6 +241,20 @@ module Ewelink
294
241
  switches.find { |switch| switch[:uuid] == uuid } || raise(Error.new("No such switch with UUID: #{uuid.inspect}"))
295
242
  end
296
243
 
244
+ def homes_ids
245
+ synchronize(:homes_ids) do
246
+ @homes_ids ||= begin
247
+ headers = authentication_headers.merge(
248
+ 'X-CK-Appid' => APP_ID,
249
+ 'X-CK-Nonce' => nonce,
250
+ )
251
+ response = rest_request(:get, '/v2/family', headers:)
252
+ raise('Invalid homes response') unless response['data'].is_a?(Hash) && response['data']['familyList'].is_a?(Array)
253
+ response['data']['familyList'].map { |home| home['id'] }.compact.uniq
254
+ end
255
+ end
256
+ end
257
+
297
258
  def nonce
298
259
  SecureRandom.hex[0, 8]
299
260
  end
@@ -311,191 +272,27 @@ module Ewelink
311
272
 
312
273
  def rest_request(method, path, options = {})
313
274
  url = "#{URL.gsub('#{region}', region)}#{path}"
275
+ url.gsub!(/#{Regexp.escape("#{region}-api")}/, "#{region}-disp") if options[:dispatch].present?
314
276
  method = method.to_s.upcase
315
277
  headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
316
278
  Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
317
279
  response = HTTParty.send(method.downcase, url, options.merge(headers:).reverse_merge(timeout: REQUEST_TIMEOUT))
318
280
  raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
319
- if response['error'] == 301 && response['region'].present?
320
- @region = response['region']
281
+ if response['error'] == 10_004 && response['data'].present? && response['data']['region'].present?
282
+ @region = response['data']['region']
321
283
  Ewelink.logger.debug(self.class.name) { "Switched to region #{region.inspect}" }
322
284
  return rest_request(method, path, options)
323
285
  end
324
- remove_instance_variable(:@authentication_infos) if instance_variable_defined?(:@authentication_infos) && [401, 403].include?(response['error'])
325
286
  raise(Error.new("#{method} #{url}: #{response['error']} #{response['msg']}".strip)) if response['error'].present? && response['error'] != 0
326
287
  response.to_h
327
288
  rescue Errno::ECONNREFUSED, OpenSSL::OpenSSLError, SocketError, Timeout::Error => e
328
289
  raise Error.new(e)
329
290
  end
330
291
 
331
- def send_to_web_socket(message)
332
- web_socket.send(message)
333
- rescue => e
334
- reload
335
- raise Error.new(e)
336
- end
337
-
338
- def start_web_socket_authentication_check_thread
339
- raise Error.new('WebSocket authentication check must only be started once') if @web_socket_authentication_check_thread.present?
340
-
341
- @web_socket_authentication_check_thread = Thread.new do
342
- loop do
343
- Ewelink.logger.debug(self.class.name) { 'Checking if WebSocket is authenticated' }
344
- begin
345
- web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
346
- Ewelink.logger.debug(self.class.name) { 'WebSocket is authenticated' }
347
- end
348
- rescue => e
349
- Ewelink.logger.error(self.class.name) { e }
350
- end
351
- sleep(WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT)
352
- end
353
- end
354
- end
355
-
356
- def start_web_socket_ping_thread(interval)
357
- @last_web_socket_pong_at = Time.now
358
- @web_socket_ping_interval = interval
359
- Ewelink.logger.debug(self.class.name) { "Creating thread for WebSocket ping every #{@web_socket_ping_interval} seconds" }
360
- @web_socket_ping_thread = Thread.new do
361
- loop do
362
- break if Thread.current[:stop]
363
- sleep(@web_socket_ping_interval)
364
- Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
365
- send_to_web_socket('ping')
366
- end
367
- end
368
- end
369
-
370
292
  def synchronize(name, &block)
371
293
  (@mutexs[name] ||= Mutex.new).synchronize(&block)
372
294
  end
373
295
 
374
- def web_socket
375
- if web_socket_outdated_ping?
376
- Ewelink.logger.warn(self.class.name) { 'WebSocket ping is outdated' }
377
- reload
378
- end
379
-
380
- synchronize(:web_socket) do
381
- next @web_socket if @web_socket
382
-
383
- # Initializes caches before opening WebSocket: important in order to
384
- # NOT cumulate requests Timeouts from #web_socket_wait_for.
385
- api_key
386
- web_socket_url
387
-
388
- Ewelink.logger.debug(self.class.name) { "Opening WebSocket to #{web_socket_url.inspect}" }
389
-
390
- @web_socket_thread = Thread.new do
391
- EventMachine.run do
392
- @web_socket = Faye::WebSocket::Client.new(web_socket_url)
393
-
394
- @web_socket.on(:close) do
395
- Ewelink.logger.debug(self.class.name) { 'WebSocket closed' }
396
- reload
397
- end
398
-
399
- @web_socket.on(:open) do
400
- Ewelink.logger.debug(self.class.name) { 'WebSocket opened' }
401
- @last_web_socket_pong_at = Time.now
402
- authenticate_web_socket_api_key
403
- end
404
-
405
- @web_socket.on(:message) do |event|
406
- message = event.data
407
-
408
- if message == 'pong'
409
- Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.inspect} message" }
410
- @last_web_socket_pong_at = Time.now
411
- next
412
- end
413
-
414
- begin
415
- json = JSON.parse(message)
416
- rescue
417
- Ewelink.logger.error(self.class.name) { 'WebSocket JSON parse error' }
418
- reload
419
- next
420
- end
421
-
422
- if json.key?('error') && json['error'] != 0
423
- Ewelink.logger.error(self.class.name) { "WebSocket message error: #{message.inspect}" }
424
- reload
425
- next
426
- end
427
-
428
- if !@web_socket_ping_thread && json.key?('config') && json['config']['hb'] == 1 && json['config']['hbInterval'].present?
429
- start_web_socket_ping_thread(json['config']['hbInterval'] + 7)
430
- end
431
-
432
- if json['apikey'].present? && !@web_socket_authenticated && json['apikey'] == api_key
433
- @web_socket_authenticated = true
434
- Ewelink.logger.debug(self.class.name) { "WebSocket successfully authenticated API key: #{json['apikey'].truncate(16).inspect}" }
435
- Thread.new { switches.each { |switch| switch_on?(switch[:uuid]) } } if update_devices_status_on_connect?
436
- end
437
-
438
- if json['deviceid'].present? && json['params'].is_a?(Hash) && json['params']['switch'].present?
439
- switch = switches.find { |item| item[:device_id] == json['deviceid'] }
440
- if switch.present?
441
- @web_socket_switches_statuses[switch[:uuid]] = json['params']['switch']
442
- Ewelink.logger.debug(self.class.name) { "Switch #{switch[:uuid].inspect} is #{@web_socket_switches_statuses[switch[:uuid]]}" }
443
- end
444
- end
445
- end
446
- end
447
- end
448
-
449
- web_socket_wait_for(-> { @web_socket.present? }) do
450
- @web_socket
451
- end
452
- end
453
- end
454
-
455
- def web_socket_authenticated?
456
- @web_socket_authenticated.present?
457
- end
458
-
459
- def web_socket_outdated_ping?
460
- @last_web_socket_pong_at.present? && @web_socket_ping_interval.present? && @last_web_socket_pong_at < (@web_socket_ping_interval * WEB_SOCKET_PING_TOLERANCE_FACTOR).seconds.ago
461
- end
462
-
463
- def web_socket_sequence
464
- (Time.now.to_f * 1000).round.to_s
465
- end
466
-
467
- def web_socket_url
468
- synchronize(:web_socket_url) do
469
- @web_socket_url ||= begin
470
- params = {
471
- 'accept' => 'ws',
472
- 'appid' => APP_ID,
473
- 'nonce' => nonce,
474
- 'ts' => Time.now.to_i,
475
- 'version' => VERSION,
476
- }
477
- response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), headers: authentication_headers)
478
- raise('Error while getting WebSocket URL') unless response['error'] == 0
479
- domain = response['domain'].presence || raise("Can't get WebSocket server domain")
480
- port = response['port'].presence || raise("Can't get WebSocket server port")
481
- "wss://#{domain}:#{port}/api/ws".tap { |url| Ewelink.logger.debug(self.class.name) { "WebSocket URL is: #{url.inspect}" } }
482
- end
483
- end
484
- end
485
-
486
- def web_socket_wait_for(condition, initialize_web_socket: false)
487
- web_socket if initialize_web_socket
488
- begin
489
- Timeout.timeout(REQUEST_TIMEOUT) do
490
- sleep(WEB_SOCKET_WAIT_INTERVAL) until condition.call
491
- block_given? ? yield : true
492
- end
493
- rescue => e
494
- reload
495
- raise Error.new(e)
496
- end
497
- end
498
-
499
296
  end
500
297
 
501
298
  end
data/lib/ewelink.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  require 'active_support'
2
2
  require 'active_support/core_ext'
3
3
  require 'byebug' if ENV['DEBUGGER']
4
- require 'eventmachine'
5
- require 'faye/websocket'
6
4
  require 'httparty'
7
5
  require 'io/console'
8
6
  require 'json'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ewelink
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.0
4
+ version: 6.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexis Toulotte
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-03-20 00:00:00.000000000 Z
11
+ date: 2023-04-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -30,26 +30,6 @@ dependencies:
30
30
  - - "<"
31
31
  - !ruby/object:Gem::Version
32
32
  version: 8.0.0
33
- - !ruby/object:Gem::Dependency
34
- name: faye-websocket
35
- requirement: !ruby/object:Gem::Requirement
36
- requirements:
37
- - - ">="
38
- - !ruby/object:Gem::Version
39
- version: 0.11.0
40
- - - "<"
41
- - !ruby/object:Gem::Version
42
- version: 0.12.0
43
- type: :runtime
44
- prerelease: false
45
- version_requirements: !ruby/object:Gem::Requirement
46
- requirements:
47
- - - ">="
48
- - !ruby/object:Gem::Version
49
- version: 0.11.0
50
- - - "<"
51
- - !ruby/object:Gem::Version
52
- version: 0.12.0
53
33
  - !ruby/object:Gem::Dependency
54
34
  name: httparty
55
35
  requirement: !ruby/object:Gem::Requirement