ewelink 5.0.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 +4 -4
- data/README.mdown +0 -2
- data/VERSION +1 -1
- data/ewelink.gemspec +0 -1
- data/lib/ewelink/api.rb +28 -255
- data/lib/ewelink.rb +0 -2
- metadata +2 -22
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '03889642d2851312650d6419544fd7423b2b8ca0a64a272386de46646640b191'
|
4
|
+
data.tar.gz: b36f496794dce02953b423d601f4e71905afca7c0aebe4fe5bfc65d08f65daff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
@@ -11,26 +11,17 @@ module Ewelink
|
|
11
11
|
SWITCH_DEVICES_UIIDS = [1, 5, 6, 24].freeze
|
12
12
|
URL = 'https://#{region}-apia.coolkit.cc'.freeze
|
13
13
|
VERSION = 8
|
14
|
-
WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
|
15
|
-
WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
|
16
|
-
SWITCH_STATUS_CHANGE_CHECK_TIMEOUT = 2.seconds
|
17
|
-
WEB_SOCKET_WAIT_INTERVAL = 0.2.seconds
|
18
14
|
|
19
15
|
attr_reader :email, :password, :phone_number
|
20
16
|
|
21
|
-
def initialize(password:, async_actions: false, email: nil, phone_number: nil
|
17
|
+
def initialize(password:, async_actions: false, email: nil, phone_number: nil)
|
22
18
|
@async_actions = async_actions.present?
|
23
19
|
@email = email.presence.try(:strip)
|
24
20
|
@mutexs = {}
|
25
21
|
@password = password.presence || raise(Error.new(':password must be specified'))
|
26
22
|
@phone_number = phone_number.presence.try(:strip)
|
27
|
-
@update_devices_status_on_connect = update_devices_status_on_connect.present?
|
28
|
-
@web_socket_authenticated = false
|
29
|
-
@web_socket_switches_statuses = {}
|
30
23
|
|
31
24
|
raise(Error.new(':email or :phone_number must be specified')) if email.blank? && phone_number.blank?
|
32
|
-
|
33
|
-
start_web_socket_authentication_check_thread
|
34
25
|
end
|
35
26
|
|
36
27
|
def async_actions?
|
@@ -63,39 +54,13 @@ module Ewelink
|
|
63
54
|
def reload
|
64
55
|
Ewelink.logger.debug(self.class.name) { 'Reloading API (authentication token, api key, devices, region, connections,...)' }
|
65
56
|
|
66
|
-
@web_socket_authenticated = false
|
67
|
-
@web_socket_switches_statuses.clear
|
68
|
-
|
69
|
-
[@web_socket_ping_thread, @web_socket_thread].each do |thread|
|
70
|
-
next unless thread
|
71
|
-
if Thread.current == thread
|
72
|
-
thread[:stop] = true
|
73
|
-
else
|
74
|
-
thread.kill
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
if @web_socket.present?
|
79
|
-
begin
|
80
|
-
@web_socket.close if @web_socket.open?
|
81
|
-
rescue
|
82
|
-
# Ignoring close errors
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
57
|
%i(
|
87
58
|
@authentication_infos
|
88
59
|
@devices
|
89
60
|
@homes_ids
|
90
|
-
@last_web_socket_pong_at
|
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
|
@@ -138,23 +103,18 @@ module Ewelink
|
|
138
103
|
|
139
104
|
def switch_on?(uuid)
|
140
105
|
switch = find_switch!(uuid)
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
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
|
@@ -186,54 +146,30 @@ module Ewelink
|
|
186
146
|
on = false
|
187
147
|
end
|
188
148
|
switch = find_switch!(uuid)
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
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
|
@@ -353,173 +289,10 @@ module Ewelink
|
|
353
289
|
raise Error.new(e)
|
354
290
|
end
|
355
291
|
|
356
|
-
def send_to_web_socket(message)
|
357
|
-
web_socket.send(message)
|
358
|
-
rescue => e
|
359
|
-
reload
|
360
|
-
raise Error.new(e)
|
361
|
-
end
|
362
|
-
|
363
|
-
def start_web_socket_authentication_check_thread
|
364
|
-
raise Error.new('WebSocket authentication check must only be started once') if @web_socket_authentication_check_thread.present?
|
365
|
-
|
366
|
-
@web_socket_authentication_check_thread = Thread.new do
|
367
|
-
loop do
|
368
|
-
Ewelink.logger.debug(self.class.name) { 'Checking if WebSocket is authenticated' }
|
369
|
-
begin
|
370
|
-
web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
|
371
|
-
Ewelink.logger.debug(self.class.name) { 'WebSocket is authenticated' }
|
372
|
-
end
|
373
|
-
rescue => e
|
374
|
-
Ewelink.logger.error(self.class.name) { e }
|
375
|
-
end
|
376
|
-
sleep(WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT)
|
377
|
-
end
|
378
|
-
end
|
379
|
-
end
|
380
|
-
|
381
|
-
def start_web_socket_ping_thread(interval)
|
382
|
-
@last_web_socket_pong_at = Time.now
|
383
|
-
@web_socket_ping_interval = interval
|
384
|
-
Ewelink.logger.debug(self.class.name) { "Creating thread for WebSocket ping every #{@web_socket_ping_interval} seconds" }
|
385
|
-
@web_socket_ping_thread = Thread.new do
|
386
|
-
loop do
|
387
|
-
break if Thread.current[:stop]
|
388
|
-
sleep(@web_socket_ping_interval)
|
389
|
-
Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
|
390
|
-
send_to_web_socket('ping')
|
391
|
-
end
|
392
|
-
end
|
393
|
-
end
|
394
|
-
|
395
292
|
def synchronize(name, &block)
|
396
293
|
(@mutexs[name] ||= Mutex.new).synchronize(&block)
|
397
294
|
end
|
398
295
|
|
399
|
-
def web_socket
|
400
|
-
if web_socket_outdated_ping?
|
401
|
-
Ewelink.logger.warn(self.class.name) { 'WebSocket ping is outdated' }
|
402
|
-
reload
|
403
|
-
end
|
404
|
-
|
405
|
-
synchronize(:web_socket) do
|
406
|
-
next @web_socket if @web_socket
|
407
|
-
|
408
|
-
# Initializes caches before opening WebSocket: important in order to
|
409
|
-
# NOT cumulate requests Timeouts from #web_socket_wait_for.
|
410
|
-
api_key
|
411
|
-
web_socket_url
|
412
|
-
|
413
|
-
Ewelink.logger.debug(self.class.name) { "Opening WebSocket to #{web_socket_url.inspect}" }
|
414
|
-
|
415
|
-
@web_socket_thread = Thread.new do
|
416
|
-
EventMachine.run do
|
417
|
-
@web_socket = Faye::WebSocket::Client.new(web_socket_url)
|
418
|
-
|
419
|
-
@web_socket.on(:close) do
|
420
|
-
Ewelink.logger.debug(self.class.name) { 'WebSocket closed' }
|
421
|
-
reload
|
422
|
-
end
|
423
|
-
|
424
|
-
@web_socket.on(:open) do
|
425
|
-
Ewelink.logger.debug(self.class.name) { 'WebSocket opened' }
|
426
|
-
@last_web_socket_pong_at = Time.now
|
427
|
-
authenticate_web_socket_api_key
|
428
|
-
end
|
429
|
-
|
430
|
-
@web_socket.on(:message) do |event|
|
431
|
-
message = event.data
|
432
|
-
|
433
|
-
if message == 'pong'
|
434
|
-
Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.inspect} message" }
|
435
|
-
@last_web_socket_pong_at = Time.now
|
436
|
-
next
|
437
|
-
end
|
438
|
-
|
439
|
-
begin
|
440
|
-
json = JSON.parse(message)
|
441
|
-
rescue
|
442
|
-
Ewelink.logger.error(self.class.name) { 'WebSocket JSON parse error' }
|
443
|
-
reload
|
444
|
-
next
|
445
|
-
end
|
446
|
-
|
447
|
-
if json.key?('error') && json['error'] != 0
|
448
|
-
Ewelink.logger.error(self.class.name) { "WebSocket message error: #{message.inspect}" }
|
449
|
-
reload
|
450
|
-
next
|
451
|
-
end
|
452
|
-
|
453
|
-
if !@web_socket_ping_thread && json.key?('config') && json['config']['hb'] == 1 && json['config']['hbInterval'].present?
|
454
|
-
start_web_socket_ping_thread(json['config']['hbInterval'] + 7)
|
455
|
-
end
|
456
|
-
|
457
|
-
if json['apikey'].present? && !@web_socket_authenticated && json['apikey'] == api_key
|
458
|
-
@web_socket_authenticated = true
|
459
|
-
Ewelink.logger.debug(self.class.name) { "WebSocket successfully authenticated API key: #{json['apikey'].truncate(16).inspect}" }
|
460
|
-
Thread.new { switches.each { |switch| switch_on?(switch[:uuid]) } } if update_devices_status_on_connect?
|
461
|
-
end
|
462
|
-
|
463
|
-
if json['deviceid'].present? && json['params'].is_a?(Hash) && json['params']['switch'].present?
|
464
|
-
switch = switches.find { |item| item[:device_id] == json['deviceid'] }
|
465
|
-
if switch.present?
|
466
|
-
@web_socket_switches_statuses[switch[:uuid]] = json['params']['switch']
|
467
|
-
Ewelink.logger.debug(self.class.name) { "Switch #{switch[:uuid].inspect} is #{@web_socket_switches_statuses[switch[:uuid]]}" }
|
468
|
-
end
|
469
|
-
end
|
470
|
-
end
|
471
|
-
end
|
472
|
-
end
|
473
|
-
|
474
|
-
web_socket_wait_for(-> { @web_socket.present? }) do
|
475
|
-
@web_socket
|
476
|
-
end
|
477
|
-
end
|
478
|
-
end
|
479
|
-
|
480
|
-
def web_socket_authenticated?
|
481
|
-
@web_socket_authenticated.present?
|
482
|
-
end
|
483
|
-
|
484
|
-
def web_socket_outdated_ping?
|
485
|
-
@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
|
486
|
-
end
|
487
|
-
|
488
|
-
def web_socket_sequence
|
489
|
-
(Time.now.to_f * 1000).round.to_s
|
490
|
-
end
|
491
|
-
|
492
|
-
def web_socket_url
|
493
|
-
synchronize(:web_socket_url) do
|
494
|
-
@web_socket_url ||= begin
|
495
|
-
params = {
|
496
|
-
'appid' => APP_ID,
|
497
|
-
'nonce' => nonce,
|
498
|
-
'ts' => Time.now.to_i,
|
499
|
-
'version' => VERSION,
|
500
|
-
}
|
501
|
-
response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), dispatch: true, headers: authentication_headers)
|
502
|
-
raise('Error while getting WebSocket URL') unless response['error'] == 0
|
503
|
-
domain = response['domain'].presence || raise("Can't get WebSocket server domain")
|
504
|
-
port = response['port'].presence || raise("Can't get WebSocket server port")
|
505
|
-
"wss://#{domain}:#{port}/api/ws".tap { |url| Ewelink.logger.debug(self.class.name) { "WebSocket URL is: #{url.inspect}" } }
|
506
|
-
end
|
507
|
-
end
|
508
|
-
end
|
509
|
-
|
510
|
-
def web_socket_wait_for(condition, initialize_web_socket: false)
|
511
|
-
web_socket if initialize_web_socket
|
512
|
-
begin
|
513
|
-
Timeout.timeout(REQUEST_TIMEOUT) do
|
514
|
-
sleep(WEB_SOCKET_WAIT_INTERVAL) until condition.call
|
515
|
-
block_given? ? yield : true
|
516
|
-
end
|
517
|
-
rescue => e
|
518
|
-
reload
|
519
|
-
raise Error.new(e)
|
520
|
-
end
|
521
|
-
end
|
522
|
-
|
523
296
|
end
|
524
297
|
|
525
298
|
end
|
data/lib/ewelink.rb
CHANGED
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: 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:
|
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
|