Bringing Kiwi to Sim

Keywords: #Ros2 #KIWI #Gazebo #Docker #OpenScad

In our last post, we blocked out a primitive 3d model of our inverted pendulum. From here, the next step is to get the basic mesh into Gazebo, to allow us to begin testing in simulation.

From Zero to Gazebo

I suspect that everyone who has ever used ROS has had to deal with the horror of building/installing ROS. It can become a serious pain, and you wouldn’t believe the number of hours I’ve had to spend in the past dealing with missing/incorrect dependencies, or the source code refusing to compile.

Thankfully, a better way exists: Docker. I cannot recommend it strongly enough. For the unfamiliar, I think the official docs provide a pretty solid introduction.

Once you have it installed (Make sure to perform the Post Installation steps as well), launch an interactive container with access to a fully operational installation of ROS galactic with the command:

$docker run -it osrf/ros:galactic-desktop

Docker should automagically download the image, and you should find yourself inside of a terminal instance. The -it signals to run an [i]nteractive session with a [t]erminal interface.

Going beyond `$Docker run'

A frustrating number of sites that promise to introduce the magic of docker kind of end here. They’ll show you how to install Docker, show off the docker run command, and maybe 4 or 5 other docker commands like list or whatever. Frankly, it makes me sceptical whether the authors actually use Docker in any capacity outside of writing about it. If they did, they’d know that launching a container is not enough to begin using Docker productively.

Once you have confirmed you can run a docker image, you’re going to want to create a Dockerfile somewhere in your working directory. Having a look through Kiwi’s Dockerfile one can see how to grab the ros-galactic-desktop has a base, then install Gazebo and OpenScad inside of the container. This all happens through a series of shell commands, utilising apt and git.

My workflow

I’ve got the workflow to a point that works pretty seamlessly for me. To re-build the container, I have docker_build.sh. To run the container, I have docker_launch.sh, note how docker_launch mounts the kiwi workspace into the Docker container. This allows me to edit the code locally on my desktop, then just compile and launch gazebo inside of the Docker container. Once the container is up and running, this workflow feels close to identical to my workflow without Docker. I am of the opinion that interacting with the container should be as seamless and un-intrusive as possible (Probably not controversial).

/-Dockerfile change-\               /---On startup---\
|   Build Docker    |    ---------> |  Launch Docker |
|   Image           |               |   Container    |
\-------------------/               \----------------/
                                            |
                                            |
                                            V
                /--------Test--------\ -------> /---------Develop-------\
                |  Compile/launch    |          |      Write code       | 
                |   inside Container |          |   outside of container|
                \--------------------/ <------- \-----------------------/

Launching it all

I like to ensure that Gazebo will launch on its own, before I start trying to hack together and import the robot model. This reduces the search space of things that might be failing while debugging the model. A model-only gazebo launchfile can be found here, which is launched with:

$ ros2 launch kiwi_gazebo gazebo_only.launch.py

Building the XACRO

There isn’t too much to say about constructing the URDF for KIWI. The process essentially consisted of stitching together the various *.stl’s that were output by OpenScad. You can view the source file here.

If you’re interested in learning about how to construct your own XACROs, I think the best resource for getting started is the ROS2 introduction to XACRO.

Spawning the KIWI

I tried a few different approaches to explaining the process of bringing in the XACRO to the launchfile, but being honest, none of my attempts came close to the effectiveness of just showing the diff between the two files:

$ git diff HEAD:kiwi_gazebo/launch/gazebo_only.launch.py kiwi_gazebo/launch/kiwi_sim.launch.py


--- a/kiwi_gazebo/launch/gazebo_only.launch.py
+++ b/kiwi_gazebo/launch/kiwi_sim.launch.py
@@ -6,11 +6,17 @@ from ament_index_python.packages import get_package_share_directory

 from launch import LaunchDescription
 from launch.actions import (
+    ExecuteProcess,
     IncludeLaunchDescription,
+    RegisterEventHandler,
     DeclareLaunchArgument,
 )
+from launch.event_handlers import OnProcessExit
 from launch.launch_description_sources import PythonLaunchDescriptionSource

+import xacro
+from launch_ros.actions import Node
+

 def generate_launch_description():
     gazebo = IncludeLaunchDescription(
@@ -23,6 +29,27 @@ def generate_launch_description():
     )

     pkg_gazebo_ros = get_package_share_directory("gazebo_ros")
+    kiwi_gazebo_path = os.path.join(get_package_share_directory("kiwi_gazebo"))
+
+    xacro_file = os.path.join(kiwi_gazebo_path, "urdf", "kiwi.xacro.urdf")
+
+    doc = xacro.parse(open(xacro_file))
+    xacro.process_doc(doc)
+    params = {"robot_description": doc.toxml()}
+
+    node_robot_state_publisher = Node(
+        package="robot_state_publisher",
+        executable="robot_state_publisher",
+        output="screen",
+        parameters=[params],
+    )
+
+    spawn_kiwi = Node(
+        package="gazebo_ros",
+        executable="spawn_entity.py",
+        arguments=["-topic", "robot_description", "-entity", "kiwi"],
+        output="screen",
+    )

     return LaunchDescription(
         [
@@ -39,5 +66,7 @@ def generate_launch_description():
             ),
             DeclareLaunchArgument("pause", default_value="true", description="paused"),
             gazebo,
+            node_robot_state_publisher,
+            spawn_kiwi,
         ]
     )

Source code

Fin

drawing

Christ, that’s an ugly model. I’ve been wrestling OpenScad over the last few weeks, and I’m absolutely not winning. My prior riff on OpenScad being some sort of inside joke has been feeling all the more truthful the as I work with it. I’ll be giving it a few more weeks, but I’ll very likely be dropping it in the future if it doesn’t start making more sense as a design process.