Skip to content

Topics#

This page gives each topic that the ROS 2 bridge publishes and each topic that it subscribes to, for the Polaris and for the Maxxum. The Polaris topics have the names, the types, the frames and the QoS of the real Polaris.

The node sim_bridge makes the topics from the Sensor Stream and the Vehicle Bridge of the simulator. The topics are the same for the game and for ACRES Core. Services and Actions gives the other interfaces of the bridge. Messages gives the fields of the acres_interfaces messages.

Item Type Function
Names and Namespaces Convention Relative and absolute topic names.
QoS Profiles Convention The reliability, the durability and the depth of each topic.
Frames Convention The frame names in the message headers and in TF.
Time Convention The simulation time of the stamps and of /clock.
Clock and Transforms Polaris, Maxxum /clock, /tf, /tf_static.
Pose and INS Polaris /vehicle/odom, /oxts/*, /sim/ground_truth/odom.
LiDAR and Camera Polaris, Maxxum /lidar/points, /camera/image_raw, /camera/camera_info.
Drive-by-Wire Reports Polaris /vehicle/*/report, /vehicle/vehicle_velocity, /vehicle/dbw_enabled.
Drive-by-Wire Commands Polaris /vehicle/*/cmd, /vehicle/enable, /vehicle/disable.
Reset Pose Polaris, Maxxum /sim/reset.
State of the Maxxum Maxxum odom, imu/data, gnss/fix, engine_rpm, implement/state.
Commands of the Maxxum Maxxum cmd_vel, the guidance topics, the hitch and the PTO.
CAN Bus Maxxum can/rx, can/tx.
Episode Clock Session /sim/episode.
Channels of the Episode Log File /sim/agents, /sim/agent_states and the other channels of the MCAP file.
Several Agents Session The namespace and the frame prefix of each agent.
Topics of the Other Nodes Table The description, the path, the demo driver and the substitutes.
Differences in ACRES Core Table The topics without data in a Core session.
Parity with the Real Polaris Check The comparison with bags of the real vehicle.

WARNING

The command topics have the names of the command topics of the real Polaris. Publish on them only in a shell that has the DDS loopback fence (source ROS/Env/setup_env.sh).

Conventions#

Names and Namespaces#

The topic names in the tables are for a bridge in the root namespace, which is the default. In a different namespace, each name gets the namespace as a prefix, for example /polaris2/vehicle/odom. Four names are absolute and do not change with the namespace: /clock, /tf, /tf_static and /sim/episode.

The Maxxum topics have no vehicle name in them. Use a namespace for the Maxxum, for example namespace:=maxxum. The tables of the Maxxum show the names without the namespace.

QoS Profiles#

The column QoS of each table gives one of these profiles.

Profile Reliability Durability History Depth Topics
vehicle reliable volatile 10 All topics of a vehicle and /clock.
tf reliable volatile 100 /tf
static reliable transient local 1 /tf_static
latched reliable transient local 1 /sim/episode
can reliable volatile 500 can/rx, can/tx

The bridge subscribes to the command topics with the profile "vehicle". A publisher with reliable or best-effort reliability is compatible. A subscriber of /sim/episode must request transient local durability to get the last message.

Frames#

Frame Vehicle Definition
utm Both UTM zone 16N. x is the easting and y is the northing, in metres. The parameter utm_frame changes the name.
base_footprint Both The point on the ground below the centre of the rear axle. x forward, y left, z up.
lidar Both The LiDAR. The sensor stream gives the name.
reolink_camera_optical Polaris The optical frame of the camera: z forward, x right, y down.
imu_link Both The IMU. On the Polaris, the axes are those of the OxTS unit: x left, y forward, z down.
oxts_link Polaris The frame of /oxts/velocity.
navsat_link Polaris The frame of /oxts/fix.
camera, camera_optical Maxxum The camera body and its optical frame.
gnss_link Maxxum The GNSS antenna.
can Maxxum The frame name of the CAN frames.
world Both The frame of the services: east, north, up in metres, with the origin at the centre of the tile. No topic uses it.

The parameter frame_prefix adds a prefix to the frame names of one vehicle, for example polaris2/base_footprint. The frames utm, can and world get no prefix. On the Polaris, imu_link and oxts_link also get no prefix.

Time#

All stamps are simulation time: the time of the physics step that made the data. The physics step is 1/120 s. The bridge publishes /clock for each physics step. Set use_sim_time:=true on the nodes that use the topics. In lockstep, the time advances only when a client requests steps. Refer to Lockstep Stepping.

Polaris#

Name Type Direction Rate QoS Frame Description
/clock rosgraph_msgs/msg/Clock publish 120 Hz vehicle The simulation time.
/tf tf2_msgs/msg/TFMessage publish 100 Hz tf utm The transform from utm to base_footprint.
/tf_static tf2_msgs/msg/TFMessage publish one time static No transform from the bridge. The Polaris description sends the sensor frames.
/vehicle/odom nav_msgs/msg/Odometry publish 100 Hz vehicle utm The pose of base_footprint and the velocity, with the INS errors.
/oxts/imu sensor_msgs/msg/Imu publish 100 Hz vehicle imu_link The attitude, the angular rate and the specific force.
/oxts/velocity geometry_msgs/msg/TwistStamped publish 100 Hz vehicle oxts_link The velocity in the body frame.
/oxts/fix sensor_msgs/msg/NavSatFix publish 100 Hz vehicle navsat_link The latitude, the longitude and the height.
/sim/ground_truth/odom nav_msgs/msg/Odometry publish 100 Hz vehicle utm The exact pose of base_footprint, without the INS errors.
/lidar/points sensor_msgs/msg/PointCloud2 publish 10 Hz vehicle lidar The LiDAR cloud, 1800 columns and 32 rings.
/camera/image_raw sensor_msgs/msg/Image publish 10 Hz vehicle reolink_camera_optical The camera image, 896 x 512, bgr8.
/camera/camera_info sensor_msgs/msg/CameraInfo publish 10 Hz vehicle reolink_camera_optical The camera matrix and the distortion.
/vehicle/steering/report ds_dbw_msgs/msg/SteeringReport publish 50 Hz vehicle The steering wheel angle and the steering command.
/vehicle/throttle/report ds_dbw_msgs/msg/ThrottleReport publish 50 Hz vehicle The throttle input, command and output.
/vehicle/brake/report ds_dbw_msgs/msg/BrakeReport publish 50 Hz vehicle The brake pressure input, command and output.
/vehicle/gear/report ds_dbw_msgs/msg/GearReport publish 50 Hz vehicle The gear, the gear command and the gear lever.
/vehicle/ulc/report ds_dbw_msgs/msg/UlcReport publish 50 Hz vehicle The reference and the measured speed of the ULC.
/vehicle/vehicle_velocity ds_dbw_msgs/msg/VehicleVelocity publish 50 Hz vehicle The vehicle speed from the brake and the propulsion systems.
/vehicle/dbw_enabled std_msgs/msg/Bool publish 50 Hz vehicle true while the drive-by-wire is on.
/vehicle/steering/cmd ds_dbw_msgs/msg/SteeringCmd subscribe vehicle The steering command.
/vehicle/throttle/cmd ds_dbw_msgs/msg/ThrottleCmd subscribe vehicle The throttle command.
/vehicle/brake/cmd ds_dbw_msgs/msg/BrakeCmd subscribe vehicle The brake command.
/vehicle/gear/cmd ds_dbw_msgs/msg/GearCmd subscribe vehicle The gear command.
/vehicle/ulc/cmd ds_dbw_msgs/msg/UlcCmd subscribe vehicle The speed command of the ULC.
/vehicle/enable std_msgs/msg/Empty subscribe vehicle Enables the drive-by-wire.
/vehicle/disable std_msgs/msg/Empty subscribe vehicle Disables the drive-by-wire.
/sim/reset geometry_msgs/msg/PoseStamped subscribe vehicle utm Moves the vehicle to a pose.

Clock and Transforms#

/clock has one message for each physics step. The bridge of agent 0 publishes it. A second bridge of the same session must have publish_clock:=false.

/tf has one transform for each INS sample: from utm to base_footprint. The translation is the UTM position and the height. The rotation is the grid heading with roll and pitch.

The Polaris description (robot_state_publisher) publishes the transforms from base_footprint to lidar, reolink_camera and reolink_camera_optical on /tf_static. The description needs the package purdue_ranger. Refer to Build the ROS 2 Workspace.

ros2 topic echo --once /clock
clock:
  sec: 25
  nanosec: 741668009
---
ros2 topic hz /clock
average rate: 120.072

Pose and INS#

The four INS topics come from one INS sample of the sensor stream. INS and GNSS gives the error model.

/vehicle/odom

Name Type Unit Default Description
pose.pose.position point m x easting, y northing in UTM 16N. z is the ellipsoidal height.
pose.pose.orientation quaternion The attitude of base_footprint in the utm frame. The yaw is counter-clockwise from grid east.
pose.covariance number[36] m² Only the diagonal for east, north and up.
twist.twist.linear vector m/s The velocity in the body frame: x forward, y left, z up.
twist.twist.angular vector rad/s The angular rate in the body frame.

/oxts/imu

Name Type Unit Default Description
orientation quaternion The attitude of the IMU axes in the frame east, north, up.
angular_velocity vector rad/s In the IMU axes: x left, y forward, z down.
linear_acceleration vector m/s² The specific force in the IMU axes. At rest, z is -9.81.

/oxts/velocity

Name Type Unit Default Description
twist.linear vector m/s The velocity in the body frame: x forward, y left, z up.
twist.angular vector deg/s The angular rate in the body frame. The unit is degrees, as on the real vehicle.

/oxts/fix

Name Type Unit Default Description
latitude, longitude number deg The geodetic position.
altitude number m The ellipsoidal height.
status.status integer 2 2 = a fix with ground-based corrections (RTK).
position_covariance number[9] m² Only the diagonal for east, north and up.

/sim/ground_truth/odom has the exact pose in the same utm frame. twist.twist.linear.x is the exact forward speed. The other twist fields are zero. The real vehicle has no such topic.

The bridge publishes /vehicle/odom, /tf, /oxts/fix and the ground truth only when the sample has a UTM position. The first samples after the start of the simulator can be without a position.

ros2 topic echo --once --no-arr /vehicle/odom
import rclpy
from nav_msgs.msg import Odometry

rclpy.init()
node = rclpy.create_node("odom_reader")

def on_odom(msg):
    p = msg.pose.pose.position
    print(p.x, p.y, msg.twist.twist.linear.x)

node.create_subscription(Odometry, "/vehicle/odom", on_odom, 10)
rclpy.spin(node)
header:
  stamp:
    sec: 27
    nanosec: 250001421
  frame_id: utm
child_frame_id: base_footprint
pose:
  pose:
    position:
      x: 500438.12220755406
      y: 4479963.951710937
      z: 181.76825509173662
    orientation:
      x: -0.01584030324089254
      y: 0.009055761779796622
      z: 0.7071480518888165
      w: 0.7068300380638741
  covariance: '<array type: double[36]>'
twist:
  twist:
    linear:
      x: -0.0066012713313529404
      y: 0.008960991459550805
      z: 0.0077292981674181305
    angular:
      x: 0.0009210506604661536
      y: 0.00010572694792926153
      z: 0.0006802870432511093
  covariance: '<array type: double[36]>'
---

LiDAR and Camera#

/lidar/points is an organised cloud. LiDAR gives the sensor model.

Name Type Unit Default Description
height integer 1800 The number of azimuth columns (Polaris).
width integer 32 The number of rings (Polaris).
fields list m x, y, z, intensity, each float32.
point_step integer bytes 16 The size of one point.
is_dense boolean false A beam without a return has NaN in x, y and z.

/camera/image_raw has the encoding bgr8. Camera gives the camera model. /camera/camera_info has the matrix K, the distortion D (plumb_bob) and the projection P, which the bridge makes from K. The sensor stream gives these values when the session starts.

The rates are those of the sensor rig: 10 Hz for the LiDAR and 10 Hz for the camera of the Polaris. In a game session on the workstation of the project, the measured rates were 10.0 Hz and 9.9 Hz. In lockstep, a step waits for each cloud and each image that is due. The bridge drops a cloud or an image when its queue of 4 frames is full.

ros2 topic echo --once --no-arr /lidar/points
header:
  stamp:
    sec: 33
    nanosec: 108335060
  frame_id: lidar
height: 1800
width: 32
fields: '<sequence type: sensor_msgs/msg/PointField, length: 4>'
is_bigendian: false
point_step: 16
row_step: 512
data: '<sequence type: uint8, length: 921600>'
is_dense: false
---
ros2 topic echo --once /camera/camera_info
header:
  stamp:
    sec: 30
    nanosec: 108334904
  frame_id: reolink_camera_optical
height: 512
width: 896
distortion_model: plumb_bob
d:
- -0.5538868812746999
- 0.40841873696727643
- -0.009978535293476691
- 0.004423926017374143
- -0.16673270849069252
k:
- 599.5261980366653
- 0.0
- 440.1304622044219
- 0.0
- 592.3778194293697
- 270.80532448208777
- 0.0
- 0.0
- 1.0
...

Drive-by-Wire Reports#

The simulator sends one report line at 50 Hz of simulation time. The bridge publishes the seven topics from this line. The messages have the definitions of ds_dbw_msgs release 2.3.11. Their frame name is empty. Drive-by-Wire and ULC gives the model.

Name Type Unit Default Description
steering_wheel_angle number deg steering/report: the steering wheel angle.
cmd, cmd_type number, integer by type steering/report: the last steering command and its type.
percent_input, percent_cmd, percent_output number % throttle/report: the pedal input, the command and the output of the actuator.
pressure_input, pressure_cmd, pressure_output number bar brake/report: the brake pressures.
percent_cmd, percent_output number % brake/report: the brake command and output in percent.
gear, cmd, driver Gear gear/report: 1 = P, 2 = R, 3 = N, 4 = H, 5 = L.
vel_ref, vel_meas number m/s ulc/report: the reference speed and the measured speed.
accel_ref, accel_meas number m/s² ulc/report: the reference and the measured acceleration.
vehicle_velocity_brake, vehicle_velocity_propulsion number m/s vehicle_velocity: the speed from the two systems.
enabled, override_active, timeout boolean Each report: the state of the subsystem.

The bridge fills some fields with the constant values of the real vehicle. ready is true. The steering limits are 1000 deg/s and 487 deg. The actuator temperatures are -40 °C. The torque and acceleration fields of the brake report are NaN.

ros2 topic echo --once /vehicle/ulc/report
header:
  stamp:
    sec: 137
    nanosec: 183300000
  frame_id: ''
cmd_type: 1
vel_ref: 1.5
vel_meas: 1.5082000494003296
accel_ref: 0.0
accel_meas: 0.04100000113248825
coast_decel: false
ready: true
enabled: true
override_active: false
override_latched: false
preempted: false
timeout: false
bad_crc: false
bad_rc: false
---

Drive-by-Wire Commands#

The bridge sends each command to the simulator immediately. The field names and the units are those of ds_dbw_msgs. Vehicle Bridge gives the effect of each field in the simulator.

Name Type Unit Default Description
steering/cmd number by type cmd with cmd_type 2 = angle of the steering wheel (deg), 3 = curvature (1/m), 4 = yaw rate (rad/s), 14 = percent.
throttle/cmd number % cmd with cmd_type 14 = percent, 13 = pedal units.
brake/cmd number by type cmd with cmd_type 14 = percent, 1 = pressure (bar).
gear/cmd integer cmd.value: 1 = P, 2 = R, 3 = N, 4 = H, 5 = L. The bridge does not send the value 0.
ulc/cmd number by type cmd with cmd_type 1 = speed (m/s), 2 = acceleration (m/s²).
enable boolean Set to true in each steering, throttle, brake and ULC command.

Send /vehicle/enable one time. Then send the commands at 20 Hz to 100 Hz. The simulator stops a subsystem 0.1 s after its last command, as the real vehicle does.

In lockstep, the bridge sends all commands that it received before a step request, and then the step request. A command thus applies from the first physics step of that request.

ros2 topic pub --once /vehicle/enable std_msgs/msg/Empty "{}"
ros2 topic pub --once /vehicle/gear/cmd ds_dbw_msgs/msg/GearCmd \
    "{cmd: {value: 5}}"
ros2 topic pub -r 50 /vehicle/ulc/cmd ds_dbw_msgs/msg/UlcCmd \
    "{cmd: 1.5, cmd_type: 1, enable: true}"
import rclpy
from ds_dbw_msgs.msg import GearCmd, SteeringCmd, UlcCmd
from std_msgs.msg import Empty

rclpy.init()
node = rclpy.create_node("dbw_commands")
enable = node.create_publisher(Empty, "/vehicle/enable", 10)
gear = node.create_publisher(GearCmd, "/vehicle/gear/cmd", 10)
steer = node.create_publisher(SteeringCmd, "/vehicle/steering/cmd", 10)
ulc = node.create_publisher(UlcCmd, "/vehicle/ulc/cmd", 10)

def tick():
    enable.publish(Empty())
    low = GearCmd()
    low.cmd.value = low.cmd.LOW
    gear.publish(low)
    steer.publish(SteeringCmd(cmd=0.02, cmd_type=SteeringCmd.CMD_CURVATURE, enable=True))
    ulc.publish(UlcCmd(cmd=1.5, cmd_type=UlcCmd.CMD_VELOCITY, enable=True))

node.create_timer(0.02, tick)
rclpy.spin(node)

Reset Pose#

/sim/reset moves the vehicle to a pose and starts a new episode of the vehicle. The speed after the reset is zero. The marks on the fields stay.

Name Type Unit Default Description
pose.position.x, pose.position.y number m The UTM easting and northing of base_footprint.
pose.orientation quaternion The yaw, counter-clockwise from UTM east.

The bridge changes the pose into the coordinates of the simulator with the georeference of the last INS sample. The vehicle thus goes to the position that /vehicle/odom then reports. The bridge ignores the message until the first INS sample with a position arrives. To reset the full session, use the service /reset_simulation.

ros2 topic pub --once /sim/reset geometry_msgs/msg/PoseStamped \
    "{header: {frame_id: utm}, pose: {position: {x: 500438.1, y: 4479970.0}, orientation: {z: 0.7071, w: 0.7071}}}"

Maxxum#

The bridge publishes the Maxxum topics with vehicle:=maxxum, or with vehicle:=auto when the session has a Maxxum. The names in the table are relative to the namespace of the bridge.

Name Type Direction Rate QoS Frame Description
/clock rosgraph_msgs/msg/Clock publish 120 Hz vehicle The simulation time.
/tf tf2_msgs/msg/TFMessage publish 100 Hz tf utm The transform from utm to base_footprint.
/tf_static tf2_msgs/msg/TFMessage publish one time static The sensor mounts below base_footprint.
odom nav_msgs/msg/Odometry publish 100 Hz vehicle utm The pose of base_footprint and the velocity from the INS.
ground_truth/odom nav_msgs/msg/Odometry publish 100 Hz vehicle utm The exact pose of base_footprint.
imu/data sensor_msgs/msg/Imu publish 100 Hz vehicle imu_link The attitude, the angular rate and the specific force in the body axes.
gnss/fix sensor_msgs/msg/NavSatFix publish 100 Hz vehicle gnss_link The latitude, the longitude and the height.
lidar/points sensor_msgs/msg/PointCloud2 publish 10 Hz vehicle lidar The LiDAR cloud of the sensor rig. The default is 180 columns and 8 rings.
camera/image_raw sensor_msgs/msg/Image publish 10 Hz vehicle camera_optical The camera image.
camera/camera_info sensor_msgs/msg/CameraInfo publish 10 Hz vehicle camera_optical The camera matrix and the distortion.
engine_rpm std_msgs/msg/Float32 publish 10 Hz vehicle The engine speed in r/min.
steering_angle std_msgs/msg/Float32 publish 10 Hz vehicle The steering angle of the front wheels in rad. Positive = left.
implement/state std_msgs/msg/String publish 10 Hz vehicle The state of the implement as JSON text.
can/rx can_msgs/msg/Frame publish 13 frames each 0.1 s can can The frames that the tractor sends.
cmd_vel geometry_msgs/msg/Twist subscribe vehicle The speed and the yaw rate.
guidance/curvature_cmd std_msgs/msg/Float32 subscribe vehicle The path curvature in 1/m. Positive = left.
speed_cmd std_msgs/msg/Float32 subscribe vehicle The speed in m/s.
hitch/lever_cmd std_msgs/msg/Float32 subscribe vehicle The hitch lever, 0 to 1.
hitch/raise_cmd std_msgs/msg/Bool subscribe vehicle true lifts the implement.
pto/cmd std_msgs/msg/Bool subscribe vehicle true engages the PTO.
hand_throttle_cmd std_msgs/msg/Float32 subscribe vehicle The hand throttle, 0 to 1.
implement/cmd std_msgs/msg/String subscribe vehicle A JSON object with implement controls.
sim/reset geometry_msgs/msg/PoseStamped subscribe vehicle utm Moves the vehicle to a pose.
can/tx can_msgs/msg/Frame subscribe can The frames that a controller sends to the tractor.

State of the Maxxum#

The fields of odom, ground_truth/odom and gnss/fix are the same as for the Polaris. imu/data uses the body axes: x forward, y left, z up. There is no oxts/velocity topic.

engine_rpm, steering_angle and implement/state come from the observation of the vehicle bridge, each 0.1 s of simulation time. implement/state has these keys:

Name Type Unit Default Description
implement string The name of the implement.
implement_depth_m number m The working depth.
pto_kw number kW The PTO power.
pto_on boolean true while the PTO is on.
pto_rpm number r/min The PTO speed.
hitch_lever number The hitch lever, 0 to 1.
hitch_lift_deg number deg The lift angle of the hitch.
hitch_mode string position, draft or float.
raised boolean The implement is in the lifted position.
draft_n number N The draft force.
difflock integer 0 = open, 1 = rear, 2 = rear and front.
rpm number r/min The engine speed.
stale boolean true while the safety brake of the vehicle bridge holds the vehicle.

The static transforms go from base_footprint to lidar, camera, imu_link and gnss_link, and from camera to camera_optical. They come from the mount poses of the sensor rig. The sensor stream gives them when the session starts.

ros2 topic echo --once /maxxum/implement/state
data: '{"implement": "chisel_plow", "implement_depth_m": 0.1753, "pto_kw": 0.0, "hitch_lever": 0.235, "hitch_lift_deg": 3.71, "hitch_mo...'
---

The tool cuts a long text. The option --full-length shows all of it.

ros2 topic echo --once /maxxum/engine_rpm
data: 796.2999877929688
---

Commands of the Maxxum#

The bridge has a speed controller for the Maxxum. The curvature goes to the simulator without a change. The controller sets the pedal from the speed error with the parameters speed_kp and speed_ki. It operates at 20 Hz of simulation time, thus its behaviour is the same in lockstep.

Name Type Unit Default Description
cmd_vel number m/s linear.x: the speed. The limit is max_speed_mps. A negative value gives zero curvature and a full brake.
cmd_vel number rad/s angular.z: the yaw rate. The bridge divides it by the speed to get the curvature. The minimum speed in this calculation is 0.1 m/s.
guidance/curvature_cmd number 1/m The curvature, from -0.5 to 0.5.
speed_cmd number m/s The speed, from 0 to max_speed_mps. Below 0.05 m/s the controller applies the full brake.
implement/cmd JSON The object of the implement message of the vehicle bridge.

Send the guidance commands continuously. When no command arrives for cmd_timeout_s (0.5 s), the bridge stops its commands. The simulator then applies its safety brake.

ros2 topic pub -r 20 /maxxum/cmd_vel geometry_msgs/msg/Twist \
    "{linear: {x: 2.0}, angular: {z: 0.1}}"
import rclpy
from std_msgs.msg import Bool, Float32

rclpy.init()
node = rclpy.create_node("maxxum_commands")
curvature = node.create_publisher(Float32, "/maxxum/guidance/curvature_cmd", 10)
speed = node.create_publisher(Float32, "/maxxum/speed_cmd", 10)
lower = node.create_publisher(Bool, "/maxxum/hitch/raise_cmd", 10)

def tick():
    curvature.publish(Float32(data=0.05))
    speed.publish(Float32(data=2.0))
    lower.publish(Bool(data=False))

node.create_timer(0.05, tick)
rclpy.spin(node)

CAN Bus#

With can_udp_port, the bridge connects to the CAN transport of the game (-RlCanUdp=). can/rx and can/tx have the type and the QoS of the receiver and the sender of the ros2_socketcan driver. A node for a real tractor bus thus operates with the remaps from_can_bus:=can/rx and to_can_bus:=can/tx.

Name Type Unit Default Description
id integer The 29-bit identifier, without the flag bits.
is_extended boolean true All frames of the tractor have 29-bit identifiers.
dlc integer bytes 8 The data length.
data integer[8] The data bytes.
header.stamp time The simulation time.

Wheel and CAN Signals gives the frames and their fields. The Python module acres_sim.j1939 and the header acres_sim/j1939.hpp encode and decode the frames. In lockstep, the game sends a barrier frame after each step. The bridge does not publish this frame.

The game sends a group of 13 frames each 0.1 s of simulation time. A subscriber must have a history depth of 13 or more. The tools ros2 topic echo and ros2 topic hz keep only the last 5 frames of each group and thus show 50 Hz.

ros2 topic echo --once /maxxum/can/rx
header:
  stamp:
    sec: 34
    nanosec: 991668492
  frame_id: can
id: 419370368
is_rtr: false
is_extended: true
is_error: false
dlc: 8
data:
- 58
- 250
- 250
- 79
- 250
- 128
- 250
- 250
---

The identifier 419370368 is 0x18FF1580.

Session#

Episode Clock#

/sim/episode has the type acres_interfaces/msg/Episode. The bridge publishes it only when it has a control port. The simulator sends a message at the start, on each reset and on each change of the simulation state.

Name Type Unit Default Description
episode_id string The start time of the session in UTC and the episode number.
episode integer 0 The number of episodes since the start.
reset_epoch integer The number of resets of agent 0.
step integer steps The physics steps since the start.
episode_time_s number s The simulation time since the start of the episode.
state integer 1 0 = stopped, 1 = playing, 2 = paused.
lockstep boolean true when the world advances only on step requests.
seed integer The seed of the sensor noise.
source string unreal for the game, core for ACRES Core.
map string The map, for example V03ACRE.
agents string list The agent names. Agent 0 is first.

A node can use source to make sure that its data comes from a simulator and not from the real vehicle.

ros2 topic echo --once --qos-durability transient_local \
    --qos-reliability reliable /sim/episode
import rclpy
from rclpy.qos import DurabilityPolicy, QoSProfile, ReliabilityPolicy
from acres_interfaces.msg import Episode

rclpy.init()
node = rclpy.create_node("episode_reader")
latched = QoSProfile(depth=1, reliability=ReliabilityPolicy.RELIABLE,
                     durability=DurabilityPolicy.TRANSIENT_LOCAL)
episodes = []
node.create_subscription(Episode, "/sim/episode", episodes.append, latched)
while not episodes:
    rclpy.spin_once(node, timeout_sec=1.0)
print(episodes[-1].episode_id, episodes[-1].source)
header:
  stamp:
    sec: 11
    nanosec: 708333944
  frame_id: ''
episode_id: 20261002T072309-0000
episode: 0
reset_epoch: 0
step: 1405
episode_time_s: 11.708333943970501
state: 1
lockstep: false
seed: 42
source: unreal
map: V03ACRE
agents:
- polaris
---

Channels of the Episode Log#

The other /sim/* names are channels of the Episode Log. The bridge does not publish them as topics. The simulator writes them into the MCAP file. The command ros2 bag play -s mcap <file> publishes them from a file.

Name Type Direction Rate QoS Frame Description
/sim/episode acres_interfaces/msg/Episode file and topic on change latched The episode clock.
/sim/agents acres_interfaces/msg/Agents file each episode One AgentInfo for each agent: name, vehicle, implement, wheelbase, tyre radii.
/sim/agent_states acres_interfaces/msg/AgentStates file 120 Hz world One AgentState for each agent, with its EnergyLedger.
/sim/farm_stamps acres_interfaces/msg/FarmStamps file steps with contacts world One FarmStamp for each contact of a tyre or the body with the farm.
/sim/farm_events acres_interfaces/msg/FarmEvents file steps with events world One FarmEvent for each change of the crop or the soil.
/sim/conditions acres_interfaces/msg/Conditions file on change and each 1 s The clock, the weather and the soil water.
/sim/shifts acres_interfaces/msg/VehicleShifts file on change One VehicleShift for each agent with a hardware shift.
/sim/task acres_interfaces/msg/TaskStatus file by the task The status and the reward terms of a task. The task code writes it, not the simulator.

The message FieldState is part of the service /acres/farm_state. Refer to Services and Actions.

Several Agents#

With agents:=auto, one bridge process serves all agents of a session. It reads the agents and their ports from the control channel.

Agent Namespace Frame Prefix /clock
Agent 0 The namespace of the bridge (default /) none publish
Each other agent /<name> below the namespace of the bridge <name>/ no

A Polaris that is the second agent with the name polaris2 thus has the topics /polaris2/vehicle/odom and /polaris2/vehicle/ulc/cmd. Its odometry has the child frame polaris2/base_footprint. All agents use the same topic /tf. The bridge changes each character of an agent name that is not a letter, a digit or _ into _.

The services and the action stay in the root namespace. They apply to the session, not to one agent. Run Several Vehicles gives the procedure.

Topics of the Other Nodes#

Name Type Direction Rate QoS Frame Description
/robot_description std_msgs/msg/String publish one time latched robot_state_publisher: the URDF of the Polaris description.
/tf_static tf2_msgs/msg/TFMessage publish one time static robot_state_publisher: the sensor frames below base_footprint.
/vehicle/odom/path nav_msgs/msg/Path publish 100 Hz reliable, depth 10 odom_origin odom_path: the path of the vehicle for RViz.
/Demo/route nav_msgs/msg/Path publish one time latched demo_route_origin dbw_demo_driver: the route.
/Demo/status std_msgs/msg/String publish 50 Hz reliable, depth 10 dbw_demo_driver: the route index, the curvature and the speed.
/safety_stop std_msgs/msg/Bool publish 20 Hz reliable, depth 10 lab_stubs: true during a safety stop.
/obstacle_speed_limit_mps std_msgs/msg/Float32 publish 20 Hz reliable, depth 10 lab_stubs: the speed limit in m/s.

Differences in ACRES Core#

A session with core_sim has the same Polaris topics. These are the differences:

Topic ACRES Core
/camera/image_raw, /camera/camera_info The topics exist, but no message arrives. ACRES Core has no camera.
/lidar/points The cloud comes from the ray-cast LiDAR of ACRES Core. It contains the terrain, the buildings and the other agents. It does not contain the crop.
/sim/episode source is core.
Maxxum topics ACRES Core has only the Polaris.

Parity with the Real Polaris#

The tool ROS/Tools/check_topic_parity.py compares a bag of the simulator with bags of the real Polaris. For each topic that the two sides record, it compares the type, the reliability, the durability and the frame names. The tool ROS/Tools/check_msg_defs.py examines the bytes of the ds_dbw_msgs messages.

The result below is from a session with core_sim, sim_bridge and dbw_demo_driver, without the Polaris description. The vehicle bags are dbw_direct_test_01 and human_20260813_172445.

OK   /vehicle/brake/report      ds_dbw_msgs/msg/BrakeReport      qos (1, 2) / (1, 2)  frames [''] / ['']
OK   /vehicle/steering/cmd      ds_dbw_msgs/msg/SteeringCmd      qos (1, 2) / (1, 2)  frames ['base_footprint'] / ['base_footprint']
OK   /lidar/points              sensor_msgs/msg/PointCloud2      qos (1, 2) / (1, 2)  frames ['lidar'] / ['lidar']
OK   /oxts/imu                  sensor_msgs/msg/Imu              qos (1, 2) / (1, 2)  frames ['imu_link'] / ['imu_link']
OK   /vehicle/odom              nav_msgs/msg/Odometry            qos (1, 2) / (1, 2)  frames ['utm'] / ['utm']
DIFF /tf_static                 tf2_msgs/msg/TFMessage           qos (1, 1) / (1, 1)  frames ['base_footprint->lidar', ...] / ['utm->demo_route_origin']
...
18 common topic checks, 1 differences
MATCH: 1601/1601 messages round-trip byte-exactly

The one difference is /tf_static. Without the Polaris description, the session has no transform from base_footprint to lidar. The description needs the package purdue_ranger.

These are the known differences between the simulator and the real vehicle:

Topic Real Polaris Simulator
Drive-by-wire reports Steering and velocity at 100 Hz, throttle and brake at 50 Hz, dbw_enabled at 20 Hz, ULC at 15 Hz, gear at 12.5 Hz. All reports at 50 Hz.
/tf Also contains map to oxts_link and the axle frames of the OxTS driver. Only utm to base_footprint.
/oxts/fix, /clock, /sim/* The bags of the vehicle do not contain them. Published.
/camera/segformer_*, /learning/route/local_path Published by nodes of the lab. Not published.
Camera frame The vehicle publishes a camera at (1.2, 0, 1.3) m. The description uses the camera mount of the simulator: (2.745, 0.606, 1.887) m.