tp_link_smartplug 0.0.3 → 0.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/lib/tp_link_smartplug.rb +10 -7
- data/lib/tp_link_smartplug/_base.rb +15 -0
- data/lib/tp_link_smartplug/command.rb +25 -14
- data/lib/tp_link_smartplug/device.rb +153 -11
- data/lib/tp_link_smartplug/helpers.rb +14 -4
- data/lib/tp_link_smartplug/message.rb +22 -59
- data/lib/tp_link_smartplug/version.rb +3 -3
- metadata +4 -5
- data/lib/tp_link_smartplug/message_helpers.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 41a05841cdc6ffdd697f903abc1d620debf999f2a9a6c5f248edc9ec93635e30
|
4
|
+
data.tar.gz: c3f77299aaeb6b90506f04730467c70889745d85e47148ba738ccc82de484cbd
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eac7e52a043aebdeeb595aba15df19be7923d2da40a6d214bb41955f4baf4da1e2853e935cd7ade3b410981bd8698024f9598a923b20a5c0311ed2398dbced01
|
7
|
+
data.tar.gz: 74f0ba6ff80393ee43560a76ec6cab25d94fb1b8b5b3b078884be64b9db274e657053f2c90007df89aa2bde7e849b63a049e4628c91ff4bb90cf1e29b59c7d23
|
data/lib/tp_link_smartplug.rb
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
4
|
+
require_relative './message'
|
7
5
|
|
8
6
|
module TpLinkSmartplug
|
9
|
-
# Provides an
|
10
|
-
|
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(
|
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
|
-
#
|
2
|
+
# Helper methods for plug communication messages
|
9
3
|
module Message
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
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
|
-
|
63
|
-
|
64
|
-
def disconnect(socket:)
|
65
|
-
socket.close unless socket.closed? || socket.nil?
|
28
|
+
result
|
66
29
|
end
|
67
30
|
end
|
68
31
|
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
|
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:
|
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
|
-
|
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
|