odom and base_link: Where the Robot Started, Where It Is| frame | attached to | moves? | its parent — placed by |
|---|---|---|---|
odom |
the floor, where odometry started | no — but slowly drifts off the true start | none: the root of our tree |
base_link |
the robot's body, on the wheel axle | with the robot | odom — the drive, every step |
lidar_link |
the lidar, bolted to the body | with the robot, fixed on it | base_link — the URDF, once |
map |
the room, as a map knows it | no | later in the course |
odom → base_link is odometry: where the robot is, counted from where it started
odom stays where the robot started; base_link and lidar_link drive away together. Yellow: odom → base_link, drawn by RViz's TF display.lidar_link, odometry in odom, the model in base_link — one screen needs one frame to stand on: the Fixed Frameframe_id into it, with TFodom), or strapped to the robot (base_link)| Fixed Frame | stands still | moves |
|---|---|---|
odom |
the walls, the path | the robot |
base_link |
the robot | the walls, the path |
map — RViz's default |
— | nothing is drawn: Frame [map] does not exist → choose odom |
odom the room stands still; in base_link the robot does. docs.ros.org/en/jazzy/Tutorials/Intermediate/RViz/RViz-User-Guide/RViz-User-Guide.html
base_link; robot at (2, 1), facing +y (90°); the lidar sees a wall 1 m ahead| step | what happens | the point |
|---|---|---|
in lidar_link |
as measured | (1, 0, 0) |
→ base_link |
+ the mount (0.1, 0, 0.15) | (1.1, 0, 0.15) |
→ odom |
turn 90°, then + (2, 1, 0) | (2.0, 2.1, 0.15) |
Where the lidar's point is in the room: the mount first, then the robot's pose — read right to left
Legend
— the point as the lidar sees it, m · — the mount, static · — the robot now, dynamic · — the point in the room, m
lidar_link and the two wheels → base_link → odom, the rootbase_link → left_wheel is dynamic, like the robot's own; the lidar's mount is staticlidar_link → base_link → odom is two transforms
Static
/tf_static — sent once; late subscribers still get itfixed joints in the URDF — the ball, the lidar — published by robot_state_publisherDynamic
/tf — a stamped streamodom → base_link, from the drive$ ros2 run tf2_ros static_transform_publisher --x 0.1 --z 0.15 --frame-id base_link --child-frame-id lidar_link
[INFO] [static_transform_publisher_xOFKK8Khgao0R9Dm]: Spinning until stopped - publishing transform
translation: ('0.100000', '0.000000', '0.150000')
rotation: ('0.000000', '0.000000', '0.000000', '1.000000')
from 'base_link' to 'lidar_link'
One static transform by hand — enough for a test, not for a robot
TransformStamped: A Transform as a Message$ ros2 topic echo /tf_static --once
transforms:
- header: … # base_link → ball, cut
- header:
stamp:
sec: 0
nanosec: 0
frame_id: base_link
child_frame_id: lidar_link
transform:
translation:
x: 0.1
y: 0.0
z: 0.15
rotation:
x: 0.0
y: 0.0
z: 0.0
w: 1.0
| field | meaning | here |
|---|---|---|
header.frame_id |
parent frame | base_link |
child_frame_id |
child frame | lidar_link |
translation |
shift, m | 0.1, 0, 0.15 |
rotation |
turn | none here |
frame_id stringgz.msgs.Pose_V; ROS 2 /tf as tf2_msgs/msg/TFMessagetf2_echo and view_frames: One Transform, the Whole Tree$ ros2 run tf2_ros tf2_echo base_link lidar_link
At time 0.0
- Translation: [0.100, 0.000, 0.150]
- Rotation: in Quaternion (xyzw) [0.000, 0.000, 0.000, 1.000]
- Rotation: in RPY (radian) [0.000, -0.000, 0.000]
- Rotation: in RPY (degree) [0.000, -0.000, 0.000]
- Matrix:
1.000 0.000 0.000 0.100
0.000 1.000 0.000 0.000
0.000 0.000 1.000 0.150
0.000 0.000 0.000 1.000
One transform, live — once a second; a static one shows time 0
No wheels? A wheel's transform needs its angle, and nothing sends the angles in this run — later: who does
$ ros2 run tf2_tools view_frames # → frames_<date>.pdf
view_frames on our robot: odom → base_link from the drive at 50.2 Hz; ball, lidar_link static.The whole tree — like rqt_graph, but for frames instead of nodes
docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Introduction-To-Tf2.html
| tool | package | shows | use it to |
|---|---|---|---|
tf2_echo A B |
tf2_ros |
one transform, once a second | check one pair: is it there, are the numbers right |
view_frames |
tf2_tools |
the whole tree, as a PDF | one tree? every frame in it? |
| RViz → Add → TF | rviz2 |
each frame's axes, a line to its parent | see the tree on the robot |
tf2_monitor |
tf2_ros |
how late each transform arrives | find a stale publisher or a wrong clock |
static_transform_publisher |
tf2_ros |
nothing — it publishes one static transform | test without a URDF |
docs.ros.org/en/jazzy/Tutorials/Intermediate/Tf2/Debugging-Tf2-Problems.html
Describe the robot once; let the simulator and ROS 2 both read it.
<origin> is a transform; a program publishes them; Gazebo reads the same file<joint name="ball_joint" type="fixed">
<parent link="base_link"/>
<child link="ball"/>
<origin xyz="-0.15 0 -0.05"/> <!-- = the transform base_link → ball -->
</joint>
robot_state_publisher, Gazebo through create.robot_state_publisher, RViz's robot model, MoveIt, the motor-control framework| reads… | URDF | SDF |
|---|---|---|
| Gazebo | ||
robot_state_publisher, RViz |
via sdformat_urdf, limited |
|
| motor-control framework | ||
| the real robot |
docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/urdf/setup_urdf/
| what | SDF (you know) | URDF (new) |
|---|---|---|
| placing a part | <pose> on the part |
<origin> on the joint |
| parent, child | <parent>chassis</parent> |
<parent link="base_link"/> |
| axis | <axis><xyz>0 1 0</xyz></axis> |
<axis xyz="0 0 1"/> |
| shapes | <box><size>0.4 0.3 0.1</size></box> |
<box size="0.4 0.3 0.1"/> |
| sensor | <sensor> inside <link> |
<gazebo reference="…"><sensor> |
| plugin | <plugin> inside <model> |
<gazebo><plugin> |
<!-- SDF: the part carries its own pose -->
<link name="left_wheel">
<pose>0 0.175 0 -1.5707 0 0</pose> …
</link>
<joint name="left_wheel_joint" type="revolute">
<parent>chassis</parent><child>left_wheel</child>
<axis><xyz expressed_in="__model__">0 1 0</xyz></axis>
</joint>
<!-- URDF: the joint places the part -->
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/><child link="left_wheel"/>
<origin xyz="0 0.175 0" rpy="-1.5708 0 0"/>
<axis xyz="0 0 1"/>
</joint>
<link name="left_wheel"> … </link>
sdformat_urdf: A Simple SDF Robot in ROS 2sdformat_urdf lets robot_state_publisher read a simple SDF model — Nav2's own Gazebo guide does exactly this docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/sdf/setup_sdf/
| how many | which numbers | |
|---|---|---|
| a free body in space | 6 | shifts x, y, z · turns roll, pitch, yaw |
| a robot on the floor | 3 | x, y, yaw — the floor holds z, roll, pitch |
| a gear on its axle | 1 | its angle |
fixed joint |
0 | — welded |
revolute, continuous joint |
1 | the angle about <axis> |
prismatic joint |
1 | the shift along <axis> |
planar joint |
3 | x, y and the turn, in its plane |
floating joint |
6 | all six |
modernrobotics.northwestern.edu/nu-gm-book-resource/2-1-degrees-of-freedom-of-a-rigid-body/
How fast the robot moves to its own left — always zero
| the robot faces | the formula becomes | so it cannot move | |
|---|---|---|---|
| along x | 0 | along y | |
| along y | 90° | along x | |
| diagonally | 45° | off the diagonal |
Legend — the robot's velocity in odom, m/s · — heading, rad · — forward speed, m/s
modernrobotics.northwestern.edu/nu-gm-book-resource/13-1-wheeled-mobile-robots
How fast a wheel slides sideways while the robot turns — zero only on the centre's axle line
Legend — a wheel's sideways speed, m/s · — the robot's turn rate, rad/s · — the axle's distance ahead of or behind the centre, m
modernrobotics.northwestern.edu/nu-gm-book-resource/13-1-wheeled-mobile-robots
base_link (on the axle), left_wheel, right_wheel, ball, lidar_link
| link | joint | |
|---|---|---|
| what it is | a rigid part | a connection between two parts |
| inside | <visual> — what you see · <collision> — what touches, usually simpler · <inertial> — mass |
<parent>, <child> · <origin> — where the child sits · <axis> — in the child's frame · <limit> |
| in TF | a frame, with the link's name | the transform parent → child — its <origin> |
| kinds | — | fixed — welded · continuous — spins forever · revolute — turns within limits · prismatic — slides |
| ours | base_link, left_wheel, ball, lidar_link |
left_wheel_joint — continuous · ball_joint, lidar_joint — fixed |
<origin> fixes where the child sits; the angle turns it about <axis>.<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/> <!-- hangs on -->
<child link="left_wheel"/> <!-- moves -->
<!-- where the child's frame sits, in the parent's -->
<origin xyz="0 0.175 0" rpy="-1.5708 0 0"/>
<axis xyz="0 0 1"/> <!-- the child turns about it -->
</joint>
| parent | child | |
|---|---|---|
| in the joint | the part it hangs on | the part that moves |
| its frame | its own | at <origin>, measured in the parent's frame |
| the parent moves | — | moves with it |
| a moving joint | — | turns about <axis> |
| in TF | frame_id |
child_frame_id |
| every part placed in the room | every part placed on its parent | |
|---|---|---|
| the robot drives | all five poses change | one transform changes: odom → base_link |
| a wheel turns | its pose in the room | one angle, on its joint |
| the lidar moves 1 cm on the body | its pose, recomputed all the time | one <origin> in the file |
| where the lidar is from a wheel | worked out by hand | TF, along the tree |
<robot name="demo_bot">
<!-- base_link sits on the wheel axle -->
<link name="base_link">
<visual>
<origin xyz="0 0 0.05"/> <!-- the box is 5 cm above the axle -->
<geometry><box size="0.4 0.3 0.1"/></geometry>
</visual>
<collision>
<origin xyz="0 0 0.05"/>
<geometry><box size="0.4 0.3 0.1"/></geometry>
</collision>
<inertial>
<origin xyz="0 0 0.05"/>
<mass value="2.0"/>
<inertia ixx="0.016667" iyy="0.028333" izz="0.041667"
ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
<pose> on the part — the <origin> inside each block lifts the box above the axle<inertial> by hand — URDF has no auto="true"; the box, cylinder and sphere formulas are in the source docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/urdf/setup_urdf/
<!-- the left wheel: 17.5 cm left, the cylinder on its side -->
<joint name="left_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="left_wheel"/>
<origin xyz="0 0.175 0" rpy="-1.5708 0 0"/>
<axis xyz="0 0 1"/> <!-- spins about its own axis -->
</joint>
<link name="left_wheel">
<visual><geometry><cylinder radius="0.1" length="0.05"/></geometry></visual>
<collision><geometry><cylinder radius="0.1" length="0.05"/></geometry></collision>
<inertial>
<mass value="0.5"/>
<inertia ixx="0.001354" iyy="0.001354" izz="0.0025"
ixy="0" ixz="0" iyz="0"/>
</inertial>
</link>
continuous: it spins without limits, about its own axis
docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/urdf/setup_urdf/
<!-- no ball joint in URDF: fixed, frictionless -->
<joint name="ball_joint" type="fixed">
<parent link="base_link"/>
<child link="ball"/>
<origin xyz="-0.15 0 -0.05"/> <!-- at the back -->
</joint>
<link name="ball"> … a 0.05 m sphere, 0.2 kg … </link>
<gazebo reference="ball">
<mu1>0</mu1><mu2>0</mu2>
</gazebo>
<!-- inside base_link: the nose skid, a second collision -->
<collision name="nose_skid">
<origin xyz="0.15 0 -0.05"/>
<geometry><sphere radius="0.045"/></geometry>
</collision>
<collision> shapes<origin> puts the ball where it touches the floor; the nose skid hangs 5 mm above it. docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/urdf/setup_urdf/
<gazebo> Blocks for the Simulator<gazebo>
<plugin filename="gz-sim-diff-drive-system"
name="gz::sim::systems::DiffDrive">
<left_joint>left_wheel_joint</left_joint>
<right_joint>right_wheel_joint</right_joint>
<wheel_separation>0.35</wheel_separation>
<wheel_radius>0.1</wheel_radius>
<topic>cmd_vel</topic>
<odom_topic>odom</odom_topic>
<frame_id>odom</frame_id> <!-- new: the frame names -->
<child_frame_id>base_link</child_frame_id> <!-- in its transform -->
</plugin>
</gazebo>
/cmd_vel → wheels.<gazebo reference="lidar_link"> <!-- the part it sits on -->
<sensor name="lidar" type="gpu_lidar">
<topic>scan</topic>
<update_rate>10</update_rate> <!-- scans per second -->
<always_on>true</always_on>
<visualize>true</visualize> <!-- rays in Gazebo's view -->
<lidar>
<scan><horizontal>
<samples>360</samples> <!-- rays per turn -->
<min_angle>-3.14159</min_angle>
<max_angle>3.14159</max_angle>
</horizontal></scan>
<range><min>0.12</min><max>8.0</max></range>
</lidar>
</sensor>
</gazebo>
<sensor> as in SDF · ROS ignores <gazebo> blocks, Gazebo pastes them in docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/odom/setup_odom_gz/
xacro: Variables and Macros for URDFxacro turns a .urdf.xacro file with variables and macros into plain URDF${…} · macros with parameters · includes$ xacro demo_bot.urdf.xacro > demo_bot.urdf
The file Gazebo and robot_state_publisher actually read
<xacro:property name="body_l" value="0.4"/>
<xacro:macro name="bumper" params="name x">
<joint name="${name}_joint" type="fixed">
<parent link="base_link"/>
<child link="${name}"/>
<origin xyz="${x} 0 0.02"/>
</joint>
<link name="${name}">
<visual><geometry><box size="0.02 0.3 0.04"/></geometry></visual>
</link>
</xacro:macro>
<xacro:bumper name="front_bumper" x="${body_l/2}"/>
<xacro:bumper name="rear_bumper" x="${-body_l/2}"/>
docs.ros.org/en/jazzy/Tutorials/Intermediate/URDF/Using-Xacro-to-Clean-Up-a-URDF-File.html
xacro:arg: A Value Passed In From Outside<!-- declared with a default; set from outside -->
<xacro:arg name="wheel_radius" default="0.1"/>
<!-- arg → property, so it can go into ${…} -->
<xacro:property name="r" value="$(arg wheel_radius)"/>
<cylinder radius="${r}" length="0.05"/> <!-- the wheel -->
<wheel_radius>${r}</wheel_radius> <!-- DiffDrive -->
One number, used in two places — change it once
$ xacro demo_bot.urdf.xacro wheel_radius:=0.12 > demo_bot.urdf
From launch — the same name:=value, after the file name:
Command(['xacro ', urdf, ' wheel_radius:=0.12'])
| set where | read as | |
|---|---|---|
xacro:property |
inside the file | ${r} |
xacro:arg |
outside: command line, launch | $(arg wheel_radius) |
xacro:include |
in another file | its properties and macros, as if written here |
gz sdf -p: What Gazebo Builds From Your URDF$ gz sdf -p demo_bot.urdf
<model name='demo_bot'>
<link name='base_link'>
<collision name='base_link_collision'> …
<collision name='base_link_fixed_joint_lump__nose_skid_collision_1'> …
<collision name='base_link_fixed_joint_lump__ball_collision_2'> …
<visual name='base_link_fixed_joint_lump__lidar_link_visual_2'> …
<sensor name='lidar' type='gpu_lidar'> …
<joint name='left_wheel_joint' type='revolute'> …
<frame name='lidar_link' attached_to='lidar_joint'> …
$ gz sdf -k demo_bot.urdf
Warning … XML Element[gz_frame_id], child of element[sensor], not defined in SDF. Copying[gz_frame_id] …
Valid.
ball and lidar_link become parts of base_linkros_gz_sim create: Spawning From the Description| lectures 1–3 | now | |
|---|---|---|
| where the robot is written | inside the world file — a <model> or an <include> |
its own URDF, apart from the world |
| how it gets into Gazebo | loaded with the world at start | added to the running world by create |
| who else reads it | nobody — ROS saw only topics | robot_state_publisher, RViz — the same text |
$ gz sim -r walls.sdf # the world: ground, light, walls — no robot
$ ros2 run ros_gz_sim create -topic robot_description -name demo_bot -z 0.1
[INFO] [ros_gz_sim]: Requesting list of world names.
[INFO] [ros_gz_sim]: Waiting messages on topic [robot_description].
[INFO] [ros_gz_sim]: Entity creation successful.
-topic robot_description — Gazebo takes the URDF that ROS publishes: one text for both-z 0.1 lifts it by the wheel radius, so the wheels do not start inside the floor
create builds it from /robot_description.robot_state_publisher: Three Topics From One URDF| topic | what is inside | when |
|---|---|---|
/robot_description |
the URDF text, as is — for RViz's robot model and for create |
once; late subscribers still get it |
/tf_static |
one transform per fixed joint: base_link → ball, base_link → lidar_link |
once; late subscribers still get it |
/tf |
one transform per moving joint: base_link → left_wheel, … |
each time /joint_states brings the angles |
$ ros2 topic echo /tf_static --once | grep child_frame_id
child_frame_id: ball
child_frame_id: lidar_link
$ ros2 topic echo /tf | grep child_frame_id | sort -u
child_frame_id: base_link
/tf_static: both fixed joints, once — from robot_state_publisher/tf: only base_link — the drive's odom → base_link, via the bridge. No wheels: nothing sends /joint_states docs.ros.org/en/jazzy/Tutorials/Intermediate/URDF/Using-URDF-with-Robot-State-Publisher-py.html
robot_state_publisher: Starting It From Launch$ ros2 run robot_state_publisher robot_state_publisher --ros-args \
-p robot_description:="$(xacro demo_bot.urdf)" -p use_sim_time:=true
from launch.substitutions import Command
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
description = ParameterValue(Command(['xacro ', urdf]), value_type=str) # str, or launch reads the URDF as YAML
Node(package='robot_state_publisher', executable='robot_state_publisher',
parameters=[{'robot_description': description, 'use_sim_time': True}])
/robot_description docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/urdf/setup_urdf/
odom → base_linkrobot_state_publisher: it covers everything below base_link; where the robot is in odom comes from the drive/model/demo_bot/tf — the bridge brings it to /tfframe_id and child_frame_id: odom and base_link, matching the URDF — otherwise two treestf2_echo only reads: it prints what the drive sent$ ros2 run tf2_ros tf2_echo odom base_link # after 3 s at 0.3 m/s
At time 41.497000000
- Translation: [0.901, 0.000, 0.000]
- Rotation: in RPY (degree) [0.000, -0.000, 0.000]
docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/odom/setup_odom_gz/
| what | Gazebo topic · type | ROS topic · type |
|---|---|---|
| clock | /clock · gz.msgs.Clock |
/clock · rosgraph_msgs/msg/Clock |
| command | cmd_vel · gz.msgs.Twist |
/cmd_vel · geometry_msgs/msg/Twist |
| odometry | odom · gz.msgs.Odometry |
/odom · nav_msgs/msg/Odometry |
odom → base_link |
/model/demo_bot/tf · gz.msgs.Pose_V |
/tf · tf2_msgs/msg/TFMessage |
| scan | scan · gz.msgs.LaserScan |
/scan · sensor_msgs/msg/LaserScan |
- ros_topic_name: /tf
gz_topic_name: /model/demo_bot/tf # renamed on the way
ros_type_name: tf2_msgs/msg/TFMessage
gz_type_name: gz.msgs.Pose_V
direction: GZ_TO_ROS
/tf_static from robot_state_publisher is ROS-only — nothing to bridge github.com/gazebosim/ros_gz/blob/jazzy/ros_gz_bridge/README.md
| joint | parent → child | <origin> |
type | topic · sent by |
|---|---|---|---|---|
ball_joint |
base_link → ball |
-0.15 0 -0.05 |
fixed | /tf_static · robot_state_publisher, once |
lidar_joint |
base_link → lidar_link |
0.1 0 0.15 |
fixed | /tf_static · robot_state_publisher, once |
left_wheel_joint |
base_link → left_wheel |
0 0.175 0, rpy -1.5708 0 0 |
continuous | /tf · needs wheel angles — not today |
right_wheel_joint |
base_link → right_wheel |
0 -0.175 0, rpy -1.5708 0 0 |
continuous | /tf · needs wheel angles — not today |
| — not in the URDF | odom → base_link |
— | — | /tf · DiffDrive, through the bridge · 50 Hz |
Every link is a frame; a joint is the edge between two — its name never appears in TF
left_wheel, right_wheel are not there: nothing sends /joint_states, so robot_state_publisher has no wheel angles to publishodom's origin. Driving, odometry and the scan never use the wheel framesJointStatePublisher system in a <gazebo> block — and one bridge line brings them to /joint_states. Then /tf carries left_wheel and right_wheel too: measured in our setupview_frames: Checking Your Robot's Tree$ ros2 run tf2_tools view_frames # listens to TF for 5 s → frames_<date>.pdf
Check
odombase_link, ball, lidar_link presentdemo_bot/…odom → base_link updating at the drive's rateFaults
odom.
frame_id, child_frame_id: two trees — the drive's demo_bot/… and the URDF's.odom, base_link, map — another spelling is another frame, and the tools will not find itodom → base_link:$ ros2 topic info /tf --verbose # publishers only, their node names
Publisher count: 2
Node name: robot_state_publisher
Node name: ros_gz_bridge
$ # stop the bridge, then
$ ros2 run tf2_ros tf2_echo odom base_link
… Invalid frame ID "odom" passed to canTransform argument target_frame - frame does not exist
The transform vanished with the bridge — so it came through the bridge, from the drive
odom; thick: base_link. A left turn is anticlockwise, so the heading grows.$ ros2 topic echo /scan --field header --once
stamp:
sec: 6
nanosec: 100000000
frame_id: demo_bot/base_link/lidar
demo_bot/base_link/lidarlidar_link — and no frame by that name[INFO] [rviz]: Message Filter dropping message: frame 'demo_bot/base_link/lidar'
at time 4.700 for reason 'discarding message because the queue is full'
lidar_link is there, demo_bot/base_link/lidar is not.the scan's frame_id |
in the tree? | RViz | |
|---|---|---|---|
| ours now | demo_bot/base_link/lidar |
no | draws nothing |
| as it must be | lidar_link |
yes — from the URDF | the scan on the walls |
docs.ros.org/en/jazzy/Tutorials/Intermediate/RViz/RViz-User-Guide/RViz-User-Guide.html
gz_frame_id: Naming the Sensor's Frame<model>/<link>/<sensor> — and after merging, the link is base_link<gazebo reference="lidar_link"><sensor name="lidar" type="gpu_lidar">
<gz_frame_id>lidar_link</gz_frame_id>
…
<gz_frame_id> → each scan's header.frame_id → RViz asks TF where lidar_link is → draws the points in its Fixed Frame, odomlidar_link: the Fixed Frame stays odom
lidar_link and placed by TF.use_sim_time: One Clock for Every Node/clock — once, a bridge line from Gazebo · use_sim_time: true — on every node you start: robot_state_publisher, RViz, your nodes, the toolsros2 bag play --clock · a real robot: never$ ros2 run tf2_ros tf2_monitor # wall clock
Frame: base_link, … Average Delay: 1.7924e+09, Max Delay: 1.7924e+09
$ ros2 run tf2_ros tf2_monitor --ros-args -p use_sim_time:=true # Gazebo's
Frame: base_link, … Average Delay: 0.000691932, Max Delay: 0.09
Node(package='robot_state_publisher', executable='robot_state_publisher',
parameters=[{'robot_description': description, 'use_sim_time': True}])
Node(package='rviz2', executable='rviz2',
parameters=[{'use_sim_time': True}])
# or once, for every node after it: SetParameter(name='use_sim_time', value=True)
/clock falls behind the wall clock — every node on /clock falls behind with it, together.You have seen odometry drift. Now — by how much?
odom Frame Creeps Awayodom → base_link is smooth — but the odom frame itself slides away from the world
odom drawn where it really is creeps away — the overlay is measured. docs.nav2.org/jazzy/configuration_and_development/first_time_robot_setup_guide/odom/setup_odom_gz/
How far sideways the robot ends up after driving with its heading off by
Legend
— sideways error, m
— distance since the heading error, m
— heading error: the real heading minus odometry's, rad (1° = 0.01745 rad)
Not there by default. It arrives only if (each checked in our setup):
<gazebo> block in the URDF<odom_topic> — else /model/<name>/odometry, the drive's default too/odom_gt@nav_msgs/msg/Odometry[gz.msgs.OdometryIt works as the truth only if:
gz model -m demo_bot -p — ours: to the last digitworld, base_link_gt, never the drive's odom, base_linkuse_sim_timeworld → odom — they start at one pose<gazebo>
<plugin filename="gz-sim-odometry-publisher-system"
name="gz::sim::systems::OdometryPublisher">
<odom_topic>odom_gt</odom_topic> <!-- not the drive's topic -->
<odom_frame>world</odom_frame>
<robot_base_frame>base_link_gt</robot_base_frame>
<dimensions>3</dimensions> <!-- z, roll, pitch too -->
</plugin>
</gazebo>
Nothing arrives? In this order: gz topic -l → gz topic -e -t /odom_gt -n 1 → ros2 topic hz /odom_gt
/odom_gt, blue /odom. gazebosim.org/api/sim/8/classgz_1_1sim_1_1systems_1_1OdometryPublisher.html
troubleshooting: gazebosim.org/docs/harmonic/troubleshooting/
One number per run: how far, typically, odometry puts the robot from where it really is
Legend
ATE — Absolute Trajectory Error
— position by odometry at , m
— position by ground truth at the same time, m
— matched samples
How much error one metre of driving adds — wherever the earlier error came from
Legend
RPE — Relative Pose Error
, — how the robot moved during metre by odometry and truly, seen from where it stood at the metre's start, m
— metres driven
| ATE — Absolute Trajectory Error | RPE — Relative Pose Error | |
|---|---|---|
| compares | positions, at the same moments | each metre's motion, from where it began |
| an early error | counted again in every later position | counted once, in its own metre |
| answers | how far off is odometry, typically? | how much does each metre of driving spoil it? |
| good for | one run, one number | comparing drives, robots, settings |
Lecture 5 — Sensors and Noise
What each sensor really measures · how a lidar sees · how noisy it is, measured · where a 2D lidar is blind