tp_link_smartplug 0.0.3 → 0.1.0.alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/tp_link_smartplug/command.rb +13 -0
- data/lib/tp_link_smartplug/device.rb +133 -3
- data/lib/tp_link_smartplug/helpers.rb +5 -2
- data/lib/tp_link_smartplug/message.rb +23 -58
- data/lib/tp_link_smartplug/version.rb +2 -0
- data/lib/tp_link_smartplug.rb +0 -1
- 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: 23a1848523acc948dd653d39fa3b9cec937bdba9d2a0ad6970308bac4af64d8d
|
4
|
+
data.tar.gz: dd4d4a0fda92aeabfdc7adfb5f751f1b957438b168e5c96efaf4950d69e1bb3d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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(
|
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:
|
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
|
-
#
|
4
|
+
# Helper methods for plug communication messages
|
9
5
|
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)
|
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
|
-
|
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
|
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
|
-
|
63
|
-
|
64
|
-
def disconnect(socket:)
|
65
|
-
socket.close unless socket.closed? || socket.nil?
|
30
|
+
result
|
66
31
|
end
|
67
32
|
end
|
68
33
|
end
|
data/lib/tp_link_smartplug.rb
CHANGED
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.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-
|
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:
|
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
|