ewelink 4.1.0 → 6.0.0

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
  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