145 lines
3.7 KiB
Python
145 lines
3.7 KiB
Python
#!/usr/bin/env python3
|
|
#
|
|
# TP-Link Wi-Fi Smart Plug Influx Reader
|
|
# For use with TP-Link HS-110
|
|
#
|
|
# by Dennis Gunia
|
|
#
|
|
# TpLink Protocol code (reqCmd(), encrypt(), decrpyt()) based on https://github.com/softScheck/tplink-smartplug
|
|
# by Lubomir Stroetmann
|
|
# Copyright 2016 softScheck GmbH
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
#
|
|
|
|
# import libs
|
|
import yaml
|
|
import socket
|
|
import sys
|
|
import pytz
|
|
import json
|
|
from struct import pack
|
|
from influxdb import InfluxDBClient
|
|
from datetime import datetime
|
|
|
|
# list of influx data points
|
|
points = []
|
|
|
|
# read config file
|
|
with open(r'./config.yml') as file:
|
|
config = yaml.load(file, Loader=yaml.FullLoader)
|
|
|
|
# connect to influxdb
|
|
try:
|
|
client = InfluxDBClient(host=config['influx']['ip'], port=config['influx']['port'], username=config['influx']['user'], password=config['influx']['password'], database=config['influx']['db'])
|
|
client.get_list_database()
|
|
except:
|
|
exit("Cannot connect to influxdb")
|
|
|
|
# Encryption and Decryption of TP-Link Smart Home Protocol
|
|
# XOR Autokey Cipher with starting key = 171
|
|
# Python 3.x Version
|
|
if sys.version_info[0] > 2:
|
|
def encrypt(string):
|
|
key = 171
|
|
result = pack('>I', len(string))
|
|
for i in string:
|
|
a = key ^ ord(i)
|
|
key = a
|
|
result += bytes([a])
|
|
return result
|
|
|
|
def decrypt(string):
|
|
key = 171
|
|
result = ""
|
|
for i in string:
|
|
a = key ^ i
|
|
key = i
|
|
result += chr(a)
|
|
return result
|
|
|
|
# Python 2.x Version
|
|
else:
|
|
def encrypt(string):
|
|
key = 171
|
|
result = pack('>I', len(string))
|
|
for i in string:
|
|
a = key ^ ord(i)
|
|
key = a
|
|
result += chr(a)
|
|
return result
|
|
|
|
def decrypt(string):
|
|
key = 171
|
|
result = ""
|
|
for i in string:
|
|
a = key ^ ord(i)
|
|
key = ord(i)
|
|
result += chr(a)
|
|
return result
|
|
|
|
# send command to tplink device
|
|
def reqCmd(cmd, ip):
|
|
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
sock_tcp.settimeout(20)
|
|
sock_tcp.connect((ip, 9999))
|
|
sock_tcp.settimeout(None)
|
|
sock_tcp.send(encrypt(cmd))
|
|
data = sock_tcp.recv(2048)
|
|
sock_tcp.close()
|
|
return json.loads(decrypt(data[4:]))
|
|
|
|
# read single plug and add to data array
|
|
def readPlug(ip):
|
|
try:
|
|
# create utc timetsamp
|
|
dateTimeObj = datetime.now(tz=pytz.utc).isoformat()
|
|
|
|
# retrieve data from plug
|
|
dInfo = reqCmd('{"system":{"get_sysinfo":{}}}', ip)['system']['get_sysinfo']
|
|
dData = reqCmd('{"emeter":{"get_realtime":{}}}', ip)['emeter']['get_realtime']
|
|
|
|
# create point and add to points array
|
|
json_body = {
|
|
"measurement": config['influx']['measurement'],
|
|
"tags": {
|
|
"mac": dInfo['mac'],
|
|
"alias": dInfo['alias'],
|
|
"ip": ip
|
|
},
|
|
"time": dateTimeObj,
|
|
"fields": {
|
|
"volts": dData['voltage_mv'],
|
|
"amps": dData['current_ma'],
|
|
"watts": dData['power_mw'],
|
|
"wh": dData['total_wh']
|
|
}
|
|
}
|
|
points.append(json_body)
|
|
print("Read plug " + ip )
|
|
|
|
except socket.error:
|
|
print("Could not connect to plug " + ip )
|
|
|
|
# read all plugs
|
|
for deviceIp in config['plugs']:
|
|
readPlug(deviceIp)
|
|
|
|
# write to influxdb
|
|
try:
|
|
client.write_points(points)
|
|
except:
|
|
exit("error writing influxdb")
|
|
finally:
|
|
print("done!")
|