How to Use URDF with ROS Tools
View SourceExport your BB robot to URDF format for use with ROS visualisation and simulation tools.
Prerequisites
- A BB robot module (see First Robot)
- ROS 2 installed (for RViz, Gazebo)
- Understanding of the URDF Export Tutorial
Step 1: Export to URDF
Use the mix task to export:
# Print to stdout
mix bb.to_urdf MyRobot
# Write to file
mix bb.to_urdf MyRobot -o robot.urdf
Or programmatically:
urdf = BB.URDF.Exporter.export(MyRobot)
File.write!("robot.urdf", urdf)Step 2: Validate the URDF
Use check_urdf from ROS:
check_urdf robot.urdf
Expected output:
robot name is: MyRobot
---------- Successfully Parsed XML ---------------
root Link: base has 1 child(ren)
child(1): link_1
child(1): link_2Step 3: View in RViz
Create a Launch File
# robot_display.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
from launch.substitutions import Command
import os
def generate_launch_description():
urdf_path = os.path.join(
os.path.dirname(__file__), '..', 'urdf', 'robot.urdf'
)
return LaunchDescription([
Node(
package='robot_state_publisher',
executable='robot_state_publisher',
parameters=[{'robot_description': open(urdf_path).read()}]
),
Node(
package='rviz2',
executable='rviz2',
arguments=['-d', 'config/robot.rviz']
)
])Run RViz
ros2 launch my_robot_pkg robot_display.launch.py
In RViz:
- Add a RobotModel display
- Set the Description Topic to
/robot_description - Set Fixed Frame to your base link
Step 4: Publish Joint States from BB
Bridge BB joint states to ROS:
defmodule MyRobot.ROSBridge do
use GenServer
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def init(opts) do
robot_module = opts[:robot]
BB.subscribe(robot_module, [:sensor])
# Connect to ROS (using your preferred ROS Elixir bridge)
{:ok, ros} = ROS.connect()
{:ok, %{robot: robot_module, ros: ros}}
end
def handle_info({:bb, [:sensor, _joint], %{payload: joint_state}}, state) do
# Convert to ROS JointState message
ros_msg = %{
header: %{stamp: ROS.now(), frame_id: ""},
name: Enum.map(joint_state.names, &Atom.to_string/1),
position: joint_state.positions,
velocity: joint_state.velocities,
effort: joint_state.efforts
}
ROS.publish(state.ros, "/joint_states", ros_msg)
{:noreply, state}
end
endStep 5: Use with Gazebo
Add Gazebo Plugins
BB's URDF export creates basic structure. For Gazebo simulation, add plugins:
<!-- Add to robot.urdf -->
<gazebo>
<plugin name="gazebo_ros_control" filename="libgazebo_ros_control.so">
<robotNamespace>/my_robot</robotNamespace>
</plugin>
</gazebo>
<gazebo reference="shoulder">
<material>Gazebo/Orange</material>
</gazebo>Launch in Gazebo
ros2 launch gazebo_ros gazebo.launch.py
ros2 run gazebo_ros spawn_entity.py -file robot.urdf -entity my_robot
Step 6: Bidirectional Control
BB → ROS (Publish States)
# In your ROS bridge
def handle_info({:bb, [:sensor, _], %{payload: js}}, state) do
ROS.publish(state.ros, "/joint_states", to_ros_joint_state(js))
{:noreply, state}
endROS → BB (Receive Commands)
def init(opts) do
# ...
ROS.subscribe(state.ros, "/joint_commands", &handle_ros_command/1)
{:ok, state}
end
defp handle_ros_command(msg) do
for {name, position} <- Enum.zip(msg.name, msg.position) do
joint = String.to_atom(name)
BB.Actuator.set_position!(MyRobot, joint, position)
end
endURDF Limitations
BB's URDF export has limitations compared to hand-written URDF:
| Feature | BB Support | Notes |
|---|---|---|
| Links | ✓ | Position from transforms |
| Joints | ✓ | Type, limits, axis |
| Visual geometry | ✓ | Basic shapes only |
| Collision geometry | ✓ | Same as visual |
| Inertial | ✓ | Mass and inertia tensor |
| Transmissions | ✗ | Add manually for ros_control |
| Gazebo plugins | ✗ | Add manually |
| Materials | ✗ | Gazebo materials need manual addition |
Adding Missing Elements
For features BB doesn't export, post-process the URDF:
defmodule URDFEnhancer do
def add_gazebo_materials(urdf) do
urdf
|> String.replace(
"</robot>",
"""
<gazebo reference="base">
<material>Gazebo/Grey</material>
</gazebo>
</robot>
"""
)
end
def add_transmission(urdf, joint_name) do
transmission = """
<transmission name="#{joint_name}_transmission">
<type>transmission_interface/SimpleTransmission</type>
<joint name="#{joint_name}">
<hardwareInterface>hardware_interface/PositionJointInterface</hardwareInterface>
</joint>
<actuator name="#{joint_name}_motor">
<mechanicalReduction>1</mechanicalReduction>
</actuator>
</transmission>
"""
String.replace(urdf, "</robot>", transmission <> "</robot>")
end
endMoveIt Integration
For motion planning with MoveIt:
- Export URDF from BB
- Create MoveIt config package:
ros2 run moveit_setup_assistant moveit_setup_assistant - Load your URDF in the assistant
- Configure:
- Planning groups
- End effectors
- Self-collision matrix
- Generate the config package
Common Issues
Joint Names Don't Match
BB uses atoms for joint names. Ensure ROS messages use the string version:
Atom.to_string(:shoulder) #=> "shoulder"Frame ID Mismatch
BB uses frame_id in messages. Map to ROS TF frame names:
def to_ros_frame(bb_frame_id) do
"#{@robot_name}/#{bb_frame_id}"
endUnits
BB uses SI units (radians, metres). ROS also uses SI, so no conversion needed.
Next Steps
- Add visual meshes for realistic rendering
- Configure ros_control for hardware interface
- Set up MoveIt for motion planning