tp_link 0.0.1

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 1ecbd0295f05df003c78bbcc8cf1299dc1a7af18
4
+ data.tar.gz: f34c62675f5fdd65b9607f06408d0c5a66f1c90a
5
+ SHA512:
6
+ metadata.gz: 9aa8132ae642b60c8bd28623acb3dbd3c5ab1e76d0c712fba47e5026b34cb838eaae53b49cbd58b916c18a9014cb113c2a53cccd8ded999630db4d17e8b08afd
7
+ data.tar.gz: 6b13493b9a882282f9fe0c9ec7015bd5204007813b1c3397289b0aa46f4a5805ab3d11d93fb710919ebe57ef3cb878bb9e097c9e8d06a098f6b9b066849e9643
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'faraday'
5
+ require 'net/https'
6
+ require 'json'
7
+ require 'faraday_middleware'
8
+ require 'yaml'
9
+ require 'json'
10
+ require 'securerandom'
11
+
12
+ require 'tp_link/errors.rb'
13
+ require 'tp_link/config.rb'
14
+ require 'tp_link/device.rb'
15
+ require 'tp_link/light.rb'
16
+ require 'tp_link/rgb_light.rb'
17
+ require 'tp_link/plug.rb'
18
+ require 'tp_link/api.rb'
19
+ require 'tp_link/smart_home.rb'
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TPLink Module
4
+ module TPLink
5
+ # Talk to TPLink's API. This class is use internally. You should not need to call it.
6
+ # @!visibility private
7
+ class API
8
+ # @!visibility private
9
+ DEFAULT_PARAMS = {
10
+ 'appName' => 'Kasa_Android',
11
+ 'termID' => proc { @config.terminal_uuid },
12
+ 'appVer' => '1.4.4.607',
13
+ 'ospf' => 'Android+6.0.1',
14
+ 'netType' => 'wifi',
15
+ 'locale' => 'es_ES'
16
+ }.freeze
17
+
18
+ # @!visibility private
19
+ DEFAULT_HEADERS = {
20
+ 'Connection' => 'Keep-Alive',
21
+ 'User-Agent' => 'Dalvik/2.1.0 (Linux; U; Android 6.0.1; ' \
22
+ 'A0001 Build/M4B30X)',
23
+ 'Content-Type' => 'applicatoin/json;charset=utf-8',
24
+ 'Accept' => 'application/json, text/plain, */*'
25
+ }.freeze
26
+
27
+ # # @!visibility private
28
+ # TOKEN_API = Faraday.new('https://wap.tplinkcloud.com') do |c|
29
+ # c.request :json
30
+ # c.response :json, content_type: /\bjson$/i
31
+ # c.adapter Faraday.default_adapter
32
+ # c.params.merge!(DEFAULT_PARAMS)
33
+ # c.headers.merge!(DEFAULT_HEADERS)
34
+ # end
35
+
36
+ # @!visibility private
37
+ TPLINK_API = Faraday.new('https://wap.tplinkcloud.com') do |c|
38
+ c.request :json
39
+ c.response :json, content_type: /\bjson$/i
40
+ c.adapter Faraday.default_adapter
41
+ c.params.merge!(DEFAULT_PARAMS)
42
+ c.headers.merge!(DEFAULT_HEADERS)
43
+ end
44
+
45
+ # @!visibility private
46
+ def initialize(opts = nil)
47
+ options = opts || "#{ENV['HOME']}/.tp_link"
48
+ @config = Config.new(options)
49
+ @token = nil
50
+ @device_list = []
51
+ end
52
+
53
+ def token(regen = false)
54
+ return @token if @token && regen == false
55
+ response = TPLINK_API.post do |req|
56
+ req.body = { method: 'login', url: 'https://wap.tplinkcloud.com',
57
+ params: @config.to_hash }.to_json
58
+ end
59
+ @token = parse_response(response)['token']
60
+ end
61
+
62
+ def device_list
63
+ @device_list if @device_list
64
+ response = TPLINK_API.post do |req|
65
+ req.params['token'] = token
66
+ req.body = { method: 'getDeviceList' }.to_json
67
+ end
68
+ @device_list = parse_response(response)['deviceList']
69
+ end
70
+
71
+ def send_data(device, data)
72
+ conn = data_connection(device)
73
+ res = conn.post do |req|
74
+ req.body = { method: 'passthrough',
75
+ params: { deviceId: device.id,
76
+ requestData: data.to_json } }.to_json
77
+ end
78
+ parse_response(res)
79
+ end
80
+
81
+ private
82
+
83
+ def data_connection(device)
84
+ Faraday.new(device.url) do |c|
85
+ c.request :json
86
+ c.response :json, content_type: /\bjson$/i
87
+ c.adapter Faraday.default_adapter
88
+ c.params['token'] = token
89
+ c.headers.merge!(DEFAULT_HEADERS)
90
+ end
91
+ end
92
+
93
+ def parse_response(res)
94
+ raise TPLink::TPLinkCloudError, 'Generic TPLinkCloud Error' unless res.success?
95
+ response = JSON.parse(res.body)
96
+ raise TPLink::DeviceOffline if response['error_code'].to_i == -20_571
97
+ raise TPLink::TPLinkCloudError, 'TPLinkCloud API Error' \
98
+ unless response['error_code'].to_i.zero?
99
+ raise TPLink::TPLinkCloudError, 'No respone data' \
100
+ unless res.body['result']
101
+ if response['result'].key?('responseData') && \
102
+ response['result']['responseData'].class == String
103
+ response['result']['responseData'] = \
104
+ JSON.parse(response['result']['responseData'])
105
+ end
106
+ response['result']
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TPLink
4
+ # This class handles the configuration.
5
+ # It is used internally and should not need to be called.
6
+ # @!visibility private
7
+ class Config
8
+ def initialize(config = "#{ENV['HOME']}/.tp_link")
9
+ @config_file = nil
10
+ @config = get_config(config)
11
+ raise 'User name not spcified in configuration.' \
12
+ unless @config.key?('user')
13
+ raise 'Password not specified in configuration.' \
14
+ unless @config.key?('password')
15
+ generate_uuid unless @config.key? 'uuid'
16
+ end
17
+
18
+ def generate_uuid
19
+ if @config_file && @config['uuid'].nil?
20
+ @config['uuid'] ||= SecureRandom.uuid
21
+ File.open(@config_file, 'w') do |f|
22
+ f.write @config.to_yaml
23
+ end
24
+ end
25
+ @config['uuid']
26
+ end
27
+
28
+ def app_type
29
+ 'Kasa_Android'
30
+ end
31
+
32
+ def terminal_uuid
33
+ @config['uuid']
34
+ end
35
+
36
+ def cloud_user_name
37
+ @config['user']
38
+ end
39
+
40
+ def cloud_password
41
+ @config['password']
42
+ end
43
+
44
+ def to_hash
45
+ {
46
+ appType: app_type,
47
+ cloudUserName: cloud_user_name,
48
+ cloudPassword: cloud_password,
49
+ terminalUUID: terminal_uuid
50
+ }
51
+ end
52
+
53
+ def to_json(_o)
54
+ to_hash.to_json
55
+ end
56
+
57
+ private
58
+
59
+ def get_config(config)
60
+ if config.is_a? String
61
+ raise "Config file missing: #{config}" \
62
+ unless File.exist?(config)
63
+ @config_file = config
64
+ return YAML.load_file(config) || {}
65
+ elsif !config.is_a?(Hash)
66
+ raise "Invalid config type #{config.class}"
67
+ end
68
+ config
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TPLink
4
+ # Generic class for all TPLink devices0
5
+ class Device
6
+ # Returns satus of device.
7
+ # @return [Boolean<1>] if device is on.
8
+ # @return [Boolean<0>] if device is off.
9
+ attr_reader :status
10
+
11
+ # Returns alais of device.
12
+ # @return [String] Device alias (name in kasa app).
13
+ attr_reader :alias
14
+
15
+ # Returns name of device.
16
+ # @return [String] Name of device (e.g. TP Link Smart Plug).
17
+ attr_reader :name
18
+
19
+ # @!visibility private
20
+ attr_reader :type, :url, :model, :mac, :role
21
+ # @!visibility private
22
+ attr_reader :same_region, :hw_id, :fw_id, :id, :hw_version, :fw_version
23
+
24
+ # @!visibility private
25
+ # This should only be called internally
26
+ def initialize(parent, dev)
27
+ @parent = parent
28
+ @alias = dev['alias']
29
+ @name = dev['deviceName']
30
+ @status = dev['status']
31
+ @type = dev['deviceType']
32
+ @url = dev['appServerUrl']
33
+ @model = dev['deviceModel']
34
+ @mac = dev['deviceMac']
35
+ @role = dev['role']
36
+ @same_region = dev['isSameRegion']
37
+ @hw_id = dev['hwId']
38
+ @fw_id = dev['fwId']
39
+ @id = dev['deviceId']
40
+ @hw_version = dev['deviceHwVersion']
41
+ @fw_version = dev['fwVer']
42
+ end
43
+
44
+ # Get Wifi signal strength in dB
45
+ # @returns [Integer]
46
+ def rssi
47
+ reload
48
+ @rssi
49
+ end
50
+
51
+ # Reload data / device state
52
+ def reload
53
+ res = @parent.send_data(self,
54
+ "system":
55
+ { "get_sysinfo": nil },
56
+ "emeter": { "get_realtime": nil })
57
+ @rssi = res['responseData']['system']['get_sysinfo']['rssi']
58
+ case self.class.to_s
59
+ when 'TPLink::Light'
60
+ reload_light(res)
61
+ when 'TPLink::Plug'
62
+ reload_plug(res)
63
+ end
64
+ true
65
+ end
66
+
67
+ # Turn device on
68
+ def on; end
69
+
70
+ # Turn device off
71
+ def off; end
72
+
73
+ # @return [True] if device is on.
74
+ # @return [False] if device is off.
75
+ def on?
76
+ reload
77
+ @status == 1
78
+ end
79
+
80
+ # @return [True] if device is off.
81
+ # @return [False] if device is on.
82
+ def off?
83
+ !on?
84
+ end
85
+
86
+ private
87
+
88
+ def reload_plug(res)
89
+ @status = res['responseData']['system']['get_sysinfo']['relay_state']
90
+ end
91
+
92
+ def reload_light(res)
93
+ @status = res['responseData']['system']['get_sysinfo']['light_state']['on_off']
94
+ end
95
+
96
+ def reload_rgb(res)
97
+ # TODO: Add variables for Hue, and Saturation
98
+ @status = res['responseData']['system']['get_sysinfo']['light_state']['on_off']
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Provides errors raised by the gem.
4
+
5
+ module TPLink
6
+ # Handle Errrors related to TPLink's Cloud
7
+ class DeviceOffline < StandardError
8
+ def initialize
9
+ super('Device Offline')
10
+ end
11
+ end
12
+ class TPLinkCloudError < StandardError
13
+ attr_reader :action
14
+ def initialize(message, action = nil)
15
+ super(message)
16
+ @action = action
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TPLink
4
+ # Control TPLink Dimmable lights
5
+ # @example
6
+ # light.on # turn on light
7
+ # light.off # turn off light
8
+ #
9
+ # # turn on light and set brightness to 50%
10
+ # light.on
11
+ # light.on(50)
12
+ class Light < TPLink::Device
13
+ # Turn light on
14
+ # @param b [Integer<1-100>] Set light intensity between 1 and 100
15
+ def on(b = 100)
16
+ transition_light_state(1, b)
17
+ end
18
+
19
+ # Turn light off
20
+ def off
21
+ transition_light_state(0, 100)
22
+ end
23
+
24
+ # Toggle device (turn off if on, on if off)
25
+ def toggle
26
+ if on?
27
+ off
28
+ else
29
+ on
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def transition_light_state(o, b)
36
+ @parent.send_data(self,
37
+ "smartlife.iot.smartbulb.lightingservice": {
38
+ "transition_light_state": {
39
+ "on_off": o,
40
+ "brightness": b
41
+ }
42
+ })
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TPLink
4
+ # Control TPLink Smart Plugs
5
+ class Plug < TPLink::Device
6
+ # Turn device on
7
+ def on
8
+ relay_state(1)
9
+ end
10
+
11
+ # Turn device off
12
+ def off
13
+ relay_state(0)
14
+ end
15
+
16
+ # Toggle device (turn off if on, on if off)
17
+ def toggle
18
+ if on?
19
+ off
20
+ else
21
+ on
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def relay_state(s)
28
+ @parent.send_data(self, "system": { "set_relay_state": { "state": s } })
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TPLink
4
+ # Control TP-Link LS130 / RGB lights
5
+ class RGBLight < TPLink::Device
6
+ # Turn Light on
7
+ def on(b = 100, h = 100, s = 100)
8
+ transition_light_state(1, b, h, s)
9
+ end
10
+
11
+ # Turn light off
12
+ def off
13
+ transition_light_state(0, 100, 100, 100)
14
+ end
15
+
16
+ # Toggle device (turn off if on, on if off)
17
+ def toggle
18
+ if on?
19
+ off
20
+ else
21
+ on
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def transition_light_state(o, b, h, s)
28
+ @parent.send_data(self,
29
+ "smartlife.iot.smartbulb.lightingservice": {
30
+ "transition_light_state": {
31
+ "on_off": o,
32
+ "brightness": b,
33
+ "hue": h,
34
+ "saturation": s
35
+ }
36
+ })
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TPLink
4
+ # Main class for TPLink. This is likely the only class you will initialize.
5
+ # @example
6
+ # sh = TPLink::SmartHome.new
7
+ #
8
+ # # Get array of TPLink Devices (currently only dimmable lights work).
9
+ # sh.devices
10
+ #
11
+ # # Find a device by name:
12
+ # light = sh.find("kitchen")
13
+ #
14
+ # # Turn light on
15
+ # light.on
16
+ #
17
+ # # Turn light off
18
+ # light.off
19
+ #
20
+ # # Dim light to 50%
21
+ # light.on(50)
22
+ class SmartHome
23
+ # @!visibility private
24
+ attr_reader :api
25
+ # @param [Hash,String] config options for TPLink
26
+ # @option config [String] :user Your Kasa user name
27
+ # @option config [String] :password Your Kasa password
28
+ # @option config [String] :uuid Your "device" (program) uuid.
29
+ # @example
30
+ # smarthome = TPLink::SmartHome.new(user: kasa@example.com, password: password: 1234)
31
+ # kitchen_light = smarthome.find("kitchen")
32
+ # kitchen_light.on
33
+ # kitchen_light.off
34
+ def initialize(config = {})
35
+ @api = TPLink::API.new(config)
36
+ reload
37
+ end
38
+
39
+ # Find a device by it's alias. Search is case insensitive.
40
+ # @param [String] a device alias.
41
+ # @return [TPLink::Light] If device is a dimmable light.
42
+ # @return [TPLink::RGBLight] if device is an RGB light.
43
+ # @return [TPLink::Plug] if device is a smart plug.
44
+ # @return [nil] if no device is found.
45
+ # @example Find your kitchen light
46
+ # smarthome.find("kitchen")
47
+ def find(a)
48
+ devices.find { |d| d.alias.match(/^#{a}$/i) }
49
+ end
50
+
51
+ # Find a device by it's alias.
52
+ # @return [Array<TPLink::Light,TPLink::RGBLight,TPLlink::Plug>] an array of devices.
53
+ # @example Turn everything off
54
+ # smarthome.devices.each { |device| device.off }
55
+ def devices
56
+ @devices ||= @raw_devices.map { |d| dev_to_class(d) }.compact
57
+ end
58
+
59
+ def send_data(device, data)
60
+ @api.send_data(device, data)
61
+ end
62
+
63
+ # Reload devices from TPLink api.
64
+ def reload
65
+ @raw_devices = @api.device_list
66
+ @devices = nil
67
+ end
68
+
69
+ private
70
+
71
+ # TODO: Test LB130, LB120, LB110, and BR30 devices.
72
+ def dev_to_class(device)
73
+ case device['deviceType']
74
+ when 'IOT.SMARTBULB'
75
+ return TPLink::Light.new(self, device) \
76
+ if device['deviceModel'].match(/^(LB100|LB110|BR30)/)
77
+ return TPLink::RGBLight.new(self, device) \
78
+ if device['deviceModel'].match(/^(LB130|LB120)/)
79
+ when 'IOT.SMARTPLUGSWITCH'
80
+ return TPLink::Plug.new(self, device)
81
+ end
82
+ nil
83
+ end
84
+ end
85
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tp_link
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - James Paterni
8
+ - Ian MOrgan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2018-03-05 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: faraday
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '0.13'
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.13.1
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '0.13'
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.13.1
34
+ - !ruby/object:Gem::Dependency
35
+ name: faraday_middleware
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.12'
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.12.2
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: '0.12'
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 0.12.2
54
+ description: Control various TPLink smart home devices.
55
+ email: tp_link@ruby-code.com
56
+ executables: []
57
+ extensions: []
58
+ extra_rdoc_files: []
59
+ files:
60
+ - "./lib/tp_link.rb"
61
+ - "./lib/tp_link/api.rb"
62
+ - "./lib/tp_link/config.rb"
63
+ - "./lib/tp_link/device.rb"
64
+ - "./lib/tp_link/errors.rb"
65
+ - "./lib/tp_link/light.rb"
66
+ - "./lib/tp_link/plug.rb"
67
+ - "./lib/tp_link/rgb_light.rb"
68
+ - "./lib/tp_link/smart_home.rb"
69
+ homepage: https://ruby-code.com/james/tp_link
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.6.14
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: TPLink smarthome library
93
+ test_files: []