tp_link_smartplug 0.0.3

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f53d7ea800a82c44bc660a56066f5e7ee81d2ff2f786ddcb668c8d3267b8960c
4
+ data.tar.gz: af92b7cd428a8a3e1ceafb1868b3651153b5b192d051f0266174b14d4bbfba67
5
+ SHA512:
6
+ metadata.gz: f406f2bc6b7a7ebb0d0e8434146593a6080f4a2840d4420f96b3fd9454f31645adbfb8e248c5dab75e35c54ed9e72129b0e2355b508949271f1aa75371eb7269
7
+ data.tar.gz: 1b21d836bac38def6558452bd6fa6bbb9a4c0fe5023f70abb07c91bdd6921d5f4bb6db7db44d52ecf47d07d7fd417c0a5d8b71e10ccf5832becf864abb90844d
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TpLinkSmartplug
4
+ 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":{}}}'
17
+ end
18
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'ipaddr'
5
+ require 'json'
6
+ require 'tp_link_smartplug/message'
7
+
8
+ module TpLinkSmartplug
9
+ # Provides an interface to a plug
10
+ class Device
11
+ include TpLinkSmartplug::Message
12
+
13
+ attr_accessor :address
14
+ attr_accessor :timeout
15
+ attr_accessor :port
16
+ attr_accessor :debug
17
+
18
+ def initialize(address:, port: 9999)
19
+ @address = IPAddr.new(address, Socket::AF_INET)
20
+ @port = port
21
+ @timeout = 3
22
+ @debug = false
23
+ end
24
+
25
+ [
26
+ :info,
27
+ :on,
28
+ :off,
29
+ :cloudinfo,
30
+ :wlanscan,
31
+ :time,
32
+ :schedule,
33
+ :countdown,
34
+ :antitheft,
35
+ :reboot,
36
+ :reset,
37
+ :energy
38
+ ].each do |method|
39
+ define_method method do
40
+ JSON.parse(poll(address: @address, port: @port, command: TpLinkSmartplug::Command.const_get(method.upcase), timeout: @timeout, debug: @debug))
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+
5
+ module TpLinkSmartplug
6
+ # Generic helper methods
7
+ module Helpers
8
+ 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)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'socket'
4
+ require 'tp_link_smartplug/helpers'
5
+ require 'tp_link_smartplug/message_helpers'
6
+
7
+ module TpLinkSmartplug
8
+ # Provides methods to send and receive messages from plugs
9
+ 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)
25
+ 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
35
+ end
36
+
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
61
+ end
62
+ end
63
+
64
+ def disconnect(socket:)
65
+ socket.close unless socket.closed? || socket.nil?
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,31 @@
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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TpLinkSmartplug
4
+ VERSION = '0.0.1'
5
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
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'
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tp_link_smartplug
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Ben Hughes
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-06-07 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Control and retrieve data from a TP-Link HS100/110 (Metered) Smartplug
14
+ email: bmhughes@bmhughes.co.uk
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - "./lib/tp_link_smartplug.rb"
20
+ - "./lib/tp_link_smartplug/command.rb"
21
+ - "./lib/tp_link_smartplug/device.rb"
22
+ - "./lib/tp_link_smartplug/helpers.rb"
23
+ - "./lib/tp_link_smartplug/message.rb"
24
+ - "./lib/tp_link_smartplug/message_helpers.rb"
25
+ - "./lib/tp_link_smartplug/version.rb"
26
+ homepage: https://github.com/bmhughes/tp_link_smartplug
27
+ licenses:
28
+ - Apache-2.0
29
+ metadata: {}
30
+ post_install_message:
31
+ rdoc_options: []
32
+ require_paths:
33
+ - lib
34
+ required_ruby_version: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '2.5'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ requirements: []
45
+ rubyforge_project:
46
+ rubygems_version: 2.7.7
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: TP-Link HS100/110 Smart Plug interaction library
50
+ test_files: []