tp_link_smartplug 0.0.3 → 0.1.0.alpha1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f53d7ea800a82c44bc660a56066f5e7ee81d2ff2f786ddcb668c8d3267b8960c
4
- data.tar.gz: af92b7cd428a8a3e1ceafb1868b3651153b5b192d051f0266174b14d4bbfba67
3
+ metadata.gz: 23a1848523acc948dd653d39fa3b9cec937bdba9d2a0ad6970308bac4af64d8d
4
+ data.tar.gz: dd4d4a0fda92aeabfdc7adfb5f751f1b957438b168e5c96efaf4950d69e1bb3d
5
5
  SHA512:
6
- metadata.gz: f406f2bc6b7a7ebb0d0e8434146593a6080f4a2840d4420f96b3fd9454f31645adbfb8e248c5dab75e35c54ed9e72129b0e2355b508949271f1aa75371eb7269
7
- data.tar.gz: 1b21d836bac38def6558452bd6fa6bbb9a4c0fe5023f70abb07c91bdd6921d5f4bb6db7db44d52ecf47d07d7fd417c0a5d8b71e10ccf5832becf864abb90844d
6
+ metadata.gz: e2259524ac344b200b19d4824178dfe18b5b7d92c8fbead1b3a4c40f3c073a3078b529f73bf035c4e1505f9827c9323f2f5c8e98f8076270783ed701d1ce6058
7
+ data.tar.gz: 24ae9be641e069804c884abd18594ee8a2c355370033c06c36091dff990c0846325cc9878c1e08550658e88fabebe42e98ce60b0edf82cc57f71006b0b5d1753
@@ -1,18 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module TpLinkSmartplug
4
+ # Module containing the static commands for the plug
4
5
  module Command
6
+ # Plug information command
5
7
  INFO = '{"system":{"get_sysinfo":{}}}'
8
+ # Plug output on command
6
9
  ON = '{"system":{"set_relay_state":{"state":1}}}'
10
+ # Plug output off command
7
11
  OFF = '{"system":{"set_relay_state":{"state":0}}}'
12
+ # Plug cloud info command
8
13
  CLOUDINFO = '{"cnCloud":{"get_info":{}}}'
14
+ # Plug WLAN SSID scan command
9
15
  WLANSCAN = '{"netif":{"get_scaninfo":{"refresh":0}}}'
16
+ # Plug time command
10
17
  TIME = '{"time":{"get_time":{}}}'
18
+ # Plug schedule command
11
19
  SCHEDULE = '{"schedule":{"get_rules":{}}}'
20
+ # Plug countdown command
12
21
  COUNTDOWN = '{"count_down":{"get_rules":{}}}'
22
+ # Plug antitheft command
13
23
  ANTITHEFT = '{"anti_theft":{"get_rules":{}}}'
24
+ # Plug reboot command
14
25
  REBOOT = '{"system":{"reboot":{"delay":1}}}'
26
+ # Plug reset command
15
27
  RESET = '{"system":{"reset":{"delay":1}}}'
28
+ # Plug energy command
16
29
  ENERGY = '{"emeter":{"get_realtime":{}}}'
17
30
  end
18
31
  end
@@ -3,16 +3,24 @@
3
3
  require 'socket'
4
4
  require 'ipaddr'
5
5
  require 'json'
6
+ require 'tp_link_smartplug/helpers'
6
7
  require 'tp_link_smartplug/message'
7
8
 
8
9
  module TpLinkSmartplug
9
- # Provides an interface to a plug
10
+ # Provides an object to represent to a plug
11
+ #
12
+ # @author Ben Hughes
13
+ # @attr [IPAddr] address IP address of the plug
14
+ # @attr [Integer] port Port to connect to on the plug
15
+ # @attr [Integer] timeout Timeout value for connecting and sending commands to the plug
16
+ # @attr [true, false] debug Control debug logging
10
17
  class Device
18
+ include TpLinkSmartplug::Helpers
11
19
  include TpLinkSmartplug::Message
12
20
 
13
21
  attr_accessor :address
14
- attr_accessor :timeout
15
22
  attr_accessor :port
23
+ attr_accessor :timeout
16
24
  attr_accessor :debug
17
25
 
18
26
  def initialize(address:, port: 9999)
@@ -20,8 +28,130 @@ module TpLinkSmartplug
20
28
  @port = port
21
29
  @timeout = 3
22
30
  @debug = false
31
+ @sockaddr = Addrinfo.getaddrinfo(address.to_s, port, Socket::PF_INET, :STREAM, 6).first.to_sockaddr
32
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
33
+ end
34
+
35
+ # Open connection to plug
36
+ #
37
+ def connect
38
+ debug_message("Connecting to #{@address} port #{@port}") if @debug
39
+ debug_message("Connecting, socket state: #{@socket.closed? ? 'closed' : 'open'}") if @debug
40
+
41
+ @socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) if closed?
42
+
43
+ begin
44
+ @socket.connect_nonblock(@sockaddr)
45
+ rescue IO::WaitWritable
46
+ if IO.select(nil, [@socket], nil, timeout)
47
+ begin
48
+ @socket.connect_nonblock(@sockaddr)
49
+ rescue Errno::EISCONN
50
+ debug_message('Connected') if debug
51
+ rescue StandardError => e
52
+ disconnect
53
+ debug_message('Unexpected exception encountered.') if debug
54
+ raise e
55
+ end
56
+ else
57
+ @socket.close
58
+ raise "Connection timeout connecting to address #{@address}, port #{@port}."
59
+ end
60
+ rescue Errno::EISCONN
61
+ debug_message('Connected') if debug
62
+ end
63
+ end
64
+
65
+ # Close connection to plug
66
+ #
67
+ def disconnect
68
+ @socket.close unless @socket.closed?
69
+ end
70
+
71
+ # Return connection state
72
+ #
73
+ # @return [True, False]
74
+ def open?
75
+ !@socket.closed?
76
+ end
77
+
78
+ # Return connection state
79
+ #
80
+ # @return [True, False]
81
+ def closed?
82
+ @socket.closed?
83
+ end
84
+
85
+ # Polls a plug with a command
86
+ #
87
+ # @param command [String] the command to send to the plug
88
+ # @return [Hash] the output from the plug command
89
+ def poll(command:)
90
+ connect
91
+
92
+ begin
93
+ debug_message("Sending: #{decrypt(encrypt(command)[4..(command.length + 4)])}") if @debug
94
+ @socket.write_nonblock(encrypt(command))
95
+ rescue IO::WaitWritable
96
+ IO.select(nil, [@socket])
97
+ retry
98
+ end
99
+
100
+ begin
101
+ data = @socket.recv_nonblock(2048)
102
+ rescue IO::WaitReadable
103
+ IO.select([@socket])
104
+ retry
105
+ ensure
106
+ disconnect unless closed?
107
+ end
108
+
109
+ raise 'Error occured during disconnect' unless closed?
110
+ raise 'No data received' if data.nil? || data.empty?
111
+
112
+ debug_message("Received Raw: #{data}") if debug
113
+ data = decrypt(data[4..data.length])
114
+ debug_message("Received Decrypted: #{data}") if debug
115
+
116
+ data
23
117
  end
24
118
 
119
+ # @!method info
120
+ # Return plug information
121
+ # @return [Hash]
122
+ # @!method on
123
+ # Turn plug output on
124
+ # @return [Hash]
125
+ # @!method off
126
+ # Turn plug output off
127
+ # @return [Hash]
128
+ # @!method cloudinfo
129
+ # Return plug cloud account configuration
130
+ # @return [Hash]
131
+ # @!method wlanscan
132
+ # Perform a scan for wireless SSIDs
133
+ # @return [Hash]
134
+ # @!method time
135
+ # Return system time from the plug
136
+ # @return [Hash]
137
+ # @!method schedule
138
+ # Return schedule configured on the plug
139
+ # @return [Hash]
140
+ # @!method countdown
141
+ # Return countdown configured on the plug
142
+ # @return [Hash]
143
+ # @!method antitheft
144
+ # Unsure
145
+ # @return [Hash]
146
+ # @!method reboot
147
+ # Reboot plug
148
+ # @return [Hash]
149
+ # @!method resry
150
+ # Reset plug
151
+ # @return [Hash]
152
+ # @!method energy
153
+ # Return plug energy data
154
+ # @return [Hash]
25
155
  [
26
156
  :info,
27
157
  :on,
@@ -37,7 +167,7 @@ module TpLinkSmartplug
37
167
  :energy
38
168
  ].each do |method|
39
169
  define_method method do
40
- JSON.parse(poll(address: @address, port: @port, command: TpLinkSmartplug::Command.const_get(method.upcase), timeout: @timeout, debug: @debug))
170
+ JSON.parse(poll(command: TpLinkSmartplug::Command.const_get(method.upcase)))
41
171
  end
42
172
  end
43
173
  end
@@ -5,9 +5,12 @@ require 'time'
5
5
  module TpLinkSmartplug
6
6
  # Generic helper methods
7
7
  module Helpers
8
+ # Formats a message for output as a debug log
9
+ #
10
+ # @param string [String] the message to be formatted for debug output
8
11
  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)
12
+ caller_method = caller_locations(1..1).first.label
13
+ STDOUT.puts(Time.now.strftime('%Y-%m-%d %H:%M:%S: ').concat("#{caller_method}: ").concat(string))
11
14
  end
12
15
  end
13
16
  end
@@ -1,68 +1,33 @@
1
- # frozen_string_literal: true
2
-
3
- require 'socket'
4
- require 'tp_link_smartplug/helpers'
5
- require 'tp_link_smartplug/message_helpers'
1
+ # frozen_string_literal: false
6
2
 
7
3
  module TpLinkSmartplug
8
- # Provides methods to send and receive messages from plugs
4
+ # Helper methods for plug communication messages
9
5
  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)
6
+ # Encrypts a message to send to the smart plug
7
+ #
8
+ # @param string [String] the message to be encrypted
9
+ def encrypt(string)
10
+ key = 171
11
+ result = [string.length].pack('N')
12
+ string.each_char do |char|
13
+ key = a = key ^ char.ord
14
+ result.concat(a.chr)
25
15
  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
16
+ result
35
17
  end
36
18
 
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
19
+ # Decrypts a message received from the smart plug
20
+ #
21
+ # @param string [String] the message to be decrypted
22
+ def decrypt(string)
23
+ key = 171
24
+ result = ''
25
+ string.each_char do |char|
26
+ a = key ^ char.ord
27
+ key = char.ord
28
+ result.concat(a.chr)
61
29
  end
62
- end
63
-
64
- def disconnect(socket:)
65
- socket.close unless socket.closed? || socket.nil?
30
+ result
66
31
  end
67
32
  end
68
33
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # @author Ben Hughes
3
4
  module TpLinkSmartplug
5
+ # Gem version
4
6
  VERSION = '0.0.1'
5
7
  end
@@ -3,6 +3,5 @@
3
3
  require 'tp_link_smartplug/command'
4
4
  require 'tp_link_smartplug/device'
5
5
  require 'tp_link_smartplug/helpers'
6
- require 'tp_link_smartplug/message_helpers'
7
6
  require 'tp_link_smartplug/message'
8
7
  require 'tp_link_smartplug/version'
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.1.0.alpha1
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: 2019-06-08 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
@@ -21,7 +21,6 @@ files:
21
21
  - "./lib/tp_link_smartplug/device.rb"
22
22
  - "./lib/tp_link_smartplug/helpers.rb"
23
23
  - "./lib/tp_link_smartplug/message.rb"
24
- - "./lib/tp_link_smartplug/message_helpers.rb"
25
24
  - "./lib/tp_link_smartplug/version.rb"
26
25
  homepage: https://github.com/bmhughes/tp_link_smartplug
27
26
  licenses:
@@ -38,9 +37,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
38
37
  version: '2.5'
39
38
  required_rubygems_version: !ruby/object:Gem::Requirement
40
39
  requirements:
41
- - - ">="
40
+ - - ">"
42
41
  - !ruby/object:Gem::Version
43
- version: '0'
42
+ version: 1.3.1
44
43
  requirements: []
45
44
  rubyforge_project:
46
45
  rubygems_version: 2.7.7
@@ -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