Skip to content

MicroROS on Espressif #9955

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 25 commits into from
Jun 11, 2025
Merged

MicroROS on Espressif #9955

merged 25 commits into from
Jun 11, 2025

Conversation

hierophect
Copy link
Collaborator

@hierophect hierophect commented Jan 12, 2025

This PR adds MicroROS as a board-specific option to the Espressif port. The Robot Operating System (ROS and successor ROS2) is kind of a misnomer: it is not an operating system, but it is a very commonly used collection of middleware and type standards for different robot parts to talk to each other. MicroROS is a tiny implementation of the ROS API that lets embedded systems talk to ROS system on a linux machine, such as a big computer running perception, kinematics or machine learning tasks.

The goal of this project is to allow Espressif Circuitpython boards to recieve and transmit data over WIFI to a computer running ROS, to publish sensor data, take in motor commands, and other tasks. I'm hoping it'll be useful to experimenters who want to learn about ROS without having to dive too deep into embedded systems.

Implementation notes (edited 4/5/25):

  • Module is rclcpy, after the standard ROS2 python API rclpy (RCL: Ros Client Library)
  • I'm attempting to follow rclpy where possible, excluding features that aren't supported by MicroROS.
  • ROS is a big project with lots of message definitions, build tools, and build output. MicroROS uses many of these tools when building the embedded version, which results in extra python requirements and lots of excess build output. To spare the Circuitpython CI this build complexity/load this module uses pre-built libraries in a separate repository.
  • Work Log:
    • Hardfault at init
    • MicroROS initialization failure, getting more informative debug output.
    • Reset support
    • Inline documentation

Will resolve #8139

@hierophect hierophect added the espressif applies to multiple Espressif chips label Jan 12, 2025
@hierophect hierophect added this to the Long term milestone Jan 12, 2025
Copy link
Member

@tannewt tannewt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where do these microros-ext files come from? Can you submodule them instead?

@hierophect
Copy link
Collaborator Author

hierophect commented Jan 13, 2025

I agree we should submodule somehow - microros-ext is sourced from https://github.com/micro-ROS/micro_ros_espidf_component, which I avoided including directly due to the additional requirements in the idf virtual environment, pip3 install catkin_pkg lark-parser colcon-common-extensions empy==3.3.4, and the extended build process caused by colcon fetching all the message type packages. I didn't want them clogging up Circuitpython's CI process, but @dhalbert suggested it might not be a big deal to include those directly, as long as the component isn't enabled willy-nilly. I'm happy to either include it directly and refactor the build system changes, or make a submodule of the precompiled version to use instead.

@tannewt
Copy link
Member

tannewt commented Jan 14, 2025

I don't understand the build stuff you are referring to. A README.md in microros-ext would be helpful in explaining where it came from and how to update it.

@hierophect
Copy link
Collaborator Author

I don't understand the build stuff you are referring to.

Sorry, let me say that in a less confusing way. Compiling the MicroROS component installs a bunch of python package managers, is slow, and has a lot of verbose build output. To avoid adding that to Circuitpython's CI tests, I made a pre-compiled version, which is what I've included. I've recieved feedback already that that might be less easy to maintain, so I could revert back to including the original component as a submodule if you feel this feature being more futureproofed would be worth a small performance hit to the CI. Which would you prefer?

@tannewt
Copy link
Member

tannewt commented Jan 16, 2025

How about splitting the difference and making a separate repo for the precompiled files? That way it isn't in CP but it doesn't require building every time.

@furbrain
Copy link

I'd just like to comment that this looks awesome, having ROS on CircuitPython could open up lots of opportunities. I'm currently thinking of making a CircuitPython sensor -> ROS factory utility class - so you could just set up your sensor, pass it into the class and it starts publishing on an appropriate topic - so an accelerometer+gyro would start publishing IMU messages for example.

@hierophect
Copy link
Collaborator Author

hierophect commented Jan 19, 2025

a separate repo for the precompiled files?

Ok, this sounds good to me. I can maintain the repo and we can revisit if another solution becomes better. I'm out for 1 week starting tomorrow but I'll implement this and the shared-bindings as soon as I get back.

@hierophect
Copy link
Collaborator Author

hierophect commented Apr 3, 2025

I'm currently thinking of making a CircuitPython sensor -> ROS factory utility class - so you could just set up your sensor, pass it into the class and it starts publishing on an appropriate topic

I've just implemented the first bit of the shared-bindings layer so I'd love to hear more about what you meant by this. I'm currently basing the module API off of rclpy, but we could make a python wrapper library for other tasks.

@furbrain
Copy link

furbrain commented Apr 3, 2025

@hierophect I've actually implemented the adaptor using circuitpython on a raspberry pi. It should work on ROS1 and ROS2 - but I've only tested on ROS1.

The code is here and a usage example is here

It basically allows you to create any sensor that complies with the adafruit sensor conventions - simply create a normal sensor and then call ROSAdapter.create_from_sensor with that class as an argument.

That wraps the sensor in a class and allows you to publish the sensor data. I should probably get around to publishing this properly, not buried in a repo with a bunch of other stuff.

@hierophect
Copy link
Collaborator Author

Getting a hardfault on init, which probably means there's an issue with the microros allocator overrides I provided using the micropython allocator tools (m_alloc, m_free etc). If anyone's willing to lend an eyeball to see if there's something grossly wrong there it'd be very helpful.

tannewt
tannewt previously requested changes Apr 7, 2025
@dhalbert
Copy link
Collaborator

dhalbert commented Apr 8, 2025

What is the reason for calling it rclcpy vs microros or similar?

@tannewt
Copy link
Member

tannewt commented Apr 8, 2025

rclcpy

That's what the ROS2 CPython version is called: https://github.com/ros2/rclpy

@hierophect
Copy link
Collaborator Author

I'd be ok with either. Originally I was naming the whole thing microros but after deciding to follow the rclpy API in terms of implementation I felt rclcpy was more in line with Circuitpython conventions for adapting existing Cpython modules.

@dhalbert
Copy link
Collaborator

dhalbert commented Apr 8, 2025

Got it. I missed the rclpy reference above, and thought cpy was for CircuitPython, not CPython. I ahve to say, though, that rcl standing for ROS client library is quite obscure.

@hierophect
Copy link
Collaborator Author

hierophect commented Apr 9, 2025

Fixed some lost changes that should have been in the last commit, I think I missed adding changes and reset them by accident. Hence the mismatched macros etc.

I'm having no luck even calling rclc_support_init(&_my_default_support, 0, NULL, &_my_default_allocator); with the default allocation settings, as an isolated call with no followup. Is there any kind of memory protection I need to be taking into account here? It seems like other modules use m_malloc, etc, without much issue.

@tannewt
Copy link
Member

tannewt commented Apr 10, 2025

I'd suggest getting a backtrace via a DEBUG=1 build. That should explain further why it is crashing.

m_malloc will crash if the VM isn't active but if you are doing an import rclcpy then it should be.

@hierophect
Copy link
Collaborator Author

Will DEBUG=1 override the Circuitpython USB with the esp monitor? I was looking around for docs on that but didn't come up with anything initially. Currently trying a new build with the Espressif Devkit C.

@hierophect
Copy link
Collaborator Author

hierophect commented Apr 10, 2025

Whoops, looks like this could actually be a wifi coordination issue:

assert failed: tcpip_send_msg_wait_sem /IDF/components/lwip/lwip/src/api/tcpip.c:449 (Invalid mbox)

This is actually the kind of problem I expected, just not for it to manifest as a hardfault. I'm glad it's not a problem the allocator.

Edit: can confirm hardfault goes away when wifi.radio is initialized properly first.

@tannewt
Copy link
Member

tannewt commented Apr 14, 2025

Will DEBUG=1 override the Circuitpython USB with the esp monitor? I was looking around for docs on that but didn't come up with anything initially. Currently trying a new build with the Espressif Devkit C.

No, it puts the IDF logging out the default UART.

@hierophect
Copy link
Collaborator Author

Might be stuck on some ROS issues for a little bit:

  • microros initialization is failing, with a generic error code (error 1). Could be for any number of reasons, but it's hard to tell because microros logging is not printing when set up with rcutils_logging_set_output_handler(my_custom_log_handler);. Might need to double-check if I'm missing a build setting on the pre-built library and recompile.
  • ESP JTAG gives limited visibility because I don't have debug symbols in the library, also a reason to recompile.
  • Still have to make sure that I'm bringing in all the appropriate SDKConfig settings microros expects, but it doesn't make sense to focus on this until the above two bullets are done.

@hierophect hierophect requested review from tannewt and dhalbert May 31, 2025 21:19
@hierophect
Copy link
Collaborator Author

If anyone's interested in trying this out, this test script includes the basic functionality of this PR:

import os, wifi, time
import rclcpy
time.sleep(2) # give USB a bit of time to see first session
print("connecting...")
wifi.radio.connect(ssid=os.getenv('CIRCUITPY_WIFI_SSID'),
                   password=os.getenv('CIRCUITPY_WIFI_PASSWORD'))
print("my IP addr:", wifi.radio.ipv4_address)

rclcpy.init("<IP ADDRESS>", "8888")
mynode = rclcpy.Node("foo")
print(mynode.get_name())

mypub = mynode.create_publisher("bar")
print(mypub.get_topic_name())
mypub.publish_int32(42)

The ROS agent can be run without a native ROS2 installation using Docker.
Linux:

docker run -it --rm --net=host microros/micro-ros-agent:foxy udp4 --port 8888 -v6

Mac:

docker run -it --rm -p 8888:8888/udp microromicro-ros-agent:foxy udp4 --port 8888 -v6

You'll need to obtain the appropriate IP address with ip addr show/ifconfig, respectively.

Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks clean!

It looks like you chose a couple of boards to enable this on because they were what you had on hand. Is that right? Ultimately, there could be many more, but we might just suggest that people do a rebuild themselves. I am not sure we want eventually dozens of boards with and without ROS.

@hierophect
Copy link
Collaborator Author

hierophect commented Jun 5, 2025

It looks like you chose a couple of boards to enable this on because they were what you had on hand. Is that right? Ultimately, there could be many more, but we might just suggest that people do a rebuild themselves. I am not sure we want eventually dozens of boards with and without ROS.

Thanks for the review! I have lots of ESP32 boards but picked these two specifically because the DevkitC is a nice standard option for the ESP32S3 with lots of debugging options, and the Cardputer has some immediate utility in a ROS system as a robot controller. I figured I'd do one more follow-up PR for one of the Adafruit ESP32S3 feathers to tap into the feather ecosystem, and then recommend that people either compile new boards themselves or make a request.

@hierophect
Copy link
Collaborator Author

And yes micro-ros works on ESP32, ESP32-S2, ESP32-S3, ESP32-C3 and ESP32-C6, and so could be enabled on almost every Circuitpython espressif board, but takes up extra flash space.

@hierophect
Copy link
Collaborator Author

I've discovered that rcl_node_fini has actually been returning some form of error code, but does not impact performance - subsequent runs of circuitpython and Micro-ROS proceed fine despite whatever's going on. Since it doesn't impact the functionality of the module I'm wondering if I can leave this for another PR? I've left the debug messages in there for now.

@hierophect hierophect requested a review from dhalbert June 9, 2025 22:18
Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi - one stray file, and some ESP_LOGW() to deal with.

@hierophect
Copy link
Collaborator Author

Making a more complete note here that this PR has a known issue where Micro-ROS is unable to clean up the Node and Publisher objects without returning an internal error code. This doesn't currently inhibit the module in any known way, as it soft restarts and both creates and uses new objects of both types just fine, but I'm leaving in the associated catches and internal (ESP monitor) debug messages while investigate further. Since this occurs during soft-reboot, it's not practical to print this information to circuitpython itself using mp_printf.

@hierophect hierophect requested a review from dhalbert June 11, 2025 22:32
Copy link
Collaborator

@dhalbert dhalbert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright! I'll merge. This will create some new boards for circuitpython.org, I think, and so it would be good to submit a PR for the new boards to https://github.com/adafruit/circuitpython-org, assuming that happens.

@dhalbert dhalbert dismissed tannewt’s stale review June 11, 2025 22:36

Should be addressed now

@hierophect
Copy link
Collaborator Author

Alright! I'll merge. This will create some new boards for circuitpython.org, I think, and so it would be good to submit a PR for the new boards to https://github.com/adafruit/circuitpython-org, assuming that happens.

Hooray! Should I do that now, or wait until I have some Readme content up with examples on how to use it?

@dhalbert dhalbert merged commit e1a6d0b into adafruit:main Jun 11, 2025
17 checks passed
@hierophect hierophect deleted the esp-microros branch June 11, 2025 22:42
@dhalbert
Copy link
Collaborator

The new boards won't show up on circuitpython.org until I do the next 10.0.0 alpha release. Here's an example of multiple builds for one board: https://circuitpython.org/downloads?q=playground

@hierophect
Copy link
Collaborator Author

Gotcha, I misunderstood. Sure, I can put that together.

@hierophect hierophect added the rclcpy Micro-ROS client module label Jun 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
espressif applies to multiple Espressif chips rclcpy Micro-ROS client module
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ROS2 networking via MicroROS
4 participants