tp_link_smartplug 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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: []