ewelink 3.3.1 → 4.1.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/bin/ewelink +1 -1
- data/ewelink.gemspec +7 -5
- data/lib/ewelink/api.rb +39 -41
- data/lib/ewelink/runner.rb +9 -11
- metadata +52 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8cba69a24adaf18a0be08cbf1618b612d6d0e90fd788d8b9f2360f7d19f67406
|
4
|
+
data.tar.gz: adc650eac1533d343320490132302827a79f0fc4a0571d0dcba91ef926e5f682
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39ab9a66085401bb70f34f74b9220e70ad691c5db65b180816d5a284462162064364d6e6ed3f2b5d990f9c3fa3450472fb0b13ba0b3ecbca1a22042de3926d28
|
7
|
+
data.tar.gz: 19b1627d07a411ef4fe04578f924a7d339413d65d7132b8cd7614877b1e72300b5cbd51f1f9a6469807fe3179e37a65c048f1e699601c4eb3b4f05b82b0e663f
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
4.1.0
|
data/bin/ewelink
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative '../lib/ewelink'
|
4
4
|
|
5
|
-
Ewelink.logger = Logger.new(
|
5
|
+
Ewelink.logger = Logger.new($stdout, formatter: lambda { |_severity, _time, progname, message|
|
6
6
|
text = ''
|
7
7
|
text << "[#{progname}] " if progname.present?
|
8
8
|
text << message.to_s << "\n"
|
data/ewelink.gemspec
CHANGED
@@ -9,17 +9,19 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.description = 'Manage eWeLink smart home devices'
|
10
10
|
s.license = 'MIT'
|
11
11
|
|
12
|
-
s.files =
|
13
|
-
s.executables =
|
12
|
+
s.files = %x(git ls-files | grep -vE '^(spec/|test/|\\.|Gemfile|Rakefile)').split("\n")
|
13
|
+
s.executables = %x(git ls-files -- bin/*).split("\n").map { |f| File.basename(f) }
|
14
14
|
s.require_paths = ['lib']
|
15
15
|
|
16
|
-
s.required_ruby_version = '>=
|
16
|
+
s.required_ruby_version = '>= 3.1.0'
|
17
17
|
|
18
|
-
s.add_dependency 'activesupport', '>=
|
18
|
+
s.add_dependency 'activesupport', '>= 7.0.0', '< 8.0.0'
|
19
19
|
s.add_dependency 'faye-websocket', '>= 0.11.0', '< 0.12.0'
|
20
|
-
s.add_dependency 'httparty', '>= 0.
|
20
|
+
s.add_dependency 'httparty', '>= 0.20.0', '< 0.21.0'
|
21
21
|
s.add_dependency 'thread', '>= 0.2.0', '< 0.3.0'
|
22
22
|
|
23
23
|
s.add_development_dependency 'byebug', '>= 11.0.0', '< 12.0.0'
|
24
24
|
s.add_development_dependency 'rake', '>= 13.0.0', '< 14.0.0'
|
25
|
+
s.add_development_dependency 'rubocop', '>= 1.25.0', '< 2.0.0'
|
26
|
+
s.add_development_dependency 'rubocop-rake', '>= 0.6.0', '< 1.0.0'
|
25
27
|
end
|
data/lib/ewelink/api.rb
CHANGED
@@ -2,14 +2,13 @@ module Ewelink
|
|
2
2
|
|
3
3
|
class Api
|
4
4
|
|
5
|
-
APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'
|
6
|
-
APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'
|
7
|
-
DEFAULT_REGION = 'us'
|
5
|
+
APP_ID = 'oeVkj2lYFGnJu5XUtWisfW4utiN4u9Mq'.freeze
|
6
|
+
APP_SECRET = '6Nz4n0xA8s8qdxQf2GqurZj2Fs55FUvM'.freeze
|
7
|
+
DEFAULT_REGION = 'us'.freeze
|
8
8
|
REQUEST_TIMEOUT = 10.seconds
|
9
9
|
RF_BRIDGE_DEVICE_UIID = 28
|
10
|
-
SWITCH_DEVICES_UIIDS = [1, 5, 6, 24]
|
11
|
-
URL = 'https://#{region}-api.coolkit.cc:8080'
|
12
|
-
UUID_NAMESPACE = 'e25750fb-3710-41af-b831-23224f4dd609';
|
10
|
+
SWITCH_DEVICES_UIIDS = [1, 5, 6, 24].freeze
|
11
|
+
URL = 'https://#{region}-api.coolkit.cc:8080'.freeze
|
13
12
|
VERSION = 8
|
14
13
|
WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
|
15
14
|
WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
|
@@ -18,11 +17,11 @@ module Ewelink
|
|
18
17
|
|
19
18
|
attr_reader :email, :password, :phone_number
|
20
19
|
|
21
|
-
def initialize(async_actions: false, email: nil,
|
20
|
+
def initialize(password:, async_actions: false, email: nil, phone_number: nil, update_devices_status_on_connect: false)
|
22
21
|
@async_actions = async_actions.present?
|
23
22
|
@email = email.presence.try(:strip)
|
24
23
|
@mutexs = {}
|
25
|
-
@password = password.presence || raise(Error.new(
|
24
|
+
@password = password.presence || raise(Error.new(':password must be specified'))
|
26
25
|
@phone_number = phone_number.presence.try(:strip)
|
27
26
|
@update_devices_status_on_connect = update_devices_status_on_connect.present?
|
28
27
|
@web_socket_authenticated = false
|
@@ -85,19 +84,19 @@ module Ewelink
|
|
85
84
|
end
|
86
85
|
end
|
87
86
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
87
|
+
%i(
|
88
|
+
@authentication_infos
|
89
|
+
@devices
|
90
|
+
@last_web_socket_pong_at
|
91
|
+
@region
|
92
|
+
@rf_bridge_buttons
|
93
|
+
@switches
|
94
|
+
@web_socket_ping_interval
|
95
|
+
@web_socket_ping_thread
|
96
|
+
@web_socket_thread
|
97
|
+
@web_socket_url
|
98
|
+
@web_socket
|
99
|
+
).each do |variable|
|
101
100
|
remove_instance_variable(variable) if instance_variable_defined?(variable)
|
102
101
|
end
|
103
102
|
self
|
@@ -115,10 +114,10 @@ module Ewelink
|
|
115
114
|
device_name = device['name'].presence || next
|
116
115
|
buttons = device['params']['rfList'].each do |rf|
|
117
116
|
button = {
|
118
|
-
api_key
|
117
|
+
api_key:,
|
119
118
|
channel: rf['rfChl'],
|
120
|
-
device_id
|
121
|
-
device_name
|
119
|
+
device_id:,
|
120
|
+
device_name:,
|
122
121
|
}
|
123
122
|
remote_info = device['tags']['zyx_info'].find { |info| info['buttonName'].find { |data| data.key?(button[:channel].to_s) } }.presence || next
|
124
123
|
remote_name = remote_info['name'].try(:squish).presence || next
|
@@ -126,10 +125,10 @@ module Ewelink
|
|
126
125
|
button_name = button_info.values.first.try(:squish).presence || next
|
127
126
|
button.merge!({
|
128
127
|
name: button_name,
|
129
|
-
remote_name
|
128
|
+
remote_name:,
|
130
129
|
remote_type: remote_info['remote_type'],
|
131
130
|
})
|
132
|
-
button[:uuid] = Digest::UUID.uuid_v5(
|
131
|
+
button[:uuid] = Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, "#{button[:device_id]}/#{button[:channel]}")
|
133
132
|
buttons << button
|
134
133
|
end
|
135
134
|
end
|
@@ -167,11 +166,12 @@ module Ewelink
|
|
167
166
|
device_id = device['deviceid'].presence || next
|
168
167
|
name = device['name'].presence || next
|
169
168
|
switch = {
|
170
|
-
api_key
|
171
|
-
device_id
|
172
|
-
|
169
|
+
api_key:,
|
170
|
+
device_id:,
|
171
|
+
model: device['productModel'],
|
172
|
+
name:,
|
173
173
|
}
|
174
|
-
switch[:uuid] = Digest::UUID.uuid_v5(
|
174
|
+
switch[:uuid] = Digest::UUID.uuid_v5(Digest::UUID::DNS_NAMESPACE, switch[:device_id])
|
175
175
|
switches << switch
|
176
176
|
end
|
177
177
|
end.tap { |switches| Ewelink.logger.debug(self.class.name) { "Found #{switches.size} switch(es)" } }
|
@@ -255,7 +255,7 @@ module Ewelink
|
|
255
255
|
params['phoneNumber'] = phone_number
|
256
256
|
end
|
257
257
|
body = JSON.generate(params)
|
258
|
-
response = rest_request(:post, '/api/user/login', { body
|
258
|
+
response = rest_request(:post, '/api/user/login', { body:, headers: { 'Authorization' => "Sign #{Base64.encode64(OpenSSL::HMAC.digest('SHA256', APP_SECRET, body))}" } })
|
259
259
|
raise(Error.new('Authentication token not found')) if response['at'].blank?
|
260
260
|
raise(Error.new('API key not found')) if response['user'].blank? || response['user']['apikey'].blank?
|
261
261
|
{
|
@@ -302,7 +302,7 @@ module Ewelink
|
|
302
302
|
return yield unless async_actions?
|
303
303
|
@async_actions_thread_pool ||= Thread.pool(1)
|
304
304
|
@async_actions_thread_pool.process(&block)
|
305
|
-
|
305
|
+
true
|
306
306
|
end
|
307
307
|
|
308
308
|
def region
|
@@ -314,7 +314,7 @@ module Ewelink
|
|
314
314
|
method = method.to_s.upcase
|
315
315
|
headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
|
316
316
|
Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
|
317
|
-
response = HTTParty.send(method.downcase, url, options.merge(headers:
|
317
|
+
response = HTTParty.send(method.downcase, url, options.merge(headers:).reverse_merge(timeout: REQUEST_TIMEOUT))
|
318
318
|
raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
|
319
319
|
if response['error'] == 301 && response['region'].present?
|
320
320
|
@region = response['region']
|
@@ -391,12 +391,12 @@ module Ewelink
|
|
391
391
|
EventMachine.run do
|
392
392
|
@web_socket = Faye::WebSocket::Client.new(web_socket_url)
|
393
393
|
|
394
|
-
@web_socket.on(:close) do
|
394
|
+
@web_socket.on(:close) do
|
395
395
|
Ewelink.logger.debug(self.class.name) { 'WebSocket closed' }
|
396
396
|
reload
|
397
397
|
end
|
398
398
|
|
399
|
-
@web_socket.on(:open) do
|
399
|
+
@web_socket.on(:open) do
|
400
400
|
Ewelink.logger.debug(self.class.name) { 'WebSocket opened' }
|
401
401
|
@last_web_socket_pong_at = Time.now
|
402
402
|
authenticate_web_socket_api_key
|
@@ -413,7 +413,7 @@ module Ewelink
|
|
413
413
|
|
414
414
|
begin
|
415
415
|
json = JSON.parse(message)
|
416
|
-
rescue
|
416
|
+
rescue
|
417
417
|
Ewelink.logger.error(self.class.name) { 'WebSocket JSON parse error' }
|
418
418
|
reload
|
419
419
|
next
|
@@ -436,7 +436,7 @@ module Ewelink
|
|
436
436
|
end
|
437
437
|
|
438
438
|
if json['deviceid'].present? && json['params'].is_a?(Hash) && json['params']['switch'].present?
|
439
|
-
switch = switches.find { |
|
439
|
+
switch = switches.find { |item| item[:device_id] == json['deviceid'] }
|
440
440
|
if switch.present?
|
441
441
|
@web_socket_switches_statuses[switch[:uuid]] = json['params']['switch']
|
442
442
|
Ewelink.logger.debug(self.class.name) { "Switch #{switch[:uuid].inspect} is #{@web_socket_switches_statuses[switch[:uuid]]}" }
|
@@ -483,13 +483,11 @@ module Ewelink
|
|
483
483
|
end
|
484
484
|
end
|
485
485
|
|
486
|
-
def web_socket_wait_for(condition, initialize_web_socket: false
|
486
|
+
def web_socket_wait_for(condition, initialize_web_socket: false)
|
487
487
|
web_socket if initialize_web_socket
|
488
488
|
begin
|
489
489
|
Timeout.timeout(REQUEST_TIMEOUT) do
|
490
|
-
|
491
|
-
sleep(WEB_SOCKET_WAIT_INTERVAL)
|
492
|
-
end
|
490
|
+
sleep(WEB_SOCKET_WAIT_INTERVAL) until condition.call
|
493
491
|
block_given? ? yield : true
|
494
492
|
end
|
495
493
|
rescue => e
|
data/lib/ewelink/runner.rb
CHANGED
@@ -9,7 +9,7 @@ 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].
|
12
|
+
puts(JSON.pretty_generate(options[:switch_status_uuids].to_h { |uuid| [uuid, api.switch_on?(uuid) ? 'on' : 'off'] })) if options[:switch_status_uuids].present?
|
13
13
|
end
|
14
14
|
|
15
15
|
private
|
@@ -56,23 +56,21 @@ module Ewelink
|
|
56
56
|
end
|
57
57
|
arguments = parser.parse!
|
58
58
|
if arguments.any?
|
59
|
-
|
60
|
-
|
59
|
+
warn("Invalid option specified: #{arguments.first}")
|
60
|
+
warn(parser.summarize)
|
61
61
|
exit(1)
|
62
62
|
end
|
63
63
|
if options[:email].blank? && options[:phone_number].blank?
|
64
|
-
|
65
|
-
|
64
|
+
warn('Email or phone number must be specified')
|
65
|
+
warn(parser.summarize)
|
66
66
|
exit(1)
|
67
67
|
end
|
68
|
-
if
|
69
|
-
|
70
|
-
|
68
|
+
if %i(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?)
|
69
|
+
warn('An action must be specified (listing switches, press RF bridge button, etc.)')
|
70
|
+
warn(parser.summarize)
|
71
71
|
exit(1)
|
72
72
|
end
|
73
|
-
while options[:password].blank?
|
74
|
-
options[:password] = IO::console.getpass("Enter eWeLink account's password: ")
|
75
|
-
end
|
73
|
+
options[:password] = IO.console.getpass("Enter eWeLink account's password: ") while options[:password].blank?
|
76
74
|
options
|
77
75
|
end
|
78
76
|
end
|
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: 4.1.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: 2022-03-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -16,20 +16,20 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - ">="
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version:
|
19
|
+
version: 7.0.0
|
20
20
|
- - "<"
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version:
|
22
|
+
version: 8.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version:
|
29
|
+
version: 7.0.0
|
30
30
|
- - "<"
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version:
|
32
|
+
version: 8.0.0
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
34
|
name: faye-websocket
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
@@ -56,20 +56,20 @@ dependencies:
|
|
56
56
|
requirements:
|
57
57
|
- - ">="
|
58
58
|
- !ruby/object:Gem::Version
|
59
|
-
version: 0.
|
59
|
+
version: 0.20.0
|
60
60
|
- - "<"
|
61
61
|
- !ruby/object:Gem::Version
|
62
|
-
version: 0.
|
62
|
+
version: 0.21.0
|
63
63
|
type: :runtime
|
64
64
|
prerelease: false
|
65
65
|
version_requirements: !ruby/object:Gem::Requirement
|
66
66
|
requirements:
|
67
67
|
- - ">="
|
68
68
|
- !ruby/object:Gem::Version
|
69
|
-
version: 0.
|
69
|
+
version: 0.20.0
|
70
70
|
- - "<"
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version: 0.
|
72
|
+
version: 0.21.0
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: thread
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -130,6 +130,46 @@ dependencies:
|
|
130
130
|
- - "<"
|
131
131
|
- !ruby/object:Gem::Version
|
132
132
|
version: 14.0.0
|
133
|
+
- !ruby/object:Gem::Dependency
|
134
|
+
name: rubocop
|
135
|
+
requirement: !ruby/object:Gem::Requirement
|
136
|
+
requirements:
|
137
|
+
- - ">="
|
138
|
+
- !ruby/object:Gem::Version
|
139
|
+
version: 1.25.0
|
140
|
+
- - "<"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 2.0.0
|
143
|
+
type: :development
|
144
|
+
prerelease: false
|
145
|
+
version_requirements: !ruby/object:Gem::Requirement
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
version: 1.25.0
|
150
|
+
- - "<"
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: 2.0.0
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: rubocop-rake
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: 0.6.0
|
160
|
+
- - "<"
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: 1.0.0
|
163
|
+
type: :development
|
164
|
+
prerelease: false
|
165
|
+
version_requirements: !ruby/object:Gem::Requirement
|
166
|
+
requirements:
|
167
|
+
- - ">="
|
168
|
+
- !ruby/object:Gem::Version
|
169
|
+
version: 0.6.0
|
170
|
+
- - "<"
|
171
|
+
- !ruby/object:Gem::Version
|
172
|
+
version: 1.0.0
|
133
173
|
description: Manage eWeLink smart home devices
|
134
174
|
email: al@alweb.org
|
135
175
|
executables:
|
@@ -157,14 +197,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
157
197
|
requirements:
|
158
198
|
- - ">="
|
159
199
|
- !ruby/object:Gem::Version
|
160
|
-
version:
|
200
|
+
version: 3.1.0
|
161
201
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
162
202
|
requirements:
|
163
203
|
- - ">="
|
164
204
|
- !ruby/object:Gem::Version
|
165
205
|
version: '0'
|
166
206
|
requirements: []
|
167
|
-
rubygems_version: 3.
|
207
|
+
rubygems_version: 3.3.3
|
168
208
|
signing_key:
|
169
209
|
specification_version: 4
|
170
210
|
summary: Manage eWeLink devices
|