It’s generally the XBee mode against how you’re reading the data, not Python being strange.
First and foremost, you should avoid reading raw serial, such as pyserial, while using the digi-xbee library when the module is in API mode.readline(). Since API frames are binary rather than line-based, treating them as text would undoubtedly result in “weird characters.” That’s to be anticipated. Let the library decode the frames for you by using the add_data_received_callback() or read_data() methods.
In the event that the XBee is operating in transparent (AT) mode:
Verify that the baud and parity/stop bits are agreed upon by both parties.
Many XBee payloads are simply raw bytes, so don’t assume they include newline-terminated data.
read bytes rather than strings, and only decode after you are familiar with the format.
It is also worthwhile to verify:
(0 = transparent, 1/2 = API) is the AP parameter. 90% of these problems are caused by mixing this up.
If you don’t parse frames, escaping (API mode 2) may seem particularly jumbled.
To avoid decoding outdated information, use buffering by using flushInput() before reading.
Printing repr(data) rather than decoding to utf-8 is a simple sanity check in Python. It’s binary payloads, not text, if it all of a sudden “makes sense.”
In summary, the ability to execute AT commands does not imply that your data route is text-friendly. If you’re in API mode, match your read method to the XBee mode and let the digi-xbee library handle frames.