1

I am using a tutorial from BeardMonkey in order to retrieve and store the measured data from a smart plug (a smart plug is monitoring the power consumption of my kettle at the moment)

https://www.beardmonkey.eu/tplink/hs110/2017/11/21/collect-and-store-realtime-data-from-the-tp-link-hs110.html

I think I followed it pretty well but I am only receiving this back:"No data returned on power request."

The only thing I changed in the script is the IP Address of the smart plug and replaced it with the IP Address of mine.

My question is, do any of you experience programmers know from a glance, if there is something I am not seeing?

import sys
import time
import socket
import json
import threading

#localtime = time.asctime( time.localtime(time.time()) )


# https://stackoverflow.com/questions/21017698/converting-int-to-bytes-in-python-3
def int_to_bytes(x):
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')


def int_from_bytes(xbytes):
    return int.from_bytes(xbytes, 'big')


# Based on: https://github.com/softScheck/tplink-smartplug/blob/master/tplink-smartplug.py
def encrypt(string):
    key = 171
    result = b"\0\0\0\0"
    for i in string:
        a = key ^ i
        key = a
        result += int_to_bytes(a)
    return result


def decrypt(string):
    key = 171
    result = b""
    for i in string:
        a = key ^ i
        key = i
        result += int_to_bytes(a)
    return result


def send_hs_command(address, port, cmd):
    data = b""

    tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        tcp_sock.connect((address, port))
        tcp_sock.send(encrypt(cmd))
        data = tcp_sock.recv(2048)
    except socket.error:        
        print(time.asctime( time.localtime(time.time()) ), "Socket closed.", file=sys.stderr)
    finally:
        tcp_sock.close()
    return data


def store_metrics(current, voltage, power):
    current_time = time.time()

    tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        tcp_sock.connect(("localhost", 2003))
        tcp_sock.send("hs110-tv.voltage {0} {1} \n".format(voltage, current_time).encode())
        tcp_sock.send("hs110-tv.current {0} {1} \n".format(current, current_time).encode())
        tcp_sock.send("hs110-tv.power {0} {1} \n".format(power, current_time).encode())
    except socket.error:
        print("Unable to open socket on graphite-carbon.", file=sys.stderr)
    finally:
        tcp_sock.close()


def run():
    threading.Timer(5.0, run).start()

    data = send_hs_command("192.168.1.4", 9999, b'{"emeter":{"get_realtime":{}}}')

    if not data:
        print("No data returned on power request.", file=sys.stderr)
        store_metrics(0, 0, 0)
        return

    decrypted_data = decrypt(data[4:]).decode()
    json_data = json.loads(decrypted_data)
    emeter = json_data["emeter"]["get_realtime"]

    if not emeter:
        print("No emeter data returned on power request.", file=sys.stderr)
        store_metrics(0, 0, 0)
        return

    store_metrics(emeter["current"], emeter["voltage"], emeter["power"])

    print("Stored values, current: {0}, voltage: {1}, power: {2}".format(
        emeter["current"], emeter["voltage"], emeter["power"]))


run()

Implementing the solution from @JaromandaX I get this return:

Traceback (most recent call last):
  File "hs110-data-collect.py", line 98, in <module>
    run()
  File "hs110-data-collect.py", line 92, in run
    store_metrics(emeter["current"], emeter["voltage"], emeter["power"])
KeyError: 'current'
b'\x00\x00\x00\x1e\xd0\xf2\x97\xfa\x9f\xeb\x8e\xfc\xde\xe4\x9f\xbd\xda\xbf\xcb\x94\xe6\x83\xe2\x8e\xfa\x93\xfe\x9b\xb9\x83\xf8\x85\xf8\x85'
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python3.5/threading.py", line 914, in _bootstrap_inner
    self.run()
  File "/usr/lib/python3.5/threading.py", line 1180, in run
    self.function(*self.args, **self.kwargs)
  File "hs110-data-collect.py", line 92, in run
    store_metrics(emeter["current"], emeter["voltage"], emeter["power"])
KeyError: 'current'

UPDATE ********************************

I have taken the original script (no editions except for the IP-Address of my smart plug) and replaced ["current"], emeter["voltage"], emeter["power"] with 0,1,2. For some reason now I get a message back with some values, but I am not sure they are getting stored or what I should write instead of 0,1,2 (or why ["current"], emeter["voltage"], emeter["power"] seems to be a problem)

{"emeter":{"get_realtime":{"voltage_mv":226139,"current_ma":7382,"power_mw":1669548,"total_wh":366,"err_code":0}}}``` 

  • The only thing I changed in the script is the IP Address of the smart plug and replaced it with the IP Address of mine. - and yet, the script in the question and the script in the tutorial only differ in one removed comment, some extra info in the socket.error, and threading.Timer(15.0, run).start() instead of threading.Timer(5.0, run).start() - are you sure you changed the IP address? – Jaromanda X Mar 26 at 10:27
  • Did you get the Socket Closed message as well? – Jaromanda X Mar 26 at 10:28
  • Hi @JaromandaX Sorry you are right. I did change the IP address in the script I am running, but I wrote the original one with a comment here. If I don't change the IP address I get "Socket closed. No data returned on power request". If I put the IP address of my smart plug, I only get "No data returned on power request". Even when there should be data, because I can see the consumption on the app of the smart plug, but the data doesn't come through to the raspberry pi. – xoani Mar 26 at 10:41
  • ahh, ok, so it's communicating ... that's a positive start – Jaromanda X Mar 26 at 10:42
  • Yeah, it is, but no data comes through. I am wondering if there is a problem with Graphite. I know my question is very broad because I haven't written the code myself, but I am thankful for any ideas that might point me to the problem. Cause right now I am stuck. – xoani Mar 26 at 10:48
0

The original code by the people that hacked the HS110 was in python2 - but the first 4 bytes of encrypt should be the length of the command - so

add import from struct at the top

import sys
import time
import socket
import json
import threading

from struct import *

and fix the result= line in encrypt

def encrypt(string):
    key = 171
    result = pack('>I', len(string))
    for i in string:
        a = key ^ i
        key = a
        result += int_to_bytes(a)
    return result

Now you should be sending the right commands

  • Hi! Thanks your your help. I have edited my question and written the output I get when implementing your solution(with the kettle off). I don't know if something is still wrong, or if I don't know how to interpret the return. Also, I don't know if it's obvious, but I am using python3 – xoani Mar 26 at 12:47
  • any ideas? I would be happy to understand anything else, even the output that I just got. I am stuck. – xoani Mar 27 at 7:32
  • put in a print(decrypted_data) after decryption – Jaromanda X Mar 27 at 8:09
  • I have tried that and gotten a weird message again . After that, I took the original script with my smart plug IP address and exchanged the field emeter["current"], emeter["voltage"], emeter["power"] for 0,1,2. For some reason, now I see values. I have no idea why or what I have changed. I just saw the problem was with this and I tested by putting numbers. (see UPDATE in question) – xoani Mar 27 at 10:10

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.