-1

I need to read time data from sensor. Here are the instructions from manual:

enter image description here

I have written code in Python, but I feel like there should be some better way:

# 1
data_bytes = b'\x43\x32\x21\x10'
print('data_bytes: ', data_bytes, len(data_bytes))  # why it changes b'\x43\x32\x21\x10' to b'C2!\x10'  ???

# 2
data_binary = bin(int.from_bytes(data_bytes, 'little'))  # remove '0b' from string
print('data_binary: ', data_binary, len(data_binary))
data_binary = data_binary[2:]
print('data_binary: ', data_binary, len(data_binary))  # should be: 0001 0000 0010 0001 0011 0010 0100 0011, 32

# 3
sec = data_binary[0:-20]
print(sec, len(sec))  # should be: 0001 0000 0010, 12
sec = int(sec, 2)
print(sec)

usec = data_binary[-20:]
print(usec, len(usec))  # 0001 0011 0010 0100 0011, 20
usec = int(usec, 2)
print(usec)

# 4
print('time: ', sec + usec/1000000)  # should be: 258.078403

Results:

data_bytes:  b'C2!\x10' 4
data_binary:  0b10000001000010011001001000011 31
data_binary:  10000001000010011001001000011 29
100000010 9
258
00010011001001000011 20
78403
time:  258.078403

I have questions:

  1. Why Python changes b'\x43\x32\x21\x10' to b'C2!\x10'?
  2. Why is the length of the message 29 bits and not 32?
  3. Is it possible to do this in better/cleaner/faster way?

Thanks!

dany
  • 173
  • 1
  • 8
  • 2
    2. leading zeros are irrelevant, thus are stripped from the binary object. – matszwecja Feb 16 '23 at 13:36
  • 2
    1. Because `__repr__` of a bytes object replaces bytes with ASCII characters where possible – Pranav Hosangadi Feb 16 '23 at 13:48
  • 1
    it is also important to mention that __repr__ is converting escape sequences to string, which start from x20 as null, x21 as !... this is HEX ascii not decimal. @Pranav Hosangadi says this as well – TheHappyBee Feb 16 '23 at 13:52

4 Answers4

4
  1. Both are the same data. Per the documentation, bytes objects are represented as ASCII characters or, for values over 127, by the appropriate hexadecimal literal. If you check an ASCII table, the hexadecimal values 0x43 0x32 and 0x21 are the characters C2!

  2. As noted by other comments, leading zeros are stripped

  3. you can avoid most of the conversions to and from strings by using binary operations:

data_bytes = b'\x43\x32\x21\x10'
# convert to int
data = int.from_bytes(data_bytes,'little')
# zero out the leading bits, leaving only the 20 bits corresponding to the microseconds
microseconds = data & 0x0fffff
# shift right by 20, thus keeping only the seconds (upper bits)
seconds = data >> 20

print(seconds) # 258
print(microseconds) #78403
Xilef11
  • 108
  • 7
1

One example would be to convert the bytestring into an int, and extract relevant information using bit operations.

code00.py:

#!/usr/bin/env python

import sys


def decode(byte_str):
    i = int.from_bytes(byte_str, byteorder="little")  # Convert to int reversing bytes (little endian)
    #i = int.from_bytes(byte_str[::-1], byteorder="big")  # Equivalent as the above line: reverse explicitly and convert without reversing bytes
    print(hex(i))  # @TODO - cfati: Comment this line
    secs = i >> 20  # Discard last 20 bytes (that belong to usecs)
    usecs = i & 0x000FFFFF  # Only retain last 20 bytes (5 hex digits)
    return secs + usecs / 1000000


def main(*argv):
    rec = b"\x43\x32\x21\x10"
    dec = decode(rec)
    print("Result: {:.6f}".format(dec))


if __name__ == "__main__":
    print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                   64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.\n")
    sys.exit(rc)

Output:

[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q075472971]> "e:\Work\Dev\VEnvs\py_pc064_03.10_test0\Scripts\python.exe" ./code00.py
Python 3.10.9 (tags/v3.10.9:1dd9be6, Dec  6 2022, 20:01:21) [MSC v.1934 64 bit (AMD64)] 064bit on win32

0x10213243
Result: 258.078403

Done.

Might also worth reading:

CristiFati
  • 38,250
  • 9
  • 50
  • 87
1

Have you tried using divmod ?

Dividing the integer value by 2^20 or 1048576 will directly split it in the two parts you are looking for.

sec,usec = divmod(int.from_bytes(data_bytes, 'little'),1048576)

print(sec,usec)
# 258 78403
Alain T.
  • 40,517
  • 4
  • 31
  • 51
0

Is it possible to do this in better/cleaner/faster way?

I suggest taking look at struct part of standard library, consider following simple example, say you have message which is big-endian and contain one char and unsigned int then you could do

import struct
data_bytes = b'\x56\xFF\xFF\xFF\xFF'
character, value = struct.unpack('>cI',data_bytes)
print(character)  # b'V'
print(value)  # 4294967295
Daweo
  • 31,313
  • 3
  • 12
  • 25
  • Can you modify the struct format to work with my case above? I have tried to do it with struct, but there is dividing 1 byte on half and i don't know how to do it. – dany Feb 16 '23 at 14:15