Skip to content

Getting started ​

This guide takes the demo robot from four source files to a run in Gazebo: a mecanum base with a lidar that drives around a box to a goal, turns a quarter turn and stops. Its four files are in examples/demo and its parts in examples/library, so it runs from a checkout of the repository, which is not public yet (Installation).

The program ​

The four files are in examples/demo; the tabs show one each.

robotdsl
// The demo robot: a mecanum base with a battery and two motor drivers on its deck, a motor
// on each drive mount with its wheel on the shaft, and a top lidar.
Robot drone_mecanum {
    intent "Drone mecanum mobile base with four black mecanum wheels and a top lidar"

    Mecanum_four_wheel chassis(ref = "tslot_1515_drone")

    Battery pack(ref = "lipo_3s_2200", glue = (-0.1, 0, 0.0344, 0, 0, -1))
    MotorDriver driver_l(ref = "driver_vnh5019", glue = (0, 0.1, 0.0344, 0, 0, -1), power = @core/pack)
    MotorDriver driver_r(ref = "driver_vnh5019", glue = (0, -0.1, 0.0344, 0, 0, -1), power = @core/pack)

    Motor motor_lf(ref = "gearmotor_37d", glue = (0.1275, 0.1294, -0.0216, 0, 1, 0), command = @core/driver_l)
    Motor motor_lb(ref = "gearmotor_37d", glue = (-0.1275, 0.1294, -0.0216, 0, 1, 0), command = @core/driver_l)
    Motor motor_rf(ref = "gearmotor_37d", glue = (0.1275, -0.1294, -0.0216, 0, -1, 0), command = @core/driver_r)
    Motor motor_rb(ref = "gearmotor_37d", glue = (-0.1275, -0.1294, -0.0216, 0, -1, 0), command = @core/driver_r)

    Mecanum wheel_lf(ref = "mecanum_black", glue = (0.1275, 0.1494, -0.0216, 0, 1, 0))
    Mecanum wheel_lb(ref = "mecanum_black", glue = (-0.1275, 0.1494, -0.0216, 0, 1, 0))
    Mecanum wheel_rf(ref = "mecanum_black", glue = (0.1275, -0.1494, -0.0216, 0, -1, 0))
    Mecanum wheel_rb(ref = "mecanum_black", glue = (-0.1275, -0.1494, -0.0216, 0, -1, 0))

    Lidar top_lidar(ref = "lidar_ld06", glue = (0, 0.0001, 0.0344, 0, 0, -1))
}
robotdsl
World demo {
    physics(gravity = (0, 0, -9.81))
    spawn(robot = @core/drone_mecanum, pose = (0, 0, 0.05, 0, 0, 0, 1))
    Scenario room(ref = "empty", ground_plane = true) {
        Obstacle box(
            type = "box", pos = (1.2, 0, 0.25, 0, 0, 0, 1), size = (0.3, 0.3, 0.5),
        ),
        Obstacle wall_north(
            type = "box", pos = (2, 5, 0.25, 0, 0, 0, 1), size = (8.2, 0.1, 0.5),
        ),
    }
    Goal goal(pose = (2.5, 0, 0, 0, 0, 0, 1))
}
robotdsl
Task drone_demo(timeout_s = 120) {
    Retry(count = 2) {
        Navigate(goal = @world/goal)
    }
    Sequence {
        Spin(angle_rad = 1.57),
        Wait(duration_s = 2),
        Stop()
    }
}
robotdsl
Evaluate demo_eval {
    Check_clearance(target = @world/goal, min = 0.3)
    Check_vel(target = @core/wheel_lf, min = 0, max = 60)
    Check_time(target = @task/drone_demo, max = 300)
}

The robot is a chassis part and twelve parts glued onto it: a battery and two motor driver boards on the deck, a motor on each of the four drive mounts, a wheel on each motor's shaft, and a lidar on top. Each glue is the position and direction of a connector, of the chassis or of a part already placed. Each driver's power names the battery that feeds it, and each motor's command the driver that drives it. The world has two obstacles and a goal. The task navigates to the goal, retrying once, then spins, waits and stops. The evaluation asks for 0.3 m of clearance, a wheel speed under 60 rad/s and a task under 300 s.

Check it ​

From the repository root, --check runs every phase up to validation and writes nothing:

sh
mobiledsl compile examples/demo/demo.* --library examples/library --check
text
Checked examples/demo/demo.core examples/demo/demo.eval examples/demo/demo.task examples/demo/demo.world (14 checks)

The fourteen validation rules that apply to this robot passed. Change Lidar to Lidra in demo.core and run it again to see what a problem looks like:

text
error[name.unknown_component_type]: unknown component type 'Lidra'
   --> examples/demo/demo.core:22:5
   |
22 |     Lidra top_lidar(ref = "lidar_ld06", glue = (0, 0.0001, 0.0344, 0, 0, -1))
   |     ^^^^^ not a component type
   |
   = help: did you mean 'Lidar'?

error: 1 error; examples/demo/demo.core examples/demo/demo.eval examples/demo/demo.task examples/demo/demo.world did not compile

Compile it ​

Name a target and an output directory:

sh
mobiledsl compile examples/demo/demo.* --library examples/library \
  --target ros-gz-nav2 --output build
text
Compiled examples/demo/demo.core examples/demo/demo.eval examples/demo/demo.task examples/demo/demo.world (14 checks) into build (40 files)

build/ is the whole robot stack's input:

text
build/
├── robot/        robot.urdf, controllers.yaml, meshes
├── world/        world.sdf, bridge.yaml
├── nav/          Nav2 parameters and the map drawn from the obstacles
├── task/         behavior_tree.xml, mission.json, evaluation.json
├── launch/       bringup.launch.py, the entry point
├── runtime/      the task runner, which needs only ROS 2
└── compile.json  what the compiler knew, for people and tools

Run it ​

In a ROS 2 Jazzy environment with Gazebo Harmonic, ros2_control and Nav2:

sh
ros2 launch build/launch/bringup.launch.py

The launch starts Gazebo without a window, spawns the robot, starts its controllers and Nav2, and runs the task. When the task ends it shuts everything down. Add headless:=false to watch the run in Gazebo's window.

Without such an environment, run it in the test image. Mount the output and launch it inside:

sh
docker run --rm --user "$(id -u):$(id -g)" -e HOME=/tmp -e GZ_PARTITION=robotdsl \
  -v "$PWD/build:/work/build" --entrypoint bash robotdsl/ros-gz-nav2:jazzy-harmonic \
  -lc "source /opt/ros/jazzy/setup.bash && ros2 launch /work/build/launch/bringup.launch.py"

The container runs as you, so the results it writes are yours. Gazebo names its transport partition after the user, and the image does not know your user, so GZ_PARTITION gives it a name; without it the robot never spawns. --entrypoint bash replaces the image's own entry point.

Read the result ​

The run writes two files under build/run/:

FileSays
task_result.jsonThe task's own account: success, failure, timeout, error or not_started, and one event per action with its times and outcome
evaluation.jsonThree verdicts: the task's status, the physical outcome judged from the simulator's own pose, and each check of demo.eval with the value it was judged on

A successful demo run navigates around the box, stops near the goal and never touches the box. Its clearance check can still fail: Nav2 may pass the box closer than the 0.3 m the evaluation asks for, and the check reports how close it came. Judging a run explains the three verdicts.

Next ​

The Basics pages take each file in turn: