ewelink 1.2.0 → 2.2.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/ewelink.gemspec +1 -0
- data/lib/ewelink.rb +4 -0
- data/lib/ewelink/api.rb +272 -42
- data/lib/ewelink/runner.rb +6 -2
- metadata +25 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2392627c54c851fb5fd14194b484b56b1013b7a26e8d5bafe2a79e37dfa80464
|
4
|
+
data.tar.gz: 2a5362bafa04f9e3cf212a5899b4e6183514fdff68b3146a4fa245c52e8fcadd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2d3ecbf884461e11a51cf7e4efe7438f1c066cc7c076629371c96b3255b50f2a3f4e0314f6a40f9b7247adef06d69eb0b1f66b31ebcabff1c46b7def21d8291
|
7
|
+
data.tar.gz: a2d064d2af9e800ef0165bf5d614e4857abb0503b1faad7e280f3f2df422f08c5c49e57e10b3202bfcac700dc667861690c0a29a44faaf13a5ae00307201fe87
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
2.2.0
|
data/ewelink.gemspec
CHANGED
@@ -16,6 +16,7 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.required_ruby_version = '>= 2.0.0'
|
17
17
|
|
18
18
|
s.add_dependency 'activesupport', '>= 6.0.0', '< 7.0.0'
|
19
|
+
s.add_dependency 'faye-websocket', '>= 0.11.0', '< 0.12.0'
|
19
20
|
s.add_dependency 'httparty', '>= 0.18.0', '< 0.19.0'
|
20
21
|
|
21
22
|
s.add_development_dependency 'byebug', '>= 11.0.0', '< 12.0.0'
|
data/lib/ewelink.rb
CHANGED
@@ -1,12 +1,16 @@
|
|
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'
|
4
6
|
require 'httparty'
|
5
7
|
require 'io/console'
|
6
8
|
require 'json'
|
7
9
|
require 'logger'
|
8
10
|
require 'openssl'
|
9
11
|
require 'optparse'
|
12
|
+
require 'set'
|
13
|
+
require 'timeout'
|
10
14
|
|
11
15
|
module Ewelink
|
12
16
|
|
data/lib/ewelink/api.rb
CHANGED
@@ -5,12 +5,14 @@ module Ewelink
|
|
5
5
|
APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'
|
6
6
|
APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'
|
7
7
|
DEFAULT_REGION = 'us'
|
8
|
+
REQUEST_TIMEOUT = 10.seconds
|
8
9
|
RF_BRIDGE_DEVICE_UIID = 28
|
9
10
|
SWITCH_DEVICES_UIIDS = [1, 5, 6, 24]
|
10
|
-
TIMEOUT = 10
|
11
11
|
URL = 'https://#{region}-api.coolkit.cc:8080'
|
12
12
|
UUID_NAMESPACE = 'e25750fb-3710-41af-b831-23224f4dd609';
|
13
13
|
VERSION = 8
|
14
|
+
WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
|
15
|
+
WEB_SOCKET_WAIT_INTERVAL = 0.2.seconds
|
14
16
|
|
15
17
|
attr_reader :email, :password, :phone_number
|
16
18
|
|
@@ -19,31 +21,45 @@ module Ewelink
|
|
19
21
|
@mutexs = {}
|
20
22
|
@password = password.presence || raise(Error.new(":password must be specified"))
|
21
23
|
@phone_number = phone_number.presence.try(:strip)
|
24
|
+
@web_socket_authenticated_api_keys = Set.new
|
25
|
+
@web_socket_switches_statuses = {}
|
22
26
|
raise(Error.new(":email or :phone_number must be specified")) if email.blank? && phone_number.blank?
|
23
27
|
end
|
24
28
|
|
25
29
|
def press_rf_bridge_button!(uuid)
|
26
30
|
synchronize(:press_rf_bridge_button) do
|
27
31
|
button = find_rf_bridge_button!(uuid)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
'
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
32
|
+
web_socket_wait_for(-> { web_socket_authenticated? }) do
|
33
|
+
params = {
|
34
|
+
'action' => 'update',
|
35
|
+
'apikey' => button[:api_key],
|
36
|
+
'deviceid' => button[:device_id],
|
37
|
+
'params' => {
|
38
|
+
'cmd' => 'transmit',
|
39
|
+
'rfChl' => button[:channel],
|
40
|
+
},
|
41
|
+
'sequence' => web_socket_sequence,
|
42
|
+
'ts' => 0,
|
43
|
+
'userAgent' => 'app',
|
44
|
+
}
|
45
|
+
Ewelink.logger.debug(self.class.name) { "Pressing RF bridge button #{button[:uuid].inspect}" }
|
46
|
+
send_to_web_socket(JSON.generate(params))
|
47
|
+
true
|
48
|
+
end
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
44
52
|
def reload
|
45
|
-
Ewelink.logger.debug(self.class.name) { 'Reloading API (authentication token, devices
|
46
|
-
|
53
|
+
Ewelink.logger.debug(self.class.name) { 'Reloading API (authentication token, devices, region,...)' }
|
54
|
+
dispose_web_socket
|
55
|
+
[
|
56
|
+
:@api_keys,
|
57
|
+
:@authentication_token,
|
58
|
+
:@devices,
|
59
|
+
:@region,
|
60
|
+
:@rf_bridge_buttons,
|
61
|
+
:@switches,
|
62
|
+
].each do |variable|
|
47
63
|
remove_instance_variable(variable) if instance_variable_defined?(variable)
|
48
64
|
end
|
49
65
|
self
|
@@ -56,10 +72,12 @@ module Ewelink
|
|
56
72
|
Ewelink.logger.debug(self.class.name) { "Found #{devices.size} RF 433MHz bridge device(s)" }
|
57
73
|
end
|
58
74
|
rf_bridge_devices.each do |device|
|
75
|
+
api_key = device['apikey'].presence || next
|
59
76
|
device_id = device['deviceid'].presence || next
|
60
77
|
device_name = device['name'].presence || next
|
61
78
|
buttons = device['params']['rfList'].each do |rf|
|
62
79
|
button = {
|
80
|
+
api_key: api_key,
|
63
81
|
channel: rf['rfChl'],
|
64
82
|
device_id: device_id,
|
65
83
|
device_name: device_name,
|
@@ -83,15 +101,23 @@ module Ewelink
|
|
83
101
|
|
84
102
|
def switch_on?(uuid)
|
85
103
|
switch = find_switch!(uuid)
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
104
|
+
if @web_socket_switches_statuses[switch[:uuid]].nil?
|
105
|
+
web_socket_wait_for(-> { web_socket_authenticated? }) do
|
106
|
+
Ewelink.logger.debug(self.class.name) { "Checking switch #{switch[:uuid].inspect} status" }
|
107
|
+
params = {
|
108
|
+
'action' => 'query',
|
109
|
+
'apikey' => switch[:api_key],
|
110
|
+
'deviceid' => switch[:device_id],
|
111
|
+
'sequence' => web_socket_sequence,
|
112
|
+
'ts' => 0,
|
113
|
+
'userAgent' => 'app',
|
114
|
+
}
|
115
|
+
send_to_web_socket(JSON.generate(params))
|
116
|
+
end
|
117
|
+
end
|
118
|
+
web_socket_wait_for(-> { !@web_socket_switches_statuses[switch[:uuid]].nil? }) do
|
119
|
+
@web_socket_switches_statuses[switch[:uuid]] == 'on'
|
120
|
+
end
|
95
121
|
end
|
96
122
|
|
97
123
|
def switches
|
@@ -99,9 +125,11 @@ module Ewelink
|
|
99
125
|
@switches ||= [].tap do |switches|
|
100
126
|
switch_devices = devices.select { |device| SWITCH_DEVICES_UIIDS.include?(device['uiid']) }
|
101
127
|
switch_devices.each do |device|
|
128
|
+
api_key = device['apikey'].presence || next
|
102
129
|
device_id = device['deviceid'].presence || next
|
103
130
|
name = device['name'].presence || next
|
104
131
|
switch = {
|
132
|
+
api_key: api_key,
|
105
133
|
device_id: device_id,
|
106
134
|
name: name,
|
107
135
|
}
|
@@ -119,22 +147,52 @@ module Ewelink
|
|
119
147
|
on = false
|
120
148
|
end
|
121
149
|
switch = find_switch!(uuid)
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
'
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
150
|
+
@web_socket_switches_statuses[switch[:uuid]] = nil
|
151
|
+
web_socket_wait_for(-> { web_socket_authenticated? }) do
|
152
|
+
params = {
|
153
|
+
'action' => 'update',
|
154
|
+
'apikey' => switch[:api_key],
|
155
|
+
'deviceid' => switch[:device_id],
|
156
|
+
'params' => {
|
157
|
+
'switch' => on ? 'on' : 'off',
|
158
|
+
},
|
159
|
+
'sequence' => web_socket_sequence,
|
160
|
+
'ts' => 0,
|
161
|
+
'userAgent' => 'app',
|
162
|
+
}
|
163
|
+
Ewelink.logger.debug(self.class.name) { "Turning switch #{switch[:uuid].inspect} #{on ? 'on' : 'off'}" }
|
164
|
+
send_to_web_socket(JSON.generate(params))
|
165
|
+
end
|
166
|
+
switch_on?(switch[:uuid]) # Waiting for switch status update
|
133
167
|
true
|
134
168
|
end
|
135
169
|
|
136
170
|
private
|
137
171
|
|
172
|
+
def api_keys
|
173
|
+
synchronize(:api_keys) do
|
174
|
+
@api_keys ||= Set.new(devices.map { |device| device['apikey'] })
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def authenticate_web_socket_api_keys
|
179
|
+
api_keys.each do |api_key|
|
180
|
+
params = {
|
181
|
+
'action' => 'userOnline',
|
182
|
+
'apikey' => api_key,
|
183
|
+
'appid' => APP_ID,
|
184
|
+
'at' => authentication_token,
|
185
|
+
'nonce' => nonce,
|
186
|
+
'sequence' => web_socket_sequence,
|
187
|
+
'ts' => Time.now.to_i,
|
188
|
+
'userAgent' => 'app',
|
189
|
+
'version' => VERSION,
|
190
|
+
}
|
191
|
+
Ewelink.logger.debug(self.class.name) { "Authenticating WebSocket API key: #{api_key.truncate(16).inspect}" }
|
192
|
+
send_to_web_socket(JSON.generate(params))
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
138
196
|
def authentication_headers
|
139
197
|
{ 'Authorization' => "Bearer #{authentication_token}" }
|
140
198
|
end
|
@@ -156,7 +214,7 @@ module Ewelink
|
|
156
214
|
params['phoneNumber'] = phone_number
|
157
215
|
end
|
158
216
|
body = JSON.generate(params)
|
159
|
-
response =
|
217
|
+
response = rest_request(:post, '/api/user/login', { body: body, headers: { 'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}" } })
|
160
218
|
raise(Error.new('Authentication token not found')) if response['at'].blank?
|
161
219
|
response['at'].tap { Ewelink.logger.debug(self.class.name) { 'Authentication token found' } }
|
162
220
|
end
|
@@ -173,12 +231,46 @@ module Ewelink
|
|
173
231
|
'ts' => Time.now.to_i,
|
174
232
|
'version' => VERSION,
|
175
233
|
}
|
176
|
-
response =
|
234
|
+
response = rest_request(:get, '/api/user/device', headers: authentication_headers, query: params)
|
177
235
|
response['devicelist'].tap { |devices| Ewelink.logger.debug(self.class.name) { "Found #{devices.size} device(s)" } }
|
178
236
|
end
|
179
237
|
end
|
180
238
|
end
|
181
239
|
|
240
|
+
def dispose_web_socket
|
241
|
+
Ewelink.logger.debug(self.class.name) { 'Dispose WebSocket' }
|
242
|
+
@web_socket_authenticated_api_keys.clear
|
243
|
+
@web_socket_switches_statuses.clear
|
244
|
+
|
245
|
+
[@web_socket_ping_thread, @web_socket_thread].each do |thread|
|
246
|
+
next unless thread
|
247
|
+
if Thread.current == thread
|
248
|
+
thread[:stop] = true
|
249
|
+
else
|
250
|
+
thread.kill
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
if @web_socket.present?
|
255
|
+
begin
|
256
|
+
@web_socket.close if @web_socket.open?
|
257
|
+
rescue
|
258
|
+
# Ignoring close errors
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
[
|
263
|
+
:@last_web_socket_pong_at,
|
264
|
+
:@web_socket_ping_interval,
|
265
|
+
:@web_socket_ping_thread,
|
266
|
+
:@web_socket_thread,
|
267
|
+
:@web_socket_url,
|
268
|
+
:@web_socket,
|
269
|
+
].each do |variable|
|
270
|
+
remove_instance_variable(variable) if instance_variable_defined?(variable)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
182
274
|
def find_rf_bridge_button!(uuid)
|
183
275
|
rf_bridge_buttons.find { |button| button[:uuid] == uuid } || raise(Error.new("No such RF bridge button with UUID: #{uuid.inspect}"))
|
184
276
|
end
|
@@ -195,29 +287,167 @@ module Ewelink
|
|
195
287
|
@region ||= DEFAULT_REGION
|
196
288
|
end
|
197
289
|
|
198
|
-
def
|
290
|
+
def rest_request(method, path, options = {})
|
199
291
|
url = "#{URL.gsub('#{region}', region)}#{path}"
|
200
292
|
method = method.to_s.upcase
|
201
293
|
headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
|
202
294
|
Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
|
203
|
-
response = HTTParty.send(method.downcase, url, options.merge(headers: headers).reverse_merge(timeout:
|
295
|
+
response = HTTParty.send(method.downcase, url, options.merge(headers: headers).reverse_merge(timeout: REQUEST_TIMEOUT))
|
204
296
|
raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
|
205
297
|
if response['error'] == 301 && response['region'].present?
|
206
298
|
@region = response['region']
|
207
299
|
Ewelink.logger.debug(self.class.name) { "Switched to region #{region.inspect}" }
|
208
|
-
return
|
300
|
+
return rest_request(method, path, options)
|
209
301
|
end
|
210
302
|
remove_instance_variable(:@authentication_token) if instance_variable_defined?(:@authentication_token) && [401, 403].include?(response['error'])
|
211
303
|
raise(Error.new("#{method} #{url}: #{response['error']} #{response['msg']}".strip)) if response['error'].present? && response['error'] != 0
|
212
|
-
response
|
304
|
+
response.to_h
|
213
305
|
rescue Errno::ECONNREFUSED, OpenSSL::OpenSSLError, SocketError, Timeout::Error => e
|
214
306
|
raise Error.new(e)
|
215
307
|
end
|
216
308
|
|
309
|
+
def send_to_web_socket(message)
|
310
|
+
if web_socket_outdated_ping?
|
311
|
+
Ewelink.logger.warn(self.class.name) { 'WebSocket ping is outdated' }
|
312
|
+
dispose_web_socket
|
313
|
+
end
|
314
|
+
web_socket.send(message)
|
315
|
+
rescue => e
|
316
|
+
dispose_web_socket
|
317
|
+
raise Error.new(e)
|
318
|
+
end
|
319
|
+
|
320
|
+
def start_web_socket_ping_thread(interval)
|
321
|
+
@last_web_socket_pong_at = Time.now
|
322
|
+
@web_socket_ping_interval = interval
|
323
|
+
Ewelink.logger.debug(self.class.name) { "Creating thread for WebSocket ping every #{@web_socket_ping_interval} seconds" }
|
324
|
+
@web_socket_ping_thread = Thread.new do
|
325
|
+
loop do
|
326
|
+
break if Thread.current[:stop]
|
327
|
+
sleep(@web_socket_ping_interval)
|
328
|
+
Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
|
329
|
+
send_to_web_socket('ping')
|
330
|
+
end
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
217
334
|
def synchronize(name, &block)
|
218
335
|
(@mutexs[name] ||= Mutex.new).synchronize(&block)
|
219
336
|
end
|
220
337
|
|
338
|
+
def web_socket
|
339
|
+
synchronize(:web_socket) do
|
340
|
+
next @web_socket if @web_socket
|
341
|
+
|
342
|
+
@web_socket_thread = Thread.new do
|
343
|
+
EventMachine.run do
|
344
|
+
Ewelink.logger.debug(self.class.name) { "Opening WebSocket to #{web_socket_url.inspect}" }
|
345
|
+
|
346
|
+
@web_socket = Faye::WebSocket::Client.new('wss://as-pconnect3.coolkit.cc:8080/api/ws')
|
347
|
+
|
348
|
+
@web_socket.on(:close) do |event|
|
349
|
+
Ewelink.logger.debug(self.class.name) { 'WebSocket closed' }
|
350
|
+
dispose_web_socket
|
351
|
+
end
|
352
|
+
|
353
|
+
@web_socket.on(:open) do |event|
|
354
|
+
Ewelink.logger.debug(self.class.name) { 'WebSocket opened' }
|
355
|
+
@last_web_socket_pong_at = Time.now
|
356
|
+
authenticate_web_socket_api_keys
|
357
|
+
end
|
358
|
+
|
359
|
+
@web_socket.on(:message) do |event|
|
360
|
+
message = event.data
|
361
|
+
|
362
|
+
if message == 'pong'
|
363
|
+
Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.inspect} message" }
|
364
|
+
@last_web_socket_pong_at = Time.now
|
365
|
+
next
|
366
|
+
end
|
367
|
+
|
368
|
+
begin
|
369
|
+
json = JSON.parse(message)
|
370
|
+
rescue => e
|
371
|
+
Ewelink.logger.error(self.class.name) { 'WebSocket JSON parse error' }
|
372
|
+
next
|
373
|
+
end
|
374
|
+
|
375
|
+
if json.key?('error') && json['error'] != 0
|
376
|
+
Ewelink.logger.error(self.class.name) { "WebSocket message error: #{message.inspect}" }
|
377
|
+
next
|
378
|
+
end
|
379
|
+
|
380
|
+
if !@web_socket_ping_thread && json.key?('config') && json['config']['hb'] == 1 && json['config']['hbInterval'].present?
|
381
|
+
start_web_socket_ping_thread(json['config']['hbInterval'] + 7)
|
382
|
+
end
|
383
|
+
|
384
|
+
if json['apikey'].present? && !@web_socket_authenticated_api_keys.include?(json['apikey'])
|
385
|
+
@web_socket_authenticated_api_keys << json['apikey']
|
386
|
+
Ewelink.logger.debug(self.class.name) { "WebSocket successfully authenticated API key: #{json['apikey'].truncate(16).inspect}" }
|
387
|
+
end
|
388
|
+
|
389
|
+
if json['deviceid'].present? && json['params'].is_a?(Hash) && json['params']['switch'].present?
|
390
|
+
switch = switches.find { |switch| switch[:device_id] == json['deviceid'] }
|
391
|
+
if switch.present?
|
392
|
+
@web_socket_switches_statuses[switch[:uuid]] = json['params']['switch']
|
393
|
+
Ewelink.logger.debug(self.class.name) { "Switch #{switch[:uuid].inspect} is #{@web_socket_switches_statuses[switch[:uuid]]}" }
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
Timeout.timeout(REQUEST_TIMEOUT) do
|
401
|
+
while @web_socket.blank?
|
402
|
+
sleep(WEB_SOCKET_WAIT_INTERVAL)
|
403
|
+
end
|
404
|
+
end
|
405
|
+
|
406
|
+
@web_socket
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def web_socket_authenticated?
|
411
|
+
api_keys == @web_socket_authenticated_api_keys
|
412
|
+
end
|
413
|
+
|
414
|
+
def web_socket_outdated_ping?
|
415
|
+
@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
|
416
|
+
end
|
417
|
+
|
418
|
+
def web_socket_sequence
|
419
|
+
(Time.now.to_f * 1000).round.to_s
|
420
|
+
end
|
421
|
+
|
422
|
+
def web_socket_url
|
423
|
+
synchronize(:web_socket_url) do
|
424
|
+
@web_socket_url ||= begin
|
425
|
+
params = {
|
426
|
+
'accept' => 'ws',
|
427
|
+
'appid' => APP_ID,
|
428
|
+
'nonce' => nonce,
|
429
|
+
'ts' => Time.now.to_i,
|
430
|
+
'version' => VERSION,
|
431
|
+
}
|
432
|
+
response = rest_request(:post, '/dispatch/app', body: JSON.generate(params), headers: authentication_headers)
|
433
|
+
raise('Error while getting WebSocket URL') unless response['error'] == 0
|
434
|
+
domain = response['domain'].presence || raise("Can't get WebSocket server domain")
|
435
|
+
port = response['port'].presence || raise("Can't get WebSocket server port")
|
436
|
+
"wss://#{domain}:#{port}/api/ws".tap { |url| Ewelink.logger.debug(self.class.name) { "WebSocket URL is: #{url.inspect}" } }
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
def web_socket_wait_for(condition, &block)
|
442
|
+
web_socket # Initializes WebSocket
|
443
|
+
Timeout.timeout(REQUEST_TIMEOUT) do
|
444
|
+
while !condition.call
|
445
|
+
sleep(WEB_SOCKET_WAIT_INTERVAL)
|
446
|
+
end
|
447
|
+
block_given? ? yield : true
|
448
|
+
end
|
449
|
+
end
|
450
|
+
|
221
451
|
end
|
222
452
|
|
223
453
|
end
|
data/lib/ewelink/runner.rb
CHANGED
@@ -9,13 +9,14 @@ module Ewelink
|
|
9
9
|
options[:turn_switches_on_uuids].each { |uuid| api.turn_switch!(uuid, :on) }
|
10
10
|
options[:turn_switches_off_uuids].each { |uuid| api.turn_switch!(uuid, :off) }
|
11
11
|
options[:press_rf_bridge_buttons_uuids].each { |uuid| api.press_rf_bridge_button!(uuid) }
|
12
|
+
puts(JSON.pretty_generate(options[:switch_status_uuids].map { |uuid| [uuid, api.switch_on?(uuid) ? 'on' : 'off'] }.to_h)) if options[:switch_status_uuids].present?
|
12
13
|
end
|
13
14
|
|
14
15
|
private
|
15
16
|
|
16
17
|
def options
|
17
18
|
@options ||= begin
|
18
|
-
options = { press_rf_bridge_buttons_uuids: [], turn_switches_off_uuids: [], turn_switches_on_uuids: [] }
|
19
|
+
options = { press_rf_bridge_buttons_uuids: [], turn_switches_off_uuids: [], turn_switches_on_uuids: [], switch_status_uuids: [] }
|
19
20
|
parser = OptionParser.new do |opts|
|
20
21
|
opts.banner = 'Manage eWeLink smart home devices'
|
21
22
|
opts.version = File.read(File.expand_path('../../VERSION', __dir__)).strip
|
@@ -46,6 +47,9 @@ module Ewelink
|
|
46
47
|
opts.on('--press-rf-bridge-button BUTTON_UUID', 'Press RF 433MHz bridge button with specified UUID') do |uuid|
|
47
48
|
options[:press_rf_bridge_buttons_uuids] << uuid
|
48
49
|
end
|
50
|
+
opts.on('--switch-status SWITCH_UUID', 'Displays switch status of specified UUID') do |uuid|
|
51
|
+
options[:switch_status_uuids] << uuid
|
52
|
+
end
|
49
53
|
opts.on('-v', '--verbose', 'Verbose mode') do
|
50
54
|
Ewelink.logger.level = :debug
|
51
55
|
end
|
@@ -61,7 +65,7 @@ module Ewelink
|
|
61
65
|
STDERR.puts(parser.summarize)
|
62
66
|
exit(1)
|
63
67
|
end
|
64
|
-
if [:list_switches, :list_rf_bridge_buttons, :turn_switches_on_uuids, :turn_switches_off_uuids, :press_rf_bridge_buttons_uuids].map { |action| options[action] }.all?(&:blank?)
|
68
|
+
if [:list_switches, :list_rf_bridge_buttons, :turn_switches_on_uuids, :turn_switches_off_uuids, :press_rf_bridge_buttons_uuids, :switch_status_uuids].map { |action| options[action] }.all?(&:blank?)
|
65
69
|
STDERR.puts('An action must be specified (listing switches, press RF bridge button, etc.)')
|
66
70
|
STDERR.puts(parser.summarize)
|
67
71
|
exit(1)
|
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: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexis Toulotte
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-09-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -30,6 +30,26 @@ dependencies:
|
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 7.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
|
33
53
|
- !ruby/object:Gem::Dependency
|
34
54
|
name: httparty
|
35
55
|
requirement: !ruby/object:Gem::Requirement
|
@@ -109,7 +129,7 @@ homepage: https://github.com/alexistoulotte/ewelink
|
|
109
129
|
licenses:
|
110
130
|
- MIT
|
111
131
|
metadata: {}
|
112
|
-
post_install_message:
|
132
|
+
post_install_message:
|
113
133
|
rdoc_options: []
|
114
134
|
require_paths:
|
115
135
|
- lib
|
@@ -125,7 +145,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
125
145
|
version: '0'
|
126
146
|
requirements: []
|
127
147
|
rubygems_version: 3.0.3
|
128
|
-
signing_key:
|
148
|
+
signing_key:
|
129
149
|
specification_version: 4
|
130
150
|
summary: Manage eWeLink devices
|
131
151
|
test_files: []
|