ewelink 2.1.0 → 2.3.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 -1
- data/lib/ewelink.rb +2 -1
- data/lib/ewelink/api.rb +173 -139
- metadata +12 -12
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: d17e40d48a819572e83369500edbe6295a991626c3333ce4ff0765876f25ed51
         | 
| 4 | 
            +
              data.tar.gz: 9caca646a1355592a276e5e4595ab486e407ecaecb105a9e7134028ecab4cb42
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6e8a2717428e2b57c7a30a7d4e9a1658032b1155a2f9bd2ba9b8b0cb2309748d248f24d66babfbe2212af9d8096e6be18ebdc3c60d799654571526edff1204d5
         | 
| 7 | 
            +
              data.tar.gz: 9b9e517a3c8629d6a69b8195cab906d12f8f4d4231ab22311083deb75e5db97b02ec9c13a8509710017bb17cfcf1750c46919f52b393b73a0d453c423f120153
         | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            2. | 
| 1 | 
            +
            2.3.0
         | 
    
        data/ewelink.gemspec
    CHANGED
    
    | @@ -16,8 +16,8 @@ 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 | 
            -
              s.add_dependency 'websocket-client-simple', '>= 0.3.0', '< 0.4.0'
         | 
| 21 21 |  | 
| 22 22 | 
             
              s.add_development_dependency 'byebug', '>= 11.0.0', '< 12.0.0'
         | 
| 23 23 | 
             
              s.add_development_dependency 'rake', '>= 12.0.0', '< 13.0.0'
         | 
    
        data/lib/ewelink.rb
    CHANGED
    
    | @@ -1,6 +1,8 @@ | |
| 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'
         | 
| @@ -9,7 +11,6 @@ require 'openssl' | |
| 9 11 | 
             
            require 'optparse'
         | 
| 10 12 | 
             
            require 'set'
         | 
| 11 13 | 
             
            require 'timeout'
         | 
| 12 | 
            -
            require 'websocket-client-simple'
         | 
| 13 14 |  | 
| 14 15 | 
             
            module Ewelink
         | 
| 15 16 |  | 
    
        data/lib/ewelink/api.rb
    CHANGED
    
    | @@ -5,13 +5,15 @@ 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.seconds
         | 
| 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_CHECK_AUTHENTICATION_TIMEOUT = 30.seconds
         | 
| 14 15 | 
             
                WEB_SOCKET_PING_TOLERANCE_FACTOR = 1.5
         | 
| 16 | 
            +
                SWITCH_STATUS_CHANGE_CHECK_TIMEOUT = 2.seconds
         | 
| 15 17 | 
             
                WEB_SOCKET_WAIT_INTERVAL = 0.2.seconds
         | 
| 16 18 |  | 
| 17 19 | 
             
                attr_reader :email, :password, :phone_number
         | 
| @@ -21,15 +23,18 @@ module Ewelink | |
| 21 23 | 
             
                  @mutexs = {}
         | 
| 22 24 | 
             
                  @password = password.presence || raise(Error.new(":password must be specified"))
         | 
| 23 25 | 
             
                  @phone_number = phone_number.presence.try(:strip)
         | 
| 24 | 
            -
                  @switches_statuses = {}
         | 
| 25 26 | 
             
                  @web_socket_authenticated_api_keys = Set.new
         | 
| 26 | 
            -
                   | 
| 27 | 
            +
                  @web_socket_switches_statuses = {}
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  raise(Error.new(':email or :phone_number must be specified')) if email.blank? && phone_number.blank?
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  start_web_socket_authentication_check_thread
         | 
| 27 32 | 
             
                end
         | 
| 28 33 |  | 
| 29 34 | 
             
                def press_rf_bridge_button!(uuid)
         | 
| 30 35 | 
             
                  synchronize(:press_rf_bridge_button) do
         | 
| 31 36 | 
             
                    button = find_rf_bridge_button!(uuid)
         | 
| 32 | 
            -
                    web_socket_wait_for(-> { web_socket_authenticated? }) do
         | 
| 37 | 
            +
                    web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
         | 
| 33 38 | 
             
                      params = {
         | 
| 34 39 | 
             
                        'action' => 'update',
         | 
| 35 40 | 
             
                        'apikey' => button[:api_key],
         | 
| @@ -51,15 +56,40 @@ module Ewelink | |
| 51 56 |  | 
| 52 57 | 
             
                def reload
         | 
| 53 58 | 
             
                  Ewelink.logger.debug(self.class.name) { 'Reloading API (authentication token, devices, region, connections,...)' }
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                  @ | 
| 59 | 
            +
             | 
| 60 | 
            +
                  @web_socket_authenticated_api_keys.clear
         | 
| 61 | 
            +
                  @web_socket_switches_statuses.clear
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                  [@web_socket_ping_thread, @web_socket_thread].each do |thread|
         | 
| 64 | 
            +
                    next unless thread
         | 
| 65 | 
            +
                    if Thread.current == thread
         | 
| 66 | 
            +
                      thread[:stop] = true
         | 
| 67 | 
            +
                    else
         | 
| 68 | 
            +
                      thread.kill
         | 
| 69 | 
            +
                    end
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  if @web_socket.present?
         | 
| 73 | 
            +
                    begin
         | 
| 74 | 
            +
                      @web_socket.close if @web_socket.open?
         | 
| 75 | 
            +
                    rescue
         | 
| 76 | 
            +
                      # Ignoring close errors
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
                  end
         | 
| 79 | 
            +
             | 
| 56 80 | 
             
                  [
         | 
| 57 81 | 
             
                    :@api_keys,
         | 
| 58 82 | 
             
                    :@authentication_token,
         | 
| 59 83 | 
             
                    :@devices,
         | 
| 84 | 
            +
                    :@last_web_socket_pong_at,
         | 
| 60 85 | 
             
                    :@region,
         | 
| 61 86 | 
             
                    :@rf_bridge_buttons,
         | 
| 62 87 | 
             
                    :@switches,
         | 
| 88 | 
            +
                    :@web_socket_ping_interval,
         | 
| 89 | 
            +
                    :@web_socket_ping_thread,
         | 
| 90 | 
            +
                    :@web_socket_thread,
         | 
| 91 | 
            +
                    :@web_socket_url,
         | 
| 92 | 
            +
                    :@web_socket,
         | 
| 63 93 | 
             
                  ].each do |variable|
         | 
| 64 94 | 
             
                    remove_instance_variable(variable) if instance_variable_defined?(variable)
         | 
| 65 95 | 
             
                  end
         | 
| @@ -102,23 +132,22 @@ module Ewelink | |
| 102 132 |  | 
| 103 133 | 
             
                def switch_on?(uuid)
         | 
| 104 134 | 
             
                  switch = find_switch!(uuid)
         | 
| 105 | 
            -
                  if @ | 
| 106 | 
            -
                     | 
| 107 | 
            -
                      'action' => 'query',
         | 
| 108 | 
            -
                      'apikey' => switch[:api_key],
         | 
| 109 | 
            -
                      'deviceid' => switch[:device_id],
         | 
| 110 | 
            -
                      'sequence' => web_socket_sequence,
         | 
| 111 | 
            -
                      'ts' => 0,
         | 
| 112 | 
            -
                      'userAgent' => 'app',
         | 
| 113 | 
            -
                    }
         | 
| 114 | 
            -
                    web_socket_wait_for(-> { web_socket_authenticated? }) do
         | 
| 135 | 
            +
                  if @web_socket_switches_statuses[switch[:uuid]].nil?
         | 
| 136 | 
            +
                    web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
         | 
| 115 137 | 
             
                      Ewelink.logger.debug(self.class.name) { "Checking switch #{switch[:uuid].inspect} status" }
         | 
| 138 | 
            +
                      params = {
         | 
| 139 | 
            +
                        'action' => 'query',
         | 
| 140 | 
            +
                        'apikey' => switch[:api_key],
         | 
| 141 | 
            +
                        'deviceid' => switch[:device_id],
         | 
| 142 | 
            +
                        'sequence' => web_socket_sequence,
         | 
| 143 | 
            +
                        'ts' => 0,
         | 
| 144 | 
            +
                        'userAgent' => 'app',
         | 
| 145 | 
            +
                      }
         | 
| 116 146 | 
             
                      send_to_web_socket(JSON.generate(params))
         | 
| 117 147 | 
             
                    end
         | 
| 118 148 | 
             
                  end
         | 
| 119 | 
            -
                  web_socket_wait_for(-> { !@ | 
| 120 | 
            -
                     | 
| 121 | 
            -
                    @switches_statuses[switch[:uuid]] == 'on'
         | 
| 149 | 
            +
                  web_socket_wait_for(-> { !@web_socket_switches_statuses[switch[:uuid]].nil? }, initialize_web_socket: true) do
         | 
| 150 | 
            +
                    @web_socket_switches_statuses[switch[:uuid]] == 'on'
         | 
| 122 151 | 
             
                  end
         | 
| 123 152 | 
             
                end
         | 
| 124 153 |  | 
| @@ -149,8 +178,8 @@ module Ewelink | |
| 149 178 | 
             
                    on = false
         | 
| 150 179 | 
             
                  end
         | 
| 151 180 | 
             
                  switch = find_switch!(uuid)
         | 
| 152 | 
            -
                  @ | 
| 153 | 
            -
                  web_socket_wait_for(-> { web_socket_authenticated? }) do
         | 
| 181 | 
            +
                  @web_socket_switches_statuses[switch[:uuid]] = nil
         | 
| 182 | 
            +
                  web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
         | 
| 154 183 | 
             
                    params = {
         | 
| 155 184 | 
             
                      'action' => 'update',
         | 
| 156 185 | 
             
                      'apikey' => switch[:api_key],
         | 
| @@ -165,6 +194,7 @@ module Ewelink | |
| 165 194 | 
             
                    Ewelink.logger.debug(self.class.name) { "Turning switch #{switch[:uuid].inspect} #{on ? 'on' : 'off'}" }
         | 
| 166 195 | 
             
                    send_to_web_socket(JSON.generate(params))
         | 
| 167 196 | 
             
                  end
         | 
| 197 | 
            +
                  sleep(SWITCH_STATUS_CHANGE_CHECK_TIMEOUT)
         | 
| 168 198 | 
             
                  switch_on?(switch[:uuid]) # Waiting for switch status update
         | 
| 169 199 | 
             
                  true
         | 
| 170 200 | 
             
                end
         | 
| @@ -177,6 +207,24 @@ module Ewelink | |
| 177 207 | 
             
                  end
         | 
| 178 208 | 
             
                end
         | 
| 179 209 |  | 
| 210 | 
            +
                def authenticate_web_socket_api_keys
         | 
| 211 | 
            +
                  api_keys.each do |api_key|
         | 
| 212 | 
            +
                    params = {
         | 
| 213 | 
            +
                      'action' => 'userOnline',
         | 
| 214 | 
            +
                      'apikey' => api_key,
         | 
| 215 | 
            +
                      'appid' => APP_ID,
         | 
| 216 | 
            +
                      'at' => authentication_token,
         | 
| 217 | 
            +
                      'nonce' => nonce,
         | 
| 218 | 
            +
                      'sequence' => web_socket_sequence,
         | 
| 219 | 
            +
                      'ts' => Time.now.to_i,
         | 
| 220 | 
            +
                      'userAgent' => 'app',
         | 
| 221 | 
            +
                      'version' => VERSION,
         | 
| 222 | 
            +
                    }
         | 
| 223 | 
            +
                    Ewelink.logger.debug(self.class.name) { "Authenticating WebSocket API key: #{api_key.truncate(16).inspect}" }
         | 
| 224 | 
            +
                    send_to_web_socket(JSON.generate(params))
         | 
| 225 | 
            +
                  end
         | 
| 226 | 
            +
                end
         | 
| 227 | 
            +
             | 
| 180 228 | 
             
                def authentication_headers
         | 
| 181 229 | 
             
                  { 'Authorization' => "Bearer #{authentication_token}" }
         | 
| 182 230 | 
             
                end
         | 
| @@ -221,36 +269,6 @@ module Ewelink | |
| 221 269 | 
             
                  end
         | 
| 222 270 | 
             
                end
         | 
| 223 271 |  | 
| 224 | 
            -
                def dispose_web_socket
         | 
| 225 | 
            -
                  @web_socket_authenticated_api_keys = Set.new
         | 
| 226 | 
            -
             | 
| 227 | 
            -
                  if @web_socket_ping_thread
         | 
| 228 | 
            -
                    if Thread.current == @web_socket_ping_thread
         | 
| 229 | 
            -
                      Thread.current[:stop] = true
         | 
| 230 | 
            -
                    else
         | 
| 231 | 
            -
                      @web_socket_ping_thread.kill
         | 
| 232 | 
            -
                    end
         | 
| 233 | 
            -
                  end
         | 
| 234 | 
            -
             | 
| 235 | 
            -
                  if @web_socket.present?
         | 
| 236 | 
            -
                    begin
         | 
| 237 | 
            -
                      @web_socket.close if @web_socket.open?
         | 
| 238 | 
            -
                    rescue
         | 
| 239 | 
            -
                      # Ignoring close errors
         | 
| 240 | 
            -
                    end
         | 
| 241 | 
            -
                  end
         | 
| 242 | 
            -
             | 
| 243 | 
            -
                  [
         | 
| 244 | 
            -
                    :@last_web_socket_pong_at,
         | 
| 245 | 
            -
                    :@web_socket_ping_interval,
         | 
| 246 | 
            -
                    :@web_socket_ping_thread,
         | 
| 247 | 
            -
                    :@web_socket_url,
         | 
| 248 | 
            -
                    :@web_socket,
         | 
| 249 | 
            -
                  ].each do |variable|
         | 
| 250 | 
            -
                    remove_instance_variable(variable) if instance_variable_defined?(variable)
         | 
| 251 | 
            -
                  end
         | 
| 252 | 
            -
                end
         | 
| 253 | 
            -
             | 
| 254 272 | 
             
                def find_rf_bridge_button!(uuid)
         | 
| 255 273 | 
             
                  rf_bridge_buttons.find { |button| button[:uuid] == uuid } || raise(Error.new("No such RF bridge button with UUID: #{uuid.inspect}"))
         | 
| 256 274 | 
             
                end
         | 
| @@ -272,7 +290,7 @@ module Ewelink | |
| 272 290 | 
             
                  method = method.to_s.upcase
         | 
| 273 291 | 
             
                  headers = (options[:headers] || {}).reverse_merge('Content-Type' => 'application/json')
         | 
| 274 292 | 
             
                  Ewelink.logger.debug(self.class.name) { "#{method} #{url}" }
         | 
| 275 | 
            -
                  response = HTTParty.send(method.downcase, url, options.merge(headers: headers).reverse_merge(timeout:  | 
| 293 | 
            +
                  response = HTTParty.send(method.downcase, url, options.merge(headers: headers).reverse_merge(timeout: REQUEST_TIMEOUT))
         | 
| 276 294 | 
             
                  raise(Error.new("#{method} #{url}: #{response.code}")) unless response.success?
         | 
| 277 295 | 
             
                  if response['error'] == 301 && response['region'].present?
         | 
| 278 296 | 
             
                    @region = response['region']
         | 
| @@ -286,15 +304,43 @@ module Ewelink | |
| 286 304 | 
             
                  raise Error.new(e)
         | 
| 287 305 | 
             
                end
         | 
| 288 306 |  | 
| 289 | 
            -
                def send_to_web_socket( | 
| 290 | 
            -
                   | 
| 291 | 
            -
             | 
| 292 | 
            -
             | 
| 307 | 
            +
                def send_to_web_socket(message)
         | 
| 308 | 
            +
                  web_socket.send(message)
         | 
| 309 | 
            +
                rescue => e
         | 
| 310 | 
            +
                  reload
         | 
| 311 | 
            +
                  raise Error.new(e)
         | 
| 312 | 
            +
                end
         | 
| 313 | 
            +
             | 
| 314 | 
            +
                def start_web_socket_authentication_check_thread
         | 
| 315 | 
            +
                  raise Error.new('WebSocket authentication check must only be started once') if @web_socket_authentication_check_thread.present?
         | 
| 316 | 
            +
             | 
| 317 | 
            +
                  @web_socket_authentication_check_thread = Thread.new do
         | 
| 318 | 
            +
                    loop do
         | 
| 319 | 
            +
                      Ewelink.logger.debug(self.class.name) { 'Checking if WebSocket is authenticated' }
         | 
| 320 | 
            +
                      begin
         | 
| 321 | 
            +
                        web_socket_wait_for(-> { web_socket_authenticated? }, initialize_web_socket: true) do
         | 
| 322 | 
            +
                          Ewelink.logger.debug(self.class.name) { 'WebSocket is authenticated' }
         | 
| 323 | 
            +
                        end
         | 
| 324 | 
            +
                      rescue => e
         | 
| 325 | 
            +
                        Ewelink.logger.error(self.class.name) { e }
         | 
| 326 | 
            +
                      end
         | 
| 327 | 
            +
                      sleep(WEB_SOCKET_CHECK_AUTHENTICATION_TIMEOUT)
         | 
| 328 | 
            +
                    end
         | 
| 329 | 
            +
                  end
         | 
| 330 | 
            +
                end
         | 
| 331 | 
            +
             | 
| 332 | 
            +
                def start_web_socket_ping_thread(interval)
         | 
| 333 | 
            +
                  @last_web_socket_pong_at = Time.now
         | 
| 334 | 
            +
                  @web_socket_ping_interval = interval
         | 
| 335 | 
            +
                  Ewelink.logger.debug(self.class.name) { "Creating thread for WebSocket ping every #{@web_socket_ping_interval} seconds" }
         | 
| 336 | 
            +
                  @web_socket_ping_thread = Thread.new do
         | 
| 337 | 
            +
                    loop do
         | 
| 338 | 
            +
                      break if Thread.current[:stop]
         | 
| 339 | 
            +
                      sleep(@web_socket_ping_interval)
         | 
| 340 | 
            +
                      Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
         | 
| 341 | 
            +
                      send_to_web_socket('ping')
         | 
| 342 | 
            +
                    end
         | 
| 293 343 | 
             
                  end
         | 
| 294 | 
            -
                  web_socket.send(data)
         | 
| 295 | 
            -
                rescue
         | 
| 296 | 
            -
                  dispose_web_socket
         | 
| 297 | 
            -
                  raise
         | 
| 298 344 | 
             
                end
         | 
| 299 345 |  | 
| 300 346 | 
             
                def synchronize(name, &block)
         | 
| @@ -302,96 +348,82 @@ module Ewelink | |
| 302 348 | 
             
                end
         | 
| 303 349 |  | 
| 304 350 | 
             
                def web_socket
         | 
| 351 | 
            +
                  if web_socket_outdated_ping?
         | 
| 352 | 
            +
                    Ewelink.logger.warn(self.class.name) { 'WebSocket ping is outdated' }
         | 
| 353 | 
            +
                    reload
         | 
| 354 | 
            +
                  end
         | 
| 355 | 
            +
             | 
| 305 356 | 
             
                  synchronize(:web_socket) do
         | 
| 306 | 
            -
                    @web_socket  | 
| 307 | 
            -
                      api = self
         | 
| 357 | 
            +
                    next @web_socket if @web_socket
         | 
| 308 358 |  | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 359 | 
            +
                    # Initializes caches before opening WebSocket: important in order to
         | 
| 360 | 
            +
                    # NOT cumulate requests Timeouts from #web_socket_wait_for.
         | 
| 361 | 
            +
                    api_keys
         | 
| 362 | 
            +
                    web_socket_url
         | 
| 311 363 |  | 
| 312 | 
            -
             | 
| 313 | 
            -
             | 
| 314 | 
            -
             | 
| 315 | 
            -
             | 
| 316 | 
            -
             | 
| 364 | 
            +
                    Ewelink.logger.debug(self.class.name) { "Opening WebSocket to #{web_socket_url.inspect}" }
         | 
| 365 | 
            +
             | 
| 366 | 
            +
                    @web_socket_thread = Thread.new do
         | 
| 367 | 
            +
                      EventMachine.run do
         | 
| 368 | 
            +
                        @web_socket = Faye::WebSocket::Client.new(web_socket_url)
         | 
| 369 | 
            +
             | 
| 370 | 
            +
                        @web_socket.on(:close) do |event|
         | 
| 371 | 
            +
                          Ewelink.logger.debug(self.class.name) { 'WebSocket closed' }
         | 
| 372 | 
            +
                          reload
         | 
| 317 373 | 
             
                        end
         | 
| 318 374 |  | 
| 319 | 
            -
                        web_socket.on(: | 
| 320 | 
            -
                           | 
| 321 | 
            -
             | 
| 322 | 
            -
             | 
| 323 | 
            -
                          end
         | 
| 375 | 
            +
                        @web_socket.on(:open) do |event|
         | 
| 376 | 
            +
                          Ewelink.logger.debug(self.class.name) { 'WebSocket opened' }
         | 
| 377 | 
            +
                          @last_web_socket_pong_at = Time.now
         | 
| 378 | 
            +
                          authenticate_web_socket_api_keys
         | 
| 324 379 | 
             
                        end
         | 
| 325 380 |  | 
| 326 | 
            -
                        web_socket.on(:message) do | | 
| 327 | 
            -
                           | 
| 328 | 
            -
                            if message.data == 'pong'
         | 
| 329 | 
            -
                              Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.data.inspect} message" }
         | 
| 330 | 
            -
                              @last_web_socket_pong_at = Time.now
         | 
| 331 | 
            -
                              next
         | 
| 332 | 
            -
                            end
         | 
| 381 | 
            +
                        @web_socket.on(:message) do |event|
         | 
| 382 | 
            +
                          message = event.data
         | 
| 333 383 |  | 
| 334 | 
            -
             | 
| 335 | 
            -
             | 
| 336 | 
            -
                             | 
| 337 | 
            -
             | 
| 338 | 
            -
             | 
| 339 | 
            -
                            end
         | 
| 384 | 
            +
                          if message == 'pong'
         | 
| 385 | 
            +
                            Ewelink.logger.debug(self.class.name) { "Received WebSocket #{message.inspect} message" }
         | 
| 386 | 
            +
                            @last_web_socket_pong_at = Time.now
         | 
| 387 | 
            +
                            next
         | 
| 388 | 
            +
                          end
         | 
| 340 389 |  | 
| 341 | 
            -
             | 
| 342 | 
            -
             | 
| 343 | 
            -
             | 
| 344 | 
            -
                             | 
| 390 | 
            +
                          begin
         | 
| 391 | 
            +
                            json = JSON.parse(message)
         | 
| 392 | 
            +
                          rescue => e
         | 
| 393 | 
            +
                            Ewelink.logger.error(self.class.name) { 'WebSocket JSON parse error' }
         | 
| 394 | 
            +
                            reload
         | 
| 395 | 
            +
                            next
         | 
| 396 | 
            +
                          end
         | 
| 345 397 |  | 
| 346 | 
            -
             | 
| 347 | 
            -
             | 
| 348 | 
            -
             | 
| 349 | 
            -
             | 
| 350 | 
            -
             | 
| 351 | 
            -
                              @web_socket_ping_thread = Thread.new do
         | 
| 352 | 
            -
                                loop do
         | 
| 353 | 
            -
                                  break if Thread.current[:stop]
         | 
| 354 | 
            -
                                  sleep(@web_socket_ping_interval)
         | 
| 355 | 
            -
                                  Ewelink.logger.debug(self.class.name) { 'Sending WebSocket ping' }
         | 
| 356 | 
            -
                                  send_to_web_socket('ping')
         | 
| 357 | 
            -
                                end
         | 
| 358 | 
            -
                              end
         | 
| 359 | 
            -
                            end
         | 
| 398 | 
            +
                          if json.key?('error') && json['error'] != 0
         | 
| 399 | 
            +
                            Ewelink.logger.error(self.class.name) { "WebSocket message error: #{message.inspect}" }
         | 
| 400 | 
            +
                            reload
         | 
| 401 | 
            +
                            next
         | 
| 402 | 
            +
                          end
         | 
| 360 403 |  | 
| 361 | 
            -
             | 
| 362 | 
            -
             | 
| 363 | 
            -
             | 
| 364 | 
            -
                            end
         | 
| 404 | 
            +
                          if !@web_socket_ping_thread && json.key?('config') && json['config']['hb'] == 1 && json['config']['hbInterval'].present?
         | 
| 405 | 
            +
                            start_web_socket_ping_thread(json['config']['hbInterval'] + 7)
         | 
| 406 | 
            +
                          end
         | 
| 365 407 |  | 
| 366 | 
            -
             | 
| 367 | 
            -
             | 
| 368 | 
            -
             | 
| 369 | 
            -
                            end
         | 
| 408 | 
            +
                          if json['apikey'].present? && !@web_socket_authenticated_api_keys.include?(json['apikey'])
         | 
| 409 | 
            +
                            @web_socket_authenticated_api_keys << json['apikey']
         | 
| 410 | 
            +
                            Ewelink.logger.debug(self.class.name) { "WebSocket successfully authenticated API key: #{json['apikey'].truncate(16).inspect}" }
         | 
| 370 411 | 
             
                          end
         | 
| 371 | 
            -
                        end
         | 
| 372 412 |  | 
| 373 | 
            -
             | 
| 374 | 
            -
             | 
| 375 | 
            -
                             | 
| 376 | 
            -
             | 
| 377 | 
            -
                               | 
| 378 | 
            -
                                'action' => 'userOnline',
         | 
| 379 | 
            -
                                'apikey' => api_key,
         | 
| 380 | 
            -
                                'appid' => APP_ID,
         | 
| 381 | 
            -
                                'at' => authentication_token,
         | 
| 382 | 
            -
                                'nonce' => nonce,
         | 
| 383 | 
            -
                                'sequence' => web_socket_sequence,
         | 
| 384 | 
            -
                                'ts' => Time.now.to_i,
         | 
| 385 | 
            -
                                'userAgent' => 'app',
         | 
| 386 | 
            -
                                'version' => VERSION,
         | 
| 387 | 
            -
                              }
         | 
| 388 | 
            -
                              Ewelink.logger.debug(self.class.name) { "Authenticating WebSocket API key: #{api_key.truncate(16).inspect}" }
         | 
| 389 | 
            -
                              send_to_web_socket(JSON.generate(params))
         | 
| 413 | 
            +
                          if json['deviceid'].present? && json['params'].is_a?(Hash) && json['params']['switch'].present?
         | 
| 414 | 
            +
                            switch = switches.find { |switch| switch[:device_id] == json['deviceid'] }
         | 
| 415 | 
            +
                            if switch.present?
         | 
| 416 | 
            +
                              @web_socket_switches_statuses[switch[:uuid]] = json['params']['switch']
         | 
| 417 | 
            +
                              Ewelink.logger.debug(self.class.name) { "Switch #{switch[:uuid].inspect} is #{@web_socket_switches_statuses[switch[:uuid]]}" }
         | 
| 390 418 | 
             
                            end
         | 
| 391 419 | 
             
                          end
         | 
| 392 420 | 
             
                        end
         | 
| 393 421 | 
             
                      end
         | 
| 394 422 | 
             
                    end
         | 
| 423 | 
            +
             | 
| 424 | 
            +
                    web_socket_wait_for(-> { @web_socket.present? }) do
         | 
| 425 | 
            +
                      @web_socket
         | 
| 426 | 
            +
                    end
         | 
| 395 427 | 
             
                  end
         | 
| 396 428 | 
             
                end
         | 
| 397 429 |  | 
| @@ -426,16 +458,18 @@ module Ewelink | |
| 426 458 | 
             
                  end
         | 
| 427 459 | 
             
                end
         | 
| 428 460 |  | 
| 429 | 
            -
                def web_socket_wait_for(condition, &block)
         | 
| 430 | 
            -
                  web_socket  | 
| 431 | 
            -
                   | 
| 432 | 
            -
                     | 
| 433 | 
            -
                       | 
| 434 | 
            -
                         | 
| 435 | 
            -
                        return true
         | 
| 461 | 
            +
                def web_socket_wait_for(condition, initialize_web_socket: false, &block)
         | 
| 462 | 
            +
                  web_socket if initialize_web_socket
         | 
| 463 | 
            +
                  begin
         | 
| 464 | 
            +
                    Timeout.timeout(REQUEST_TIMEOUT) do
         | 
| 465 | 
            +
                      while !condition.call
         | 
| 466 | 
            +
                        sleep(WEB_SOCKET_WAIT_INTERVAL)
         | 
| 436 467 | 
             
                      end
         | 
| 437 | 
            -
                       | 
| 468 | 
            +
                      block_given? ? yield : true
         | 
| 438 469 | 
             
                    end
         | 
| 470 | 
            +
                  rescue => e
         | 
| 471 | 
            +
                    reload
         | 
| 472 | 
            +
                    raise Error.new(e)
         | 
| 439 473 | 
             
                  end
         | 
| 440 474 | 
             
                end
         | 
| 441 475 |  | 
    
        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: 2. | 
| 4 | 
            +
              version: 2.3.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: 2020-09- | 
| 11 | 
            +
            date: 2020-09-09 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: activesupport
         | 
| @@ -31,45 +31,45 @@ dependencies: | |
| 31 31 | 
             
                  - !ruby/object:Gem::Version
         | 
| 32 32 | 
             
                    version: 7.0.0
         | 
| 33 33 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 34 | 
            -
              name:  | 
| 34 | 
            +
              name: faye-websocket
         | 
| 35 35 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 36 36 | 
             
                requirements:
         | 
| 37 37 | 
             
                - - ">="
         | 
| 38 38 | 
             
                  - !ruby/object:Gem::Version
         | 
| 39 | 
            -
                    version: 0. | 
| 39 | 
            +
                    version: 0.11.0
         | 
| 40 40 | 
             
                - - "<"
         | 
| 41 41 | 
             
                  - !ruby/object:Gem::Version
         | 
| 42 | 
            -
                    version: 0. | 
| 42 | 
            +
                    version: 0.12.0
         | 
| 43 43 | 
             
              type: :runtime
         | 
| 44 44 | 
             
              prerelease: false
         | 
| 45 45 | 
             
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 46 46 | 
             
                requirements:
         | 
| 47 47 | 
             
                - - ">="
         | 
| 48 48 | 
             
                  - !ruby/object:Gem::Version
         | 
| 49 | 
            -
                    version: 0. | 
| 49 | 
            +
                    version: 0.11.0
         | 
| 50 50 | 
             
                - - "<"
         | 
| 51 51 | 
             
                  - !ruby/object:Gem::Version
         | 
| 52 | 
            -
                    version: 0. | 
| 52 | 
            +
                    version: 0.12.0
         | 
| 53 53 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 54 | 
            -
              name:  | 
| 54 | 
            +
              name: httparty
         | 
| 55 55 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| 56 56 | 
             
                requirements:
         | 
| 57 57 | 
             
                - - ">="
         | 
| 58 58 | 
             
                  - !ruby/object:Gem::Version
         | 
| 59 | 
            -
                    version: 0. | 
| 59 | 
            +
                    version: 0.18.0
         | 
| 60 60 | 
             
                - - "<"
         | 
| 61 61 | 
             
                  - !ruby/object:Gem::Version
         | 
| 62 | 
            -
                    version: 0. | 
| 62 | 
            +
                    version: 0.19.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.18.0
         | 
| 70 70 | 
             
                - - "<"
         | 
| 71 71 | 
             
                  - !ruby/object:Gem::Version
         | 
| 72 | 
            -
                    version: 0. | 
| 72 | 
            +
                    version: 0.19.0
         | 
| 73 73 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 74 74 | 
             
              name: byebug
         | 
| 75 75 | 
             
              requirement: !ruby/object:Gem::Requirement
         |