Python 3 min read

Bytes and struct

A sensor doesn't send numbers; it sends bytes. Python's struct module turns a frame of raw bytes into named values in one line, as long as you tell it the layout and the byte order.

The five bytes AA 29 09 01 DD are unpacked with the struct format <BhBB. B takes byte 0 as start, 0xAA. h takes bytes 1 and 2 as a signed 16-bit temp; little-endian puts the low byte first, so 29 09 reads as 0x0929, which is 2345, or 23.45 degrees. B takes byte 3 as status, 1, and B takes byte 4 as the checksum, 0xDD.
One format character per field; < sets the byte order.

From five bytes to four values

Our sensor sends five bytes: a start byte 0xAA, a signed 16-bit temperature in hundredths of a degree, a status byte and a checksum. The format string "<BhBB" describes exactly that: B is an unsigned byte and h a signed 16-bit integer.

frame.pyPython
import structframe = bytes([0xAA, 0x29, 0x09, 0x01, 0xDD])print(frame.hex(" "))start, raw, status, check = struct.unpack("<BhBB", frame)ok = sum(frame[:-1]) & 0xFF == checkprint(f"start=0x{start:02X} temp={raw / 100:.2f} C status={status} ok={ok}")
Output
aa 29 09 01 dd
start=0xAA temp=23.45 C status=1 ok=True

Byte order matters

Most microcontrollers, Arm Cortex-M parts among them, are used little-endian: low byte first. But the frame's byte order is set by the sensor's protocol, so check its datasheet. Read the same two bytes big-endian (">h") and 23.45 °C becomes 105.05 °C.

It works in reverse too: struct.pack("<BhBB", 0xAA, 2345, 1, 0xDD) builds the same five bytes, which is how a test script sends a command or fakes a sensor. A bytes object is read-only; when you fill a buffer piece by piece, use a bytearray.

On the bench the bytes come from a serial port. With the pyserial package, installed with pip install pyserial, reading them is one more line:

read_frame.pyPython
import serial  # the pyserial packagewith serial.Serial("COM3", 115200, timeout=1) as port:    frame = port.read(5)  # the same five bytes, from the board