So, you can run firmware(MicroPython Project) and software(Python Project) concurrently(same time).

So, you can run firmware(MicroPython Project) and software(Python Project) concurrently(same time) on an XBee3 network? Like if I turn on an xbee that has micropython uploaded to the device (firmware) that runs automatically when powered on, and at the same time I run my main Python program that has all the GUI buttons and functionality already developed… is that fine/good/normal/ok?


I have an XBee3 network where my GUI developed in Python allows you to press buttons and stuff (widgets) which run functions that turn on/off pins on the XBee3 chip. I ran out of I/O so I attached a slave device MCP23017 i2c I/O expander on an i2c bus facilitated by each XBee remote router module acting as the master. (Each router module is the master to its respective io expander slave on its own i2c bus)

So the i2c interface on the XBee3 is accessible via MicroPython. Separately, once an XBee3 module is powered on, MicroPython runs on the chip in a way that sends the coordinator an AT command telling the coordinator that router module’s MAC address so then the coordinator can reply back with a configuration profile that the router must apply to itself (the powered on router reconfigures depending on where it is plugged in).

All this works as long as:
1.) Python program and MicroPython program both may run same time.
2.) sending and receiving back and forth between micropython and python is fine and good.

Anything critically flawed with my understanding here?

thank you very much.



Absolutely, that is fine.

You really can think of MicroPython running on an XBee 3 device completely separately from any programs you run on a host device (such as Python in your case). The XBee 3 device is completely agnostic to what kind of program is communicating with it.

You could do any of the following:

  • Have no MicroPython program on any nodes of the network, and do all the programming on one or more hosts. (This is the only option when using just older XBee models that do not support MicroPython programmability.)
  • Have no programs talking to the XBee 3 at all, and run only MicroPython on nodes in your network. (That wouldn’t be a very useful network without a way to get data/messages out to some host, but nothing’s stopping you.)
  • Run MicroPython on one or more XBee 3 nodes, and use Python/Java/C/C++/any programming language (or manual entry of API frames if you’re desperate!) to manage communicating with the XBee 3. You can use User Data Relay API frames to communicate between MicroPython and the serial port.

Hope this helps.


this is very helpful. thanks!

2 further questions about this:

1.) But if you have a micropython program flashed onto a node(s), any node running micropython MUST be in REPL mode (AP=4) to run said MicroPython, no??? And if that is correct then… yeah I’ve had a tough time finding good enough documentation that discusses this issue… like if you are in REPL mode… then I cant run my python program because the nodes need to be in API mode… I think… and then switching between REPL and API mode would stop your flashed MicroPython from running?.. and there is no way to, in middle of program, restart the micropython without powercycling or whatever…?
don’t the modules in the network have to be in API mode (AP=1) for a network to be um… like a network?

2.) I am using an i2c io expander slave attached to the xbee(master). So, MUST I use MicroPython for this i2c or should a python i2c library type thing work fine instead?

This would require like being in REPL mode for the micropython to work? but then in API mode for the network to work? Apologies for my confused understanding here.

I guess in short, after you flash/upload your firmware (MicroPython program)… does it if you set the device to API mode (AP=1) and enable auto-run at start-up (PS=1)… does the firmware still run and does it stay in API mode? (like does setting PS=1 do anything funky in the background and switch API mode to REPL mode?.. lol rambling! Does the flashed micropython run if in API mode or does the device need to be in REPL mode all the time?

Helping me straighten out these confusions would be much much appreciated. thanks!


If PS=1 then when the XBee turns on, the MicroPython program will automatically start and begin running “in the background”. The XBee can be in API mode, transparent, or REPL mode; the program will run either way. One thing to watch out for is any unintentional calls to functions like input() or which can block, since those will only receive input when the XBee is in REPL mode.

For an I2C I/O expander like you describe, in order to communicate with it you will need to use MicroPython. I am not aware of any standard-Python libraries that would be compatible - in order to use I2C on an XBee you will do from machine import I2C etc.

Since Python and MicroPython are mostly compatible languages, you COULD find or write a library that is compatible with both XBee 3 (or other MicroPython devices) and Python. But in reality it’s best to just stick with finding or writing a MicroPython library or program for that.

Hopefully that all makes sense.

ok. interesting. thank you kindly. do you mind checking my code here.

ok… so
I upload the firmware code in ATAP=4, PS=1. Consistently, when I press reset on the device… it auto-runs, sends to coordinator into a running Python program where there is a data reception callback function waiting for this message from the device with uploaded micropython. I see what I expect. it works!

then I go in xctu, switch the device to ATAP=1 (API mode), PS still = 1. then I press reset on the device. nothing… I don’t any confirmation that the Python callback function received the message.

then I go back into xctu, set back to REPL (ATAP=4). press reset on the device. works great again (I see what I want in the python print outs and the NI gets reassigned).

here is the code (micropython sample import… almost no changes) again, in REPL mode (ATAP=4) it clearly does what I want, then doesn’t cooperate once I switch ATAP=1… then back to working when I set ATAP back to 4.

Do you mind checking my micropython code to see if there are any obvious things im doing that would cause the blocking issue or something like you mentioned that would make this fail to successfully auto-run in API mode vs successful auto-run in REPL mode? thanks!


import sys
import xbee
import time

TODO: replace with the node identifier of your target device.

filler = “don’t mind me”

while xbee.atcmd(“AI”) != 0:

def find_device(node_id):
Finds the XBee device with the given node identifier in the network and
returns it.

:param node_id: Node identifier of the XBee device to find.

:return: The XBee device with the given node identifier or ``None`` if it
    could not be found.
for dev in
    if dev['node_id'] == node_id:
        return dev
return None

Find the device with the configure node identifier.

device = find_device(TARGET_NODE_ID)
if not device:
print(“Could not find the device with node identifier ‘%s’” % TARGET_NODE_ID)

addr16 = device[‘sender_nwk’]
addr64 = device[‘sender_eui64’]

print(“Sending data to %s” % TARGET_NODE_ID)

micropython.mem_info([verbose])… SUPPOSE TO PRINT OUT how much memory is being used or something?

# Some protocols do not have 16-bit address. In those cases, use the 64-bit one.
xbee.transmit(addr16 if addr16 else addr64, filler)
print(“Data sent successfully”)
except Exception as e:
print(“Transmit failure: %s” % str(e))

thanks again!

Hi edunn106,

Can you confirm what XBee 3 firmware you are using (Zigbee, 802.15.4, DigiMesh) and which firmware version (ATVR)?

It seems you may have found a bug with and AP=1. I have reported your findings internally.

As a workaround, I think you can just send a message to address 0x0 (the coordinator), assuming your goal is to find the network coordinator. This would not require a discovery operation.

How do you know/think this is a bug?

Do you work for digi?

I am using ZigBee, all devices are using 100A firmware version.

Lastly, no im not really trying to find the coordinator. The coordinator will already be plugged in and a python program would be running that is waiting to receive a message. The message it is waiting for is what the code above is intended for. Each router would have that code uploaded. If you grab any router module at random and plug it in, upon start-up the code I posted above would run and the router would send a message containing it’s MAC address to the coordinator. Then the Python program would receive that message through a data reception callback function, take the MAC address and assign that MAC’s device a new Node Identifier (NI) depending on some stuff.

The work around I am presently working on is as follows:

I am setting Join Notification (JN) to enabled (1) on each router. so when you plug in a router it sends an API packet to all the network nodes. Right now im trying to figure out how to catch this message and extract the MAC address out of that message, which is a series of bytes. If this works then that’s great… but I would still need to use the code above to send i2c bus device information that might be attached to the xbee routers.

Yes I am a Digi employee.

At this point I think you should contact Digi Technical Support at for more assistance.

ok will do.

how do you think/know it is a bug? just because it should work?

also why the redirection to digi support? do you mean just for more general assistance or specifically about this bug / about this specific matter of questions?.. like what should I ask that’s different than this forum thread?

also thank you very much for your assistance.

Yes, I would expect it to work, but from your experience and a quick look at the firmware code it appears it does not.

This support forum is more or less community driven (some of the community being Digi employees). Tech support should be able to assist you more directly with this and other specific questions.

You are welcome.

the “from my experience part” is actually highly probable that I am doing something fundamentally wrong lol. but ok cool.

If the tech support line is more helpful/available than this forum then I will be super in luck.


One more thing if you’re willing…

do you know, in Python, how to get a specific segment of a received Join Notification frame?

I know its in here somewheres:

In consoles working mode with the serial connectioned opened between router and coordinator I see the many bytes that were sent from me plugging in a router module with JN=1 set. just need to grab it in python and extract the MAC address… data reception callback function?.. some magical phrase I should whisper?

Yes, you can use add_packet_received_callback to get told about every frame output by the XBee. You can then get the information from there.

Any questions specifically about the xbee-python library can also be asked on the library’s GitHub: That way the developers in charge of the library can respond. You can also search past issues there in case a question has already been asked.

awesome, I got that to work. At least primitively. I just print out this ugly cluster of the packet that it sends from the join notification. its a dictionary of crud. the bytearray is sent in right to left bit index format.

for this I just had it print out whatever packet it sent through the callback function parameter.

do you happen to know of any “prettier / easier” way to print out or work with the packet that it sends? instead of this raw dictionary thing?


question about that add_packet_received_callback:

if a packet is received does it trigger that functrion no matter what? im trying to make it trigger on the first packet (the one I am interested) but instead it triggers there and then now any time I do anything that requires a packet sent in the background, it gets triggered and the callback does its thing. I don’t want that.

Yes it triggers no matter what. If you want to disable the callback you should be able to use del_packet_received_callback ( ). Otherwise you could write your code to ignore calls/return immediately if you don’t care about the packet.

yeah, that’s what im trying to implement.

I tried to basically have this flow but doesn’t quite work out yet:


which goes to:

def my_packet_received_callback1(xbee_packet):

which, I thought, should delete the callback and then send you to:

def my_packet_received_callback(xbee_packet):

which finally manipulate the received packet… and then recalls the function that the initial callback_add trigger while loop is in, thereby awaiting another join notification packet (which is my intention after im done manipulating the first reception event).

does that make sense to you? because my issue was that I would receive the packet through the callback trigger, then hops me to the function, but then when I set other configs for the an xbee of course that would prompt packets sends that would trigger the callback again…

so through the above code I was trying to hide/delete the callback trigger so I could just get the first packet and then manipulate it and then when done recall the function that reimplements the callback trigger.
but its not quite right.

at first I was deleting the callback right after the callback_add In the while loop but of course the callback_add would send you to the callback function through the passed parameter…

so then I tried to delete the callback in the function that the callback_add passed parameter would send you to… but then that’s weird cuz the callback_del has the same function passed as the parameter lol… so hence the code flow I posted above:
callback_add -> callback function that deletes then passes on the xbee_packet to the next function that manipulates the xbee_packet

any ummm obvious terribleness?


I think you should submit that as a question/issue on the library’s GitHub page ( ) so the library developers can assist you.



It looks like on XBee3 Zigbee doesn’t work when ATAP != 4. That’s a bug that isn’t present in the 802.15.4 and DigiMesh firmware. It’s also possible that xbee.receive() won’t work either. There are firmware issues with selecting where to deliver the frames.