Brent
Brent

Reputation: 1

bytes-like object is required / ord() expected string of length 1, but int found (python 3.6)

I'm trying to work with: softScheck/tplink-smartplug

I'm stuck in a loop of errors. The fix for the first, causes the other, and the fix for the other, causes the first. The code is all found in tplink-smartplug.py at the git link.

cmd = "{\"system\":{\"set_relay_state\":{\"state\":0}}}"

sock_tcp.send(encrypt(cmd))

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

As it is, result = 'Ðòøÿ÷Õï¶Å Ôùðè·Ä°Ñ¥ÀâØ£òçöÔîÞ£Þ' and I get the error (on line 92 in original file: sock_tcp.send(encrypt(cmd)):

a bytes-like object is required, not 'str'

so I change the function call too:

sock_tcp.send(encrypt(cmd.encode('utf-8')))

and my error changes too:

ord() expected string of length 1, but int found

I understand what ord() is trying to do, and I understand the encoding. But what I don't understand is...how am I supposed to send this encrypted message to my smart plugin, if I can't give the compiler what it wants? Is there a work around? I'm pretty sure the original git was written in Python 2 or earlier. So maybe I'm not converting to Python 3 correctly?

Thanks for reading, I appreciate any help.

Upvotes: 0

Views: 2065

Answers (1)

abarnert
abarnert

Reputation: 366213

In Python 2, the result of encode is a str byte-string, which is a sequence of 1-byte str values. So, when you do for i in string:, each i is a str, and you have to call ord(i) to turn it into a number from 0 to 255.

In Python 3, the result of encode is a bytes byte-string, which is a sequence of 1-byte integers. So when you do for i in string:, each i is already an int from 0 to 255, so you don't have to do anything to convert it. (And, if you try to do it anyway, you'll get the TypeError you're seeing.)

Meanwhile, you're building result up as a str. In Python 2, that's fine, but in Python 3, that means it's Unicode, not bytes. Hence the other TypeError you're seeing.

For more details on how to port Python 2 string-handling code to Python 3, you should read the Porting Guide, especially Text versus binary data, and maybe the Unicode HOWTO if you need more background.


One way you can write the code to work the same way for both Python 2 and 3 is to use a bytearray for both values:

def encrypt(string):
    key = 171
    result = bytearray(b"\0\0\0\0")
    for i in bytearray(string): 
        a = key ^ i
        key = a
        result.append(a)
    return result

cmd = u"{\"system\":{\"set_relay_state\":{\"state\":0}}}"

sock_tcp.send(encrypt(cmd.encode('utf-8')))

Notice the u prefix on cmd, which makes sure it's Unicode even in Python 2, and the b prefix on result, which makes sure it's bytes even in Python 3. Although since you know cmd is pure ASCII, it might be simpler to just do this:

cmd = b"{\"system\":{\"set_relay_state\":{\"state\":0}}}"

sock_tcp.send(encrypt(cmd))

If you don't care about Python 2, you can just for for i in string: without converting it to a bytearray, but you still probably want to use one for result. (Being able to append an int directly to it makes for simpler code—and it's even more efficient, as a nice bonus.)

Upvotes: 1

Related Questions