ewelink 4.1.0 → 5.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 +4 -4
- data/VERSION +1 -1
- data/lib/ewelink/api.rb +79 -55
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f2f26d6875823958f38a894377c7dfbce3531d7f98b1c1c69b18bd88caf44c1d
|
4
|
+
data.tar.gz: d25335eb91529c57d2780c42e8cef5856cb7bfa7f222e5282816bff122854b08
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 42786625ddc6d8482a5d85922e25b4b650e080866148b378aeb579ad25e0dcee00d4f12b8ebf74492e207d80e6ed0d65b6f0c71ccdaf5c3f81280b1efbd4bc7b
|
7
|
+
data.tar.gz: 8eb9cc344312a30a7b0924ff02b052516bf5123a7d706acb15e000b91db2c91ecf56a2173d22543403f7fbbc31773e30d97d3c5379ad37ab42e34b55f8f67366
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
5.0.0
|
data/lib/ewelink/api.rb
CHANGED
@@ -2,13 +2,14 @@ module Ewelink
|
|
2
2
|
|
3
3
|
class Api
|
4
4
|
|
5
|
-
APP_ID = '
|
6
|
-
APP_SECRET = '
|
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}-
|
12
|
+
URL = 'https://#{region}-apia.coolkit.cc'.freeze
|
12
13
|
VERSION = 8
|
13
14
|
WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
|
14
15
|
WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
|
@@ -40,23 +41,21 @@ module Ewelink
|
|
40
41
|
process_action do
|
41
42
|
synchronize(:press_rf_bridge_button) do
|
42
43
|
button = find_rf_bridge_button!(uuid)
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
'
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
true
|
59
|
-
end
|
44
|
+
headers = authentication_headers.merge(
|
45
|
+
'X-CK-Appid' => APP_ID,
|
46
|
+
'X-CK-Nonce' => nonce,
|
47
|
+
)
|
48
|
+
params = {
|
49
|
+
'type' => 1,
|
50
|
+
'id' => button[:device_id],
|
51
|
+
'params' => {
|
52
|
+
'cmd' => 'transmit',
|
53
|
+
'rfChl' => button[:channel],
|
54
|
+
},
|
55
|
+
}
|
56
|
+
body = JSON.generate(params)
|
57
|
+
response = rest_request(:post, '/v2/device/thing/status', headers:, body:)
|
58
|
+
response['error'] == 0
|
60
59
|
end
|
61
60
|
end
|
62
61
|
end
|
@@ -87,6 +86,7 @@ module Ewelink
|
|
87
86
|
%i(
|
88
87
|
@authentication_infos
|
89
88
|
@devices
|
89
|
+
@homes_ids
|
90
90
|
@last_web_socket_pong_at
|
91
91
|
@region
|
92
92
|
@rf_bridge_buttons
|
@@ -105,21 +105,21 @@ module Ewelink
|
|
105
105
|
def rf_bridge_buttons
|
106
106
|
synchronize(:rf_bridge_buttons) do
|
107
107
|
@rf_bridge_buttons ||= [].tap do |buttons|
|
108
|
-
rf_bridge_devices = devices.select { |device| device['uiid'] == RF_BRIDGE_DEVICE_UIID }.tap do |devices|
|
108
|
+
rf_bridge_devices = devices.select { |device| device['itemData']['extra']['uiid'] == RF_BRIDGE_DEVICE_UIID }.tap do |devices|
|
109
109
|
Ewelink.logger.debug(self.class.name) { "Found #{devices.size} RF 433MHz bridge device(s)" }
|
110
110
|
end
|
111
111
|
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|
|
112
|
+
api_key = device['itemData']['apikey'].presence || next
|
113
|
+
device_id = device['itemData']['deviceid'].presence || next
|
114
|
+
device_name = device['itemData']['name'].presence || next
|
115
|
+
buttons = device['itemData']['params']['rfList'].each do |rf|
|
116
116
|
button = {
|
117
117
|
api_key:,
|
118
118
|
channel: rf['rfChl'],
|
119
119
|
device_id:,
|
120
120
|
device_name:,
|
121
121
|
}
|
122
|
-
remote_info = device['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
|
122
|
+
remote_info = device['itemData']['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
|
123
123
|
remote_name = remote_info['name'].try(:squish).presence || next
|
124
124
|
button_info = remote_info['buttonName'].find { |info| info.key?(button[:channel].to_s) }.presence || next
|
125
125
|
button_name = button_info.values.first.try(:squish).presence || next
|
@@ -160,15 +160,15 @@ module Ewelink
|
|
160
160
|
def switches
|
161
161
|
synchronize(:switches) do
|
162
162
|
@switches ||= [].tap do |switches|
|
163
|
-
switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['uiid']) }
|
163
|
+
switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['itemData']['extra']['uiid']) }
|
164
164
|
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
|
165
|
+
api_key = device['itemData']['apikey'].presence || next
|
166
|
+
device_id = device['itemData']['deviceid'].presence || next
|
167
|
+
name = device['itemData']['name'].presence || next
|
168
168
|
switch = {
|
169
169
|
api_key:,
|
170
170
|
device_id:,
|
171
|
-
model: device['productModel'],
|
171
|
+
model: device['itemData']['productModel'],
|
172
172
|
name:,
|
173
173
|
}
|
174
174
|
switch[:uuid] = Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, switch[:device_id])
|
@@ -242,12 +242,8 @@ module Ewelink
|
|
242
242
|
synchronize(:authentication_infos) do
|
243
243
|
@authentication_infos ||= begin
|
244
244
|
params = {
|
245
|
-
'
|
246
|
-
'imei' => SecureRandom.uuid.upcase,
|
247
|
-
'nonce' => nonce,
|
245
|
+
'countryCode' => DEFAULT_COUNTRY_CODE,
|
248
246
|
'password' => password,
|
249
|
-
'ts' => Time.now.to_i,
|
250
|
-
'version' => VERSION,
|
251
247
|
}
|
252
248
|
if email.present?
|
253
249
|
params['email'] = email
|
@@ -255,12 +251,20 @@ module Ewelink
|
|
255
251
|
params['phoneNumber'] = phone_number
|
256
252
|
end
|
257
253
|
body = JSON.generate(params)
|
258
|
-
response = rest_request(:post, '/
|
259
|
-
|
260
|
-
|
254
|
+
response = rest_request(:post, '/v2/user/login', {
|
255
|
+
body:,
|
256
|
+
headers: {
|
257
|
+
'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}",
|
258
|
+
'X-CK-Appid': APP_ID,
|
259
|
+
'X-CK-Nonce': nonce,
|
260
|
+
},
|
261
|
+
})
|
262
|
+
raise(Error.new('Invalid authentication response')) unless response['data'].is_a?(Hash) && response['data']['user'].is_a?(Hash)
|
263
|
+
raise(Error.new('Authentication token not found')) unless response['data']['at'].present?
|
264
|
+
raise(Error.new('API key not found')) unless response['data']['user']['apikey'].present?
|
261
265
|
{
|
262
|
-
|
263
|
-
|
266
|
+
api_key: response['data']['user']['apikey'].tap { Ewelink.logger.debug(self.class.name) { 'API key found' } },
|
267
|
+
authentication_token: response['data']['at'].tap { Ewelink.logger.debug(self.class.name) { 'Authentication token found' } },
|
264
268
|
}
|
265
269
|
end
|
266
270
|
end
|
@@ -273,15 +277,22 @@ module Ewelink
|
|
273
277
|
def devices
|
274
278
|
synchronize(:devices) do
|
275
279
|
@devices ||= begin
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
280
|
+
devices = []
|
281
|
+
homes_ids.each do |home_id|
|
282
|
+
params = {
|
283
|
+
'familyid' => home_id,
|
284
|
+
'num' => 0,
|
285
|
+
}
|
286
|
+
headers = authentication_headers.merge(
|
287
|
+
'X-CK-Appid' => APP_ID,
|
288
|
+
'X-CK-Nonce' => nonce,
|
289
|
+
)
|
290
|
+
response = rest_request(:get, '/v2/device/thing', headers:, query: params)
|
291
|
+
raise('Invalid devices response') unless response['data'].is_a?(Hash) && response['data']['thingList'].is_a?(Array)
|
292
|
+
devices += response['data']['thingList']
|
293
|
+
end
|
294
|
+
Ewelink.logger.debug(self.class.name) { "Found #{devices.size} device(s)" }
|
295
|
+
devices
|
285
296
|
end
|
286
297
|
end
|
287
298
|
end
|
@@ -294,6 +305,20 @@ module Ewelink
|
|
294
305
|
switches.find { |switch| switch[:uuid] == uuid } || raise(Error.new("No such switch with UUID: #{uuid.inspect}"))
|
295
306
|
end
|
296
307
|
|
308
|
+
def homes_ids
|
309
|
+
synchronize(:homes_ids) do
|
310
|
+
@homes_ids ||= begin
|
311
|
+
headers = authentication_headers.merge(
|
312
|
+
'X-CK-Appid' => APP_ID,
|
313
|
+
'X-CK-Nonce' => nonce,
|
314
|
+
)
|
315
|
+
response = rest_request(:get, '/v2/family', headers:)
|
316
|
+
raise('Invalid homes response') unless response['data'].is_a?(Hash) && response['data']['familyList'].is_a?(Array)
|
317
|
+
response['data']['familyList'].map { |home| home['id'] }.compact.uniq
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
297
322
|
def nonce
|
298
323
|
SecureRandom.hex[0, 8]
|
299
324
|
end
|
@@ -311,17 +336,17 @@ module Ewelink
|
|
311
336
|
|
312
337
|
def rest_request(method, path, options = {})
|
313
338
|
url = "#{URL.gsub('#{region}', region)}#{path}"
|
339
|
+
url.gsub!(/#{Regexp.escape("#{region}-api")}/, "#{region}-disp") if options[:dispatch].present?
|
314
340
|
method = method.to_s.upcase
|
315
341
|
headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
|
316
342
|
Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
|
317
343
|
response = HTTParty.send(method.downcase, url, options.merge(headers:).reverse_merge(timeout: REQUEST_TIMEOUT))
|
318
344
|
raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
|
319
|
-
if response['error'] ==
|
320
|
-
@region = response['region']
|
345
|
+
if response['error'] == 10_004 && response['data'].present? && response['data']['region'].present?
|
346
|
+
@region = response['data']['region']
|
321
347
|
Ewelink.logger.debug(self.class.name) { "Switched to region #{region.inspect}" }
|
322
348
|
return rest_request(method, path, options)
|
323
349
|
end
|
324
|
-
remove_instance_variable(:@authentication_infos) if instance_variable_defined?(:@authentication_infos) && [401, 403].include?(response['error'])
|
325
350
|
raise(Error.new("#{method} #{url}: #{response['error']} #{response['msg']}".strip)) if response['error'].present? && response['error'] != 0
|
326
351
|
response.to_h
|
327
352
|
rescue Errno::ECONNREFUSED, OpenSSL::OpenSSLError, SocketError, Timeout::Error => e
|
@@ -468,13 +493,12 @@ module Ewelink
|
|
468
493
|
synchronize(:web_socket_url) do
|
469
494
|
@web_socket_url ||= begin
|
470
495
|
params = {
|
471
|
-
'accept' => 'ws',
|
472
496
|
'appid' => APP_ID,
|
473
497
|
'nonce' => nonce,
|
474
498
|
'ts' => Time.now.to_i,
|
475
499
|
'version' => VERSION,
|
476
500
|
}
|
477
|
-
response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), headers: authentication_headers)
|
501
|
+
response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), dispatch: true, headers: authentication_headers)
|
478
502
|
raise('Error while getting WebSocket URL') unless response['error'] == 0
|
479
503
|
domain = response['domain'].presence || raise("Can't get WebSocket server domain")
|
480
504
|
port = response['port'].presence || raise("Can't get WebSocket server port")
|
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
|
+
version: 5.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-
|
11
|
+
date: 2022-07-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|