Values and types
Every Python value has a type, but you never declare it. An int grows as large as it needs, so it never overflows the way a uint8_t does. And text (str) is not the same as the raw bytes a UART sends.
Numbers without overflow
Python has no uint8_t or int32_t. An int has arbitrary precision: 2 ** 64 just works. That is convenient, but firmware arithmetic wraps. When you model what a register or counter on the chip would hold, mask the result yourself. A float is a 64-bit double, like C's double, with the same rounding surprises.
reading = 300 # an int: as big as it needs to beprint(reading, reading & 0xFF) # & 0xFF keeps what a uint8_t would holdprint(2 ** 64)print(0.1 + 0.2)print(type(3.3), type("ok"), type(b"ok"))
300 44 18446744073709551616 0.30000000000000004 <class 'float'> <class 'str'> <class 'bytes'>
Collections hold many values. A list such as [3.31, 3.30] grows as you append readings. A tuple is a fixed group, handy for a (time, value) pair. A dict maps keys to values, like register names to offsets: {"CTRL": 0x00, "STATUS": 0x04}. You will use all three when you parse logs later.
Text is not bytes
A str holds text: Unicode characters. A bytes object holds raw 8-bit values, which is what a serial port, a file or a socket delivers. Convert between them on purpose: encode() turns text into bytes, and decode() turns bytes into text. Indexing bytes gives you an int, not a character.
command = "AT\r\n"raw = command.encode("ascii")print(raw, len(raw), raw[0])print(raw.decode("ascii").strip())
b'AT\r\n' 4 65 AT