Sending data over digimesh from Circuit Python

Hi,

I am trying to send data from an ESP-32 running Circuitpython over a mesh network of XBEE PRO 900 S3Bs and then receive it on a PC connected to another XBEE PRO 900. Normal Python the PC side and I can certainly receive packets and interpet data when generating packets on the remote XBEE using the frame generator.

The issue is that I can’t completely seem to figure out the circuitpython to make the packets completely correct. The XBEE python library doesn’t have a circuit python port. I only ever need to send data from the ESP-32 and it is simple numerical values so… real simple.

Here’s my circuit python:
code START

import busio
import board

uart = busio.UART(board.TX, board.RX, baudrate=9600)

def calculate_checksum(frame_data):
“”“Calculate the checksum for a given frame.”“”
return 0xFF - (sum(frame_data) & 0xFF)

def escape_byte(byte):
“”“Escape a byte if necessary.”“”
escape_bytes = [0x7E, 0x7D, 0x11, 0x13]
if byte in escape_bytes:
return bytes([0x7D, byte ^ 0x20])
else:
return bytes([byte])

def escape_frame(frame_data):
“”“Escape special bytes in the frame data.”“”
escaped_frame = bytearray()
for byte in frame_data:
escaped_frame.extend(escape_byte(byte))
return escaped_frame

def send_message(message, dest_address):

if len(dest_address) != 16:
    raise ValueError("Destination address must be a 16-character hexadecimal string")

# Frame type 0x10 (Transmit Request), Frame ID 0x01 (to receive ACK)
frame_data = bytearray([0x10, 0x01])
# 64-bit dest_address
frame_data += bytes.fromhex(dest_address)
# 16-bit network address (unknown or broadcast), broadcast radius, options
frame_data += bytes([0xFF, 0xFE, 0x00, 0x00])
# RF data (payload)
frame_data += message.encode()
# Calculate checksum
checksum = calculate_checksum(frame_data)
# Append checksum to get the complete frame data before escaping
complete_frame_data = frame_data + bytes([checksum])
# Escape the frame data
escaped_frame = escape_frame(complete_frame_data)
# Frame delimiter and length (excluding delimiter, length, and checksum bytes)
final_frame = bytes([0x7E]) + len(frame_data).to_bytes(2, 'big') + escaped_frame
print(len(frame_data).to_bytes(2, 'big'))
# Send the frame
uart.write(final_frame)
print(final_frame)

dest_address = “000000000000FFFF” # Broadcast address for example
send_message(“hello”, dest_address)

code END

If I print the final frame I get : b’~\x00\x13\x10\x01\x00\x00\x00\x00\x00\x00\xff\xff\xff\xfe\x00\x00hello\xdf’

But on my receive SBEE side I receive " 7E 00 23 90 00 7D 33 A2 00 42 3F 49 F6 FF FE C2 7D 5E 00 7D 33 10 01 00 00 00 00 00 00 FF FF FF FE 00 00 68 65 6C 6C 6F DF AB" and a little extra ascii… ~ÿÿÿþhelloß

Any ideas?

why don’t you put the sensor side in transparent mode (AP0) and then simply feed it the raw data you want to send. The sensor radio will send it to what ever modules (SL and SH) address matches the DL and DH values of the sensor module.

Because I need to collect data via i2c on the remote side so I have it interfaced to an ESP32

Just because you are interfacing with an ESP32 does not mean that the remote radio connected to the ESP32 needs to be in API mode. It can simply be in transparent mode. In this mode, you would only need to feed it serial data at the given baud rate. The radio will transmit that data over to the Coordinator and output it in either API mode (If enabled) or in transparent mode.

I’ve tried that and it works well until I have had more than one sensor/digi transmit at the same time and I have had a few collisions on the local side. I also really like the digi python module on the local side.

Is there no way to use API mode to get all the benefits of that mode? It seems as though I am super close with the code to do this? I’d by happy to share it on github once complete.

This sounds like you are sending the data too often. Also, the ASCII output of ~ÿÿÿþhelloß is only the ASCII values for the Hex characters sent.

So - Is my frame/packet in the wrong order? Or have I calculated something wrong like length or checksum? Is there a guide that explains the exact frame/packet format?

If your frame was an invalid frame, then you would not receive anything. It is either everything or nothing.

If you are not expecting the data to be the yybhellob, then I would tend to think it is more of you not converting the ASCII data to hex properly and back.

I am using XCTU to decode the frame. If I use the digipython library it throws an error so I’m guessing something is wrong in my frame? Is there no guide on how to construct a digimesh frame/packet?

I would suggest using the frame generator and decoder functions in XCTU to verify your frame is valid.

For anyone looking for the material if they are following my post it is available here: DigiMesh supported frames

I’ve been making progress but now I’m stuck… I’ve been trying to write circuitpython code to send data over xbee digimesh using APv2 (with escapes). For the life of me, I cannot seem to calculate the frame length. I am happy to share all of this code when I am finished. Here is what I currently have:

import busio
import board
import digitalio
import time

Initialize UART for XBee communication

uart = busio.UART(board.TX, board.RX, baudrate=9600)

def escape_data(data):
“”“Escape special bytes in data.”“”
escaped_data = bytearray()
for byte in data:
if byte in [0x7E, 0x7D, 0x11, 0x13]:
escaped_data += bytearray([0x7D, byte ^ 0x20])
else:
escaped_data.append(byte)
return escaped_data

def calculate_checksum(frame_data):
“”“Calculate frame checksum.”“”
return 0xFF - (sum(frame_data) & 0xFF)

def create_frame_and_send(dest_address, message):
“”“Create and escape a transmit request frame, then send it over UART.”“”
frame_type = 0x10
frame_id = 0x01
dest_16_addr = bytes.fromhex(“FFFE”)
options = bytes([0x00, 0x00]) # No options, placeholder for RF data

Construct the frame data excluding the checksum

frame_data = bytearray([frame_type, frame_id]) + bytes.fromhex(dest_address) +
dest_16_addr + options + message.encode(‘utf-8’)

Calculate checksum for the unescaped frame data

checksum = calculate_checksum(frame_data)

Escape frame data (excluding checksum)

escaped_frame_data = escape_data(frame_data)

Append escaped checksum since checksum itself needs escaping if it contains reserved characters

escaped_checksum = escape_data(bytearray([checksum]))

Calculate length from escaped frame data

length = len(escaped_frame_data).to_bytes(2, ‘big’)

Assemble final frame with start delimiter, length, escaped frame data, and escaped checksum

final_frame = bytearray([0x7E]) + length + escaped_frame_data + escaped_checksum

Sending frame over UART

uart.write(final_frame)
print(“Frame sent:”, final_frame.hex().upper())

Direct usage example

dest_address = “0013A200423F49FB”
message = “cool beans”
create_frame_and_send(dest_address, message)

My issue is that this code generates:

7E 00 19 10 01 00 7D 33 A2 00 42 3F 49 FB FF FE 00 00 63 6F 6F 6C 20 62 65 61 6E 73 A1

But the XCTU frame generator says it needs to be:

7E 00 18 10 01 00 7D 33 A2 00 42 3F 49 FB FF FE 00 00 63 6F 6F 6C 20 62 65 61 6E 73 A1

Looks like you are calculating the length bytes wrong.