This project implements the classic ROS "Hello World" publisher and subscriber, but with a few twists. First, this project is using the newer ROS2, as opposed to the more battle-tested ROS1. Second, it's written in Rust. Rust is a systems programming language like C++, but it's not commonly used with ROS. Lastly, the publisher and subscriber are compiled to WebAssembly (Wasm). They are executed in a non-web embedding of a Wasm runtime.
This project attempts to combine ROS2, Rust, and Wasm, to demonstrate the deployment of robotic Wasm applications to a ROS2-enabled Wasm runtime. This is an extremely early stage demo, with unstable pre-alpha dependencies. I can't recommend using this in a production environment. But nevertheless, I think it's an interesting concept with great potential.
ROS2 Wasm App: ros2_wasm_app_rust
and ros2_wasm_app_cpp
ROS2 Wasm Host: ros2_ws/src/ros2_rust_wasm
The project is basically split into two parts, the app and the host. The app is a program that is compiled to Wasm. It is written against a specific simplified ROS2 Wasm API, which the host then needs to provide during the execution of the app. The host is a native program embedding a non-web Wasm runtime, and provides the simplified ROS2 Wasm API to the app. Because the host is essentially a ROS node, it needs to be built as part of a ROS workspace.
The project also has a few dependencies that are included as Git submodules:
ROS2 Rust Client: ros2_ws/src/ros2_rust
I'm using Gérald Lelong's lelongg/ros2_rust
Crystal branch for the ROS2 Rust
Client. The upstream ROS2 Rust Client doesn't work with Crystal and has not
accepted the corresponding pull request yet.
Wasmer: extern/wasmer
I'm using Wasmer for the non-web Wasm runtime. I'm using my own fork, which allows for combining the ROS2 imports with Emscripten imports. That is not currently possible in the master branch. Unfortunately my fork is already out of date and will not be merged upstream. Wasmer is a very exciting project and is under heavy, active development.
Emscripten: extern/emsdk
Emscripten is a pretty mature project, it's basically a C and C++ compiler that
targets Wasm. I'm using it to compile the C++ Wasm app. Emscripten usually
targets web browsers, but can be made to work with Wasmer. See the Makefile
in
ros2_wasm_app_cpp
for details on how I do that.
If you haven't install Rust yet, you can do that easily with rustup
!
# See also: https://rustup.rs/
curl https://sh.rustup.rs -sSf | sh
You may also need to enable the wasm32-unknown-unknown
target.
rustup target add wasm32-unknown-unknown
You will also need to install Clang, ros2_rust
requires it:
sudo apt-get update
sudo apt-get install clang-6.0
You will need to install ROS2. I recommend following the instructions to install Debian packages. Here's some summary commands:
sudo sh -c 'echo "deb [arch=amd64,arm64] http://packages.ros.org/ros2/ubuntu `lsb_release -cs` main" \
> /etc/apt/sources.list.d/ros2-latest.list'
curl http://repo.ros2.org/repos.key | sudo apt-key add -
sudo apt-get update
sudo apt-get install ros-crystal-desktop python3-colcon-common-extensions
You will need to initialize and update all of the submodules in this repo. This will get the ROS2 Rust Client, Wasmer, Emscripten, etc, setup in the correct locations. The following command will do that:
git submodule update --init --recursive
Once that's done, you need to setup Emscripten:
cd extern/emsdk
./emsdk update
./emsdk install latest
./emsdk activate latest
(Note that the emsdk
submodule will appear "dirty" after setting up
Emscripten, but you should just ignore that.)
You will need to build the ROS2 workspace with Colcon.
cd ros2_ws
source /opt/ros/crystal/setup.bash
colcon build --packages-up-to ros2_rust_wasm
You can then build the Rust apps:
cd ros2_wasm_app_rust
cargo build --release
cd ros2_wasm_app_rust_subscriber
cargo build --release
And finally the C++ app:
cd ros2_wasm_app_cpp
source ../extern/emsdk/emsdk_env.sh
make
First start the subscriber:
cd ros2_ws
source install/setup.bash
# Run the Rust subscriber in Wasm!
ros2 run ros2_rust_wasm ros2_rust_wasm -w \
../ros2_wasm_app_rust_subscriber/target/wasm32*/release/ros2_wasm_app_rust_subscriber.wasm &
Then run either of the publisher apps:
cd ros2_ws
source install/setup.bash
# Run the Rust publisher in Wasm!
ros2 run ros2_rust_wasm ros2_rust_wasm -w \
../ros2_wasm_app_rust/target/wasm32*/release/ros2_wasm_app_rust.wasm
cd ros2_ws
source install/setup.bash
# Run the C++ publisher in Wasm!
ros2 run ros2_rust_wasm ros2_rust_wasm -w \
../ros2_wasm_app_cpp/build/ros2_wasm_app_cpp.wasm
You should get output similar to this:
[INFO] This is NOT an Emscripten module!
[TRACE] rn_get_default_context
[TRACE] rn_create_node (0)
[TRACE] rn_create_subscription (0)
[TRACE] rn_node_spin
...
[INFO] This is NOT an Emscripten module!
[TRACE] rn_get_default_context
[TRACE] rn_create_node (0)
[TRACE] rn_create_publisher (0)
[TRACE] rn_std_msg_string_default
[TRACE] rn_std_msg_string_set_data (0)
[TRACE] rn_publish (0, 0)
[TRACE] rn_thread_sleep
[TRACE] rn_std_msg_string_get_data_len (0)
[TRACE] rn_std_msg_string_get_data (0)
[TRACE] rn_log
[LOG] Hello Rust ROS! 0
...
Wasm allows for language agnostic apps. There are two apps, written in two different languages, written against the same API and targeting the same host. The apps are portable. They only need to be compiled once; they can run wherever the host can. Additionally, the apps are sandboxed. We can choose to expose only a very specific API, block syscalls, etc. There is also the potential to recover gracefully from app crashes.