Community
OpenEnergyMonitor

Community

Python code to read TP-Link HS110

emonhub
tplink
hs110
socket
Tags: #<Tag:0x00007f13eedf7ee8> #<Tag:0x00007f13eedf7a88> #<Tag:0x00007f13eedf7290> #<Tag:0x00007f13eedf6de0>

(Paul) #1

As promised in the Wall plug (passthrough) or socket monitors? thread, here’s some code for reading tp-link hs-110 energy monitoring smart sockets and posting to an emonhub socket interfacer.

The script needs to be executable and started at boot via service unit or init script and some settings applied to the emonhub.conf (and possibly UFW). Once in emonhub (emonpi variant) it can/will be published to MQTT so I have included the nodes definitions too.

This isn’t intended as a walk through guide to installing this, it’s just providing some code to get going and example settings for emonhub to get an interfacer up and running.

#!/usr/bin/env python
"""
Script to read TP-Link HS110 Energy monitoring smart sockets and post data to emonhub via a simple socket interfacer. 

It uses "harmonized intervals" so all update timestamps are exact multiple of the interval.

It has very low timeout settings so that it doesn't dwell on a failed/slow connection, it tries and moves on swiftly so other device reads/sends are not blocked. 
"""

__author__ = 'Paul Burnell (@pb66)'

import socket
import json
import time
import datetime

# emonhub address and port as defined in the interfacer set up.
emonhub_host = "192.168.1.78"
emonhub_port = 50013

# Define node ids and addresses for each TP-link HS110 
nodes = {27:{"address":"192.168.1.184"},\
		 28:{"address":"192.168.1.185"},\
		 29:{"address":"192.168.1.164"} }

# Update interval
interval = 10

# Print some info to console, True or False
debug = True
	
# Encryption and Decryption of TP-Link Smart Home Protocol
# XOR Autokey Cipher with starting key = 171
def encrypt(string):
	key = 171
	result = "\0\0\0\0"
	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
	
# Connect to, read and disconnect from device
def read_data(timestamp,node):
	payload = False
	socket_hs110 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	socket_hs110.settimeout(2)
	socket_hs110.connect((nodes[node]["address"], 9999))
	cmd = '{"emeter":{"get_realtime":{}},\
			"system":{"get_sysinfo":{}},\
			"time"  :{"get_time":{},\
			   		  "get_timezone":{}} }'
	socket_hs110.send(encrypt(cmd))
	read_reply = json.loads(decrypt(socket_hs110.recv(4096)[4:]))
	socket_hs110.close()
	values = [	read_reply['emeter']['get_realtime']['voltage'],\
				read_reply['emeter']['get_realtime']['current'],\
				read_reply['emeter']['get_realtime']['power'],\
				read_reply['emeter']['get_realtime']['total'],\
				read_reply['system']['get_sysinfo']['relay_state'],\
				read_reply['system']['get_sysinfo']['rssi'] ]
	#print(values)
	payload = ' '.join(str(i) for i in [timestamp,node]+values)
	if debug:
		print("Read {}".format(payload))
	return payload

# Send the data to an emonhub socket interfacer
def send_data(payload):
	socket_emonhub = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	socket_emonhub.settimeout(2)
	socket_emonhub.connect((emonhub_host, emonhub_port))
	reply = socket_emonhub.sendall(payload+"\r\n")
	socket_emonhub.close()
	if debug:
		print("Sent {}".format(payload))
	return reply


# create place to hold each nodes values
for node in nodes:
	nodes[node]["payload"] = False

# Get the current interval time
timestamp = ((time.time()//interval)*interval)

while(True):

	# If the next read is due, read all the devices.
	time_now = time.time()
	if time_now >= timestamp+interval:
		timestamp = ((time_now//interval)*interval)
		for node in nodes:
			try:
				nodes[node]["payload"] = read_data(timestamp,node)
			except Exception as error:
				if debug:
					print("FAIL {} Unable to read node {}: {}".format(timestamp,node,error))
	else:
		# Inbetween reads do not loop too fast
		time.sleep(0.1)


	for node in nodes:
		if nodes[node]["payload"]:
			try:
				send_reply = send_data(nodes[node]["payload"])
				nodes[node]["payload"] = False
			except Exception as error:
				if debug:
					print("FAIL {} Unable to send node {}: {}".format(timestamp,node,error))

Example interfacer entry for emonhub.conf [interfacer] section

      [[TPLinkHS110]]
          Type = EmonHubSocketInterfacer
          [[[init_settings]]]
              port_nb = 50013
          [[[runtimesettings]]]
              timestamped = True
              subchannel = ToEmonCMS,     # Only for emonpi variant of emonhub

and if needed open the port in the firewall (ie if script not running on same device as emonhub and UFW is installed)

sudo ufw allow 50013 && sudo ufw -f enable

Example nodes entries for emonhub.conf [nodes] section

      [[27]]
          nodename = TPLinkHS110a
          [[[rx]]]
              names = Voltage,Current,Power,Energy,Status,RSSI
              units = V,A,W,kWh,state,dB
      [[28]]
          nodename = TPLinkHS110b
          [[[rx]]]
              names = Voltage,Current,Power,Energy,Status,RSSI
              units = V,A,W,kWh,state,dB
      [[29]]
          nodename = TPLinkHS110c
          [[[rx]]]
              names = Voltage,Current,Power,Energy,Status,RSSI
              units = V,A,W,kWh,state,dB
              #datacodes = 0,0,0,0,0,0
              #scales = 1,1,1,1,1,1

strictly speaking the datacodes and scales shouldn’t be needed, but I have included them in just one entry above just to indicate what would/could be used.

Example console output if debug is set to True

Read 1547767370.0 27 247.095985 0.023666 0 0 0 -53
Read 1547767370.0 28 248.32088 0.019906 0 1222.02 0 -57
Read 1547767370.0 29 246.823264 3.75433 926.64569 27.302 1 -76
Sent 1547767370.0 27 247.095985 0.023666 0 0 0 -53
Sent 1547767370.0 28 248.32088 0.019906 0 1222.02 0 -57
Sent 1547767370.0 29 246.823264 3.75433 926.64569 27.302 1 -76
Read 1547767380.0 27 247.131161 0.023279 0 0 0 -52
Read 1547767380.0 28 248.293119 0.019755 0 1222.02 0 -56
Read 1547767380.0 29 246.80449 3.738486 922.668525 27.305 1 -75
Sent 1547767380.0 27 247.131161 0.023279 0 0 0 -52
Sent 1547767380.0 28 248.293119 0.019755 0 1222.02 0 -56
Sent 1547767380.0 29 246.80449 3.738486 922.668525 27.305 1 -75
Read 1547767390.0 27 247.256333 0.023778 0 0 0 -52
Read 1547767390.0 28 248.442524 0.020359 0 1222.02 0 -57
Read 1547767390.0 29 246.940376 3.737328 922.895971 27.308 1 -74
Sent 1547767390.0 27 247.256333 0.023778 0 0 0 -52
Sent 1547767390.0 28 248.442524 0.020359 0 1222.02 0 -57
Sent 1547767390.0 29 246.940376 3.737328 922.895971 27.308 1 -74

In time I would like to perhaps keep the sockets open to reduce the workload of keep opening and closing, plus then the script could listen for a command to switch on and off, I have the code to do that (somewhere) but the emonhub socket interfacer would also need some work to accomplish this.

I do not run an emonSD and I have not had a chance to test the settings on an emonPi, so please let me know if I’ve got the settings wrong. This is not the script I use myself as I have other stuff going on in mine. I have pulled code from my script to make this more generic forum friendly version.

[edit - I’ve been running this overnight on a Pi and found the occasional timeout, I have changed the timeout settings from 1s to 2s and that seems to have fixed it, no timeouts since. That is still a tight timeout, the idea is to just allow enough time for a “usual” connection and move on as there will be another attempt within 10s, no point sitting there for 60s.]


Wall plug (passthrough) or socket monitors?