Python Forum

Full Version: Need help understanding a couple of functions (encrypt,decrypt, int_to_bytes)
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
I have a Python Script based on

https://www.beardmonkey.eu/tplink/hs110/...hs110.html

https://www.softscheck.com/en/reverse-en...ink-hs110/

It retrieves data from a smart plug. It works perfectly fine, but I am trying to understand every line of code and I am having trouble with a couple of things. Most of it is commented, the lines that are not commented are the ones I do not understand. Better than posting parts of the script, I thought I should post the whole thing and see if someone can help me decipher it. Thanks!



Basically I am having trouble with the first three def: int_to_bytes(x), encrypt(string), decrypt(string) and with this line decrypted_data = decrypt(data[4:]).decode()

Any help is truly appreciated!


import sys
import socket
import threading
from struct import *
import json
import datetime

def int_to_bytes(x):
    return x.to_bytes((x.bit_length() + 7) // 8, 'big')


def encrypt(string):
    key = 171
    result = pack('>I', len(string))
    for i in string:  #same as for (int i = 0; i < str.length(); ++i)
        a = key ^ i  #XOR the bits
        key = a
        result += int_to_bytes(a)
    return result



def decrypt(string):
    key = 171
    result = b""  #sequence of octets (integers between 0 and 255)
    for i in string:
        a = key ^ i
        key = i
        result += int_to_bytes(a)
    return result



#Function that connects to SM and returns data
def send_hs_command(address, port, cmd):
    data = b""  #sequence of octets (integers between 0 and 255)

    tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #CREATING SOCKET: SOCK_STREAM means connection oriented TCP protocol.
                                                                 #AF_INET is an address family that is used to designate the type of addresses
                                                                 #that your socket can communicate with (in this case, Internet Protocol v4 addresses)
    try:
        tcp_sock.connect((address, port))                       #socket.connect(address)Connect to a remote socket at address. (The format of address
                                                                #depends on the address family — see above.
        tcp_sock.send(encrypt(cmd))                             #Send data to the socket. The socket must be connected to a remote socket. Returns the number of bytes sent
                                                                #It encrypts the EMETER cmd and sends it to the socket
        data = tcp_sock.recv(2048)                              #Receive data from the socket. The return value is a string representing the data received.
                                                                #The maximum amount of data to be received at once is specified by bufsize
    except socket.error:                                        #raise an exception which forces your program to output an error when something in it goes wrong.      
        print(datetime.datetime.utcnow().isoformat(), "Socket closed.", file=sys.stderr)
    finally:
        tcp_sock.close()                                        #Close the socket. All future operations on the socket object will fail.
                                                                #The remote end will receive no more data
    return data



#Function that actually runs the program every 5 seconds
def run():

    
    #threading.Timer(5.0, run).start()

    
    #retrieve data from function and save it into data 
    data = send_hs_command("192.168.0.106", 9999, b'{"emeter":{"get_realtime":{}}}')
    print("data", '\n', data,'\n')

    #if the function does not return anything, send a message  
    if not data:
        print(datetime.datetime.utcnow().isoformat(), "No data returned on power request.", file=sys.stderr)
        return

    #if the function returns data, it will be decrypted because it was encrypted in the function
    decrypted_data = decrypt(data[4:]).decode()   #This method is used to convert from one encoding scheme,
                                                  #in which argument string is encoded to the desired encoding scheme. 
    print("decrypted_data", '\n',decrypted_data,'\n' )

    #decrypted data will be turned into json
    json_data = json.loads(decrypted_data)
    print("json_data", '\n',json_data,'\n' )

    #json data will be modified again, but not in order?
    emeter = json_data["emeter"]["get_realtime"]
    print("emeter", '\n', emeter,'\n')
    
 
    #I think this is only for the order of the data and to print a timestamp
    finaldata = {   
  "timestamp": datetime.datetime.utcnow().isoformat(),
  "voltage_mv":emeter["voltage_mv"],
  "current_ma":emeter["current_ma"],
  "power_mw":emeter["power_mw"] ,
  "energy_wh": emeter["total_wh"],
    }

    print("finaldata", '\n',finaldata,'\n')

    #I think this is to keep the order, if I print finaldata it is not organized
    #Writing a JSON object, serializing, DICT into JSON OBJECT
    print("json.dumps(finaldata)", '\n',json.dumps(finaldata))


    #if data was returned but no emeter was returned, then..
    if not emeter:
        print("No emeter data returned on power request.", file=sys.stderr)
   
        return

run()