autonomy_metrics_logger is a ROS 2 node that:
- Tracks travelled distance, autonomous distance, incidents, and collisions.
- Logs interventions and collisions to MongoDB with a full snapshot of system state.
- Is configured entirely via a YAML file (topics, fields, triggers, dynamic publishers).
- Supports both explicit operation mode (
/gophar/operation_mode) and inferred mode (odom + velocity).
The node:
- Loads a YAML config describing topics and logging behaviour.
- Subscribes to all configured topics.
- Maintains a live
system_snapshotdict with the latest values from each topic. - Updates metrics:
distance(total odometry distance)autonomous_distance(distance when mode = Autonomous)incidents(interventions in autonomous mode, plus manual overrides)collision_incidents(collision monitor only, separate from incidents)
- Logs events to MongoDB only when something happens:
- Manual override (explicit or observed)
- E-stop
- Faults / joy overrides / other YAML triggers
- Collisions (nav vs collision velocity)
- Each event stored in Mongo includes:
metrics(distance, autonomous_distance, speed, battery)system_snapshot(current values of all configured topics)- Event type & extra details.
MDBI is computed as:
mdbi = autonomous_distance / incidents
(Ifincidents == 0,mdbi = autonomous_distance.)
The node creates a document in robot_incidents.sessions per run:
{
"session_start_time": "...",
"robot_name": "...",
"farm_name": "...",
"field_name": "...",
"application": "...",
"scenario_name": "...",
"aoc_repos_info": [
{
"aoc_scenario_path": {
"path": "...",
"exists": true,
"remote": "...",
"branch": "...",
"commit": "...",
"short_commit": "...",
"commit_message": "...",
"tags": ["v0.1.0"],
"describe": "v0.1.0-3-gXXXXXXX",
"dirty": false,
"error": null
}
}
],
"mdbi": 123.4,
"incidents": 5,
"distance": 456.7,
"autonomous_distance": 321.0,
"collision_incidents": 2,
"events": [
{
"time": "...",
"event_type": "Manual_override",
"details": {
"operation_mode": "Manual",
"estop": false,
"metrics": {
"distance": 12.3,
"autonomous_distance": 12.3,
"speed": 0.4,
"battery_percentage": 78.0
},
"system_snapshot": {
"/gophar/system_status": {
"fault_shutdown": false,
"hv_on": true,
"battery_percentage": 78.0,
"...": "..."
},
"/gps_base/fix": { "...": "..." },
"odometry": { "x": 1.23, "y": 4.56, "vx": 0.4 },
"...": "..."
}
}
}
]
}Environment variables used to tag the session:
ROBOT_NAMEFARM_NAMEFIELD_NAMEAPPLICATIONSCENARIO_NAME
All parameters are exposed through the launch file.
| Parameter | Type | Default | Description |
|---|---|---|---|
config_yaml |
string | "" |
Path to YAML config file for topics & triggers. |
mongodb_host |
string | "localhost" |
Local MongoDB host. |
mongodb_port |
int | 27017 |
Local MongoDB port. |
remote_mongodb_host |
string | "" |
Remote MongoDB host (empty = disabled). |
remote_mongodb_port |
int | 27017 |
Remote MongoDB port. |
enable_remote_logging |
bool | false |
Enable writes to remote MongoDB. |
min_distance_threshold |
double | 0.2 |
Min odom step (m) required for distance update. |
stop_timeout |
double | 2.0 |
Time (s) after last odom update before speed is set 0. |
Used only if no topic with role: "control_mode" is configured.
| Parameter | Type | Default | Description |
|---|---|---|---|
mode_observer_cmd_timeout |
double | 1.0 |
Commands on mode_observer topic are “recent” within this many seconds. |
mode_observer_speed_threshold |
double | 0.01 |
Robot is considered moving if speed > this (m/s). |
Logic when active:
-
Topic with
role: "mode_observer"(e.g./cmd_vel/collision_smoothed) marks last command time. -
If odometry reports movement and:
- cmd is recent → mode = Autonomous
- cmd is not recent → mode = Manual
-
When observed mode changes from Auto → Manual:
Manual_override_observedevent is logged.- An incident is counted (MDBI).
| Parameter | Type | Default | Description |
|---|---|---|---|
collision_nav_threshold |
double | 0.01 |
/cmd_vel/nav.linear.x must be > this to count as a forward command. |
collision_zero_threshold |
double | 0.001 |
/cmd_vel/collision.linear.x considered zero if abs(x) ≤ this. |
collision_time_window |
double | 0.5 |
Nav and collision commands must both be within this time window (s). |
collision_log_cooldown |
double | 1.0 |
Optional legacy cooldown between collisions (can be unused if using pure falling-edge logic). |
Collision detection (falling-edge):
-
Keep last
/cmd_vel/navand/cmd_vel/collision. -
Compute a collision only when:
- Previous
/cmd_vel/collision.linear.xhad velocity (> collision_zero_threshold), - Current
/cmd_vel/collision.linear.xis ~0, - Latest
/cmd_vel/nav.linear.x>collision_nav_threshold, - Commands are recent (
≤ collision_time_window).
- Previous
-
On collision:
collision_incidentsis incremented,- A
Collisionevent is logged with snapshot, - Total collisions published on
mdbi_logger/total_collision_incidents(Int32).
git_repos:
aoc_scenario_path: "/home/ros/aoc_strawberry_scenario_ws/src/aoc_strawberry_scenario"
topics:
- name: "/gps_base/fix"
type: "sensor_msgs/msg/NavSatFix"
log_fields:
- "latitude"
- "longitude"
- "altitude"
publish:
enable: true
topic: "mdbi_logger/gps_alt"
type: "std_msgs/msg/Float32"
field: "altitude"
- name: "/gophar_vehicle_controller/odometry"
type: "nav_msgs/msg/Odometry"
role: "odometry"
log_fields:
- "pose.pose.position.x"
- "pose.pose.position.y"
- "twist.twist.linear.x"
publish:
enable: false
- name: "/gophar/system_status"
type: "dynium_gophar_interfaces/msg/SystemStatus"
role: "system_status"
log_all_fields: false
log_fields:
- "fault_shutdown"
- "hv_on"
- "ignition_switch"
- "charger_interlock"
- "battery_percentage"
battery_field: "battery_percentage"
publish:
enable: true
topic: "mdbi_logger/battery_level"
type: "std_msgs/msg/Float32"
field: "battery_percentage"
intervention_on_change:
fault_shutdown:
trigger_value: true
event_type: "Fault_shutdown"
- name: "/gophar/operation_mode"
type: "std_msgs/msg/String"
role: "control_mode"
mode_field: "data"
mode_mapping:
"AUTO": "Autonomous"
"MANUAL": "Manual"
"1": "Autonomous"
"0": "Manual"
- name: "/cmd_vel/joy"
type: "geometry_msgs/msg/Twist"
log_fields: []
intervention_on_message:
enable: true
event_type: "Joy_override"
- name: "/cmd_vel/collision_smoothed"
type: "geometry_msgs/msg/Twist"
role: "mode_observer"
log_fields: []
- name: "/cmd_vel/nav"
type: "geometry_msgs/msg/Twist"
role: "collision_nav"
log_fields: []
- name: "/cmd_vel/collision"
type: "geometry_msgs/msg/Twist"
role: "collision_output"
log_fields: []
- name: "/gophar/steering_actuator/front"
type: "dynium_gophar_interfaces/msg/SteeringActuatorStatus"
log_all_fields: true
log_fields: []
- name: "/gophar/steering_actuator/rear"
type: "dynium_gophar_interfaces/msg/SteeringActuatorStatus"
log_all_fields: true
log_fields: []
- name: "/gophar/motor_status/motor_0"
type: "dynium_gophar_interfaces/msg/MotorStatus"
log_all_fields: true
log_fields: []
- name: "/gophar/motor_status/motor_1"
type: "dynium_gophar_interfaces/msg/MotorStatus"
log_all_fields: true
log_fields: []Common fields:
-
nameROS topic name to subscribe to. -
typeROS message type string. Accepts:pkg/msg/MessageName(e.g."sensor_msgs/msg/NavSatFix"), orpkg/MessageName(e.g."sensor_msgs/NavSatFix").
-
role(optional, lowercase in logic) Special roles:"odometry": used for distance/speed computation."control_mode": explicit operation mode (AutonomousvsManual)."estop": emergency stop; used to generateEMSincidents."system_status": can carrybattery_fieldand fault triggers."mode_observer": used by mode observer (e.g./cmd_vel/collision_smoothed)."collision_nav": nav velocity command."collision_output": collision-limited velocity command.
-
log_fields(list of dotted paths) Fields to extract intosystem_snapshot[topic_name]. Only used iflog_all_fieldsisfalse. -
log_all_fields(bool, default: false) Iftrue, the full message is converted to a dict and stored insystem_snapshot[topic_name]. Ignoreslog_fields. -
battery_field(optional, string) Field name under this topic that carries battery percentage; updatescurrent_battery.
publish:
enable: true
topic: "mdbi_logger/battery_level"
type: "std_msgs/msg/Float32"
field: "battery_percentage"enable:true/falsetopic: output topic nametype: output message typefield: dotted field in input message; mapped to.dataof the output (with type casting).
For topics with role: "control_mode":
-
mode_field(string) Dotted field path inside message holding the raw mode value (e.g."data"). -
mode_mapping(dict) Map raw values to"Autonomous"or"Manual"Example:"AUTO" → "Autonomous","0" → "Manual". If unmapped andstr(value) == "3", forced to Manual; otherwise treated as Autonomous.
Changing from Autonomous to Manual:
- Ends an autonomy segment (time tracking).
- Logs
Manual_overrideevent. - Increments
incidents(MDBI) even if now in Manual (forced byforce_count=True).
For role: "estop" topics, additional config:
-
field(optional, default"data") Boolean field to check. When it changes and becomesTrue:EMSintervention is logged.- Incidents increment only if mode = Autonomous.
Per-topic:
-
intervention_on_message:intervention_on_message: enable: true event_type: "Joy_override"
If enabled, every message on that topic triggers an event of this type via
trigger_intervention. -
intervention_on_change:intervention_on_change: fault_shutdown: trigger_value: true event_type: "Fault_shutdown"
- Key (
fault_shutdown) is a field name indata. trigger_valueoptional; if present, event fires only when field changes and equalstrigger_value.event_typeis the event name stored in DB.
- Key (
-
Default mode at startup is set to
Autonomous:self.AUTO = 'Autonomous' self.MAN = 'Manual' self.details = {'estop': False, 'operation_mode': self.AUTO}
-
incidentsare only incremented when:-
trigger_intervention(...)is called and:operation_mode == "Autonomous", orforce_count=True(for explicit/observed manual overrides).
-
-
E-stop, faults, joy overrides, etc. in Manual mode:
- Are logged to DB (event + snapshot),
- Do not increment
incidents, thus do not affect MDBI.
Example launch file (simplified; adapt package/executable names):
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node
def generate_launch_description():
config_yaml_arg = DeclareLaunchArgument(
'config_yaml',
default_value='',
description='Path to AutonomyMetricsLogger YAML config',
)
# ... declare other args (mongodb_host, ports, thresholds, etc.) ...
config_yaml = LaunchConfiguration('config_yaml')
metrics_logger_node = Node(
package='autonomy_metrics', # adjust
executable='metric_logger', # adjust
name='mdbi_logger_dynamic',
output='screen',
parameters=[{
'config_yaml': config_yaml,
'mongodb_host': LaunchConfiguration('mongodb_host'),
'mongodb_port': LaunchConfiguration('mongodb_port'),
'remote_mongodb_host': LaunchConfiguration('remote_mongodb_host'),
'remote_mongodb_port': LaunchConfiguration('remote_mongodb_port'),
'enable_remote_logging': LaunchConfiguration('enable_remote_logging'),
'min_distance_threshold': LaunchConfiguration('min_distance_threshold'),
'stop_timeout': LaunchConfiguration('stop_timeout'),
'mode_observer_cmd_timeout': LaunchConfiguration('mode_observer_cmd_timeout'),
'mode_observer_speed_threshold': LaunchConfiguration('mode_observer_speed_threshold'),
'collision_nav_threshold': LaunchConfiguration('collision_nav_threshold'),
'collision_zero_threshold': LaunchConfiguration('collision_zero_threshold'),
'collision_time_window': LaunchConfiguration('collision_time_window'),
'collision_log_cooldown': LaunchConfiguration('collision_log_cooldown'),
}],
)
return LaunchDescription([
config_yaml_arg,
# ... other DeclareLaunchArgument ...
metrics_logger_node,
])Minimal:
ros2 launch autonomy_metrics_logger autonomy_metrics_logger.launch.py \
config_yaml:=/home/ros/aoc_strawberry_scenario_ws/src/aoc_strawberry_scenario/jabas/autonomy_metrics_logger/config/metrics_gophar.yamlWith remote DB + tuned thresholds:
ros2 launch autonomy_metrics_logger autonomy_metrics_logger.launch.py \
config_yaml:=/path/to/metrics_gophar.yaml \
mongodb_host:=localhost \
mongodb_port:=27017 \
enable_remote_logging:=true \
remote_mongodb_host:=10.0.0.42 \
remote_mongodb_port:=27017 \
min_distance_threshold:=0.1 \
stop_timeout:=1.5 \
mode_observer_cmd_timeout:=0.8 \
mode_observer_speed_threshold:=0.02 \
collision_nav_threshold:=0.02 \
collision_zero_threshold:=0.001 \
collision_time_window:=0.4-
Verify topics:
ros2 topic list ros2 topic info /gophar_vehicle_controller/odometry ros2 topic info /gophar/system_status
-
Trigger joystick interventions:
ros2 topic pub --once /cmd_vel/joy geometry_msgs/msg/Twist \ "{linear: {x: 0.5, y: 0.0, z: 0.0}, angular: {x: 0.0, y: 0.0, z: 0.0}}" -
Check DB content (Mongo shell or Mongo client) in
robot_incidents.sessions.
The README covers:
- All node parameters,
- YAML configuration options,
- How incidents, MDBI, collisions, and snapshots are computed,
- How to launch and test the system.