tp_link_smartplug 0.0.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f53d7ea800a82c44bc660a56066f5e7ee81d2ff2f786ddcb668c8d3267b8960c
4
- data.tar.gz: af92b7cd428a8a3e1ceafb1868b3651153b5b192d051f0266174b14d4bbfba67
3
+ metadata.gz: 41a05841cdc6ffdd697f903abc1d620debf999f2a9a6c5f248edc9ec93635e30
4
+ data.tar.gz: c3f77299aaeb6b90506f04730467c70889745d85e47148ba738ccc82de484cbd
5
5
  SHA512:
6
- metadata.gz: f406f2bc6b7a7ebb0d0e8434146593a6080f4a2840d4420f96b3fd9454f31645adbfb8e248c5dab75e35c54ed9e72129b0e2355b508949271f1aa75371eb7269
7
- data.tar.gz: 1b21d836bac38def6558452bd6fa6bbb9a4c0fe5023f70abb07c91bdd6921d5f4bb6db7db44d52ecf47d07d7fd417c0a5d8b71e10ccf5832becf864abb90844d
6
+ metadata.gz: eac7e52a043aebdeeb595aba15df19be7923d2da40a6d214bb41955f4baf4da1e2853e935cd7ade3b410981bd8698024f9598a923b20a5c0311ed2398dbced01
7
+ data.tar.gz: 74f0ba6ff80393ee43560a76ec6cab25d94fb1b8b5b3b078884be64b9db274e657053f2c90007df89aa2bde7e849b63a049e4628c91ff4bb90cf1e29b59c7d23
@@ -1,8 +1,11 @@
1
- # frozen_string_literal: true
1
+ require_relative 'tp_link_smartplug/_base'
2
+ require_relative 'tp_link_smartplug/command'
3
+ require_relative 'tp_link_smartplug/device'
4
+ require_relative 'tp_link_smartplug/helpers'
5
+ require_relative 'tp_link_smartplug/message'
6
+ require_relative 'tp_link_smartplug/version'
2
7
 
3
- require 'tp_link_smartplug/command'
4
- require 'tp_link_smartplug/device'
5
- require 'tp_link_smartplug/helpers'
6
- require 'tp_link_smartplug/message_helpers'
7
- require 'tp_link_smartplug/message'
8
- require 'tp_link_smartplug/version'
8
+ # Top level module
9
+ #
10
+ # @author Ben Hughes
11
+ module TpLinkSmartplug; end
@@ -0,0 +1,15 @@
1
+ require_relative 'helpers'
2
+
3
+ module TpLinkSmartplug
4
+ # Base plug class.
5
+ #
6
+ # @author Ben Hughes
7
+ class Base
8
+ include TpLinkSmartplug::Helpers
9
+ end
10
+
11
+ # Base plug error class.
12
+ #
13
+ # @author Ben Hughes
14
+ class BaseError < StandardError; end
15
+ end
@@ -1,18 +1,29 @@
1
- # frozen_string_literal: true
2
-
3
1
  module TpLinkSmartplug
2
+ # Module containing the static commands for the plug
4
3
  module Command
5
- INFO = '{"system":{"get_sysinfo":{}}}'
6
- ON = '{"system":{"set_relay_state":{"state":1}}}'
7
- OFF = '{"system":{"set_relay_state":{"state":0}}}'
8
- CLOUDINFO = '{"cnCloud":{"get_info":{}}}'
9
- WLANSCAN = '{"netif":{"get_scaninfo":{"refresh":0}}}'
10
- TIME = '{"time":{"get_time":{}}}'
11
- SCHEDULE = '{"schedule":{"get_rules":{}}}'
12
- COUNTDOWN = '{"count_down":{"get_rules":{}}}'
13
- ANTITHEFT = '{"anti_theft":{"get_rules":{}}}'
14
- REBOOT = '{"system":{"reboot":{"delay":1}}}'
15
- RESET = '{"system":{"reset":{"delay":1}}}'
16
- ENERGY = '{"emeter":{"get_realtime":{}}}'
4
+ # Plug information command
5
+ INFO = '{"system":{"get_sysinfo":{}}}'.freeze
6
+ # Plug output on command
7
+ ON = '{"system":{"set_relay_state":{"state":1}}}'.freeze
8
+ # Plug output off command
9
+ OFF = '{"system":{"set_relay_state":{"state":0}}}'.freeze
10
+ # Plug cloud info command
11
+ CLOUDINFO = '{"cnCloud":{"get_info":{}}}'.freeze
12
+ # Plug WLAN SSID scan command
13
+ WLANSCAN = '{"netif":{"get_scaninfo":{"refresh":0}}}'.freeze
14
+ # Plug time command
15
+ TIME = '{"time":{"get_time":{}}}'.freeze
16
+ # Plug schedule command
17
+ SCHEDULE = '{"schedule":{"get_rules":{}}}'.freeze
18
+ # Plug countdown command
19
+ COUNTDOWN = '{"count_down":{"get_rules":{}}}'.freeze
20
+ # Plug antitheft command
21
+ ANTITHEFT = '{"anti_theft":{"get_rules":{}}}'.freeze
22
+ # Plug reboot command
23
+ REBOOT = '{"system":{"reboot":{"delay":1}}}'.freeze
24
+ # Plug reset command
25
+ RESET = '{"system":{"reset":{"delay":1}}}'.freeze
26
+ # Plug energy command
27
+ ENERGY = '{"emeter":{"get_realtime":{}}}'.freeze
17
28
  end
18
29
  end
@@ -1,27 +1,167 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'socket'
4
2
  require 'ipaddr'
5
3
  require 'json'
6
- require 'tp_link_smartplug/message'
4
+ require_relative './message'
7
5
 
8
6
  module TpLinkSmartplug
9
- # Provides an interface to a plug
10
- class Device
7
+ # Provides an object to represent to a plug
8
+ #
9
+ # @author Ben Hughes
10
+ # @attr [IPAddr] address IP address of the plug
11
+ # @attr [Integer] port Port to connect to on the plug
12
+ # @attr [Integer] timeout Timeout value for connecting and sending commands to the plug
13
+ # @attr [true, false] debug Control debug logging
14
+ class Device < TpLinkSmartplug::Base
15
+ include TpLinkSmartplug::Helpers
11
16
  include TpLinkSmartplug::Message
12
17
 
13
- attr_accessor :address
14
- attr_accessor :timeout
15
- attr_accessor :port
16
- attr_accessor :debug
18
+ attr_accessor :address, :port, :timeout, :poll_auto_close, :debug
17
19
 
18
20
  def initialize(address:, port: 9999)
21
+ super
22
+
19
23
  @address = IPAddr.new(address, Socket::AF_INET)
20
24
  @port = port
21
25
  @timeout = 3
22
26
  @debug = false
27
+ @sockaddr = Addrinfo.getaddrinfo(@address.to_s, @port, Socket::PF_INET, :STREAM, 6).first.to_sockaddr
28
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
29
+ @poll_auto_close = true
30
+ end
31
+
32
+ # Open connection to plug
33
+ #
34
+ def connect
35
+ debug_message("Connecting to #{@address} port #{@port}") if @debug
36
+ debug_message("Connecting, socket state: #{@socket.closed? ? 'closed' : 'open'}") if @debug
37
+
38
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) if closed?
39
+
40
+ begin
41
+ @socket.connect_nonblock(@sockaddr)
42
+ debug_message('Connected') if @debug
43
+ rescue IO::WaitWritable
44
+ if IO.select(nil, [@socket], nil, timeout)
45
+ begin
46
+ @socket.connect_nonblock(@sockaddr)
47
+ rescue Errno::EISCONN
48
+ debug_message('Connected') if @debug
49
+ rescue Errno::ECONNREFUSED
50
+ @socket.close
51
+ raise TpLinkSmartplug::DeviceError, "Connection refused connecting to address #{@address}, port #{@port}!"
52
+ rescue StandardError => e
53
+ disconnect
54
+ debug_message("Unexpected exception encountered. Error: #{e}") if @debug
55
+ raise
56
+ end
57
+ else
58
+ @socket.close
59
+ raise TpLinkSmartplug::DeviceError, "Connection timeout connecting to address #{@address}, port #{@port}."
60
+ end
61
+ rescue Errno::EISCONN
62
+ debug_message('Connected') if @debug
63
+ rescue Errno::EINPROGRESS
64
+ debug_message('Connection in progress') if @debug
65
+ retry
66
+ rescue Errno::ECONNREFUSED
67
+ raise TpLinkSmartplug::DeviceError, "Connection refused connecting to address #{@address}, port #{@port}."
68
+ end
69
+ end
70
+
71
+ # Close connection to plug
72
+ #
73
+ def disconnect
74
+ @socket.close unless @socket.closed?
75
+ end
76
+
77
+ alias_method :close, :disconnect
78
+
79
+ # Return connection state open
80
+ #
81
+ # @return [True, False]
82
+ def open?
83
+ !@socket.closed?
84
+ end
85
+
86
+ # Return connection state closed
87
+ #
88
+ # @return [True, False]
89
+ def closed?
90
+ @socket.closed?
23
91
  end
24
92
 
93
+ # Polls plug with a command
94
+ #
95
+ # @param command [String] the command to send to the plug
96
+ # @return [Hash] the output from the plug command
97
+ def poll(command:)
98
+ connect
99
+
100
+ begin
101
+ debug_message("Sending: #{decrypt(encrypt(command)[4..(command.length + 4)])}") if @debug
102
+ @socket.write_nonblock(encrypt(command))
103
+ rescue IO::WaitWritable
104
+ IO.select(nil, [@socket])
105
+ retry
106
+ end
107
+
108
+ begin
109
+ data = @socket.recv_nonblock(2048)
110
+ rescue IO::WaitReadable
111
+ IO.select([@socket])
112
+ retry
113
+ end
114
+
115
+ if @poll_auto_close && !closed?
116
+ disconnect
117
+ raise 'Error occured during disconnect' unless closed?
118
+ end
119
+
120
+ raise 'No data received' if nil_or_empty?(data)
121
+
122
+ debug_message("Received Raw: #{data}") if @debug
123
+ data = decrypt(data[4..data.length])
124
+ debug_message("Received Decrypted: #{data}") if @debug
125
+
126
+ data
127
+ end
128
+
129
+ # @!method info
130
+ # Return plug information
131
+ # @return [Hash]
132
+ # @!method on
133
+ # Turn plug output on
134
+ # @return [Hash]
135
+ # @!method off
136
+ # Turn plug output off
137
+ # @return [Hash]
138
+ # @!method cloudinfo
139
+ # Return plug cloud account configuration
140
+ # @return [Hash]
141
+ # @!method wlanscan
142
+ # Perform a scan for wireless SSIDs
143
+ # @return [Hash]
144
+ # @!method time
145
+ # Return system time from the plug
146
+ # @return [Hash]
147
+ # @!method schedule
148
+ # Return schedule configured on the plug
149
+ # @return [Hash]
150
+ # @!method countdown
151
+ # Return countdown configured on the plug
152
+ # @return [Hash]
153
+ # @!method antitheft
154
+ # Unsure
155
+ # @return [Hash]
156
+ # @!method reboot
157
+ # Reboot plug
158
+ # @return [Hash]
159
+ # @!method resry
160
+ # Reset plug
161
+ # @return [Hash]
162
+ # @!method energy
163
+ # Return plug energy data
164
+ # @return [Hash]
25
165
  [
26
166
  :info,
27
167
  :on,
@@ -34,11 +174,13 @@ module TpLinkSmartplug
34
174
  :antitheft,
35
175
  :reboot,
36
176
  :reset,
37
- :energy
177
+ :energy,
38
178
  ].each do |method|
39
179
  define_method method do
40
- JSON.parse(poll(address: @address, port: @port, command: TpLinkSmartplug::Command.const_get(method.upcase), timeout: @timeout, debug: @debug))
180
+ JSON.parse(poll(command: TpLinkSmartplug::Command.const_get(method.upcase)))
41
181
  end
42
182
  end
43
183
  end
184
+
185
+ class DeviceError < TpLinkSmartplug::BaseError; end
44
186
  end
@@ -1,13 +1,23 @@
1
- # frozen_string_literal: true
2
-
3
1
  require 'time'
4
2
 
5
3
  module TpLinkSmartplug
6
4
  # Generic helper methods
7
5
  module Helpers
6
+ # Formats a message for output as a debug log
7
+ #
8
+ # @param string [String] the message to be formatted for debug output
8
9
  def debug_message(string)
9
- caller_method = caller_locations(1..1).label
10
- Time.now.strftime('%Y-%m-%d %H:%M:%S: ').concat("#{caller_method}: ").concat(string)
10
+ caller_method = caller_locations(1..1).first.label
11
+ $stdout.puts(Time.now.strftime('%Y-%m-%d %H:%M:%S: ').concat("#{caller_method}: ").concat(string))
12
+ end
13
+
14
+ # Tests a variable for nil or empty status
15
+ #
16
+ # @param v the variable to be tested
17
+ def nil_or_empty?(value)
18
+ return true if value.nil? || (value.respond_to?(:empty?) && value.empty?)
19
+
20
+ false
11
21
  end
12
22
  end
13
23
  end
@@ -1,68 +1,31 @@
1
- # frozen_string_literal: true
2
-
3
- require 'socket'
4
- require 'tp_link_smartplug/helpers'
5
- require 'tp_link_smartplug/message_helpers'
6
-
7
1
  module TpLinkSmartplug
8
- # Provides methods to send and receive messages from plugs
2
+ # Helper methods for plug communication messages
9
3
  module Message
10
- include TpLinkSmartplug::Helpers
11
- include TpLinkSmartplug::MessageHelpers
12
-
13
- def poll(address:, port:, command:, timeout: 3, debug: false)
14
- socket = connect(address: address, port: port, timeout: timeout, debug: debug)
15
- STDOUT.puts(debug_message("Sending: #{decrypt(encrypt(command)[4..command.length])}")) if debug
16
-
17
- begin
18
- socket.write_nonblock(encrypt(command))
19
- data = socket.recv_nonblock(2048)
20
- rescue IO::WaitReadable, IO::WaitWritable
21
- IO.select([socket], [socket])
22
- retry
23
- ensure
24
- disconnect(socket: socket)
4
+ # Encrypts a message to send to the smart plug
5
+ #
6
+ # @param string [String] the message to be encrypted
7
+ def encrypt(string)
8
+ key = 171
9
+ result = [string.length].pack('N')
10
+ string.each_char do |char|
11
+ key = a = key ^ char.ord
12
+ result.concat(a.chr)
25
13
  end
26
-
27
- raise 'Error occured during disconnect' unless socket.closed?
28
- raise 'No data received' if data.nil? || data.empty?
29
-
30
- STDOUT.puts(debug_message("Received Raw: #{data}")) if debug
31
- data = decrypt(data[4..data.length])
32
- STDOUT.puts(debug_message("Received: #{data}")) if debug
33
-
34
- data
14
+ result
35
15
  end
36
16
 
37
- private
38
-
39
- def connect(address:, port:, timeout: 3, debug: false)
40
- STDOUT.puts(debug_message("Connecting to #{address} port #{port}")) if debug
41
-
42
- Socket.new(Socket::AF_INET, Socket::SOCK_STREAM).tap do |socket|
43
- sockaddr = Addrinfo.getaddrinfo(address.to_s, port, Socket::PF_INET, :STREAM, 6).first.to_sockaddr
44
- STDOUT.puts(debug_message("Connecting, socket closed: #{socket.closed?}")) if debug
45
- socket.connect_nonblock(sockaddr)
46
- rescue IO::WaitWritable
47
- if IO.select(nil, [socket], nil, timeout)
48
- begin
49
- socket.connect_nonblock(sockaddr)
50
- rescue Errno::EISCONN
51
- STDOUT.puts(debug_message('Connected')) if debug
52
- rescue StandardError => e
53
- socket.close
54
- STDOUT.puts(debug_message('Unexpected exception encountered.')) if debug
55
- raise e
56
- end
57
- else
58
- socket.close
59
- raise "Connection timeout connecting to address #{address}, port #{port}."
60
- end
17
+ # Decrypts a message received from the smart plug
18
+ #
19
+ # @param string [String] the message to be decrypted
20
+ def decrypt(string)
21
+ key = 171
22
+ result = ''
23
+ string.each_char do |char|
24
+ a = key ^ char.ord
25
+ key = char.ord
26
+ result.concat(a.chr)
61
27
  end
62
- end
63
-
64
- def disconnect(socket:)
65
- socket.close unless socket.closed? || socket.nil?
28
+ result
66
29
  end
67
30
  end
68
31
  end
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
1
+ # @author Ben Hughes
3
2
  module TpLinkSmartplug
4
- VERSION = '0.0.1'
3
+ # Gem version
4
+ VERSION = '0.2.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tp_link_smartplug
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Hughes
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-07 00:00:00.000000000 Z
11
+ date: 2021-01-24 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Control and retrieve data from a TP-Link HS100/110 (Metered) Smartplug
14
14
  email: bmhughes@bmhughes.co.uk
@@ -17,11 +17,11 @@ extensions: []
17
17
  extra_rdoc_files: []
18
18
  files:
19
19
  - "./lib/tp_link_smartplug.rb"
20
+ - "./lib/tp_link_smartplug/_base.rb"
20
21
  - "./lib/tp_link_smartplug/command.rb"
21
22
  - "./lib/tp_link_smartplug/device.rb"
22
23
  - "./lib/tp_link_smartplug/helpers.rb"
23
24
  - "./lib/tp_link_smartplug/message.rb"
24
- - "./lib/tp_link_smartplug/message_helpers.rb"
25
25
  - "./lib/tp_link_smartplug/version.rb"
26
26
  homepage: https://github.com/bmhughes/tp_link_smartplug
27
27
  licenses:
@@ -42,8 +42,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
42
42
  - !ruby/object:Gem::Version
43
43
  version: '0'
44
44
  requirements: []
45
- rubyforge_project:
46
- rubygems_version: 2.7.7
45
+ rubygems_version: 3.1.4
47
46
  signing_key:
48
47
  specification_version: 4
49
48
  summary: TP-Link HS100/110 Smart Plug interaction library
@@ -1,31 +0,0 @@
1
- module TpLinkSmartplug
2
- # Helper methods for plug communication messages
3
- module MessageHelpers
4
- # Encrypts a message to send to the smart plug
5
- #
6
- # @string format [String] the message to be encrypted
7
- def encrypt(string)
8
- key = 171
9
- result = [string.length].pack('N')
10
- string.each_char do |char|
11
- key = a = key ^ char.ord
12
- result.concat(a.chr)
13
- end
14
- result
15
- end
16
-
17
- # Decrypts a message received from the smart plug
18
- #
19
- # @string format [String] the message to be decrypted
20
- def decrypt(string)
21
- key = 171
22
- result = ''
23
- string.each_char do |char|
24
- a = key ^ char.ord
25
- key = char.ord
26
- result.concat(a.chr)
27
- end
28
- result
29
- end
30
- end
31
- end